iatoolkit 2.72.2__tar.gz → 2.73.0__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 (279) hide show
  1. {iatoolkit-2.72.2/src/iatoolkit.egg-info → iatoolkit-2.73.0}/PKG-INFO +1 -1
  2. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/__init__.py +1 -1
  3. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/access.py +59 -0
  4. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/locales/en.yaml +1 -0
  5. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/locales/es.yaml +1 -0
  6. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +12 -3
  7. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/memory_repo.py +12 -3
  8. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/models.py +7 -0
  9. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_compiler_service.py +38 -13
  10. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_service.py +79 -11
  11. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_sources.py +7 -3
  12. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_wiki_service.py +47 -10
  13. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/tenant_wiki_service.py +56 -8
  14. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/wiki_page_view.py +3 -0
  15. {iatoolkit-2.72.2 → iatoolkit-2.73.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  16. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/LICENSE +0 -0
  17. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/LICENSE_COMMUNITY.md +0 -0
  18. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/pyproject.toml +0 -0
  19. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/readme.md +0 -0
  20. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/setup.cfg +0 -0
  21. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/base_company.py +0 -0
  22. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/cli_commands.py +0 -0
  23. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/__init__.py +0 -0
  24. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/bridge_validation.py +0 -0
  25. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/company_paths.py +0 -0
  26. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/exceptions.py +0 -0
  27. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  28. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  29. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  30. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  31. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  32. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  33. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  34. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/memory_tools.py +0 -0
  35. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/model_registry.py +0 -0
  36. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/routes.py +0 -0
  37. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/search_terms.py +0 -0
  38. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/secret_resolver.py +0 -0
  39. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/session_manager.py +0 -0
  40. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/text_splitter.py +0 -0
  41. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/url_safety.py +0 -0
  42. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/common/util.py +0 -0
  43. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/company_registry.py +0 -0
  44. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/__init__.py +0 -0
  45. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  46. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_compiler.prompt +0 -0
  47. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_correction.prompt +0 -0
  48. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_document_ingest.prompt +0 -0
  49. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_image_ingest.prompt +0 -0
  50. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_meeting_ingest.prompt +0 -0
  51. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents/memory_question_review.prompt +0 -0
  52. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_agents_pack.yaml +0 -0
  53. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  54. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  55. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  56. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  57. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  58. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  59. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  60. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  61. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  62. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  63. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  64. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  65. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  66. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  67. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  68. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  69. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  70. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  71. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  72. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  73. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  74. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  75. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  76. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  77. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
  78. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  79. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  80. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
  81. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/core.py +0 -0
  82. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/__init__.py +0 -0
  83. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  84. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/call_service.py +0 -0
  85. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  86. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  87. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  88. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  89. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  90. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  91. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  92. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
  93. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  94. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  95. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/inference_handler.py +0 -0
  96. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  97. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  98. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  99. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  100. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  101. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  102. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  103. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  104. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  105. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  106. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/llm_response.py +0 -0
  107. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  108. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/__init__.py +0 -0
  109. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/advisory_lock.py +0 -0
  110. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  111. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  112. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/database_manager.py +0 -0
  113. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/document_repo.py +0 -0
  114. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  115. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  116. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  117. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  118. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
  119. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/memory_usage_repo.py +0 -0
  120. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
  121. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  122. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  123. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  124. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
  125. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/runtime_logging.py +0 -0
  126. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/__init__.py +0 -0
  127. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/api_key_service.py +0 -0
  128. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  129. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/auth_service.py +0 -0
  130. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/branding_service.py +0 -0
  131. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/company_context_service.py +0 -0
  132. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/configuration_service.py +0 -0
  133. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/context_builder_service.py +0 -0
  134. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
  135. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/embedding_service.py +0 -0
  136. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/excel_service.py +0 -0
  137. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/file_processor_service.py +0 -0
  138. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/history_manager_service.py +0 -0
  139. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/http_tool_service.py +0 -0
  140. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/i18n_service.py +0 -0
  141. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/inference_service.py +0 -0
  142. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/jwt_service.py +0 -0
  143. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/knowledge_base_service.py +0 -0
  144. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/language_service.py +0 -0
  145. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/license_service.py +0 -0
  146. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/llm_client_service.py +0 -0
  147. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/mail_service.py +0 -0
  148. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  149. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
  150. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  151. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_question_service.py +0 -0
  152. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/memory_usage_service.py +0 -0
  153. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  154. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
  155. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
  156. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
  157. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  158. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  159. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  160. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  161. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  162. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  163. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  164. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  165. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/parsers/validator.py +0 -0
  166. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/pdf_service.py +0 -0
  167. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/profile_service.py +0 -0
  168. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  169. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/prompt_service.py +0 -0
  170. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/query_service.py +0 -0
  171. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/resource_access_scope.py +0 -0
  172. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  173. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  174. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/sql_service.py +0 -0
  175. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/sql_source_service.py +0 -0
  176. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/storage_service.py +0 -0
  177. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/structured_output_service.py +0 -0
  178. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/system_agent_catalog.py +0 -0
  179. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/system_agent_runner.py +0 -0
  180. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  181. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/system_tools.py +0 -0
  182. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/telemetry_service.py +0 -0
  183. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
  184. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/tool_service.py +0 -0
  185. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  186. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  187. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
  188. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
  189. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/warmup_service.py +0 -0
  190. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
  191. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  192. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  193. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  194. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/web_search_service.py +0 -0
  195. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/services/wiki_page_index_service.py +0 -0
  196. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/images/favicon.ico +0 -0
  197. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/images/favicon.png +0 -0
  198. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  199. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  200. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  201. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  202. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  203. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
  204. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  205. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  206. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  207. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_main.js +0 -0
  208. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  209. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  210. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  211. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  212. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  213. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/account.css +0 -0
  214. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  215. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  216. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
  217. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/documents.css +0 -0
  218. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
  219. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  220. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
  221. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/static/styles/wiki_page.css +0 -0
  222. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/_chat_header.html +0 -0
  223. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/_company_header.html +0 -0
  224. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/_login_widget.html +0 -0
  225. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/account.html +0 -0
  226. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/base.html +0 -0
  227. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/change_password.html +0 -0
  228. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/chat.html +0 -0
  229. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  230. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  231. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/error.html +0 -0
  232. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  233. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  234. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/login_global.html +0 -0
  235. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  236. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  237. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/pdf/base.html +0 -0
  238. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
  239. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/pdf/report.html +0 -0
  240. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
  241. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/signup.html +0 -0
  242. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/templates/wiki_page.html +0 -0
  243. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/__init__.py +0 -0
  244. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/account_view.py +0 -0
  245. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/api_key_api_view.py +0 -0
  246. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/base_login_view.py +0 -0
  247. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/categories_api_view.py +0 -0
  248. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/change_password_view.py +0 -0
  249. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
  250. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/chat_view.py +0 -0
  251. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/configuration_api_view.py +0 -0
  252. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
  253. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
  254. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  255. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
  256. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/history_api_view.py +0 -0
  257. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/home_view.py +0 -0
  258. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
  259. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  260. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
  261. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/login_view.py +0 -0
  262. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/logout_api_view.py +0 -0
  263. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/memory_api_view.py +0 -0
  264. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/profile_api_view.py +0 -0
  265. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/prompt_api_view.py +0 -0
  266. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
  267. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
  268. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/rag_api_view.py +0 -0
  269. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
  270. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/signup_view.py +0 -0
  271. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/static_page_view.py +0 -0
  272. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/tool_api_view.py +0 -0
  273. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  274. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/users_api_view.py +0 -0
  275. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  276. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit.egg-info/SOURCES.txt +0 -0
  277. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  278. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  279. {iatoolkit-2.72.2 → iatoolkit-2.73.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 2.72.2
3
+ Version: 2.73.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- __version__ = "2.72.2"
6
+ __version__ = "2.73.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -224,6 +224,65 @@ def sql_table_filter(company_short_name: str, user_identifier: str | None):
224
224
  return allowed
225
225
 
226
226
 
227
+ # ---------------------------------------------------------------------- audiences
228
+ # Who may see a piece of Memoria content (BL-010, Fase 3). An audience key is None
229
+ # (the whole company) or names what decides it: "memory_folder:<id>", "group:<key>".
230
+
231
+ def audience_visible(company_short_name: str, user_identifier: str | None, audience_key: str | None) -> bool:
232
+ if not audience_key:
233
+ return True
234
+ decider = Access._decider
235
+ hook = getattr(decider, "audience_visible", None) if decider is not None else None
236
+ if not callable(hook):
237
+ return True
238
+ try:
239
+ return bool(hook(company_short_name, user_identifier, audience_key))
240
+ except Exception as exc:
241
+ logging.warning("Audience %s for %s not resolved: %s", audience_key, user_identifier, exc)
242
+ return False
243
+
244
+
245
+ def visible_audiences(company_short_name: str, user_identifier: str | None) -> list[str] | None:
246
+ """The restricted audiences a person sees; None when they see every one (or nothing is restricted).
247
+
248
+ The whole company's content (audience None) is always visible besides these."""
249
+ decider = Access._decider
250
+ hook = getattr(decider, "visible_audiences", None) if decider is not None else None
251
+ if not callable(hook):
252
+ return None
253
+ try:
254
+ return hook(company_short_name, user_identifier)
255
+ except Exception as exc:
256
+ logging.warning("Visible audiences of %s not resolved: %s", user_identifier, exc)
257
+ return []
258
+
259
+
260
+ def audience_label(company_short_name: str, audience_key: str | None) -> str | None:
261
+ """The name people see for an audience (the folder's or group's name); None for the whole company."""
262
+ if not audience_key:
263
+ return None
264
+ decider = Access._decider
265
+ hook = getattr(decider, "audience_label", None) if decider is not None else None
266
+ try:
267
+ return (hook(company_short_name, audience_key) if callable(hook) else None) or audience_key
268
+ except Exception:
269
+ return audience_key
270
+
271
+
272
+ def audience_for_folder(company_short_name: str, folder_id) -> str | None:
273
+ """The audience new content from a Memoria folder takes: its own when the folder is restricted."""
274
+ decider = Access._decider
275
+ hook = getattr(decider, "audience_for_folder", None) if decider is not None else None
276
+ if not callable(hook) or folder_id in (None, ""):
277
+ return None
278
+ try:
279
+ return hook(company_short_name, folder_id)
280
+ except Exception as exc:
281
+ # Unknown: the folder's own audience, the restrictive side.
282
+ logging.warning("Audience of folder %s not resolved: %s", folder_id, exc)
283
+ return f"memory_folder:{folder_id}"
284
+
285
+
227
286
  def person_can(company_short_name: str, auth_result: dict | None, level: int, resource_type: str = "company",
228
287
  resource_key: str | None = None) -> bool:
229
288
  """A signed-in person's access, from an AuthService result.
@@ -794,6 +794,7 @@ ui:
794
794
  access_add_group: "Add group"
795
795
  access_reset: "Back to company access"
796
796
  access_saved: "Access updated."
797
+ access_memory_restricted: "What Memoria knows from this folder ({count} pages) is now seen only by those with access to it."
797
798
  access_group: "Group"
798
799
  access_level: "Level"
799
800
  team_invite_user: "Invite user"
@@ -808,6 +808,7 @@ ui:
808
808
  access_add_group: "Agregar grupo"
809
809
  access_reset: "Volver al acceso de la empresa"
810
810
  access_saved: "Acceso actualizado."
811
+ access_memory_restricted: "Lo que Memoria sabe de esta carpeta ({count} páginas) ahora solo lo ven quienes tienen acceso a ella."
811
812
  access_group: "Grupo"
812
813
  access_level: "Nivel"
813
814
  team_invite_user: "Invitar usuario"
@@ -299,7 +299,7 @@ class KnowledgeWikiRepo:
299
299
  RRF_K = 60
300
300
 
301
301
  def hybrid_search_pages(self, wiki_ids: list[int], query_text: str, query_embedding: list[float] | None,
302
- candidates: int = 40) -> list[tuple[int, float]]:
302
+ candidates: int = 40, audience_keys: list | None = None) -> list[tuple[int, float]]:
303
303
  """
304
304
  (page_id, rrf_score) of the best pages of these wikis, best first: by
305
305
  words (Postgres full text, 'simple', terms OR'ed, the GIN index) and, when
@@ -313,6 +313,15 @@ class KnowledgeWikiRepo:
313
313
  tsquery = "replace(plainto_tsquery('simple'::regconfig, :text_terms)::text, ' & ', ' | ')::tsquery"
314
314
  params = {"wiki_ids": list(wiki_ids), "text_terms": query_text or "", "candidates": int(candidates),
315
315
  "archived": KnowledgeWikiPageStatus.ARCHIVED.name}
316
+ # Audiences (BL-010, Fase 3): only pages whose audience is listed, None being the
317
+ # whole company. Filtered here, before the limit, not after the ranking.
318
+ audience_sql = ""
319
+ if audience_keys is not None:
320
+ keys = [k for k in audience_keys if k]
321
+ clauses = (["p.audience_key IS NULL"] if None in audience_keys else []) + \
322
+ (["p.audience_key = ANY(:audience_keys)"] if keys else [])
323
+ audience_sql = f" AND ({' OR '.join(clauses) or 'FALSE'})"
324
+ params["audience_keys"] = keys
316
325
  branches = [f"""
317
326
  SELECT page_id, ROW_NUMBER() OVER (ORDER BY text_rank DESC, page_id ASC) AS rank
318
327
  FROM (
@@ -320,7 +329,7 @@ class KnowledgeWikiRepo:
320
329
  FROM iat_knowledge_wiki_page_index i
321
330
  JOIN iat_knowledge_wiki_pages p ON p.id = i.page_id,
322
331
  (SELECT {tsquery} AS q) terms
323
- WHERE i.wiki_id = ANY(:wiki_ids) AND p.status::text <> :archived
332
+ WHERE i.wiki_id = ANY(:wiki_ids) AND p.status::text <> :archived{audience_sql}
324
333
  AND {tsvector} @@ terms.q
325
334
  ORDER BY text_rank DESC
326
335
  LIMIT :candidates
@@ -336,7 +345,7 @@ class KnowledgeWikiRepo:
336
345
  i.embedding::vector({dims}) <=> CAST(:query_embedding AS vector({dims})) AS distance
337
346
  FROM iat_knowledge_wiki_page_index i
338
347
  JOIN iat_knowledge_wiki_pages p ON p.id = i.page_id
339
- WHERE i.wiki_id = ANY(:wiki_ids) AND p.status::text <> :archived
348
+ WHERE i.wiki_id = ANY(:wiki_ids) AND p.status::text <> :archived{audience_sql}
340
349
  AND i.embedding IS NOT NULL AND vector_dims(i.embedding) = {dims}
341
350
  ORDER BY distance ASC
342
351
  LIMIT :candidates
@@ -109,12 +109,21 @@ class MemoryRepo:
109
109
  .all()
110
110
  )
111
111
 
112
- def list_recent_items(self, company_id: int, limit: int = 25) -> list[MemoryItem]:
112
+ def list_recent_items(self, company_id: int, limit: int = 25, audience_keys: list | None = None) -> list[MemoryItem]:
113
+ """The company's latest items; with `audience_keys`, only those of captures of those
114
+ audiences (None in the list is the whole company; BL-010, Fase 3)."""
113
115
  if not company_id:
114
116
  return []
117
+ query = self.session.query(MemoryItem).filter(MemoryItem.company_id == company_id)
118
+ if audience_keys is not None:
119
+ from sqlalchemy import false, or_
120
+ keys = [k for k in audience_keys if k]
121
+ clauses = ([MemoryCapture.audience_key.is_(None)] if None in audience_keys else []) + \
122
+ ([MemoryCapture.audience_key.in_(keys)] if keys else [])
123
+ query = query.join(MemoryCapture, MemoryCapture.id == MemoryItem.capture_id) \
124
+ .filter(or_(*clauses) if clauses else false())
115
125
  return (
116
- self.session.query(MemoryItem)
117
- .filter_by(company_id=company_id)
126
+ query
118
127
  .order_by(MemoryItem.created_at.desc(), MemoryItem.id.desc())
119
128
  .limit(limit)
120
129
  .all()
@@ -527,6 +527,10 @@ class MemoryCapture(Base):
527
527
  title = Column(String, nullable=True)
528
528
  status = Column(Enum(MemoryCaptureStatus), nullable=False, default=MemoryCaptureStatus.PENDING)
529
529
  meta = Column(JSON_NATIVE, nullable=True)
530
+ # Who may see what this capture teaches (BL-010, Fase 3): NULL is the whole company,
531
+ # "memory_folder:<id>" a folder with its own access. The compiler writes it only into
532
+ # pages of the same audience. Migration: migrations/2026-10-18-memoria-audiencia.sql
533
+ audience_key = Column(String(255), nullable=True)
530
534
  compile_error = Column(Text, nullable=True)
531
535
  last_compiled_at = Column(DateTime, nullable=True)
532
536
  created_at = Column(DateTime, default=datetime.now, nullable=False)
@@ -859,6 +863,9 @@ class KnowledgeWikiPage(Base):
859
863
  # An empty list denies the page under access control, which is why the sync
860
864
  # writes them from the wiki's access manifest and never from frontmatter.
861
865
  access_tags = Column(JSON_NATIVE, nullable=False, default=list)
866
+ # A Memoria page's audience (BL-010, Fase 3): NULL is the whole company. A page never
867
+ # mixes audiences. Migration: migrations/2026-10-18-memoria-audiencia.sql
868
+ audience_key = Column(String(255), nullable=True)
862
869
  owner = Column(String, nullable=True)
863
870
  source_meta = Column(JSON_NATIVE, nullable=False, default=dict)
864
871
  last_synced_at = Column(DateTime, nullable=True)
@@ -26,11 +26,12 @@ from iatoolkit.repositories.models import (
26
26
  Company, MemoryCapture, MemoryCaptureStatus, MemoryConflict, MemoryItem, MemoryItemStatus, MemoryItemType, VSDoc,
27
27
  )
28
28
  from iatoolkit.repositories.profile_repo import ProfileRepo
29
- from iatoolkit.services.memory_sources import MemorySourceRegistry, register_core_sources
29
+ from iatoolkit.services.memory_sources import VISIBILITY_FOLDER, MemorySourceRegistry, register_core_sources
30
30
  from iatoolkit.services.memory_wiki_service import MemoryWikiService
31
31
  from iatoolkit.services.storage_service import StorageService
32
32
  from iatoolkit.services.system_agent_runner import SystemAgentDisabled, SystemAgentRunner
33
33
  from iatoolkit.services.tenant_wiki_service import TenantWikiService
34
+ from iatoolkit.common import access
34
35
 
35
36
 
36
37
  class MemoryCompilerService:
@@ -222,6 +223,14 @@ class MemoryCompilerService:
222
223
  def _compile_single_capture(self, company: Company, capture: MemoryCapture, items: list[MemoryItem]) -> bool:
223
224
  """Reads the capture with the step its source declares (memory_sources)."""
224
225
  source = MemorySourceRegistry.for_capture(dict(capture.meta or {}))
226
+ if source.visibility == VISIBILITY_FOLDER and not getattr(capture, "audience_key", None):
227
+ # A document saved before its folder was restricted (or before audiences
228
+ # existed) takes the folder's audience now: the restrictive side.
229
+ folder_id = (dict(capture.meta or {}).get("source") or {}).get("folder_id")
230
+ audience = access.audience_for_folder(company.short_name, folder_id)
231
+ if audience:
232
+ capture.audience_key = audience
233
+ self.memory_repo.save_capture(capture)
225
234
  return getattr(self, source.handler)(company, capture, items)
226
235
 
227
236
  def _compile_person_capture(self, company: Company, capture: MemoryCapture, items: list[MemoryItem]) -> bool:
@@ -244,6 +253,7 @@ class MemoryCompilerService:
244
253
  compiled,
245
254
  candidates,
246
255
  edited_by=capture.user_identifier,
256
+ audience_key=self._audience(capture)[0],
247
257
  default_title=self._default_page_title(capture, items),
248
258
  revision_metadata={
249
259
  "source": self.COMPILER_SOURCE,
@@ -282,7 +292,8 @@ class MemoryCompilerService:
282
292
  *,
283
293
  edited_by: str,
284
294
  default_title: str,
285
- revision_metadata: dict) -> dict:
295
+ revision_metadata: dict,
296
+ audience_key: str | None = None) -> dict:
286
297
  """Creates or updates the page the model returned; the wiki's response (it may say the page changed)."""
287
298
  target = self._resolve_target(compiled, candidates)
288
299
  title = str(compiled.get("title") or (target or {}).get("title") or default_title).strip()
@@ -298,6 +309,7 @@ class MemoryCompilerService:
298
309
  edited_by=edited_by,
299
310
  change_summary=change_summary,
300
311
  revision_metadata=revision_metadata,
312
+ audience_key=audience_key,
301
313
  )
302
314
  return self.memory_wiki_service.update_page(
303
315
  company.short_name,
@@ -356,7 +368,7 @@ class MemoryCompilerService:
356
368
  touched: list[int] = []
357
369
  for index, (part_text, read) in enumerate(readers, start=1):
358
370
  for attempt in range(1, self.MAX_SAVE_ATTEMPTS + 1):
359
- candidates = self._document_candidates(company, source, part_text, touched)
371
+ candidates = self._document_candidates(company, source, part_text, touched, capture)
360
372
  answer = read(candidates)
361
373
  if answer is None:
362
374
  raise RuntimeError(f"the model could not read part {index} of {len(readers)}")
@@ -367,6 +379,7 @@ class MemoryCompilerService:
367
379
  response = self._write_page(
368
380
  company, compiled, candidates,
369
381
  edited_by=capture.user_identifier,
382
+ audience_key=self._audience(capture)[0],
370
383
  default_title=label,
371
384
  revision_metadata=revision_metadata,
372
385
  )
@@ -535,30 +548,35 @@ class MemoryCompilerService:
535
548
  parts.append("\n\n".join(current))
536
549
  return parts
537
550
 
538
- def _document_candidates(self, company: Company, source: dict, part_text: str, touched: list[int]) -> list[dict]:
551
+ def _document_candidates(self, company: Company, source: dict, part_text: str, touched: list[int],
552
+ capture: MemoryCapture | None = None) -> list[dict]:
539
553
  """The pages this document already wrote first, then the ones its part is most likely about."""
554
+ audience = self._audience(capture) if capture is not None else [None]
540
555
  ordered: list[int] = list(touched)
541
556
  card = source.get("card") or {}
542
557
  query = " ".join(part for part in (
543
558
  str(source.get("label") or ""), str(card.get("summary") or ""), part_text[:300],
544
559
  ) if part).strip()
545
560
  if query:
546
- for result in self.memory_wiki_service.search_pages(company.short_name, query, limit=self.MAX_CANDIDATE_PAGES):
561
+ for result in self.memory_wiki_service.search_pages(company.short_name, query, limit=self.MAX_CANDIDATE_PAGES,
562
+ audience_keys=audience):
547
563
  if result.get("id") not in ordered:
548
564
  ordered.append(result.get("id"))
549
565
  candidates = []
550
566
  for page_id in ordered[:self.MAX_CANDIDATE_PAGES + len(touched)]:
551
- page = self.memory_wiki_service.get_page(company.short_name, page_id)
567
+ page = self.memory_wiki_service.get_page(company.short_name, page_id, audience_keys=audience)
552
568
  if page:
553
569
  candidates.append(page)
554
570
  return candidates
555
571
 
556
- def _wiki_context(self, company: Company, candidates: list[dict]) -> dict:
572
+ def _wiki_context(self, company: Company, candidates: list[dict], capture: MemoryCapture | None = None) -> dict:
557
573
  return {
558
574
  "wiki_schema": str(self.memory_wiki_service.read_schema() or "")[:7000],
559
575
  "page_index": [
560
576
  {"page_id": entry.get("id"), "title": entry.get("title"), "summary": entry.get("summary")}
561
- for entry in (self.memory_wiki_service.list_pages(company.short_name) or [])[:self.MAX_INDEX_ENTRIES_IN_PROMPT]
577
+ for entry in (self.memory_wiki_service.list_pages(
578
+ company.short_name, audience_keys=self._audience(capture) if capture is not None else [None]) or []
579
+ )[:self.MAX_INDEX_ENTRIES_IN_PROMPT]
562
580
  ],
563
581
  "candidate_pages": [
564
582
  {"page_id": page.get("id"), "title": page.get("title"),
@@ -576,7 +594,7 @@ class MemoryCompilerService:
576
594
  captured_on: str):
577
595
  return self._ingest_with_llm(
578
596
  company, capture, self._agent_for(capture) or self.DOCUMENT_AGENT,
579
- {"source": source, "part": part, "captured_on": captured_on, **self._wiki_context(company, candidates)},
597
+ {"source": source, "part": part, "captured_on": captured_on, **self._wiki_context(company, candidates, capture)},
580
598
  question=f"Read memory document {source.get('label')} (part {part['index']} of {part['total']})",
581
599
  )
582
600
 
@@ -589,7 +607,7 @@ class MemoryCompilerService:
589
607
  captured_on: str):
590
608
  return self._ingest_with_llm(
591
609
  company, capture, self.IMAGE_AGENT,
592
- {"source": source, "captured_on": captured_on, **self._wiki_context(company, candidates)},
610
+ {"source": source, "captured_on": captured_on, **self._wiki_context(company, candidates, capture)},
593
611
  question=f"Look at memory image {source.get('label')}",
594
612
  images=images,
595
613
  )
@@ -776,6 +794,11 @@ class MemoryCompilerService:
776
794
  capture.last_compiled_at = datetime.now()
777
795
  self.memory_repo.save_capture(capture)
778
796
 
797
+ @staticmethod
798
+ def _audience(capture: MemoryCapture) -> list:
799
+ """The one audience a capture compiles into (BL-010, Fase 3): it sees and writes only its pages."""
800
+ return [getattr(capture, "audience_key", None) or None]
801
+
779
802
  def _load_candidate_pages(self, company: Company, capture: MemoryCapture, items: list[MemoryItem]) -> list[dict]:
780
803
  """Pages the capture most likely belongs to, read in full (markdown and checksum)."""
781
804
  ordered_ids: list[int] = []
@@ -788,15 +811,17 @@ class MemoryCompilerService:
788
811
  for page_id in self.memory_repo.list_page_ids_for_items([item.id for item in items if getattr(item, "id", None)]):
789
812
  add(page_id)
790
813
  query = self._memory_capture_query(capture, items)
814
+ audience = self._audience(capture)
791
815
  if query:
792
- for result in self.memory_wiki_service.search_pages(company.short_name, query, limit=self.MAX_CANDIDATE_PAGES):
816
+ for result in self.memory_wiki_service.search_pages(company.short_name, query, limit=self.MAX_CANDIDATE_PAGES,
817
+ audience_keys=audience):
793
818
  add(result.get("id"))
794
819
 
795
820
  candidates = []
796
821
  for page_id in ordered_ids:
797
822
  if len(candidates) >= self.MAX_CANDIDATE_PAGES:
798
823
  break
799
- page = self.memory_wiki_service.get_page(company.short_name, page_id)
824
+ page = self.memory_wiki_service.get_page(company.short_name, page_id, audience_keys=audience)
800
825
  if page:
801
826
  candidates.append(page)
802
827
  return candidates
@@ -827,7 +852,7 @@ class MemoryCompilerService:
827
852
  author=author,
828
853
  captured_on=captured_on,
829
854
  wiki_schema=self.memory_wiki_service.read_schema(),
830
- index_entries=self.memory_wiki_service.list_pages(company.short_name),
855
+ index_entries=self.memory_wiki_service.list_pages(company.short_name, audience_keys=self._audience(capture)),
831
856
  )
832
857
  native_pdf_attachments = self._build_capture_native_pdf_attachments(company.short_name, items)
833
858
  try:
@@ -32,6 +32,7 @@ from iatoolkit.repositories.profile_repo import ProfileRepo
32
32
  from iatoolkit.services.memory_compiler_service import MemoryCompilerService
33
33
  from iatoolkit.services.memory_wiki_service import MemoryWikiService
34
34
  from iatoolkit.services.storage_service import StorageService
35
+ from iatoolkit.common import access
35
36
 
36
37
 
37
38
  class MemoryService:
@@ -110,7 +111,9 @@ class MemoryService:
110
111
  capture_text: str | None = None,
111
112
  new_items: list[dict] | None = None,
112
113
  title: str | None = None,
113
- meta: dict | None = None) -> dict:
114
+ meta: dict | None = None,
115
+ audience_key: str | None = None) -> dict:
116
+ """`audience_key` (BL-010, Fase 3): who may see what it teaches; None is the whole company."""
114
117
  company = self.profile_repo.get_company_by_short_name(company_short_name)
115
118
  if not company:
116
119
  return {"status": "error", "error_message": "company not found"}
@@ -123,6 +126,7 @@ class MemoryService:
123
126
  title=(title or capture_text or "")[:200] or None,
124
127
  status=MemoryCaptureStatus.PENDING,
125
128
  meta=dict(meta or {}),
129
+ audience_key=audience_key or None,
126
130
  )
127
131
  self.memory_repo.create_capture(capture)
128
132
 
@@ -166,7 +170,8 @@ class MemoryService:
166
170
  path: str = "",
167
171
  author: str | None = None,
168
172
  source_meta: dict | None = None,
169
- kind: str | None = None) -> dict:
173
+ kind: str | None = None,
174
+ audience_key: str | None = None) -> dict:
170
175
  """
171
176
  A RAG document that feeds the wiki (BL-014), queued as a capture so the
172
177
  compiler reads it whole, part by part (`memory_document_ingest`; a
@@ -177,10 +182,14 @@ class MemoryService:
177
182
  reads the document's fragments from the RAG when it gets to it.
178
183
  """
