databricks-tellr-app 0.4.3.dev21__tar.gz → 0.4.3.dev22__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/chat_service.py +11 -0
  5. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/builder.py +19 -2
  6. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/event_emitter.py +40 -0
  7. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/nodes.py +134 -21
  8. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/README.md +0 -0
  9. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app/__init__.py +0 -0
  10. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app/run.py +0 -0
  11. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  12. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  13. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app.egg-info/requires.txt +0 -0
  14. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  15. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/setup.cfg +0 -0
  16. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/setup.py +0 -0
  17. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/__init__.py +0 -0
  18. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/__init__.py +0 -0
  19. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/_otel_bootstrap.py +0 -0
  20. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/fixtures/tour_demo_deck.json +0 -0
  21. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/main.py +0 -0
  22. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/mcp_auth.py +0 -0
  23. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/mcp_server.py +0 -0
  24. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/middleware/__init__.py +0 -0
  25. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/middleware/csrf.py +0 -0
  26. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/middleware/request_logging.py +0 -0
  27. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/middleware/security_headers.py +0 -0
  28. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/__init__.py +0 -0
  29. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/_authz.py +0 -0
  30. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/admin.py +0 -0
  31. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/admin_usage.py +0 -0
  32. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/agent_config.py +0 -0
  33. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/chat.py +0 -0
  34. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/deck_contributors.py +0 -0
  35. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/export.py +0 -0
  36. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/feedback.py +0 -0
  37. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/google_slides.py +0 -0
  38. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/images.py +0 -0
  39. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/local_version.py +0 -0
  40. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/profiles.py +0 -0
  41. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/sessions.py +0 -0
  42. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/__init__.py +0 -0
  43. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/contributors.py +0 -0
  44. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/deck_prompts.py +0 -0
  45. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/design_systems.py +0 -0
  46. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/identities.py +0 -0
  47. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/settings/slide_styles.py +0 -0
  48. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/setup.py +0 -0
  49. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/slides.py +0 -0
  50. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/tools.py +0 -0
  51. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/tour.py +0 -0
  52. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/verification.py +0 -0
  53. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/routes/version.py +0 -0
  54. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/__init__.py +0 -0
  55. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/agent_config.py +0 -0
  56. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/feedback.py +0 -0
  57. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/requests.py +0 -0
  58. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/responses.py +0 -0
  59. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/settings/__init__.py +0 -0
  60. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/settings/requests.py +0 -0
  61. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/settings/responses.py +0 -0
  62. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/schemas/streaming.py +0 -0
  63. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/__init__.py +0 -0
  64. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/deck_level_writer.py +0 -0
  65. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/export_job_queue.py +0 -0
  66. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/feedback_service.py +0 -0
  67. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/job_queue.py +0 -0
  68. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/session_manager.py +0 -0
  69. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/session_naming.py +0 -0
  70. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/slide_repository.py +0 -0
  71. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/usage_events.py +0 -0
  72. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/services/usage_service.py +0 -0
  73. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/utils/__init__.py +0 -0
  74. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/api/utils/validation.py +0 -0
  75. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/__init__.py +0 -0
  76. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/backfill_session_slides_startup.py +0 -0
  77. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/checkpointer.py +0 -0
  78. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/config_loader.py +0 -0
  79. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/context_utils.py +0 -0
  80. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/database.py +0 -0
  81. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/databricks_client.py +0 -0
  82. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/defaults.py +0 -0
  83. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/encryption.py +0 -0
  84. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/init_default_profile.py +0 -0
  85. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/lakebase.py +0 -0
  86. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/migrate_profiles_to_agent_config.py +0 -0
  87. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/mlflow_agent_spans.py +0 -0
  88. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/mlflow_tracing.py +0 -0
  89. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/otel_logging.py +0 -0
  90. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/permission_context.py +0 -0
  91. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/prompt_modules.py +0 -0
  92. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/settings_db.py +0 -0
  93. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/__init__.py +0 -0
  94. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/architect.py +0 -0
  95. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/build_reviewer.py +0 -0
  96. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/builder.py +0 -0
  97. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/data_analyst.py +0 -0
  98. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/deck_reviewer.py +0 -0
  99. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/fix_reviewer.py +0 -0
  100. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/skills/fixer.py +0 -0
  101. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/strip_retired_prompt_keys.py +0 -0
  102. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/core/user_context.py +0 -0
  103. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/__init__.py +0 -0
  104. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/__init__.py +0 -0
  105. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/deck_contributor.py +0 -0
  106. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/deck_review.py +0 -0
  107. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/design_system.py +0 -0
  108. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/encryption_key.py +0 -0
  109. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/feedback.py +0 -0
  110. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/genie_space.py +0 -0
  111. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/google_global_credentials.py +0 -0
  112. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/google_oauth_token.py +0 -0
  113. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/graph_checkpoint.py +0 -0
  114. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/identity.py +0 -0
  115. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/image.py +0 -0
  116. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/profile.py +0 -0
  117. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/profile_contributor.py +0 -0
  118. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/prompts.py +0 -0
  119. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/request_log.py +0 -0
  120. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/session.py +0 -0
  121. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/slide_deck_prompt.py +0 -0
  122. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/slide_style_library.py +0 -0
  123. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/usage_event.py +0 -0
  124. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/models/user_preference.py +0 -0
  125. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/database/types.py +0 -0
  126. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/__init__.py +0 -0
  127. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/deck_spec.py +0 -0
  128. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/finding.py +0 -0
  129. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/skill_io.py +0 -0
  130. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/slide.py +0 -0
  131. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/domain/slide_deck.py +0 -0
  132. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/__init__.py +0 -0
  133. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/agent.py +0 -0
  134. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/agent_factory.py +0 -0
  135. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/agent_resolution.py +0 -0
  136. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/config_service.py +0 -0
  137. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/config_validator.py +0 -0
  138. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/__init__.py +0 -0
  139. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/ast_guard.py +0 -0
  140. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/codeprep.py +0 -0
  141. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/gslides_runner.py +0 -0
  142. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/jail.py +0 -0
  143. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/pptx_runner.py +0 -0
  144. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/converter_jail/protocol.py +0 -0
  145. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/deck_css_aggregator.py +0 -0
  146. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/deck_review_store.py +0 -0
  147. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/design_system_compiler.py +0 -0
  148. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/design_system_service.py +0 -0
  149. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/design_system_templates.py +0 -0
  150. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/drive_uploader.py +0 -0
  151. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/evaluation/__init__.py +0 -0
  152. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/evaluation/llm_judge.py +0 -0
  153. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/foreman_service.py +0 -0
  154. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/genie_service.py +0 -0
  155. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/google_slides_auth.py +0 -0
  156. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/google_slides_prompts_defaults.py +0 -0
  157. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/__init__.py +0 -0
  158. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/routers.py +0 -0
  159. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/graph/state.py +0 -0
  160. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/html_to_google_slides.py +0 -0
  161. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/html_to_pptx.py +0 -0
  162. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/identity_provider.py +0 -0
  163. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/identity_providers/__init__.py +0 -0
  164. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/identity_providers/account_provider.py +0 -0
  165. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/identity_providers/local_provider.py +0 -0
  166. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/identity_providers/workspace_provider.py +0 -0
  167. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/image_service.py +0 -0
  168. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/image_tools.py +0 -0
  169. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/permission_service.py +0 -0
  170. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/pptx_from_html_huashu.py +0 -0
  171. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/pptx_from_records.py +0 -0
  172. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/pptx_prompts_defaults.py +0 -0
  173. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/profile_service.py +0 -0
  174. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/spec_sync.py +0 -0
  175. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/streaming_callback.py +0 -0
  176. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/template_sections.py +0 -0
  177. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/__init__.py +0 -0
  178. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/agent_bricks_tool.py +0 -0
  179. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/ds_asset_tool.py +0 -0
  180. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/genie_tool.py +0 -0
  181. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/mcp_tool.py +0 -0
  182. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/model_endpoint_tool.py +0 -0
  183. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/tools/vector_tool.py +0 -0
  184. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/services/validator.py +0 -0
  185. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/__init__.py +0 -0
  186. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/css_utils.py +0 -0
  187. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/ds_asset_utils.py +0 -0
  188. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/error_handling.py +0 -0
  189. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/graph_safety.py +0 -0
  190. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/html_safety.py +0 -0
  191. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/html_utils.py +0 -0
  192. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/image_utils.py +0 -0
  193. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/js_validator.py +0 -0
  194. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/logging_config.py +0 -0
  195. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/pi_filter.py +0 -0
  196. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/slide_hash.py +0 -0
  197. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/spotlight.py +0 -0
  198. {databricks_tellr_app-0.4.3.dev21 → databricks_tellr_app-0.4.3.dev22}/src/utils/text_caps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev21
