iatoolkit 2.69.0__tar.gz → 2.71.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 (278) hide show
  1. {iatoolkit-2.69.0/src/iatoolkit.egg-info → iatoolkit-2.71.0}/PKG-INFO +1 -1
  2. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/__init__.py +1 -1
  3. iatoolkit-2.71.0/src/iatoolkit/common/access.py +206 -0
  4. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/core.py +11 -1
  5. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/locales/en.yaml +49 -0
  6. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/locales/es.yaml +49 -0
  7. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/models.py +6 -0
  8. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/api_key_service.py +13 -4
  9. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/auth_service.py +45 -7
  10. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_wiki_service.py +5 -3
  11. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/profile_service.py +25 -1
  12. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/prompt_service.py +7 -1
  13. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/query_service.py +7 -0
  14. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tenant_wiki_service.py +13 -12
  15. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_chat_header.html +1 -1
  16. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/api_key_api_view.py +7 -4
  17. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/base_login_view.py +1 -1
  18. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/chat_context_preview_api_view.py +2 -2
  19. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/chat_view.py +1 -1
  20. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/configuration_api_view.py +4 -7
  21. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/login_view.py +1 -1
  22. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_api_view.py +11 -6
  23. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +2 -2
  24. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_resource_api_view.py +3 -4
  25. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/tool_api_view.py +2 -2
  26. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/users_api_view.py +5 -0
  27. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/wiki_page_view.py +2 -4
  28. {iatoolkit-2.69.0 → iatoolkit-2.71.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  29. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/SOURCES.txt +1 -0
  30. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/LICENSE +0 -0
  31. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/LICENSE_COMMUNITY.md +0 -0
  32. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/pyproject.toml +0 -0
  33. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/readme.md +0 -0
  34. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/setup.cfg +0 -0
  35. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/base_company.py +0 -0
  36. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/cli_commands.py +0 -0
  37. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/__init__.py +0 -0
  38. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/bridge_validation.py +0 -0
  39. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/company_paths.py +0 -0
  40. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/exceptions.py +0 -0
  41. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  42. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  43. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  44. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  45. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  46. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  47. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  48. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/memory_tools.py +0 -0
  49. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/model_registry.py +0 -0
  50. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/routes.py +0 -0
  51. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/search_terms.py +0 -0
  52. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/secret_resolver.py +0 -0
  53. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/session_manager.py +0 -0
  54. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/text_splitter.py +0 -0
  55. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/url_safety.py +0 -0
  56. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/util.py +0 -0
  57. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/company_registry.py +0 -0
  58. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/__init__.py +0 -0
  59. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  60. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_compiler.prompt +0 -0
  61. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_correction.prompt +0 -0
  62. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_document_ingest.prompt +0 -0
  63. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_image_ingest.prompt +0 -0
  64. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_meeting_ingest.prompt +0 -0
  65. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_question_review.prompt +0 -0
  66. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents_pack.yaml +0 -0
  67. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  68. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  69. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  70. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  71. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  72. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  73. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  74. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  75. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  76. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  77. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  78. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  79. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  80. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  81. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  82. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  83. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  84. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  85. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  86. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  87. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  88. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  89. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  90. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  91. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
  92. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  93. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  94. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
  95. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/__init__.py +0 -0
  96. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  97. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/call_service.py +0 -0
  98. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  99. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  100. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  101. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  102. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  103. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  104. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  105. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
  106. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  107. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  108. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/inference_handler.py +0 -0
  109. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  110. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  111. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  112. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  113. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  114. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  115. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  116. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  117. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  118. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  119. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_response.py +0 -0
  120. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  121. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/__init__.py +0 -0
  122. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/advisory_lock.py +0 -0
  123. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  124. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  125. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/database_manager.py +0 -0
  126. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/document_repo.py +0 -0
  127. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  128. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  129. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
  130. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  131. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  132. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
  133. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_repo.py +0 -0
  134. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_usage_repo.py +0 -0
  135. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
  136. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  137. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  138. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  139. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
  140. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/runtime_logging.py +0 -0
  141. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/__init__.py +0 -0
  142. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  143. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/branding_service.py +0 -0
  144. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/company_context_service.py +0 -0
  145. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/configuration_service.py +0 -0
  146. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/context_builder_service.py +0 -0
  147. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
  148. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/embedding_service.py +0 -0
  149. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/excel_service.py +0 -0
  150. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/file_processor_service.py +0 -0
  151. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/history_manager_service.py +0 -0
  152. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/http_tool_service.py +0 -0
  153. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/i18n_service.py +0 -0
  154. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/inference_service.py +0 -0
  155. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/jwt_service.py +0 -0
  156. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/knowledge_base_service.py +0 -0
  157. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/language_service.py +0 -0
  158. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/license_service.py +0 -0
  159. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/llm_client_service.py +0 -0
  160. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/mail_service.py +0 -0
  161. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  162. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
  163. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_compiler_service.py +0 -0
  164. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  165. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_question_service.py +0 -0
  166. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_service.py +0 -0
  167. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_sources.py +0 -0
  168. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_usage_service.py +0 -0
  169. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  170. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
  171. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
  172. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
  173. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  174. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  175. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  176. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  177. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  178. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  179. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  180. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  181. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/validator.py +0 -0
  182. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/pdf_service.py +0 -0
  183. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  184. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  185. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  186. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_service.py +0 -0
  187. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_source_service.py +0 -0
  188. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/storage_service.py +0 -0
  189. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/structured_output_service.py +0 -0
  190. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_agent_catalog.py +0 -0
  191. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_agent_runner.py +0 -0
  192. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  193. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_tools.py +0 -0
  194. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/telemetry_service.py +0 -0
  195. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
  196. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tool_service.py +0 -0
  197. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  198. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  199. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
  200. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
  201. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/warmup_service.py +0 -0
  202. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
  203. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  204. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  205. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  206. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search_service.py +0 -0
  207. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/wiki_page_index_service.py +0 -0
  208. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/favicon.ico +0 -0
  209. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/favicon.png +0 -0
  210. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  211. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  212. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  213. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  214. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  215. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
  216. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  217. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  218. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  219. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_main.js +0 -0
  220. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  221. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  222. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  223. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  224. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  225. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/account.css +0 -0
  226. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  227. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  228. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
  229. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/documents.css +0 -0
  230. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
  231. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  232. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
  233. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/wiki_page.css +0 -0
  234. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_company_header.html +0 -0
  235. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_login_widget.html +0 -0
  236. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/account.html +0 -0
  237. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/base.html +0 -0
  238. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/change_password.html +0 -0
  239. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat.html +0 -0
  240. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  241. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  242. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/error.html +0 -0
  243. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  244. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  245. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/login_global.html +0 -0
  246. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  247. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  248. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/base.html +0 -0
  249. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
  250. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/report.html +0 -0
  251. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
  252. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/signup.html +0 -0
  253. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/wiki_page.html +0 -0
  254. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/__init__.py +0 -0
  255. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/account_view.py +0 -0
  256. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/categories_api_view.py +0 -0
  257. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/change_password_view.py +0 -0
  258. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
  259. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
  260. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  261. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
  262. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/history_api_view.py +0 -0
  263. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/home_view.py +0 -0
  264. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
  265. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  266. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
  267. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/logout_api_view.py +0 -0
  268. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/memory_api_view.py +0 -0
  269. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/profile_api_view.py +0 -0
  270. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/rag_api_view.py +0 -0
  271. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
  272. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/signup_view.py +0 -0
  273. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/static_page_view.py +0 -0
  274. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  275. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  276. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  277. {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  278. {iatoolkit-2.69.0 → iatoolkit-2.71.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.69.0
3
+ Version: 2.71.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.69.0"
6
+ __version__ = "2.71.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -0,0 +1,206 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ """Who may do what: the one question every check asks (BL-010, Fase 1).
7
+
8
+ Code never names a role. It asks whether a principal (a person's email, or a
9
+ service account `service:<key>`) has a level over a resource:
10
+
11
+ access.can(company, user_identifier, EDIT, "company")
12
+ access.can(company, user_identifier, USE, "agent", "cobranza")
13
+
14
+ Three levels, each including the one below:
15
+ 1 USE use an agent, tool, model, SQL source / see content
16
+ 2 EDIT change the resource (an agent's prompt, a folder's documents)
17
+ 3 ADMIN decide on it: who has access, delete it, its settings
18
+
19
+ Over the resource type "company", EDIT is entering the admin console and
20
+ editing every resource, and ADMIN is administering the company (its team,
21
+ settings). Billing and deleting the tenant are for its owners only (`is_owner`).
22
+
23
+ Enterprise registers the decider backed by groups and grants. Without it,
24
+ the decision falls back to the person's role in the company, as before.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ from typing import Callable, Protocol
31
+
32
+ USE = 1
33
+ EDIT = 2
34
+ ADMIN = 3
35
+
36
+ LEVEL_NAMES = {USE: "use", EDIT: "edit", ADMIN: "admin"}
37
+
38
+ # The level each legacy role holds over everything (core without enterprise).
39
+ ROLE_LEVELS = {"user": USE, "editor": EDIT, "admin": ADMIN, "owner": ADMIN}
40
+
41
+
42
+ class AccessDecider(Protocol):
43
+ def can(self, company_short_name: str, user_identifier: str | None, level: int,
44
+ resource_type: str, resource_key: str | None = None,
45
+ resource_path: str | None = None) -> bool: ...
46
+
47
+ def is_owner(self, company_short_name: str, user_identifier: str | None) -> bool: ...
48
+
49
+ def effective_role(self, company_short_name: str, user_identifier: str | None) -> str | None: ...
50
+
51
+
52
+ class RoleAccessDecider:
53
+ """Core's fallback: the role of iat_user_company grants its level over everything."""
54
+
55
+ def __init__(self, role_reader: Callable[[str, str], str | None]):
56
+ self.role_reader = role_reader
57
+
58
+ def _role(self, company_short_name, user_identifier) -> str | None:
59
+ if not user_identifier:
60
+ return None
61
+ return self.role_reader(company_short_name, user_identifier)
62
+
63
+ def can(self, company_short_name, user_identifier, level, resource_type, resource_key=None,
64
+ resource_path=None) -> bool:
65
+ if level <= USE and resource_type != "company":
66
+ # Without groups nothing is restricted: whoever enters the company uses
67
+ # what it offers (members, and the end users a key acts for).
68
+ return True
69
+ role = self._role(company_short_name, user_identifier)
70
+ if role is None:
71
+ return False
72
+ return ROLE_LEVELS.get(role, USE) >= level
73
+
74
+ def is_owner(self, company_short_name, user_identifier) -> bool:
75
+ return self._role(company_short_name, user_identifier) == "owner"
76
+
77
+ def effective_role(self, company_short_name, user_identifier) -> str | None:
78
+ return self._role(company_short_name, user_identifier)
79
+
80
+
81
+ class Access:
82
+ """The registered decider; enterprise replaces core's fallback at boot."""
83
+
84
+ _decider: AccessDecider | None = None
85
+
86
+ @classmethod
87
+ def register(cls, decider: AccessDecider) -> None:
88
+ cls._decider = decider
89
+
90
+ @classmethod
91
+ def clear(cls) -> None:
92
+ cls._decider = None
93
+
94
+ @classmethod
95
+ def registered(cls) -> bool:
96
+ """Whether a decider other than core's role fallback is in place (enterprise)."""
97
+ return cls._decider is not None and not isinstance(cls._decider, RoleAccessDecider)
98
+
99
+ @classmethod
100
+ def decider(cls) -> AccessDecider:
101
+ if cls._decider is None:
102
+ cls._decider = RoleAccessDecider(_role_from_profile_service)
103
+ return cls._decider
104
+
105
+
106
+ def decider_for(role_reader: Callable[[str, str], str | None]) -> AccessDecider:
107
+ """The registered decider, or the role fallback reading roles with `role_reader`
108
+ (views that already hold a ProfileRepo)."""
109
+ return Access.decider() if Access.registered() else RoleAccessDecider(role_reader)
110
+
111
+
112
+ def _role_from_profile_service(company_short_name: str, user_identifier: str) -> str | None:
113
+ from iatoolkit import current_iatoolkit
114
+ from iatoolkit.services.profile_service import ProfileService
115
+
116
+ return current_iatoolkit().get_injector().get(ProfileService).get_role_in_company(
117
+ company_short_name, user_identifier)
118
+
119
+
120
+ def can(company_short_name: str, user_identifier: str | None, level: int, resource_type: str,
121
+ resource_key: str | None = None, resource_path: str | None = None) -> bool:
122
+ try:
123
+ return bool(Access.decider().can(company_short_name, user_identifier, level, resource_type,
124
+ resource_key, resource_path))
125
+ except Exception as exc:
126
+ # Deciding failed: deny, never fall open.
127
+ logging.warning("Access check failed (%s, %s, %s %s): %s", company_short_name, user_identifier,
128
+ level, resource_type, exc)
129
+ return False
130
+
131
+
132
+ def is_owner(company_short_name: str, user_identifier: str | None) -> bool:
133
+ try:
134
+ return bool(Access.decider().is_owner(company_short_name, user_identifier))
135
+ except Exception as exc:
136
+ logging.warning("Owner check failed (%s, %s): %s", company_short_name, user_identifier, exc)
137
+ return False
138
+
139
+
140
+ def effective_roles(company_short_name: str, user_identifiers: list[str]) -> dict[str, str | None]:
141
+ """`effective_role` for many people at once (the Users screen)."""
142
+ decider = Access.decider()
143
+ try:
144
+ bulk = getattr(decider, "effective_roles", None)
145
+ if callable(bulk):
146
+ return bulk(company_short_name, user_identifiers)
147
+ return {u: decider.effective_role(company_short_name, u) for u in user_identifiers}
148
+ except Exception as exc:
149
+ logging.warning("Effective roles failed (%s): %s", company_short_name, exc)
150
+ return {}
151
+
152
+
153
+ def effective_role(company_short_name: str, user_identifier: str | None) -> str | None:
154
+ """What the person can do over the company, as the console's old role names.
155
+
156
+ Only for screens that still show or branch on a role name (labels, menus);
157
+ access is always decided with `can`.
158
+ """
159
+ try:
160
+ return Access.decider().effective_role(company_short_name, user_identifier)
161
+ except Exception as exc:
162
+ logging.warning("Effective role failed (%s, %s): %s", company_short_name, user_identifier, exc)
163
+ return None
164
+
165
+
166
+ def role_assigned(company_short_name: str, user_identifier: str, role: str) -> None:
167
+ """Somebody was given a legacy role (signup's bootstrap admin, company activation,
168
+ `init-install --owner`): the registered decider puts them in the matching group."""
169
+ decider = Access._decider
170
+ hook = getattr(decider, "role_assigned", None) if decider is not None else None
171
+ if callable(hook):
172
+ try:
173
+ hook(company_short_name, user_identifier, role)
174
+ except Exception as exc:
175
+ logging.warning("Role %s of %s in %s not moved to its group: %s", role, user_identifier,
176
+ company_short_name, exc)
177
+
178
+
179
+ def person_can(company_short_name: str, auth_result: dict | None, level: int, resource_type: str = "company",
180
+ resource_key: str | None = None) -> bool:
181
+ """A signed-in person's access, from an AuthService result.
182
+
183
+ Only sessions carry `user_role`; an API key is not a person and gets False
184
+ (each view decides on its own whether a key may pass).
185
+ """
186
+ if not isinstance(auth_result, dict) or "user_role" not in auth_result:
187
+ return False
188
+ if not Access.registered():
189
+ # Core alone: the session's role was just read from iat_user_company.
190
+ return _session_role_decider(auth_result).can(company_short_name, auth_result.get("user_identifier") or "session",
191
+ level, resource_type, resource_key)
192
+ return can(company_short_name, auth_result.get("user_identifier"), level, resource_type, resource_key)
193
+
194
+
195
+ def person_is_owner(company_short_name: str, auth_result: dict | None) -> bool:
196
+ if not isinstance(auth_result, dict) or "user_role" not in auth_result:
197
+ return False
198
+ if not Access.registered():
199
+ return _session_role_decider(auth_result).is_owner(company_short_name,
200
+ auth_result.get("user_identifier") or "session")
201
+ return is_owner(company_short_name, auth_result.get("user_identifier"))
202
+
203
+
204
+ def _session_role_decider(auth_result: dict) -> RoleAccessDecider:
205
+ role = str(auth_result.get("user_role") or "").strip().lower() or None
206
+ return RoleAccessDecider(lambda company_short_name, user_identifier: role)
@@ -195,6 +195,13 @@ class IAToolkit:
195
195
  thread.start()
196
196
  return thread
197
197
 
198
+ @staticmethod
199
+ def _effective_role(company_short_name: str | None, user_identifier: str | None) -> str | None:
200
+ if not company_short_name or not user_identifier:
201
+ return None
202
+ from iatoolkit.common import access
203
+ return access.effective_role(company_short_name, user_identifier)
204
+
198
205
  def _get_config_value(self, key: str, default=None):
199
206
  # get a value from the config dict or the environment variable
200
207
  return self.config.get(key, os.getenv(key, default))
@@ -653,7 +660,10 @@ class IAToolkit:
653
660
  'app_name': 'IAToolkit',
654
661
  'user_identifier': session_info.get('user_identifier'),
655
662
  'company_short_name': route_company_short_name or session_info.get('company_short_name'),
656
- 'user_role': user_profile.get('user_role'),
663
+ # What the person can do over the company (BL-010, Fase 1), never the
664
+ # role an SSO hook may have written into the session profile.
665
+ 'user_role': self._effective_role(route_company_short_name or session_info.get('company_short_name'),
666
+ session_info.get('user_identifier')),
657
667
  'user_is_local': user_profile.get('user_is_local'),
658
668
  'user_email': user_profile.get('user_email'),
659
669
  'iatoolkit_base_url': request.url_root,
@@ -645,6 +645,48 @@ ui:
645
645
  admin:
646
646
  workspace: "Workspace"
647
647
  team_invitations: "Invitations"
648
+ team_groups: "Groups"
649
+ team_groups_edit: "Groups"
650
+ team_groups_modal_title: "Person's groups"
651
+ team_groups_save: "Save"
652
+ team_groups_close: "Close"
653
+ team_groups_saved: "Groups updated."
654
+ team_groups_everyone_hint: "Every member is in Everyone: no need to tick it."
655
+ team_group_new: "New group"
656
+ team_group_name: "Name"
657
+ team_group_description: "Description"
658
+ team_group_members: "Members"
659
+ team_group_add_member: "Add"
660
+ team_group_remove: "Remove"
661
+ team_group_delete: "Delete group"
662
+ team_group_builtin: "Built-in"
663
+ team_group_save: "Save"
664
+ team_group_edit: "Edit"
665
+ team_group_everyone_note: "Includes everyone in the company."
666
+ team_group_owners_note: "Only an owner can change the owners. Owners is never empty."
667
+ team_group_grants: "Can"
668
+ team_group_no_grants: "No permissions over the company."
669
+ team_group_owners_all: "everything, billing included"
670
+ team_group_delete_confirm: "Delete this group? Its permissions are lost."
671
+ team_group_none_members: "No members."
672
+ team_group_member_placeholder: "person's email"
673
+ team_group_created: "Group created."
674
+ team_group_deleted: "Group deleted."
675
+ team_group_updated: "Group updated."
676
+ access_level_1: "use / see"
677
+ access_level_2: "edit"
678
+ access_level_3: "administer"
679
+ access_all_resources: "every resource"
680
+ access_company: "the company"
681
+ access_title: "Access"
682
+ access_default: "Company access (default)"
683
+ access_default_hint: "Used by whoever has access to every agent. Add groups to restrict it."
684
+ access_own_hint: "Only these groups (and those who administer the company) have access."
685
+ access_add_group: "Add group"
686
+ access_reset: "Back to company access"
687
+ access_saved: "Access updated."
688
+ access_group: "Group"
689
+ access_level: "Level"
648
690
  team_invite_user: "Invite user"
649
691
  team_invite_none: "No invitations yet."
650
692
  team_invite_loading: "Loading invitations..."
@@ -2087,6 +2129,11 @@ ui:
2087
2129
  routing_profile_hint: "Used by enterprise tool routing to rank and select candidate tools."
2088
2130
 
2089
2131
  api_key:
2132
+ can_act_for_users: "May act on behalf of users"
2133
+ can_act_for_users_help: "Without this, the key acts as itself (service:<name>) and cannot pass for anyone. Turn it on only for integrations that must name the user they act for (a portal with its own login, an MCP gateway)."
2134
+ acts: "Acts"
2135
+ acts_as_itself: "As itself"
2136
+ acts_for_users: "On behalf of users"
2090
2137
  title: "API Keys"
2091
2138
  subtitle: "Manage company API keys used to authenticate external requests."
2092
2139
  new_btn: "New API Key"
@@ -2597,6 +2644,8 @@ ui:
2597
2644
 
2598
2645
  errors:
2599
2646
  company_not_found: "The company {company_short_name} does not exist."
2647
+ access:
2648
+ agent_denied: "You do not have access to the agent {prompt_name}."
2600
2649
  timeout: "timeout expired."
2601
2650
  auth:
2602
2651
  invalid_password: "The provided password is incorrect."
@@ -659,6 +659,48 @@ ui:
659
659
  admin:
660
660
  workspace: "Recursos"
661
661
  team_invitations: "Invitaciones"
662
+ team_groups: "Grupos"
663
+ team_groups_edit: "Grupos"
664
+ team_groups_modal_title: "Grupos de la persona"
665
+ team_groups_save: "Guardar"
666
+ team_groups_close: "Cerrar"
667
+ team_groups_saved: "Grupos actualizados."
668
+ team_groups_everyone_hint: "Todos los miembros están en Todos: no hace falta marcarlo."
669
+ team_group_new: "Nuevo grupo"
670
+ team_group_name: "Nombre"
671
+ team_group_description: "Descripción"
672
+ team_group_members: "Miembros"
673
+ team_group_add_member: "Agregar"
674
+ team_group_remove: "Quitar"
675
+ team_group_delete: "Eliminar grupo"
676
+ team_group_builtin: "De fábrica"
677
+ team_group_save: "Guardar"
678
+ team_group_edit: "Editar"
679
+ team_group_everyone_note: "Incluye a todas las personas de la empresa."
680
+ team_group_owners_note: "Solo un dueño puede cambiar los dueños. Dueños nunca queda vacío."
681
+ team_group_grants: "Puede"
682
+ team_group_no_grants: "Sin permisos sobre la empresa."
683
+ team_group_owners_all: "todo, incluida la facturación"
684
+ team_group_delete_confirm: "¿Eliminar este grupo? Sus permisos se pierden."
685
+ team_group_none_members: "Sin miembros."
686
+ team_group_member_placeholder: "email de la persona"
687
+ team_group_created: "Grupo creado."
688
+ team_group_deleted: "Grupo eliminado."
689
+ team_group_updated: "Grupo actualizado."
690
+ access_level_1: "usar / ver"
691
+ access_level_2: "editar"
692
+ access_level_3: "administrar"
693
+ access_all_resources: "todos los recursos"
694
+ access_company: "la empresa"
695
+ access_title: "Acceso"
696
+ access_default: "Acceso de la empresa (por defecto)"
697
+ access_default_hint: "Lo usan quienes tienen acceso a todos los agentes. Agrega grupos para restringirlo."
698
+ access_own_hint: "Solo estos grupos (y quienes administran la empresa) tienen acceso."
699
+ access_add_group: "Agregar grupo"
700
+ access_reset: "Volver al acceso de la empresa"
701
+ access_saved: "Acceso actualizado."
702
+ access_group: "Grupo"
703
+ access_level: "Nivel"
662
704
  team_invite_user: "Invitar usuario"
663
705
  team_invite_none: "Todavía no hay invitaciones."
664
706
  team_invite_loading: "Cargando invitaciones..."
@@ -2105,6 +2147,11 @@ ui:
2105
2147
  routing_profile_hint: "Usado por el router enterprise para rankear y seleccionar herramientas candidatas."
2106
2148
 
2107
2149
  api_key:
2150
+ can_act_for_users: "Puede actuar en nombre de usuarios"
2151
+ can_act_for_users_help: "Sin esta opción, la llave actúa como sí misma (service:<nombre>) y no puede hacerse pasar por nadie. Actívala solo para integraciones que deben indicar el usuario por el que actúan (un portal con login propio, un gateway MCP)."
2152
+ acts: "Actúa"
2153
+ acts_as_itself: "Como sí misma"
2154
+ acts_for_users: "En nombre de usuarios"
2108
2155
  title: "API Keys"
2109
2156
  subtitle: "Administra las API keys de la empresa para autenticar solicitudes externas."
2110
2157
  new_btn: "Nueva API Key"
@@ -2612,6 +2659,8 @@ ui:
2612
2659
 
2613
2660
  errors:
2614
2661
  company_not_found: "La empresa {company_short_name} no existe."
2662
+ access:
2663
+ agent_denied: "No tienes acceso al agente {prompt_name}."
2615
2664
  timeout: "El tiempo de espera ha expirado."
2616
2665
 
2617
2666
 
@@ -9,6 +9,7 @@ from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Enum, Text
9
9
  from sqlalchemy.orm import DeclarativeBase
10
10
  from sqlalchemy.orm import relationship, class_mapper
11
11
  from sqlalchemy.sql import func, literal_column
12
+ from sqlalchemy.sql import text as sql_text
12
13
  from sqlalchemy import UniqueConstraint, Index
13
14
  from sqlalchemy.dialects.postgresql import JSONB
14
15
  from datetime import datetime
@@ -125,6 +126,11 @@ class ApiKey(Base):
125
126
  is_active = Column(Boolean, default=True, nullable=False)
126
127
  created_at = Column(DateTime, default=datetime.now)
127
128
  last_used_at = Column(DateTime, nullable=True) # Opcional: para rastrear uso
129
+ # A key is a service account that acts as itself ("service:<key name>"). Only with
130
+ # this on may it name the user it acts for, who must belong to the company
131
+ # (BL-010, Fase 0). Keys created before it existed keep acting for users;
132
+ # migration: iatoolkit-enterprise/migrations/2026-10-18-api-keys-can-act-for-users.sql
133
+ can_act_for_users = Column(Boolean, nullable=False, default=False, server_default=sql_text("true"))
128
134
 
129
135
  company = relationship("Company", back_populates="api_keys")
130
136
 
@@ -30,6 +30,8 @@ class ApiKeyService:
30
30
  "key_name": api_key.key_name,
31
31
  "key": api_key.key,
32
32
  "is_active": api_key.is_active,
33
+ # BL-010, Fase 0: whether the key may name the user it acts for.
34
+ "can_act_for_users": bool(getattr(api_key, "can_act_for_users", False)),
33
35
  "created_at": api_key.created_at.isoformat() if api_key.created_at else None,
34
36
  "last_used_at": api_key.last_used_at.isoformat() if api_key.last_used_at else None,
35
37
  }
@@ -65,7 +67,7 @@ class ApiKeyService:
65
67
 
66
68
  return {"data": self._api_key_to_dict(api_key)}
67
69
 
68
- def create_api_key_entry(self, company_short_name: str, key_name: str) -> Dict:
70
+ def create_api_key_entry(self, company_short_name: str, key_name: str, can_act_for_users: bool = False) -> Dict:
69
71
  company, error = self._get_company(company_short_name)
70
72
  if error:
71
73
  return error
@@ -81,7 +83,8 @@ class ApiKeyService:
81
83
  alphabet = string.ascii_letters + string.digits
82
84
  key = ''.join(secrets.choice(alphabet) for _ in range(length))
83
85
 
84
- api_key = ApiKey(key=key, company_id=company.id, key_name=key_name)
86
+ # A new key acts as itself unless the admin allows it to act for users (BL-010).
87
+ api_key = ApiKey(key=key, company_id=company.id, key_name=key_name, can_act_for_users=bool(can_act_for_users))
85
88
  created = self.api_key_repo.create_api_key(api_key)
86
89
  return {"data": self._api_key_to_dict(created)}
87
90
 
@@ -89,7 +92,8 @@ class ApiKeyService:
89
92
  company_short_name: str,
90
93
  api_key_id: int,
91
94
  key_name: str | None = None,
92
- is_active: bool | None = None) -> Dict:
95
+ is_active: bool | None = None,
96
+ can_act_for_users: bool | None = None) -> Dict:
93
97
  company, error = self._get_company(company_short_name)
