databricks-tellr-app 0.3.10.dev3__tar.gz → 0.3.10.dev4__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 (144) hide show
  1. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/setup.py +10 -1
  5. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/mcp_server.py +0 -8
  6. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/deck_contributors.py +6 -1
  7. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/sessions.py +65 -3
  8. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/session_manager.py +2 -0
  9. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/database.py +26 -13
  10. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/session.py +4 -0
  11. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/permission_service.py +81 -20
  12. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/README.md +0 -0
  13. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app/__init__.py +0 -0
  14. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app/run.py +0 -0
  15. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  16. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  17. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app.egg-info/requires.txt +0 -0
  18. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  19. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/setup.cfg +0 -0
  20. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/__init__.py +0 -0
  21. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/__init__.py +0 -0
  22. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/_otel_bootstrap.py +0 -0
  23. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/fixtures/tour_demo_deck.json +0 -0
  24. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/main.py +0 -0
  25. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/mcp_auth.py +0 -0
  26. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/middleware/__init__.py +0 -0
  27. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/middleware/request_logging.py +0 -0
  28. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/__init__.py +0 -0
  29. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/admin.py +0 -0
  30. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/agent_config.py +0 -0
  31. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/chat.py +0 -0
  32. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/export.py +0 -0
  33. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/feedback.py +0 -0
  34. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/google_slides.py +0 -0
  35. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/images.py +0 -0
  36. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/local_version.py +0 -0
  37. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/profiles.py +0 -0
  38. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/settings/__init__.py +0 -0
  39. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/settings/contributors.py +0 -0
  40. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/settings/deck_prompts.py +0 -0
  41. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/settings/identities.py +0 -0
  42. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/settings/slide_styles.py +0 -0
  43. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/setup.py +0 -0
  44. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/slides.py +0 -0
  45. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/tools.py +0 -0
  46. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/tour.py +0 -0
  47. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/verification.py +0 -0
  48. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/routes/version.py +0 -0
  49. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/__init__.py +0 -0
  50. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/agent_config.py +0 -0
  51. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/feedback.py +0 -0
  52. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/requests.py +0 -0
  53. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/responses.py +0 -0
  54. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/settings/__init__.py +0 -0
  55. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/settings/requests.py +0 -0
  56. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/settings/responses.py +0 -0
  57. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/schemas/streaming.py +0 -0
  58. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/__init__.py +0 -0
  59. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/chat_service.py +0 -0
  60. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/export_job_queue.py +0 -0
  61. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/feedback_service.py +0 -0
  62. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/job_queue.py +0 -0
  63. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/services/session_naming.py +0 -0
  64. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/utils/__init__.py +0 -0
  65. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/api/utils/validation.py +0 -0
  66. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/__init__.py +0 -0
  67. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/config_loader.py +0 -0
  68. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/context_utils.py +0 -0
  69. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/databricks_client.py +0 -0
  70. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/defaults.py +0 -0
  71. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/encryption.py +0 -0
  72. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/init_default_profile.py +0 -0
  73. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/lakebase.py +0 -0
  74. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/migrate_profiles_to_agent_config.py +0 -0
  75. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/mlflow_agent_spans.py +0 -0
  76. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/mlflow_tracing.py +0 -0
  77. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/otel_logging.py +0 -0
  78. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/permission_context.py +0 -0
  79. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/prompt_modules.py +0 -0
  80. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/settings_db.py +0 -0
  81. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/core/user_context.py +0 -0
  82. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/__init__.py +0 -0
  83. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/__init__.py +0 -0
  84. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/deck_contributor.py +0 -0
  85. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/feedback.py +0 -0
  86. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/genie_space.py +0 -0
  87. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/google_global_credentials.py +0 -0
  88. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/google_oauth_token.py +0 -0
  89. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/identity.py +0 -0
  90. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/image.py +0 -0
  91. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/profile.py +0 -0
  92. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/profile_contributor.py +0 -0
  93. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/prompts.py +0 -0
  94. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/request_log.py +0 -0
  95. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/slide_deck_prompt.py +0 -0
  96. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/slide_style_library.py +0 -0
  97. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/database/models/user_preference.py +0 -0
  98. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/domain/__init__.py +0 -0
  99. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/domain/slide.py +0 -0
  100. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/domain/slide_deck.py +0 -0
  101. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/__init__.py +0 -0
  102. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/agent.py +0 -0
  103. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/agent_factory.py +0 -0
  104. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/config_service.py +0 -0
  105. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/config_validator.py +0 -0
  106. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/drive_uploader.py +0 -0
  107. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/evaluation/__init__.py +0 -0
  108. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/evaluation/llm_judge.py +0 -0
  109. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/genie_service.py +0 -0
  110. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/google_slides_auth.py +0 -0
  111. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/google_slides_prompts_defaults.py +0 -0
  112. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/html_to_google_slides.py +0 -0
  113. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/html_to_pptx.py +0 -0
  114. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/identity_provider.py +0 -0
  115. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/identity_providers/__init__.py +0 -0
  116. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/identity_providers/account_provider.py +0 -0
  117. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/identity_providers/local_provider.py +0 -0
  118. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/identity_providers/workspace_provider.py +0 -0
  119. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/image_service.py +0 -0
  120. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/image_tools.py +0 -0
  121. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/pptx_from_html_huashu.py +0 -0
  122. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/pptx_from_records.py +0 -0
  123. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/pptx_prompts_defaults.py +0 -0
  124. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/profile_service.py +0 -0
  125. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/streaming_callback.py +0 -0
  126. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/__init__.py +0 -0
  127. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/agent_bricks_tool.py +0 -0
  128. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/genie_tool.py +0 -0
  129. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/mcp_tool.py +0 -0
  130. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/model_endpoint_tool.py +0 -0
  131. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/tools/vector_tool.py +0 -0
  132. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/services/validator.py +0 -0
  133. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/__init__.py +0 -0
  134. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/css_utils.py +0 -0
  135. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/error_handling.py +0 -0
  136. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/html_safety.py +0 -0
  137. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/html_utils.py +0 -0
  138. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/image_utils.py +0 -0
  139. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/js_validator.py +0 -0
  140. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/logging_config.py +0 -0
  141. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/pi_filter.py +0 -0
  142. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/slide_hash.py +0 -0
  143. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/src/utils/spotlight.py +0 -0
  144. {databricks_tellr_app-0.3.10.dev3 → databricks_tellr_app-0.3.10.dev4}/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.3.10.dev3