179
184
  label = str(label or "").strip() or f"Documento {document_id}"
185
+ # A document of a restricted folder teaches only that folder's audience (BL-010, Fase 3).
186
+ if audience_key is None:
187
+ audience_key = access.audience_for_folder(company_short_name, (source_meta or {}).get("folder_id"))
180
188
  return self.save_capture(
181
189
  company_short_name=company_short_name,
182
190
  user_identifier=author or self.DOCUMENT_SOURCE_IDENTITY,
183
191
  title=label,
192
+ audience_key=audience_key,
184
193
  new_items=[{
185
194
  "item_type": "file",
186
195
  "title": label,
@@ -216,13 +225,17 @@ class MemoryService:
216
225
  company = self.profile_repo.get_company_by_short_name(company_short_name)
217
226
  if not company:
218
227
  return {"status": "error", "error_message": "company not found"}
219
- if not self.memory_repo.list_page_ids_for_document(company.id, document_id):
228
+ page_ids = self.memory_repo.list_page_ids_for_document(company.id, document_id)
229
+ if not page_ids:
220
230
  return {"status": "skipped", "reason": "no page cites this document"}
221
231
  label = str(label or "").strip() or f"Documento {document_id}"
232
+ # The pages that cite it share an audience: the mark runs in it.
233
+ first_page = self.memory_wiki_service.get_page(company_short_name, page_ids[0]) or {}
222
234
  return self.save_capture(
223
235
  company_short_name=company_short_name,
224
236
  user_identifier=removed_by or self.DOCUMENT_SOURCE_IDENTITY,
225
237
  title=label,
238
+ audience_key=first_page.get("audience_key"),
226
239
  new_items=[{
227
240
  "item_type": "note",
228
241
  "title": label,
@@ -335,6 +348,46 @@ class MemoryService:
335
348
  "capture": self.serialize_capture(capture, refreshed_items, company_short_name=company_short_name),
336
349
  }
337
350
 
351
+ @staticmethod
352
+ def audiences_for(company_short_name: str, user_identifier: str | None) -> list | None:
353
+ """The audiences a person may read (BL-010, Fase 3): the whole company (None) plus the
354
+ restricted ones they see; None when nothing is filtered (administration, core alone)."""
355
+ if not access.Access.registered():
356
+ return None
357
+ visible = access.visible_audiences(company_short_name, user_identifier)
358
+ return None if visible is None else [None, *visible]
359
+
360
+ def restrict_document_pages(self, company_short_name: str, document_ids, audience_key: str) -> int:
361
+ """A folder became restricted (BL-010, Fase 3): the company pages that cite its documents
362
+ move to its audience, so what they say stops reaching the whole company. The page keeps
363
+ its path; how many moved is returned."""
364
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
365
+ if not company or not audience_key:
366
+ return 0
367
+ page_ids = set()
368
+ for document_id in {int(d) for d in document_ids or [] if d}:
369
+ page_ids.update(self.memory_repo.list_page_ids_for_document(company.id, document_id))
370
+ moved = 0
371
+ for page_id in sorted(page_ids):
372
+ page = self.memory_wiki_service.get_page(company_short_name, page_id, audience_keys=[None])
373
+ if page is None:
374
+ continue
375
+ if self.memory_wiki_service.set_page_audience(company_short_name, page_id, audience_key):
376
+ moved += 1
377
+ if moved:
378
+ logging.info("Memoria: %s company pages of %s moved to audience %s", moved, company_short_name, audience_key)
379
+ return moved
380
+
381
+ def delete_audience_pages(self, company_short_name: str, audience_key: str, deleted_by: str | None = None) -> int:
382
+ """The audience is gone (its folder was deleted): its pages go with it. How many were deleted."""
383
+ if not audience_key:
384
+ return 0
385
+ deleted = 0
386
+ for page in self.memory_wiki_service.list_audience_pages(company_short_name, audience_key):
387
+ result = self.memory_wiki_service.delete_page(company_short_name, page=page, edited_by=deleted_by)
388
+ deleted += 1 if result.get("status") == "success" else 0
389
+ return deleted
390
+
338
391
  def get_memory_dashboard(self, company_short_name: str, user_identifier: str) -> dict:
339
392
  """What the chat's Memoria panel shows: the user's own captures and the company's pages."""
340
393
  self._compile_on_demand(company_short_name)
@@ -351,11 +404,16 @@ class MemoryService:
351
404
  "status": "success",
352
405
  "captures": serialized_captures,
353
406
  "recent_items": serialized_captures,
354
- "pages": self.list_pages(company_short_name),
407
+ "pages": self.list_pages(company_short_name, user_identifier=user_identifier),
355
408
  }
356
409
 
357
- def list_pages(self, company_short_name: str) -> list[dict]:
358
- return [self.serialize_page(entry) for entry in self.memory_wiki_service.list_pages(company_short_name)]
410
+ def list_pages(self, company_short_name: str, user_identifier: str | None = None,
411
+ audience_keys: list | None = None) -> list[dict]:
412
+ """The pages; with a person, those of the audiences they may read."""
413
+ if audience_keys is None and user_identifier:
414
+ audience_keys = self.audiences_for(company_short_name, user_identifier)
415
+ return [self.serialize_page(entry)
416
+ for entry in self.memory_wiki_service.list_pages(company_short_name, audience_keys=audience_keys)]
359
417
 
360
418
  def search_pages(self,
361
419
  company_short_name: str,
@@ -368,9 +426,11 @@ class MemoryService:
368
426
  if not company:
369
427
  return {"status": "error", "results": [], "error_message": "company not found"}
370
428
 
429
+ audiences = self.audiences_for(company_short_name, user_identifier)
371
430
  results = []
372
431
  source_items_by_page_id = {}
373
- for entry in self.memory_wiki_service.search_pages(company_short_name, query, limit=limit):
432
+ for entry in self.memory_wiki_service.search_pages(company_short_name, query, limit=limit,
433
+ audience_keys=audiences):
374
434
  page_id = entry.get("id")
375
435
  source_items = self._load_page_source_items(company.id, page_id)
376
436
  source_items_by_page_id[page_id] = source_items
@@ -399,6 +459,7 @@ class MemoryService:
399
459
  normalized_query=normalized_query,
400
460
  query_tokens=self._tokenize(query),
401
461
  limit=limit,
462
+ audience_keys=audiences,
402
463
  )
403
464
 
404
465
  response = {
@@ -441,7 +502,9 @@ class MemoryService:
441
502
  if not company:
442
503
  return {"status": "error", "error_message": "company not found"}
443
504
 
444
- page = self.memory_wiki_service.get_page(company_short_name, page_id)
505
+ # A page of an audience the person may not read is "not found" (BL-010, Fase 3).
506
+ page = self.memory_wiki_service.get_page(company_short_name, page_id,
507
+ audience_keys=self.audiences_for(company_short_name, user_identifier))
445
508
  if not page:
446
509
  return {"status": "error", "error_message": "page not found"}
447
510
 
@@ -489,12 +552,16 @@ class MemoryService:
489
552
  text = str(instruction or "").strip()
490
553
  if not text:
491
554
  return {"status": "error", "error_message": "write what to correct or add"}
492
- if not self.memory_wiki_service.get_page(company_short_name, page_id):
555
+ page = self.memory_wiki_service.get_page(company_short_name, page_id,
556
+ audience_keys=self.audiences_for(company_short_name, user_identifier))
557
+ if not page:
493
558
  return {"status": "error", "error_message": "page not found"}
494
559
  response = self.save_capture(
495
560
  company_short_name=company_short_name,
496
561
  user_identifier=user_identifier,
497
562
  capture_text=text,
563
+ # A correction lives in its page's audience (BL-010, Fase 3).
564
+ audience_key=page.get("audience_key"),
498
565
  title=f"Corrección: {text}"[:200],
499
566
  new_items=[{"item_type": "note", "content_text": text, "source_meta": {"origin": "page_correction"}}],
500
567
  meta={
@@ -782,8 +849,9 @@ class MemoryService:
782
849
  company_id: int,
783
850
  normalized_query: str,
784
851
  query_tokens: list[str],
785
- limit: int) -> list[dict]:
786
- items = self.memory_repo.list_recent_items(company_id, limit=max(limit * 12, 60))
852
+ limit: int,
853
+ audience_keys: list | None = None) -> list[dict]:
854
+ items = self.memory_repo.list_recent_items(company_id, limit=max(limit * 12, 60), audience_keys=audience_keys)
787
855
  ranked = []
788
856
  for item in items:
789
857
  score = self._score_memory_item_match(normalized_query, query_tokens, item)
@@ -20,8 +20,10 @@ over three services. A source now declares, in one place:
20
20
  version is read; "add": both stay, as two people saying things);
21
21
  - `on_removal`: what its removal does ("mark_obsolete": its lines are marked,
22
22
  never erased; "none");
23
- - `visibility`: who may read what it teaches. "company" for every source today;
24
- the field is where Fase 4 (BL-010) will declare restricted sources.
23
+ - `visibility`: who may read what it teaches: "company" (people, corrections in the
24
+ page's audience) or "folder" (a document: its folder's audience when restricted).
25
+ The capture carries the audience (`audience_key`) and the compiler writes it only
26
+ into pages of that audience (BL-010, Fase 3).
25
27
 
26
28
  A new source registers here (`MemorySourceRegistry.register`) instead of adding
27
29
  another branch to the compiler.
@@ -36,6 +38,8 @@ VERSION_ADD = "add"
36
38
  REMOVAL_MARK_OBSOLETE = "mark_obsolete"
37
39
  REMOVAL_NONE = "none"
38
40
  VISIBILITY_COMPANY = "company"
41
+ # What it teaches is for its folder's audience when the folder is restricted (BL-010, Fase 3).
42
+ VISIBILITY_FOLDER = "folder"
39
43
 
40
44
 
41
45
  @dataclass(frozen=True)
@@ -93,7 +97,7 @@ def register_core_sources(*, compiler_agent: str, correction_agent: str, documen
93
97
  MemorySourceRegistry.register(MemorySource(
94
98
  key="document", mode=document_mode, handler="_ingest_document", agent=document_agent,
95
99
  kind_agents={meeting_kind: meeting_agent},
96
- on_new_version=VERSION_REPLACE, on_removal=REMOVAL_MARK_OBSOLETE,
100
+ on_new_version=VERSION_REPLACE, on_removal=REMOVAL_MARK_OBSOLETE, visibility=VISIBILITY_FOLDER,
97
101
  description="A document of a Memoria folder (Drive or uploaded), or a meeting transcript.",
98
102
  ))
99
103
  MemorySourceRegistry.register(MemorySource(