94
98
  if error:
95
99
  return error
@@ -98,7 +102,7 @@ class ApiKeyService:
98
102
  if not api_key:
99
103
  return {"error": "API key not found.", "status_code": 404}
100
104
 
101
- if key_name is None and is_active is None:
105
+ if key_name is None and is_active is None and can_act_for_users is None:
102
106
  return {"error": "No changes provided.", "status_code": 400}
103
107
 
104
108
  if key_name is not None:
@@ -116,6 +120,11 @@ class ApiKeyService:
116
120
  return {"error": "Invalid value for is_active.", "status_code": 400}
117
121
  api_key.is_active = is_active
118
122
 
123
+ if can_act_for_users is not None:
124
+ if not isinstance(can_act_for_users, bool):
125
+ return {"error": "Invalid value for can_act_for_users.", "status_code": 400}
126
+ api_key.can_act_for_users = can_act_for_users
127
+
119
128
  updated = self.api_key_repo.update_api_key(api_key)
120
129
  return {"data": self._api_key_to_dict(updated)}
121
130
 
@@ -14,7 +14,10 @@ from iatoolkit.repositories.models import AccessLog
14
14
  from iatoolkit.infra.google_auth_client import GoogleAuthClient, GoogleAuthError
15
15
  from flask import request
16
16
  import logging