3
+ Version: 0.3.10.dev4
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.3.10.dev3
3
+ Version: 0.3.10.dev4
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.3.10.dev3"
7
+ version = "0.3.10.dev4"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  import shutil
4
5
  import subprocess
5
6
  from pathlib import Path
@@ -62,6 +63,7 @@ class BuildWithFrontend(build_py):
62
63
  shutil.copytree(src_dir, src_target)
63
64
 
64
65
  sidecars_root.mkdir(parents=True, exist_ok=True)
66
+ # pptx-emit (~1MB) is required for editable PPTX export in deployed apps.
65
67
  if records_sidecar_src.exists():
66
68
  if records_sidecar_target.exists():
67
69
  shutil.rmtree(records_sidecar_target)
@@ -70,7 +72,14 @@ class BuildWithFrontend(build_py):
70
72
  records_sidecar_target,
71
73
  ignore=_SIDECAR_IGNORE,
72
74
  )
73
- if huashu_sidecar_src.exists():
75
+ # huashu sidecar tarballs (~30MB) exceed Databricks Apps workspace
76
+ # file limits for deploy_local; include for PyPI/CI via TELLR_INCLUDE_HUASHU_SIDECAR=1.
77
+ include_huashu = os.environ.get("TELLR_INCLUDE_HUASHU_SIDECAR", "").lower() in (
78
+ "1",
79
+ "true",
80
+ "yes",
81
+ )
82
+ if include_huashu and huashu_sidecar_src.exists():
74
83
  if huashu_sidecar_target.exists():