3
+ Version: 0.4.3.dev22
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev21
3
+ Version: 0.4.3.dev22
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.4.3.dev21"
7
+ version = "0.4.3.dev22"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1117,6 +1117,7 @@ class ChatService:
1117
1117
  session_id,
1118
1118
  message,
1119
1119
  is_first_message=is_first_message,
1120
+ request_id=request_id,
1120
1121
  )
1121
1122
  return
1122
1123
 
@@ -1788,6 +1789,7 @@ class ChatService:
1788
1789
  message: str,
1789
1790
  *,
1790
1791
  is_first_message: bool = False,
1792
+ request_id: Optional[str] = None,
1791
1793
  ) -> Generator[StreamEvent, None, None]:
1792
1794
  """Run one turn on the LangGraph engine, yielding events as they arrive.
1793
1795
 
@@ -1835,6 +1837,14 @@ class ChatService:
1835
1837
  ``architect_node`` is the sole resolver of the design contract
1836
1838
  and the template bytes.
1837
1839
  is_first_message: Whether to generate a session title this turn.
1840
+ request_id: The async transport's ``ChatRequest.request_id``, handed
1841
+ to ``invoke_graph`` so a node persisting a chat message tags the
1842
+ row with it. ``GET /chat/poll`` reads assistant text through
1843
+ ``get_messages_for_request``, which filters on that column, so
1844
+ without it the architect's reply is written but no polling client
1845
+ can see it — and polling is the transport the deployed app uses.
1846
+ ``None`` on the SSE path, which persists no request row: that
1847
+ client is reading the yielded events instead.
1838
1848
 
1839
1849
  Yields:
1840
1850
  Every ``StreamEvent`` the graph emits, then ``COMPLETE`` carrying
@@ -1858,6 +1868,7 @@ class ChatService:
1858
1868
  session_id,
1859
1869
  {"architect_message": message},
1860
1870
  emitter=event_queue,
1871
+ request_id=request_id,
1861
1872
  )
1862
1873
  except Exception as e:
1863
1874
  logger.error(
@@ -73,7 +73,10 @@ from langgraph.graph import END, START, StateGraph
73
73
  from src.core.checkpointer import get_checkpointer
74
74
  from src.core.user_context import get_current_user
75
75
  from src.services.foreman_service import CAP
76
- from src.services.graph.event_emitter import set_event_emitter
76
+ from src.services.graph.event_emitter import (
77
+ set_chat_request_id,
78
+ set_event_emitter,
79
+ )
77
80
  from src.services.graph.nodes import (
78
81
  architect_node,
79
82
  build_reviewer_node,
@@ -188,6 +191,7 @@ def invoke_graph(
188
191
  emitter: Any = None,
189
192
  principal: Optional[str] = None,
190
193
  describe_only: bool = False,
194
+ request_id: Optional[str] = None,
191
195
  ) -> Dict[str, Any]:
192
196
  """Run one turn of the graph for *session_id*.