17
+ import re
18
+ import unicodedata
17
19
  import hashlib
20
+ from iatoolkit.common import access
18
21
 
19
22
 
20
23
  class AuthService:
@@ -284,7 +287,11 @@ class AuthService:
284
287
  "success": True,
285
288
  "company_short_name": session_info['company_short_name'],
286
289
  "user_identifier": session_info['user_identifier'],
287
- "user_role": (session_info.get('profile') or {}).get('user_role'),
290
+ # From the database, never from the stored session profile: an SSO
291
+ # hook writes that profile, and must not be able to grant admin
292
+ # (BL-010, Fase 0).
293
+ "user_role": self._role_in_company(session_info['company_short_name'],
294
+ session_info['user_identifier']),
288
295
  }
289
296
 
290
297
  # --- Priority 2: Check for a valid API Key in headers ---
@@ -311,7 +318,22 @@ class AuthService:
311
318
  # get the company from the api_key_entry
312
319
  company = api_key_entry.company
313
320
 
314
- # For API calls, the external_user_id must be provided in the request.
321
+ # The key is a service account (BL-010, Fase 0): it acts as itself unless it
322
+ # was allowed to act for users. Every result says which key acted.
323
+ principal = self.service_principal(api_key_entry)
324
+ base = {
325
+ "success": True,
326
+ "company_short_name": company.short_name,
327
+ "auth_type": "api_key",
328
+ "auth_principal": principal,
329
+ "api_key_id": api_key_entry.id,
330
+ }
331
+ # Only an explicit False: a key loaded before the column existed keeps acting for users.
332
+ if getattr(api_key_entry, "can_act_for_users", None) is False:
333
+ # Whatever user the request names is ignored: the key cannot pass for anyone.
334
+ return {**base, "user_identifier": principal, "acting_for": None}
335
+
336
+ # A key allowed to act for users must say who (unless the endpoint allows none).
315
337
  data = request.get_json(silent=True) or {}