75
84
  shutil.rmtree(huashu_sidecar_target)
76
85
  shutil.copytree(
@@ -73,12 +73,6 @@ class _PermissionServiceFacade:
73
73
  )
74
74
  if session is None:
75
75
  return False
76
- # Creator always has view access (mirrors PermissionService's
77
- # get_deck_permission creator check, done here on the string-id
78
- # fast path so a brand-new session the caller just created is
79
- # viewable even before any DeckContributor row exists).
80
- if session.created_by and session.created_by == ctx.user_name:
81
- return True
82
76
  return svc.can_view_deck(
83
77
  db,
84
78
  session.id,
@@ -113,8 +107,6 @@ class _PermissionServiceFacade:
113
107
  )
114
108
  if session is None:
115
109
  return False
116
- if session.created_by and session.created_by == ctx.user_name:
117
- return True
118
110
  return svc.can_edit_deck(
119
111
  db,
120
112
  session.id,
@@ -64,6 +64,7 @@ class DeckContributorResponse(BaseModel):
64
64
  class DeckContributorListResponse(BaseModel):
65
65
  contributors: list[DeckContributorResponse]
66
66
  total: int
67
+ global_permission: Optional[str] = None
67
68
 
68
69
 
69
70
  # ---------------------------------------------------------------------------
@@ -153,7 +154,11 @@ def list_deck_contributors(
153
154
  )
154
155
  for c in contributors
155
156
  ]
156
- return DeckContributorListResponse(contributors=items, total=len(items))
157
+ return DeckContributorListResponse(
158
+ contributors=items,
159
+ total=len(items),
160
+ global_permission=session.global_permission,
161
+ )
157
162
 
158
163
 
159
164
  @router.post("", response_model=DeckContributorResponse, status_code=status.HTTP_201_CREATED)
@@ -29,7 +29,11 @@ from src.core.database import get_db, get_db_session
29
29
  from src.core.permission_context import get_permission_context
30
30
  from src.core.user_context import get_current_user
31
31
  from src.database.models.profile_contributor import PermissionLevel
32
- from src.services.permission_service import get_permission_service
32
+ from src.database.models.session import UserSession
33
+ from src.services.permission_service import (
34
+ VALID_DECK_GLOBAL_PERMISSIONS,
35
+ get_permission_service,
36
+ )
33
37
 
34
38
  logger = logging.getLogger(__name__)
35
39
 
@@ -235,7 +239,7 @@ async def list_shared_presentations(
235
239
  limit: int = Query(50, ge=1, le=100, description="Maximum presentations to return"),
236
240
  db: Session = Depends(get_db),
237
241
  ):
238
- """List presentations (slide decks) shared with the current user via deck_contributors.
242
+ """List presentations shared with the current user via deck_contributors or workspace global_permission.
239
243
 
240
244
  Returns presentation-only data from decks where the user has CAN_VIEW, CAN_EDIT,
241
245
  or CAN_MANAGE permission. Conversations (chat messages) are never exposed —
@@ -290,6 +294,8 @@ async def list_shared_presentations(
290
294
  user_name=ctx.user_name,
291
295
  group_ids=ctx.group_ids,
292
296
  )
297
+ if permission is None:
298
+ continue
293
299
 