193
197
 
@@ -210,6 +214,14 @@ def invoke_graph(
210
214
  arc-review sweeper passes it: that turn exists to re-describe the
211
215
  deck a human hand-edited, and dispatching builders would overwrite
212
216
  the very edits that scheduled it.
217
+ request_id: The ``ChatRequest.request_id`` this turn is answering, or
218
+ ``None``. Bound in a ``ContextVar`` so a node persisting a chat
219
+ message can TAG it: ``GET /chat/poll`` reads a turn's assistant text
220
+ through ``get_messages_for_request``, which filters on that column,
221
+ so an untagged row is invisible to every polling client — the
222
+ transport the deployed app uses. ``None`` is correct on the SSE path
223
+ (no request row exists) and on the sweeper (no request at all); those
224
+ rows are still durable and still reach ``_conversation``.
213
225
 
214
226
  ``describe_only`` is turn-scoped, and it is wrapped HERE because this is the
215
227
  only place that knows ``turn_id``. It is written on **every** invocation,
@@ -231,12 +243,17 @@ def invoke_graph(
231
243
 
232
244
  The emitter is set on every invocation, ``None`` included: that is the reset
233
245
  a turn resumed in a new process needs, or events queue into the first
234
- process's queue forever.
246
+ process's queue forever. ``request_id`` is set unconditionally for the same
247
+ reason, and the failure it prevents is worse than a lost event: a stale id
248
+ left in the var would tag turn 2's chat rows with turn 1's request, so turn
249
+ 1's poll would receive turn 2's reply and turn 2's own poll would never see
250
+ it.
235
251
  """
236
252
  turn_id = uuid.uuid4().hex
237
253
  initiated_by = principal or get_current_user()
238
254
 
239
255
  set_event_emitter(emitter)
256
+ set_chat_request_id(request_id)
240
257
 
241
258
  state: Dict[str, Any] = dict(initial or {})
242
259
  state.update(
@@ -69,6 +69,46 @@ slide_cursor_var: ContextVar[Optional[list]] = ContextVar(
69
69
  "graph_slide_cursor", default=None
70
70
  )
71
71
 
72
+ #: The ``ChatRequest.request_id`` this turn is running under, or ``None``.
73
+ #:
74
+ #: **This is not an emission concern, and it is here because the LIFECYCLE is the
75
+ #: same one.** ``GET /chat/poll`` reads a turn's assistant text out of
76
+ #: ``get_messages_for_request``, which filters ``SessionMessage.request_id ==
77
+ #: request_id`` — so a chat row written by a node with no request id is invisible
78
+ #: to every polling client, which is the transport the deployed app uses. The id
79
+ #: cannot travel in ``GraphState`` (undeclared keys are silently dropped and
80
+ #: declaring it would put a transport detail in the graph's contract) and it
81
+ #: cannot be re-derived inside a node, so it rides the same ``ContextVar``
82
+ #: mechanism the emitter does, is set by ``invoke_graph`` on **every** invocation,
83
+ #: and is ``None`` on the SSE path (which has no request row) and on the sweeper.
84
+ chat_request_id_var: ContextVar[Optional[str]] = ContextVar(
85
+ "graph_chat_request_id", default=None
86
+ )
87
+
88
+
89
+ def set_chat_request_id(request_id: Optional[str]) -> None:
90
+ """Bind *request_id* for this turn's context; ``None`` clears it.
91
+
92
+ Called by ``invoke_graph`` unconditionally, for the reason
93
+ :func:`set_event_emitter` is: a value left over from an earlier turn in the
94
+ same process would tag turn 2's rows with turn 1's request, and turn 1's
95
+ poll — which a client may still be draining — would then receive turn 2's
96
+ reply while turn 2's own poll never saw it.
97
+ """
98
+ chat_request_id_var.set(request_id)
99
+
100
+
101
+ def get_chat_request_id() -> Optional[str]:
102
+ """The request id this turn runs under, or ``None`` when there is no request.
103
+
104
+ ``None`` is a normal, correct answer on two paths: the SSE transport (which
105
+ persists no ``ChatRequest`` row at all) and the arc-review sweeper (which has
106
+ no request). A row written with ``None`` is still a durable transcript row and
107
+ still reaches ``_conversation``; it is only the *polling* projection that
108
+ needs the id.
109
+ """
110
+ return chat_request_id_var.get()
111
+
72
112
 
73
113
  def set_event_emitter(emitter: Optional[queue.Queue]) -> None:
74
114
  """Install *emitter* as this context's event queue, and reset the slide cursor.
@@ -78,6 +78,7 @@ from src.services.graph.event_emitter import (
78
78
  advance_slide_cursor,
79
79
  emit_event,
80
80
  emit_slide_ready,
81
+ get_chat_request_id,
81
82
  get_event_emitter,
82
83
  get_slide_cursor,
83
84
  )
@@ -160,6 +161,72 @@ def _emit(
160
161
  )
161
162
 
162
163
 
164
+ def _say(state: dict, message: str, *, message_type: str = "llm_response") -> None:
165
+ """Persist one assistant utterance so it survives the turn. Never raises.
166
+
167
+ **The durable half of speaking to the user, and it is not optional.** An
168
+ ``_emit`` alone reaches the SSE client and NOBODY ELSE:
169
+ ``job_queue.process_chat_request`` handles only ``COMPLETE`` and
170
+ ``SESSION_TITLE`` and discards every other event, and ``GET /chat/poll`` reads
171
+ a turn's assistant text out of **persisted rows**
172
+ (``get_messages_for_request``). So on the polling transport — the one the
173
+ deployed app uses — an emit-only reply is dropped between the job and the
174
+ poll, and the user is shown silence for a turn that completed and called the
175
+ model. Measured live before this existed.
176
+
177
+ The second consumer is the architect itself. ``_conversation`` builds the
178
+ architect's history from these rows and admits an assistant turn only when
179
+ ``message_type`` is in :data:`_AI_TYPES`, so a reply that is not persisted
180
+ with a replayable type leaves the architect able to ask a clarifying question
181
+ and, next turn, unable to remember asking. ``llm_response`` is the type that
182
+ replays; ``info`` deliberately does not (that exclusion is what keeps the deck
183
+ reviewer's advisory out of the architect's prose — see :data:`_AI_TYPES`), so
184
+ ``info`` is the right type for an utterance the user should SEE but the
185
+ architect must not read back as its own words.
186
+
187
+ ``request_id`` comes from the turn's ``ContextVar``, not from state: the
188
+ polling projection filters on that column, and a row without it is durable but
189
+ invisible to the client waiting on it. ``None`` (the SSE path, the sweeper) is
190
+ correct and still writes the row.
191
+
192
+ Not to be used for a machine advisory: :func:`_surface_notice` is the ungated
193
+ writer for those, and it stays ungated because a failure notice's value is that
194
+ it lands on every turn. This one is for a node's own conversational turn.
195
+
196
+ **A describe-only turn writes nothing.** ws4d's arc-review sweeper invokes
197
+ the graph with ``describe_only=True``, no emitter and no human waiting; its
198
+ reply is addressed to nobody. Persisting it would put machine-generated prose
199
+ in a human's transcript AND — worse — feed it into ``_conversation``, so the
200
+ human's next turn would find the architect apparently having said something
201
+ they never saw, in answer to a question they never asked. Suppression is
202
+ keyed on the same turn-scoped flag ``architect_router`` gates dispatch on, and
203
+ it is read HERE rather than passed by each caller so a new caller cannot
204
+ forget it.
205
+
206
+ Never raises: like emission, a transcript write must not be the thing that
207
+ fails a turn that has otherwise produced a deck.
208
+ """
209
+ if bool(scoped_vals(state, "describe_only")):
210
+ return
211
+ session_id = state.get("session_id")
212
+ if not session_id or not message:
213
+ return
214
+ try:
215
+ get_session_manager().add_message(
216
+ session_id,
217
+ role="assistant",
218
+ content=message,
219
+ message_type=message_type,
220
+ request_id=get_chat_request_id(),
221
+ )
222
+ except Exception:
223
+ logger.warning(
224
+ "Could not persist an assistant turn; the user may see silence on "
225
+ "the polling transport",
226
+ exc_info=True,
227
+ )
228
+
229
+
163
230
  def _release_slides(state: dict) -> List[int]:
164
231
  """Emit ``slide_ready`` for every position the reorder buffer NEWLY releases.
165
232
 
@@ -953,8 +1020,12 @@ def _write_reviewed_row(
953
1020
  def _surface_notice(session_id: str, message: str) -> None:
954
1021
  """Persist a machine-generated advisory as an ``info`` chat message.
955
1022
 
956
- The shipped channel for advisories the user must see. Used by every
957
- non-fatal failure path a **fanned** node can take, because those paths
1023
+ The shipped channel for advisories the user must see, and the graph's ONE
1024
+ **ungated** chat writer: it writes on every turn, describe-only included.
1025
+ That is the line between this and :func:`_say` — ``_say`` carries a node's
1026
+ conversational turn and suppresses it when nobody is listening; this carries a
1027
+ machine advisory whose whole value is that it is durable regardless. Used by
1028
+ every non-fatal failure path a **fanned** node can take, because those paths
958
1029
  cannot write ``error_state``: it is single-writer with no reducer, and two
959
1030
  branches failing in the same superstep then raise — measured on the
960
1031
  installed langgraph::
@@ -965,10 +1036,29 @@ def _surface_notice(session_id: str, message: str) -> None:
965
1036
  which would kill the turn, exactly the failure the handler exists to
966
1037
  prevent. A chat message is per-row and concurrency-safe, and unlike a
967
1038
  stream event it survives ``emitter=None`` (the sweeper path).
1039
+
1040
+ Two non-fanned callers use it too — ``foreman_node``'s unreconciled-END notice
1041
+ and ``deck_reviewer_node``'s review advisory — because they want exactly these
1042
+ semantics and each previously carried its own copy of this write, which is how
1043
+ one of them ended up untagged and invisible to the polling client.
1044
+
1045
+ ``request_id`` is stamped because "an advisory the user must see" was only
1046
+ half true before it was: ``GET /chat/poll`` reads a turn's chat text through
1047
+ ``get_messages_for_request``, which filters on that column, so an untagged
1048
+ row was durable and **invisible to every polling client** — the transport the
1049
+ deployed app uses. The value comes from the turn's ``ContextVar``, which
1050
+ survives the ``Send`` fan-out this helper is reached from, and is ``None`` on
1051
+ the SSE path and the sweeper. The ``info`` TYPE is unchanged and must stay:
1052
+ it is what keeps these notices out of ``_conversation``, so the architect
1053
+ never reads a machine advisory back as its own prose.
968
1054
  """
969
1055
  try:
970
1056
  get_session_manager().add_message(
971
- session_id, role="assistant", content=message, message_type="info"
1057
+ session_id,
1058
+ role="assistant",
1059
+ content=message,
1060
+ message_type="info",
1061
+ request_id=get_chat_request_id(),
972
1062
  )
973
1063
  except Exception:
974
1064
  logger.warning("Could not surface a failure notice", exc_info=True)
@@ -1316,8 +1406,14 @@ def architect_node(state: dict) -> Dict[str, Any]:
1316
1406
  "fix_target": None,
1317
1407
  "error_state": None,
1318
1408
  }
1409
+ # BOTH surfaces, and the pairing is the fix. The event is what an SSE client
1410
+ # sees as it happens; the row is what a POLLING client sees at all, and what
1411
+ # the architect reads back next turn as its own words. An emit without a row
1412
+ # is the shape that shipped: the model was called, the turn completed, and the
1413
+ # user saw nothing.
1319
1414
  _emit(StreamEventType.ASSISTANT, content=message,
1320
1415
  metadata={"node": "architect", "intent": intent})
1416
+ _say(state, message)
1321
1417
 
1322
1418
  if intent == "ask_data":
1323
1419
  # GraphState declares no analyst channel and undeclared keys are
@@ -1534,6 +1630,13 @@ def architect_node(state: dict) -> Dict[str, Any]:
1534
1630
  "stale": judged_nothing,
1535
1631
  },
1536
1632
  )
1633
+ # The architect's SECOND utterance of the same turn, and it takes the
1634
+ # same pair of surfaces as its first for one reason: this is the only
1635
+ # place a user is told what the re-review decided. Persisting the
1636
+ # reply above while leaving this one emit-only would make the
1637
+ # architect audible on a plain build turn and mute on a deck-level
1638
+ # one — the same defect, moved rather than fixed.
1639
+ _say(state, message)
1537
1640
 
1538
1641
  external_scripts = [SlideDeck.CHART_JS_URL]
1539
1642
  head_meta = json.dumps(_DEFAULT_HEAD_META)
@@ -1668,6 +1771,18 @@ def data_analyst_node(state: dict) -> Dict[str, Any]:
1668
1771
  content=message,
1669
1772
  metadata={"node": "data_analyst", "outcome": out.outcome},
1670
1773
  )
1774
+ # Persisted as ``info``, NOT ``llm_response``, and the difference is not
1775
+ # cosmetic. This is user-facing prose from a node that speaks to the user, so
1776
+ # it needs a durable row or the polling transport shows the user nothing where
1777
+ # the SSE transport showed them the analyst's answer. But it must NOT replay:
1778
+ # ``_conversation`` has only ``user`` and ``assistant`` roles, so an
1779
+ # ``llm_response`` here would come back to the architect next turn as the
1780
+ # ARCHITECT's own words — and it would arrive twice on THIS turn, because the
1781
+ # analyst also hands the same text to the architect on ``architect_message``
1782
+ # (GraphState declares no analyst channel, Ruling C-7), which is the field the
1783
+ # architect answers from. ``info`` is the shipped type for exactly this:
1784
+ # durable and visible, deliberately outside ``_AI_TYPES``.
1785
+ _say(state, message, message_type="info")
1671
1786
 
1672
1787
  return {"architect_message": message}
1673
1788
 
@@ -1781,15 +1896,12 @@ def foreman_node(state: dict) -> Dict[str, Any]:
1781
1896
  )
1782
1897
  logger.error("Foreman reached END with uncommitted positions: %s", outstanding)
1783
1898
  _emit(StreamEventType.ERROR, error=message, metadata={"node": "foreman"})
1784
- try:
1785
- get_session_manager().add_message(
1786
- state["session_id"],
1787
- role="assistant",
1788
- content=message,
1789
- message_type="info",
1790
- )
1791
- except Exception:
1792
- logger.warning("Could not surface the unreconciled-END notice", exc_info=True)
1899
+ # Through `_surface_notice`, the shipped ungated writer, rather than a
1900
+ # hand-rolled `add_message`: that is where the request-id tag lives, and an
1901
+ # untagged row never reaches `GET /chat/poll`. NOT through `_say` — this
1902
+ # notice is a machine advisory that must be durable on every turn, and `_say`
1903
+ # suppresses a describe-only one.
1904
+ _surface_notice(state["session_id"], message)
1793
1905
  updates["error_state"] = {
1794
1906
  "node": "foreman",
1795
1907
  "code": "end_with_outstanding_positions",
@@ -2664,15 +2776,16 @@ def deck_reviewer_node(state: dict) -> Dict[str, Any]:
2664
2776
  "message": type(exc).__name__,
2665
2777
  }
2666
2778
 
2667
- try:
2668
- manager.add_message(
2669
- session_id,
2670
- role="assistant",
2671
- content=advisory,
2672
- message_type="info",
2673
- )
2674
- except Exception:
2675
- logger.warning("Could not persist the deck-review advisory", exc_info=True)
2779
+ # Through `_surface_notice`, the shipped ungated writer, rather than a
2780
+ # hand-rolled `add_message`: that is where the request-id tag lives, and an
2781
+ # untagged row never reaches `GET /chat/poll`. NOT through `_say`, which
2782
+ # suppresses a describe-only turn's write — a describe-only turn cannot reach
2783
+ # this node today (`architect_router` ends it first) but the unit suite calls
2784
+ # this node directly with the flag set and PINS that the advisory still lands,
2785
+ # deliberately: "gating the advisory is a change to what the user is told".
2786
+ # The `info` TYPE is load-bearing and unchanged — its exclusion from
2787
+ # `_AI_TYPES` keeps this advisory out of the architect's replayed conversation.
2788
+ _surface_notice(session_id, advisory)
2676
2789
  _emit(
2677
2790
  StreamEventType.ASSISTANT,
2678
2791
  content=advisory,