316
338
  user_identifier = data.get('user_identifier', '')
317
339
  if not anonymous and not user_identifier:
@@ -320,11 +342,27 @@ class AuthService:
320
342
  "error_message": self.i18n_service.t('errors.auth.no_user_identifier_api'),
321
343
  "status_code": 403}
322
344
 
323
- return {
324
- "success": True,
325
- "company_short_name": company.short_name,
326
- "user_identifier": user_identifier
327
- }
345
+ return {**base, "user_identifier": user_identifier, "acting_for": user_identifier or None}
346
+
347
+ SERVICE_PREFIX = "service:"
348
+
349
+ @classmethod
350
+ def service_principal(cls, api_key_entry) -> str:
351
+ """The identity a key acts with by itself: "service:<key name>" (lowercase, dashes)."""
352
+ raw = unicodedata.normalize("NFKD", str(getattr(api_key_entry, "key_name", "") or ""))
353
+ ascii_name = raw.encode("ascii", "ignore").decode("ascii").strip().lower()
354
+ name = re.sub(r"[^a-z0-9._-]+", "-", ascii_name)
355
+ return f"{cls.SERVICE_PREFIX}{name.strip('-') or getattr(api_key_entry, 'id', 'key')}"
356
+
357
+ def _role_in_company(self, company_short_name: str, user_identifier: str) -> str | None:
358
+ """What the person can do over the company, by its old role name (BL-010, Fase 1).
359
+
360
+ Views decide with `iatoolkit.common.access`; this name stays in the result to
361
+ tell a session from an API key and for screens that show it.
362
+ """
363
+ if access.Access.registered():
364
+ return access.effective_role(company_short_name, user_identifier)
365
+ return self.profile_service.get_role_in_company(company_short_name, user_identifier)
328
366
 