294
300
  presentations.append({
295
301
  "session_id": s.session_id,
@@ -301,7 +307,7 @@ async def list_shared_presentations(
301
307
  "slide_count": deck.slide_count if deck else 0,
302
308
  "modified_by": getattr(deck, "modified_by", None) or s.created_by,
303
309
  "modified_at": deck.updated_at.isoformat() if deck and deck.updated_at else None,
304
- "my_permission": permission.value if permission else "CAN_VIEW",
310
+ "my_permission": permission.value,
305
311
  })
306
312
 
307
313
  return {"presentations": presentations, "count": len(presentations)}
@@ -314,6 +320,62 @@ async def list_shared_presentations(
314
320
  ) from e
315
321
 
316
322
 
323
+ class DeckGlobalPermissionResponse(BaseModel):
324
+ session_id: str
325
+ global_permission: Optional[str] = None
326
+
327
+
328
+ @router.patch("/{session_id}/global", response_model=DeckGlobalPermissionResponse)
329
+ async def set_deck_global_permission(
330
+ session_id: str,
331
+ permission: Optional[str] = Query(
332
+ None,
333
+ description="CAN_VIEW, CAN_EDIT, or omit/null to clear workspace sharing",
334
+ ),
335
+ db: Session = Depends(get_db),
336
+ ):
337
+ """Set or clear workspace-wide sharing for a deck. Requires CAN_MANAGE."""
338
+ from src.api.routes.deck_contributors import _get_root_session_or_400, _require_manage
339
+
340
+ session = _get_root_session_or_400(db, session_id)
341
+ perm_service = get_permission_service()
342
+ _require_manage(perm_service, db, session)
343
+
344
+ if permission is not None:
345
+ try:
346
+ level = PermissionLevel(permission)
347
+ except ValueError as exc:
348
+ raise HTTPException(
349
+ status_code=400,
350
+ detail=f"Invalid permission level: {permission}",
351
+ ) from exc
352
+ if level not in VALID_DECK_GLOBAL_PERMISSIONS:
353
+ raise HTTPException(
354
+ status_code=400,
355
+ detail=(
356
+ "Workspace sharing supports CAN_VIEW or CAN_EDIT only. "
357
+ "Use individual contributors for CAN_MANAGE."
358
+ ),
359
+ )
360
+ session.global_permission = level.value
361
+ else:
362
+ session.global_permission = None
363
+
364
+ db.commit()
365
+ db.refresh(session)
366
+
367
+ logger.info(
368
+ "Updated workspace sharing for session %s: global_permission=%s",
369
+ session_id,
370
+ session.global_permission,
371
+ )
372
+
373
+ return DeckGlobalPermissionResponse(
374
+ session_id=session.session_id,
375
+ global_permission=session.global_permission,
376
+ )
377
+
378
+
317
379
  @router.post("/{session_id}/contribute")
318
380
  async def get_or_create_contributor_session(
319
381
  session_id: str,
@@ -169,6 +169,7 @@ class SessionManager:
169
169
  "is_contributor_session": session.is_contributor_session,
170
170
  "parent_session_id": parent_session_id_str,
171
171
  "parent_session_internal_id": session.parent_session_id,
172
+ "global_permission": deck_owner.global_permission,
172
173
  }
173
174
 
174
175
  def get_or_create_contributor_session(
@@ -313,6 +314,7 @@ class SessionManager:
313
314
  "message_count": len(s.messages),
314
315
  "has_slide_deck": s.slide_deck is not None,
315
316
  "slide_count": s.slide_deck.slide_count if s.slide_deck is not None else 0,
317
+ "global_permission": s.global_permission,
316
318
  }
317
319
  for s in sessions
318
320
  ]
@@ -518,22 +518,12 @@ def _run_migrations(engine, schema: str | None = None):
518
518
  # --- deck-centric permissions: drop profile_id, migrate CAN_VIEW → CAN_USE ---
519
519
  _migrate_deck_permissions_model(conn, inspector, schema, _qual, is_sqlite)
520
520
 
521
+ # --- deck workspace sharing: global_permission on user_sessions ---
522
+ _migrate_deck_workspace_sharing(conn, inspector, schema, _qual)
523
+
521
524
  # --- image_assets.tags: json → jsonb (PostgreSQL) for native @> queries ---
522
525
  _migrate_image_assets_tags_json_to_jsonb(conn, schema, _qual, is_sqlite)
523
526
 
524
- # --- THROWAWAY devloop ownership probe (NOT FOR MERGE) ---
525
- # Exercises the shared-owner unblock on a fork: ALTER an *inherited*
526
- # app_data_prod table (the case that used to fail "must be owner of
527
- # table") + CREATE a new table (which the reassign step below re-homes).
528
- if not is_sqlite:
529
- logger.info("PROBE: ALTER inherited config_profiles + CREATE _probe_tbl")
530
- conn.execute(text(
531
- f"ALTER TABLE {_qual('config_profiles')} ADD COLUMN IF NOT EXISTS _probe text"
532
- ))
533
- conn.execute(text(
534
- f"CREATE TABLE IF NOT EXISTS {_qual('_probe_tbl')} (col1 text)"
535
- ))
536
-
537
527
  # --- keep newly created objects owned by the shared role (prod forks) ---
538
528
  _reassign_new_objects_to_shared_owner(conn, is_sqlite)
539
529
 
@@ -943,6 +933,29 @@ def _migrate_permissions_columns(conn, inspector, schema, _qual, is_sqlite):
943
933
  logger.info("Migration: permissions columns migration complete")
944
934
 
945
935
 
936
+ def _migrate_deck_workspace_sharing(conn, inspector, schema, _qual) -> None:
937
+ """Add global_permission to user_sessions for workspace-wide deck sharing.
938
+
939
+ NULL = private deck. CAN_VIEW or CAN_EDIT when shared with all workspace users.
940
+ Idempotent.
941
+ """
942
+ from sqlalchemy import text
943
+
944
+ try:
945
+ sessions_cols = {c["name"] for c in inspector.get_columns("user_sessions", schema=schema)}
946
+ except Exception:
947
+ sessions_cols = set()
948
+
949
+ if sessions_cols and "global_permission" not in sessions_cols:
950
+ qualified = _qual("user_sessions")
951
+ logger.info("Migration: adding global_permission column to user_sessions")
952
+ conn.execute(text(
953
+ f"ALTER TABLE {qualified} ADD COLUMN global_permission VARCHAR(20) NULL"
954
+ ))
955
+
956
+ logger.info("Migration: deck workspace sharing columns complete")
957
+
958
+
946
959
  def _migrate_deck_permissions_model(conn, inspector, schema, _qual, is_sqlite):
947
960
  """Decouple deck sharing from profiles.
948
961
 
@@ -101,6 +101,10 @@ class UserSession(Base):
101
101
  user_id = Column(String(255), nullable=True, index=True) # Legacy — kept for backward compat
102
102
  created_by = Column(String(255), nullable=True, index=True) # Username of session creator
103
103
 
104
+ # Workspace-wide deck sharing (root sessions only). NULL = private.
105
+ # CAN_VIEW or CAN_EDIT — CAN_MANAGE is not valid for workspace share.
106
+ global_permission = Column(String(20), nullable=True)
107
+
104
108
  # Contributor session support: links to the owner's session whose slide
105
109
  # deck this contributor reads/writes. NULL = owner (root) session.
106
110
  parent_session_id = Column(
@@ -11,7 +11,8 @@ Users can have permissions through:
11
11
  1. Direct USER-type contributor entry (by identity_id)
12
12
  2. Fallback USER-type contributor entry (by identity_name)
13
13
  3. GROUP-type contributor entry (if user is member of that group)
14
- 4. Being the creator (implicit CAN_MANAGE)
14
+ 4. Workspace-wide global_permission on the root user_sessions row (CAN_VIEW/CAN_EDIT)
15
+ 5. Being the creator (implicit CAN_MANAGE)
15
16
  """
16
17
 
17
18
  import logging
@@ -38,6 +39,12 @@ PERMISSION_PRIORITY = {
38
39
  PermissionLevel.CAN_MANAGE: 3,
39
40
  }