329
367
  def verify_for_company(self, company_short_name: str, anonymous: bool = False) -> dict:
330
368
  """
@@ -12,6 +12,7 @@ from injector import inject
12
12
 
13
13
  from iatoolkit.services.markdown_wiki_service import MarkdownWikiService
14
14
  from iatoolkit.services.tenant_wiki_service import TenantWikiService
15
+ from iatoolkit.common.access import ADMIN
15
16
 
16
17
 
17
18
  class MemoryWikiService:
@@ -223,7 +224,8 @@ def _read_schema_template(path: str) -> str:
223
224
  return ""
224
225
 
225
226
 
226
- # Memoria's wiki opens in the wiki viewer for admin and owner only ("Ver como wiki",
227
- # BL-014); for every other role, and for the wiki tools and MCP, it stays hidden.
228
- TenantWikiService.register_system_wiki_readers(MemoryWikiService.SYSTEM_OWNER, {"admin", "owner"})
227
+ # Memoria's wiki opens in the wiki viewer only for those who administer the company
228
+ # ("Ver como wiki", BL-014); for everyone else, and for the wiki tools and MCP, it
229
+ # stays hidden.
230
+ TenantWikiService.register_system_wiki_readers(MemoryWikiService.SYSTEM_OWNER, ADMIN)
229
231
 
@@ -28,6 +28,7 @@ from typing import List, Dict
28
28
  from sqlalchemy import update
29
29
  from iatoolkit.common.interfaces.signup_policy_resolver import SignupPolicyResolver
30
30
  from iatoolkit.services.signup_policy_resolver import AllowAllSignupPolicyResolver
31
+ from iatoolkit.common import access
31
32
 
32
33
 
33
34
  class ProfileService:
@@ -107,6 +108,10 @@ class ProfileService:
107
108
  return first_name, last_name
108
109
 
109
110
  def _build_session_user_profile(self, user: User, company: Company, user_role: str | None) -> dict:
111
+ if access.Access.registered():
112
+ # What the person can do over the company (BL-010, Fase 1), for screens and
113
+ # prompt context; access is always decided with `access.can`.
114
+ user_role = access.effective_role(company.short_name, user.email)
110
115
  auth_method = str(getattr(user, "auth_method", "local") or "local").lower()
111
116
  is_local = auth_method == "local"
112
117
  user_identifier = user.email
@@ -150,6 +155,7 @@ class ProfileService:
150
155
  )
151
156
  .values(role="admin")
152
157
  )
158
+ access.role_assigned(company.short_name, user_email, "admin")
153
159
 
154
160
  updated_params = dict(params)
155
161
  updated_params.pop("bootstrap_admin_email", None)
@@ -396,6 +402,20 @@ class ProfileService:
396
402
  "profile": profile
397
403
  }
398
404
 
405
+ def get_role_in_company(self, company_short_name: str, user_identifier: str) -> str | None:
406
+ """
407
+ The person's role in the company as iat_user_company records it; None if
408
+ not a member. Every authorization reads the role here, never from the
409
+ stored session profile: an SSO hook writes that profile (BL-010, Fase 0).
410
+ """
411
+ try:
412
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
413
+ user = self.profile_repo.get_user_by_email(str(user_identifier or "").strip().lower()) if company else None
414
+ return self.profile_repo.get_user_role_in_company(company.id, user.id) if user else None
415
+ except Exception as exc:
416
+ logging.warning("Role of %s in %s could not be read: %s", user_identifier, company_short_name, exc)
417
+ return None
418
+
399
419
  def _account_user(self, company_short_name: str, session_info: dict):
400
420
  profile = session_info.get("profile") or {}
401
421
  method = (profile.get("extras") or {}).get("auth_method")
@@ -416,7 +436,8 @@ class ProfileService:
416
436
  "last_name": user.last_name if user else "",
417
437
  "full_name": f"{user.first_name} {user.last_name}" if user else profile.get("user_fullname", ""),
418
438
  "email": profile.get("user_email") or session_info["user_identifier"],
419
- "role": profile.get("user_role") or "user",
439
+ "role": (access.effective_role(company_short_name, session_info["user_identifier"])
440
+ if access.Access.registered() else profile.get("user_role")) or "user",
420
441
  "auth_method": method,
421
442
  "can_edit_name": bool(user and method == "local"),
422
443
  "language": self.lang_service.get_current_language(),
@@ -938,8 +959,11 @@ class ProfileService:
938
959
  # get the company users from the repo
939
960
  company_users = self.profile_repo.get_company_users_with_details(company_short_name)
940
961
 
962
+ effective = access.effective_roles(company_short_name, [u.email for u, _, _ in company_users]) \
963
+ if access.Access.registered() else {}
941
964
  users_data = []
942
965
  for user, role, last_access in company_users:
966
+ role = effective.get(user.email, role) if access.Access.registered() else role
943
967
  users_data.append({
944
968
  "first_name": user.first_name,
945
969
  "last_name": user.last_name,