40
41
 
42
+ # Workspace-wide deck sharing — CAN_MANAGE is not valid for global_permission
43
+ VALID_DECK_GLOBAL_PERMISSIONS = frozenset({
44
+ PermissionLevel.CAN_VIEW,
45
+ PermissionLevel.CAN_EDIT,
46
+ })
47
+
41
48
 
42
49
  class PermissionService:
43
50
  """Stateless service for checking user permissions on profiles and decks."""
@@ -206,6 +213,23 @@ class PermissionService:
206
213
  # Deck permission methods (new)
207
214
  # ------------------------------------------------------------------
208
215
 
216
+ @staticmethod
217
+ def _resolve_root_session(db: Session, session: UserSession) -> UserSession:
218
+ """Return the deck-owning root session (walk parent_session_id chain)."""
219
+ current = session
220
+ seen = {current.id}
221
+ while current.parent_session_id is not None:
222
+ parent = (
223
+ db.query(UserSession)
224
+ .filter(UserSession.id == current.parent_session_id)
225
+ .first()
226
+ )
227
+ if parent is None or parent.id in seen:
228
+ break
229
+ seen.add(parent.id)
230
+ current = parent
231
+ return current
232
+
209
233
  def get_deck_permission(
210
234
  self,
211
235
  db: Session,
@@ -217,16 +241,21 @@ class PermissionService:
217
241
  """
218
242
  Get the user's permission level on a deck (UserSession).
219
243
 
220
- Resolution order:
221
- 1. Creator check session.created_by == user_name CAN_MANAGE
222
- 2. Direct user by identity_id
223
- 3. Fallback by identity_name
224
- 4. Group matches (highest wins)
225
- 5. None
244
+ Always resolves contributor (child) sessions to the root deck session
245
+ before evaluating grants. Contributor session creators must not inherit
246
+ CAN_MANAGE on the shared deck.
247
+
248
+ Resolution order (highest level wins):
249
+ 1. Creator check — root.created_by == user_name → CAN_MANAGE
250
+ 2. Direct user by identity_id (on root deck)
251
+ 3. Fallback by identity_name (on root deck)
252
+ 4. Group matches (on root deck)
253
+ 5. Workspace global_permission on root session (CAN_VIEW/CAN_EDIT only)
254
+ 6. None if no grants match
226
255
 
227
256
  Args:
228
257
  db: Database session
229
- session_id: UserSession.id (integer PK)
258
+ session_id: UserSession.id (integer PK), root or contributor
230
259
  user_id: Databricks user ID
231
260
  user_name: Username/email
232
261
  group_ids: List of group IDs the user belongs to
@@ -238,49 +267,73 @@ class PermissionService:
238
267
  if not session:
239
268
  return None
240
269
 
270
+ root = self._resolve_root_session(db, session)
271
+ deck_id = root.id
272
+
241
273
  permissions_found: List[PermissionLevel] = []
242
274
 
243
- # Check 1: Is user the session creator? (implicit CAN_MANAGE)
244
- if user_name and session.created_by == user_name:
245
- logger.debug(f"User {user_name} is creator of session {session_id}")
275
+ # Check 1: Is user the deck owner? (implicit CAN_MANAGE on root only)
276
+ if user_name and root.created_by == user_name:
277
+ logger.debug(f"User {user_name} is creator of deck session {deck_id}")
246
278
  permissions_found.append(PermissionLevel.CAN_MANAGE)
247
279
 
248
280
  # Check 2: Direct user permission (by identity_id)
249
281
  if user_id:
250
282
  direct_match = db.query(DeckContributor).filter(
251
- DeckContributor.user_session_id == session_id,
283
+ DeckContributor.user_session_id == deck_id,
252
284
  DeckContributor.identity_type == "USER",
253
285
  DeckContributor.identity_id == user_id,
254
286
  ).first()
255
287
 
256
288
  if direct_match:
257
- logger.debug(f"Found direct deck permission for user {user_id} on session {session_id}: {direct_match.permission_level}")
289
+ logger.debug(
290
+ f"Found direct deck permission for user {user_id} on deck {deck_id}: "
291
+ f"{direct_match.permission_level}"
292
+ )
258
293
  permissions_found.append(PermissionLevel(direct_match.permission_level))
259
294
 
260
295
  # Check 3: Fallback - Direct user permission (by identity_name)
261
- if user_name and not permissions_found:
296
+ if user_name:
262
297
  name_match = db.query(DeckContributor).filter(
263
- DeckContributor.user_session_id == session_id,
298
+ DeckContributor.user_session_id == deck_id,
264
299
  DeckContributor.identity_type == "USER",
265
300
  DeckContributor.identity_name == user_name,
266
301
  ).first()
267
302
 
268
303
  if name_match:
269
- logger.debug(f"Found deck permission by name for {user_name} on session {session_id}: {name_match.permission_level}")
304
+ logger.debug(
305
+ f"Found deck permission by name for {user_name} on deck {deck_id}: "
306
+ f"{name_match.permission_level}"
307
+ )
270
308
  permissions_found.append(PermissionLevel(name_match.permission_level))
271
309
 
272
310
  # Check 4: Group-based permission
273
311
  if group_ids:
274
312
  group_matches = db.query(DeckContributor).filter(
275
- DeckContributor.user_session_id == session_id,
313
+ DeckContributor.user_session_id == deck_id,
276
314
  DeckContributor.identity_type == "GROUP",
277
315
  DeckContributor.identity_id.in_(group_ids),
278
316
  ).all()
279
317
 
280
318
  for match in group_matches:
281
- logger.debug(f"Found group deck permission on session {session_id}: {match.identity_name} -> {match.permission_level}")
319
+ logger.debug(
320
+ f"Found group deck permission on deck {deck_id}: "
321
+ f"{match.identity_name} -> {match.permission_level}"
322
+ )
282
323
  permissions_found.append(PermissionLevel(match.permission_level))
283
324
 
325
+ # Check 5: Workspace-wide sharing (root sessions only)
326
+ if root.global_permission:
327
+ try:
328
+ global_perm = PermissionLevel(root.global_permission)
329
+ except ValueError:
330
+ global_perm = None
331
+ if global_perm in VALID_DECK_GLOBAL_PERMISSIONS:
332
+ logger.debug(
333
+ f"Found workspace global permission on deck {deck_id}: {global_perm}"
334
+ )
335
+ permissions_found.append(global_perm)
336
+
284
337
  if not permissions_found:
285
338
  return None
286
339
 
@@ -366,8 +419,8 @@ class PermissionService:
366
419
  group_ids: Optional[List[str]] = None,
367
420
  ) -> Set[int]:
368
421
  """
369
- Return set of UserSession.id values shared with this user via deck_contributors,
370
- excluding sessions where the user is the creator.
422
+ Return set of UserSession.id values shared with this user via deck_contributors
423
+ or workspace global_permission, excluding sessions where the user is the creator.
371
424
 
372
425
  Args:
373
426
  db: Database session
@@ -404,6 +457,14 @@ class PermissionService:
404
457
  ).all()
405
458
  shared_session_ids.update(r.user_session_id for r in rows)
406
459
 
460
+ # Workspace-wide shared root sessions (valid levels only)
461
+ valid_global = [p.value for p in VALID_DECK_GLOBAL_PERMISSIONS]
462
+ global_rows = db.query(UserSession.id).filter(
463
+ UserSession.parent_session_id.is_(None),
464
+ UserSession.global_permission.in_(valid_global),
465
+ ).all()
466
+ shared_session_ids.update(r.id for r in global_rows)
467
+
407
468
  if not shared_session_ids:
408
469
  return set()
409
470