fred-runtime 4.3.0__tar.gz → 4.4.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 (237) hide show
  1. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/PKG-INFO +3 -3
  2. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/agent_app.py +64 -38
  3. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_base_client.py +5 -4
  4. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_interceptors.py +7 -12
  5. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_utils.py +6 -10
  6. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/outbound_credentials.py +14 -42
  7. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/v2_runtime/adapters.py +2 -4
  8. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/tool_execution.py +19 -6
  9. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/PKG-INFO +3 -3
  10. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/requires.txt +2 -2
  11. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/pyproject.toml +3 -3
  12. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_agent_app.py +49 -14
  13. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_admission.py +397 -31
  14. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_foreground_route.py +18 -12
  15. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_mcp.py +273 -118
  16. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegation_receiver_integration.py +22 -4
  17. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_openai_compat_router.py +8 -5
  18. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_outbound_credential_paths.py +252 -219
  19. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_outbound_credentials.py +119 -55
  20. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_observability_middleware.py +138 -29
  21. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/README.md +0 -0
  22. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/__init__.py +0 -0
  23. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/__main__.py +0 -0
  24. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/__init__.py +0 -0
  25. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/_catalogs.py +0 -0
  26. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/chat_input_limit.py +0 -0
  27. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/config.py +0 -0
  28. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/config_loader.py +0 -0
  29. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/container.py +0 -0
  30. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/context.py +0 -0
  31. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/dependencies.py +0 -0
  32. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/execution_diagnostics.py +0 -0
  33. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/filesystem_factory.py +0 -0
  34. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/mcp_config.py +0 -0
  35. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/observability_factory.py +0 -0
  36. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/openai_compat_router.py +0 -0
  37. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/platform_sql.py +0 -0
  38. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/__init__.py +0 -0
  39. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/assembly.py +0 -0
  40. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/assets.py +0 -0
  41. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/errors.py +0 -0
  42. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/mcp.py +0 -0
  43. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/openapi_dump.py +0 -0
  44. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/registry.py +0 -0
  45. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/__init__.py +0 -0
  46. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/completion.py +0 -0
  47. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/entrypoint.py +0 -0
  48. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/history_display.py +0 -0
  49. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/kpi_display.py +0 -0
  50. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/pod_client.py +0 -0
  51. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/repl.py +0 -0
  52. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/repl_helpers.py +0 -0
  53. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/url_helpers.py +0 -0
  54. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/client.py +0 -0
  55. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/__init__.py +0 -0
  56. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/background_tasks.py +0 -0
  57. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/context_aware_tool.py +0 -0
  58. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_document_client.py +0 -0
  59. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_fast_text_client.py +0 -0
  60. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_http_client.py +0 -0
  61. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_markdown_media_client.py +0 -0
  62. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_tag_client.py +0 -0
  63. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
  64. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_workspace_client.py +0 -0
  65. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_runtime.py +0 -0
  66. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_toolkit.py +0 -0
  67. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/structures.py +0 -0
  68. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/table_hits.py +0 -0
  69. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/token_expiry.py +0 -0
  70. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/tool_node_utils.py +0 -0
  71. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/conversation_filesystem.py +0 -0
  72. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/__init__.py +0 -0
  73. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/concat_files.py +0 -0
  74. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/conversation_backend.py +0 -0
  75. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/conversation_port.py +0 -0
  76. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/deep_runtime.py +0 -0
  77. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/eval/__init__.py +0 -0
  78. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/eval/collector.py +0 -0
  79. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/execution_errors.py +0 -0
  80. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/__init__.py +0 -0
  81. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/graph_executor.py +0 -0
  82. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/graph_runtime.py +0 -0
  83. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/node_context.py +0 -0
  84. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/__init__.py +0 -0
  85. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/inprocess_toolkit_registry.py +0 -0
  86. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/kf_vector_search/__init__.py +0 -0
  87. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/kf_vector_search/toolkit.py +0 -0
  88. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
  89. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/__init__.py +0 -0
  90. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/env.py +0 -0
  91. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/script.py.mako +0 -0
  92. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/__init__.py +0 -0
  93. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/a1e2f3c4d5b6_create_session_history.py +0 -0
  94. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/b2f3a4e5c6d7_add_exchange_id.py +0 -0
  95. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/c3d4b5a6f7e8_add_team_and_instance.py +0 -0
  96. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/d4e5c6b7a8f9_drop_demo_echo_tables.py +0 -0
  97. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/__init__.py +0 -0
  98. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/authz.py +0 -0
  99. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/catalog.py +0 -0
  100. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/contracts.py +0 -0
  101. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/provider.py +0 -0
  102. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/resolver.py +0 -0
  103. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/__init__.py +0 -0
  104. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/__init__.py +0 -0
  105. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/checkpoint_hygiene.py +0 -0
  106. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/dynamic_prompt.py +0 -0
  107. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/frame.py +0 -0
  108. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/hitl.py +0 -0
  109. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/rate_limit_retry.py +0 -0
  110. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/shared.py +0 -0
  111. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tool_call_recovery.py +0 -0
  112. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tool_observability.py +0 -0
  113. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tracing_kpi.py +0 -0
  114. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_langchain_adapter.py +0 -0
  115. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_message_codec.py +0 -0
  116. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_model_adapter.py +0 -0
  117. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_prompting.py +0 -0
  118. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_runtime.py +0 -0
  119. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_stream_adapter.py +0 -0
  120. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_binding.py +0 -0
  121. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_loop.py +0 -0
  122. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_rendering.py +0 -0
  123. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_resolution.py +0 -0
  124. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_utils.py +0 -0
  125. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tracing.py +0 -0
  126. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_context.py +0 -0
  127. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/__init__.py +0 -0
  128. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/authority.py +0 -0
  129. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/checkpoints.py +0 -0
  130. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/model_metadata.py +0 -0
  131. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
  132. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/run_scope.py +0 -0
  133. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
  134. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/tool_approval.py +0 -0
  135. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/trace_payloads.py +0 -0
  136. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
  137. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/__init__.py +0 -0
  138. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/filesystem_context.py +0 -0
  139. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/thinking.py +0 -0
  140. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/tool_loop.py +0 -0
  141. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/SOURCES.txt +0 -0
  142. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/dependency_links.txt +0 -0
  143. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/entry_points.txt +0 -0
  144. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/top_level.txt +0 -0
  145. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/setup.cfg +0 -0
  146. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_agent_config_assets_path.py +0 -0
  147. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_agent_1973.py +0 -0
  148. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_chat_controls_1976.py +0 -0
  149. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_chat_parts_1977.py +0 -0
  150. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_endpoints_1974.py +0 -0
  151. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_registry_1973.py +0 -0
  152. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_routes_tables_1979.py +0 -0
  153. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_selection_1974.py +0 -0
  154. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_chat_input_limit.py +0 -0
  155. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_checkpoint_namespace.py +0 -0
  156. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_client.py +0 -0
  157. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_config_loader.py +0 -0
  158. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_context.py +0 -0
  159. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_context_aware_tool.py +0 -0
  160. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_conversation_filesystem.py +0 -0
  161. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_conversational_memory.py +0 -0
  162. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_dispatch.py +0 -0
  163. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_hitl_integration.py +0 -0
  164. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_middleware.py +0 -0
  165. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_compiled_filesystem_tools.py +0 -0
  166. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_concat_files.py +0 -0
  167. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_conversation_filesystem.py +0 -0
  168. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_internal_filesystem.py +0 -0
  169. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_port_error_redaction.py +0 -0
  170. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_read_scope.py +0 -0
  171. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_similarity_adapter.py +0 -0
  172. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_eval_collector.py +0 -0
  173. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_eval_trace.py +0 -0
  174. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_execution_diagnostics.py +0 -0
  175. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_filesystem_factory.py +0 -0
  176. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_fred_workspace_fs.py +0 -0
  177. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_capability_bridge.py +0 -0
  178. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_capability_hitl.py +0 -0
  179. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_checkpoint_namespace.py +0 -0
  180. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_executor_storage.py +0 -0
  181. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_invoke_agent.py +0 -0
  182. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_observability.py +0 -0
  183. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_token_usage_totals.py +0 -0
  184. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_history.py +0 -0
  185. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_history_display.py +0 -0
  186. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_hitl_resume_langgraph_integration.py +0 -0
  187. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_base_client_refresh_shapes.py +0 -0
  188. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_vector_search_toolkit.py +0 -0
  189. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_workspace_client.py +0 -0
  190. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_knowledge_search_table_ordering.py +0 -0
  191. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_knowledge_search_tool_invoker_sources.py +0 -0
  192. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kpi_display.py +0 -0
  193. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_langfuse_tracing.py +0 -0
  194. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_langgraph_interrupt_id_semantics.py +0 -0
  195. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_mcp_config.py +0 -0
  196. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_migrations.py +0 -0
  197. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_enforcement.py +0 -0
  198. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_metadata_cache_tokens.py +0 -0
  199. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_reasoning_enablement.py +0 -0
  200. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_routing.py +0 -0
  201. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_routing_provider.py +0 -0
  202. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_models_catalog_projection.py +0 -0
  203. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_platform_sql.py +0 -0
  204. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_platform_sql_postgres_integration.py +0 -0
  205. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_pod_client.py +0 -0
  206. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_rate_limit_retry_middleware.py +0 -0
  207. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_checkpoint_namespace.py +0 -0
  208. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_loop_regressions_1972.py +0 -0
  209. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_message_codec_resume.py +0 -0
  210. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_middleware_frame.py +0 -0
  211. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_prompting.py +0 -0
  212. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_thinking.py +0 -0
  213. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_token_usage_totals.py +0 -0
  214. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_binding_span_status.py +0 -0
  215. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_error_final_2244.py +0 -0
  216. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_resolution.py +0 -0
  217. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_repl_helpers.py +0 -0
  218. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_run_scope.py +0 -0
  219. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_run_stop_authority.py +0 -0
  220. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_runtime_context_prompt_injection.py +0 -0
  221. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_similarity_search_tool_invoker.py +0 -0
  222. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_smoke.py +0 -0
  223. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_spike_capability_state_1971.py +0 -0
  224. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_hitl_claim.py +0 -0
  225. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_owner.py +0 -0
  226. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_session_postgres.py +0 -0
  227. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_table_repair_wiring.py +0 -0
  228. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_team_wiki_adapter.py +0 -0
  229. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_token_expiry.py +0 -0
  230. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_call_recovery.py +0 -0
  231. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_carrier_middleware_naming.py +0 -0
  232. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_loop_trim.py +0 -0
  233. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_trace_payloads.py +0 -0
  234. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tracing_kpi_content_confinement.py +0 -0
  235. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_url_helpers.py +0 -0
  236. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_user_token_refresher.py +0 -0
  237. {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_vector_similarity_search_retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-runtime
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
5
5
  Author-email: Thales <noreply@thalesgroup.com>
6
6
  License: Apache-2.0
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
12
12
  Classifier: Operating System :: OS Independent
13
13
  Requires-Python: <3.13,>=3.12
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: fred-core>=4.3.0
16
- Requires-Dist: fred-sdk[agents]>=4.3.0
15
+ Requires-Dist: fred-core>=4.4.0
16
+ Requires-Dist: fred-sdk[agents]>=4.4.0
17
17
  Requires-Dist: alembic>=1.18.4
18
18
  Requires-Dist: deepagents>=0.7.19
19
19
  Requires-Dist: httpx>=0.28.1
@@ -43,6 +43,7 @@ from __future__ import annotations
43
43
  import asyncio
44
44
  import json
45
45
  import logging
46
+ import re
46
47
  import time
47
48
  from collections.abc import (
48
49
  AsyncGenerator,
@@ -64,8 +65,8 @@ from fastapi.middleware.cors import CORSMiddleware
64
65
  from fastapi.responses import StreamingResponse
65
66
  from fred_core.common.config_loader import get_config
66
67
  from fred_core.common.fastapi_handlers import (
68
+ ACCOUNT_STATUS_UNAVAILABLE_CAUSE,
67
69
  DENIAL_CAUSE_HEADER,
68
- STANDING_UNAVAILABLE_CAUSE,
69
70
  )
70
71
  from fred_core.common.fastapi_handlers import (
71
72
  register_exception_handlers as register_authorization_handlers,
@@ -79,7 +80,13 @@ from fred_core.logs.audit_log import emit_audit_log
79
80
  from fred_core.logs.log_setup import log_setup
80
81
  from fred_core.logs.log_store_factory import build_log_store
81
82
  from fred_core.security.backend_to_backend_auth import M2MBearerAuth
82
- from fred_core.security.delegation import AssertedUser, holds_caller_role
83
+ from fred_core.security.delegation import (
84
+ AssertedUser,
85
+ DelegationGrant,
86
+ enforce_account_status,
87
+ holds_caller_role,
88
+ )
89
+ from fred_core.security.models import AuthorizationError
83
90
  from fred_core.security.rebac.rebac_engine import (
84
91
  ORGANIZATION_ID,
85
92
  OrganizationPermission,
@@ -251,6 +258,13 @@ from .observability_factory import bootstrap_observability
251
258
 
252
259
  logger = logging.getLogger(__name__)
253
260
 
261
+ # Under delegation an audit event keeps codes only: free text can name a person.
262
+ _BOUNDED_AUDIT_CODE = re.compile(r"[a-z_]+")
263
+
264
+
265
+ def _is_bounded_audit_code(value: object) -> bool:
266
+ return isinstance(value, str) and _BOUNDED_AUDIT_CODE.fullmatch(value) is not None
267
+
254
268
 
255
269
  def _emit_audit_event(
256
270
  container: PodApplicationContext,
@@ -266,11 +280,12 @@ def _emit_audit_event(
266
280
  so every audit-worthy event across the runtime (this one, and tool-call
267
281
  invocations in ContextAwareTool) lands identically shaped.
268
282
  """
269
- delegation = get_delegation_runtime()
270
- if delegation is not None and delegation.enabled:
283
+ if delegation_enabled():
284
+ outcome, reason = fields.get("outcome"), fields.get("reason")
285
+ derived = "rejected" if level in {"warning", "error"} else "accepted"
271
286
  fields = {
272
- "outcome": "rejected" if level in {"warning", "error"} else "accepted",
273
- "reason": name,
287
+ "outcome": outcome if _is_bounded_audit_code(outcome) else derived,
288
+ "reason": reason if _is_bounded_audit_code(reason) else name,
274
289
  }
275
290
  event = cast(
276
291
  AuditEventRecord,
@@ -1752,12 +1767,13 @@ async def _resolve_agent_instance(
1752
1767
  response = await http_client.get(url, **request_kwargs)
1753
1768
  if (
1754
1769
  response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
1755
- and response.headers.get(DENIAL_CAUSE_HEADER) == STANDING_UNAVAILABLE_CAUSE
1770
+ and response.headers.get(DENIAL_CAUSE_HEADER)
1771
+ == ACCOUNT_STATUS_UNAVAILABLE_CAUSE
1756
1772
  ):
1757
1773
  raise HTTPException(
1758
1774
  status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
1759
- detail="Account standing could not be checked. Try again shortly.",
1760
- headers={DENIAL_CAUSE_HEADER: STANDING_UNAVAILABLE_CAUSE},
1775
+ detail="Account status could not be checked. Try again shortly.",
1776
+ headers={DENIAL_CAUSE_HEADER: ACCOUNT_STATUS_UNAVAILABLE_CAUSE},
1761
1777
  )
1762
1778
  if response.status_code == status.HTTP_404_NOT_FOUND:
1763
1779
  raise HTTPException(
@@ -1939,16 +1955,12 @@ async def _authorize_execution_or_raise(
1939
1955
  ),
1940
1956
  )
1941
1957
  # A service identity runs as itself, identity-only, as with delegation off.
1942
- if delegated and not is_service_agent(authenticated_user):
1958
+ team_id = request.effective_team_id()
1959
+ if delegated and team_id and not is_service_agent(authenticated_user):
1943
1960
  rebac = get_runtime_context().config.rebac_engine
1944
- if rebac is None or not rebac.enabled:
1945
- raise HTTPException(status_code=503, detail="standing_unavailable")
1946
- await rebac.require_user_standing(authenticated_user.uid)
1947
- if request.effective_team_id():
1961
+ if rebac is not None:
1948
1962
  await rebac.check_user_team_permission_or_raise(
1949
- authenticated_user,
1950
- TeamPermission.CAN_USE_TEAM_AGENTS,
1951
- request.effective_team_id(),
1963
+ authenticated_user, TeamPermission.CAN_USE_TEAM_AGENTS, team_id
1952
1964
  )
1953
1965
  return
1954
1966
 
@@ -2000,9 +2012,19 @@ async def _authorize_execution_or_raise(
2000
2012
  # already authorizes the owner and denies everyone else — no special-
2001
2013
  # casing needed here (the bare "personal" alias also denies normally,
2002
2014
  # since no tuple is ever provisioned for that literal string).
2003
- await rebac.check_user_team_permission_or_raise(
2004
- authenticated_user, TeamPermission.CAN_USE_TEAM_AGENTS, team_id
2005
- )
2015
+ try:
2016
+ await rebac.check_user_team_permission_or_raise(
2017
+ authenticated_user, TeamPermission.CAN_USE_TEAM_AGENTS, team_id
2018
+ )
2019
+ except AuthorizationError:
2020
+ _emit_audit_event(
2021
+ container,
2022
+ "warning",
2023
+ "rebac_denied",
2024
+ outcome="rejected",
2025
+ reason="permission_refused",
2026
+ )
2027
+ raise
2006
2028
  _emit_audit_event(
2007
2029
  container,
2008
2030
  "info",
@@ -2147,19 +2169,28 @@ def _admit_run_credentials(
2147
2169
  agent_instance_id or agent_id
2148
2170
  ):
2149
2171
  raise HTTPException(status_code=403, detail="delegation_target_mismatch")
2150
- record = delegation.records.admit(
2151
- RunRecord(
2152
- run_id=(
2153
- authenticated_user.run_id
2154
- if isinstance(authenticated_user, AssertedUser)
2155
- else str(uuid4())
2156
- ),
2157
- person_id=authenticated_user.uid,
2158
- agent_id=(agent_instance_id or agent_id or "unknown_agent"),
2172
+ run_id = (
2173
+ authenticated_user.run_id
2174
+ if isinstance(authenticated_user, AssertedUser)
2175
+ else str(uuid4())
2176
+ )
2177
+ agent = agent_instance_id or agent_id or "unknown_agent"
2178
+ try:
2179
+ # A receiver rejects a grant its rule refuses, so such a run would act
2180
+ # for nobody; refuse it before a record exists.
2181
+ DelegationGrant(person=authenticated_user.uid, run=run_id, agent=agent)
2182
+ except ValidationError:
2183
+ _emit_audit_event(
2184
+ container,
2185
+ "warning",
2186
+ "delegation_unavailable",
2187
+ outcome="rejected",
2188
+ reason="invalid_parameters",
2159
2189
  )
2190
+ raise HTTPException(status_code=403, detail="delegation_unavailable") from None
2191
+ record = delegation.records.admit(
2192
+ RunRecord(run_id=run_id, person_id=authenticated_user.uid, agent_id=agent)
2160
2193
  )
2161
- # Both halves of what a delegated call presents — the program that speaks and
2162
- # the person it speaks for — and neither of the credentials behind them.
2163
2194
  _emit_audit_event(
2164
2195
  container,
2165
2196
  "info",
@@ -5858,13 +5889,8 @@ def create_agent_app(
5858
5889
  if security is not None
5859
5890
  else None
5860
5891
  )
5861
- # The engine enforces standing in either direction; check its seed here.
5862
- if security is not None and security.delegation.in_use:
5863
- if rebac_engine is None:
5864
- raise ValueError("Delegation requires a relationship engine.")
5865
- await rebac_engine.validate_standing_model()
5866
- if not await rebac_engine.is_standing_seed_ready():
5867
- raise ValueError("Account standing is not ready.")
5892
+ if rebac_engine is not None:
5893
+ await enforce_account_status(rebac_engine)
5868
5894
  chat_factory = _build_chat_model_factory(config)
5869
5895
  if model_provider is not None and isinstance(
5870
5896
  chat_factory, RoutedChatModelFactory
@@ -19,8 +19,8 @@ from typing import Any, Dict, Optional, Protocol
19
19
 
20
20
  import httpx
21
21
  from fred_core.common.fastapi_handlers import (
22
+ ACCOUNT_STATUS_UNAVAILABLE_CAUSE,
22
23
  DENIAL_CAUSE_HEADER,
23
- STANDING_UNAVAILABLE_CAUSE,
24
24
  )
25
25
  from fred_core.kpi.kpi_writer_structures import KPIActor
26
26
  from fred_core.security.backend_to_backend_auth import M2MBearerAuth
@@ -237,8 +237,8 @@ class KfBaseClient:
237
237
  """
238
238
  url = f"{self.base_url}{path}"
239
239
 
240
- # One source for both halves of the call's identity: the header, and the
241
- # grant parameters a delegated call carries beside it.
240
+ # The provider supplies the grant and, for a person, the header; a
241
+ # delegated call's bearer is set on each attempt by M2MBearerAuth.
242
242
  provider = self.credential_provider()
243
243
  override_token = kwargs.pop("access_token", None)
244
244
  credentials = await provider.credentials(override_token=override_token)
@@ -304,7 +304,8 @@ class KfBaseClient:
304
304
  r.status_code in (401, 403)
305
305
  or (
306
306
  r.status_code == 503
307
- and r.headers.get(DENIAL_CAUSE_HEADER) == STANDING_UNAVAILABLE_CAUSE
307
+ and r.headers.get(DENIAL_CAUSE_HEADER)
308
+ == ACCOUNT_STATUS_UNAVAILABLE_CAUSE
308
309
  )
309
310
  ):
310
311
  # The receiver refused this run's authority after service-token
@@ -22,8 +22,8 @@ from typing import AbstractSet, Awaitable, Callable
22
22
 
23
23
  import httpx
24
24
  from fred_core.common.fastapi_handlers import (
25
+ ACCOUNT_STATUS_UNAVAILABLE_CAUSE,
25
26
  DENIAL_CAUSE_HEADER,
26
- STANDING_UNAVAILABLE_CAUSE,
27
27
  )
28
28
  from fred_sdk.contracts.runtime import unwrap_run_stop_error
29
29
  from langchain_mcp_adapters.interceptors import MCPToolCallRequest
@@ -47,9 +47,9 @@ AUTHORITY_REFUSED_STATUSES = (401, 403)
47
47
  class DelegatedAuthorityInterceptor:
48
48
  """Intercepts MCP tool calls made under delegation.
49
49
 
50
- Two jobs: put a current workload bearer on every call, so a connection that
51
- outlives one token keeps working; and end the run when a receiver refuses
52
- this run's authority, rather than asking again with another credential.
50
+ Two jobs: refuse a tool call for a run that has ended; and end the run when
51
+ a receiver refuses this run's authority, rather than asking again with
52
+ another credential.
53
53
  """
54
54
 
55
55
  def __init__(
@@ -69,14 +69,9 @@ class DelegatedAuthorityInterceptor:
69
69
  if request.server_name not in self._delegated_server_ids:
70
70
  return await handler(request)
71
71
 
72
- credentials = await self._provider.credentials()
73
- headers = dict(request.headers or {})
74
- if credentials.authorization:
75
- headers["Authorization"] = credentials.authorization
76
- call = request.override(headers=headers)
77
-
72
+ await self._provider.credentials()
78
73
  try:
79
- result = await handler(call)
74
+ result = await handler(request)
80
75
  if (
81
76
  isinstance(result, CallToolResult)
82
77
  and result.isError
@@ -100,7 +95,7 @@ class DelegatedAuthorityInterceptor:
100
95
  status == 503
101
96
  and http_err is not None
102
97
  and http_err.response.headers.get(DENIAL_CAUSE_HEADER)
103
- == STANDING_UNAVAILABLE_CAUSE
98
+ == ACCOUNT_STATUS_UNAVAILABLE_CAUSE
104
99
  ):
105
100
  raise
106
101
  logger.error(
@@ -41,8 +41,8 @@ from typing import Dict, Final, List, Tuple
41
41
 
42
42
  import httpx
43
43
  from fred_core.common.fastapi_handlers import (
44
+ ACCOUNT_STATUS_UNAVAILABLE_CAUSE,
44
45
  DENIAL_CAUSE_HEADER,
45
- STANDING_UNAVAILABLE_CAUSE,
46
46
  )
47
47
  from fred_core.security.backend_to_backend_auth import M2MBearerAuth
48
48
  from fred_core.security.delegation import scrub_grant_text
@@ -171,7 +171,7 @@ def _refusal_status(error: BaseException) -> int | None:
171
171
  if response.status_code in _REFUSAL_STATUSES or (
172
172
  response.status_code == 503
173
173
  and response.headers.get(DENIAL_CAUSE_HEADER)
174
- == STANDING_UNAVAILABLE_CAUSE
174
+ == ACCOUNT_STATUS_UNAVAILABLE_CAUSE
175
175
  ):
176
176
  return response.status_code
177
177
  pending.extend(
@@ -365,7 +365,7 @@ async def get_connected_mcp_client_for_agent(
365
365
 
366
366
  # --- Ask the provider ONCE for this connection attempt ---
367
367
  # With act_for_people off it answers with the person's bearer, on with the
368
- # runtime's own workload bearer plus the grant this run acts under.
368
+ # grant this run acts under; M2MBearerAuth sets the workload bearer per request.
369
369
  needs_credentials = any(
370
370
  _normalize_auth_mode(server.auth_mode) != AUTH_MODE_NO_TOKEN
371
371
  for server in mcp_servers
@@ -376,18 +376,14 @@ async def get_connected_mcp_client_for_agent(
376
376
  call_credentials = OutboundCredentials()
377
377
  # --------------------------------------------------
378
378
 
379
- if not call_credentials.authorization:
380
- if confined:
381
- logger.warning("[MCP] event=credential_resolution outcome=empty")
382
- else:
383
- logger.warning("MCP connect init: no outbound credential was supplied.")
379
+ if not call_credentials.authorization and not confined:
380
+ logger.warning("MCP connect init: no outbound credential was supplied.")
384
381
 
385
382
  auth_label = _mask_auth_value(call_credentials.authorization)
386
383
  # 🟢 LOG 5: Auth status
387
384
  if confined:
388
385
  logger.info(
389
- "[MCP] event=credential_resolution outcome=completed auth=%s delegated=%s",
390
- auth_label,
386
+ "[MCP] event=credential_resolution outcome=completed delegated=%s",
391
387
  call_credentials.delegated,
392
388
  )
393
389
  else:
@@ -54,7 +54,7 @@ class DelegationConfigurationError(RuntimeError):
54
54
 
55
55
  @dataclass(frozen=True)
56
56
  class OutboundCredentials:
57
- """Authorization and grant parameters for one outbound request."""
57
+ """The person's bearer, or a delegated call's grant, for one outbound request."""
58
58
 
59
59
  authorization: str | None = None
60
60
  parameters: Mapping[str, str] = field(default_factory=dict)
@@ -166,8 +166,15 @@ def static_person_provider(
166
166
  return PersonCredentialProvider(_token, agent_id=agent_id)
167
167
 
168
168
 
169
+ def _log_acquisition_failure(exc: Exception) -> None:
170
+ # Upstream errors can expose credentials; report only the type.
171
+ logger.error(
172
+ "The workload credential could not be obtained (%s).", type(exc).__name__
173
+ )
174
+
175
+
169
176
  class DelegatedCredentialProvider(OutboundCredentialProvider):
170
- """Resolve the workload token and live run authority before each call."""
177
+ """Supply a live run's grant; M2MBearerAuth obtains the bearer per request."""
171
178
 
172
179
  delegated = True
173
180
 
@@ -200,25 +207,8 @@ class DelegatedCredentialProvider(OutboundCredentialProvider):
200
207
  async def credentials(
201
208
  self, *, override_token: str | None = None
202
209
  ) -> OutboundCredentials:
203
- # Delegated calls cannot fall back to a caller-supplied person token.
204
- record = self.record
205
- if record.terminal:
206
- raise DelegationUnavailableError()
207
- scope = RunScope.current()
208
- if scope is not None:
209
- if scope.closed:
210
- raise DelegationUnavailableError()
211
- scope.raise_if_stopped()
212
- token = await self._runtime.workload_token()
213
- live_record = self.record
214
- if live_record is not record or live_record.terminal:
215
- raise DelegationUnavailableError()
216
- if scope is not None:
217
- if scope.closed:
218
- raise DelegationUnavailableError()
219
- scope.raise_if_stopped()
210
+ record = self._require_live_run()
220
211
  return OutboundCredentials(
221
- authorization=f"Bearer {token}",
222
212
  parameters={
223
213
  GRANT_PARAM_PERSON: record.person_id,
224
214
  GRANT_PARAM_RUN: record.run_id,
@@ -234,7 +224,8 @@ class DelegatedCredentialProvider(OutboundCredentialProvider):
234
224
  raise DelegationUnavailableError()
235
225
  try:
236
226
  lease = await provider.get_token_lease()
237
- except Exception:
227
+ except Exception as exc:
228
+ _log_acquisition_failure(exc)
238
229
  raise DelegationUnavailableError() from None
239
230
  if self._require_live_run() is not record:
240
231
  raise DelegationUnavailableError()
@@ -247,7 +238,8 @@ class DelegatedCredentialProvider(OutboundCredentialProvider):
247
238
  raise DelegationUnavailableError()
248
239
  try:
249
240
  replacement = await provider.refresh_rejected(lease)
250
- except Exception:
241
+ except Exception as exc:
242
+ _log_acquisition_failure(exc)
251
243
  raise DelegationUnavailableError() from None
252
244
  if self._require_live_run() is not record:
253
245
  raise DelegationUnavailableError()
@@ -302,26 +294,6 @@ class DelegationRuntime:
302
294
  "act_for_people is on but this deployment has no workload client."
303
295
  )
304
296
 
305
- async def workload_token(self) -> str:
306
- self.ensure_usable()
307
- assert self._token_provider is not None # ensure_usable proved it
308
- try:
309
- token = await self._token_provider.get_token()
310
- except Exception as exc:
311
- # Upstream errors can expose credentials; report only the type.
312
- logger.error(
313
- "The workload credential could not be obtained (%s).",
314
- type(exc).__name__,
315
- )
316
- raise DelegationUnavailableError(
317
- "The platform could not obtain its workload credential."
318
- ) from None
319
- if not token:
320
- raise DelegationUnavailableError(
321
- "The platform could not obtain its workload credential."
322
- )
323
- return token
324
-
325
297
  def provider_for(self, *, run_id: str, agent_id: str) -> OutboundCredentialProvider:
326
298
  return DelegatedCredentialProvider(
327
299
  runtime=self, run_id=run_id, agent_id=agent_id
@@ -47,8 +47,8 @@ from urllib.parse import quote
47
47
  import httpx
48
48
  from fred_core.common import OwnerFilter
49
49
  from fred_core.common.fastapi_handlers import (
50
+ ACCOUNT_STATUS_UNAVAILABLE_CAUSE,
50
51
  DENIAL_CAUSE_HEADER,
51
- STANDING_UNAVAILABLE_CAUSE,
52
52
  )
53
53
  from fred_core.common.team_id import is_personal_team_id
54
54
  from fred_core.kpi.base_kpi_writer import BaseKPIWriter
@@ -3111,14 +3111,12 @@ class TeamWikiAdapter(TeamWikiPort):
3111
3111
  or (
3112
3112
  response.status_code == 503
3113
3113
  and response.headers.get(DENIAL_CAUSE_HEADER)
3114
- == STANDING_UNAVAILABLE_CAUSE
3114
+ == ACCOUNT_STATUS_UNAVAILABLE_CAUSE
3115
3115
  )
3116
3116
  ):
3117
3117
  # Refused authority ends the run under this same identity.
3118
3118
  raise AuthorityLostError()
3119
3119
  response.raise_for_status()
3120
- except AuthorityLostError:
3121
- raise
3122
3120
  except Exception as exc:
3123
3121
  raise _wrap_team_wiki_error(exc) from exc
3124
3122
  return response.json() if response.content else None
@@ -84,20 +84,33 @@ class ToolExecution:
84
84
  # The service-agent flag is stamped by trusted admission, never tool args.
85
85
  if not user_id or is_service_agent:
86
86
  return
87
+ scope = RunScope.current()
88
+ delegated = scope is not None and scope.delegated_credentials
87
89
  try:
88
90
  if not team_id or is_personal_team_id(team_id):
89
- scope = RunScope.current()
90
- if scope is not None and scope.delegated_credentials:
91
- await rebac.require_user_standing(user_id)
91
+ if delegated:
92
+ await rebac.require_active_account(user_id)
92
93
  return
93
- await rebac.check_permission_or_raise(
94
+ permission = rebac.check_permission_or_raise(
94
95
  RebacReference(Resource.USER, user_id),
95
96
  TeamPermission.CAN_USE_TEAM_AGENTS,
96
97
  RebacReference(Resource.TEAM, team_id),
97
98
  )
99
+ if not delegated:
100
+ await permission
101
+ return
102
+ # Both are awaited to the end so an account status refusal wins over a
103
+ # permission check failure, whichever arrives first.
104
+ outcomes = await asyncio.gather(
105
+ rebac.require_active_account(user_id),
106
+ permission,
107
+ return_exceptions=True,
108
+ )
109
+ for outcome in outcomes:
110
+ if isinstance(outcome, BaseException):
111
+ raise outcome
98
112
  except AuthorizationError:
99
- scope = RunScope.current()
100
- if scope is not None and scope.delegated_credentials:
113
+ if delegated:
101
114
  raise AuthorityLostError() from None
102
115
  raise
103
116
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-runtime
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
5
5
  Author-email: Thales <noreply@thalesgroup.com>
6
6
  License: Apache-2.0
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
12
12
  Classifier: Operating System :: OS Independent
13
13
  Requires-Python: <3.13,>=3.12
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: fred-core>=4.3.0
16
- Requires-Dist: fred-sdk[agents]>=4.3.0
15
+ Requires-Dist: fred-core>=4.4.0
16
+ Requires-Dist: fred-sdk[agents]>=4.4.0
17
17
  Requires-Dist: alembic>=1.18.4
18
18
  Requires-Dist: deepagents>=0.7.19
19
19
  Requires-Dist: httpx>=0.28.1
@@ -1,5 +1,5 @@
1
- fred-core>=4.3.0
2
- fred-sdk[agents]>=4.3.0
1
+ fred-core>=4.4.0
2
+ fred-sdk[agents]>=4.4.0
3
3
  alembic>=1.18.4
4
4
  deepagents>=0.7.19
5
5
  httpx>=0.28.1
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "fred-runtime"
3
- version = "4.3.0"
3
+ version = "4.4.0"
4
4
  description = "Runtime adapters and infrastructure wiring for Fred v2 agents."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<3.13"
7
7
  license = { text = "Apache-2.0" }
8
8
  authors = [{ name = "Thales", email = "noreply@thalesgroup.com" }]
9
9
  dependencies = [
10
- "fred-core>=4.3.0",
11
- "fred-sdk[agents]>=4.3.0",
10
+ "fred-core>=4.4.0",
11
+ "fred-sdk[agents]>=4.4.0",
12
12
  "alembic>=1.18.4",
13
13
  "deepagents>=0.7.19",
14
14
  "httpx>=0.28.1",
@@ -421,8 +421,8 @@ def _secured_pod(
421
421
  tmp_path,
422
422
  *,
423
423
  caller_client_id: str,
424
- enforces_standing: bool = True,
425
- standing_seed_ready: bool = True,
424
+ requires_active_accounts: bool = True,
425
+ account_status_model_error: Exception | None = None,
426
426
  accept_delegated_calls: bool = True,
427
427
  **token: object,
428
428
  ):
@@ -451,11 +451,12 @@ def _secured_pod(
451
451
  agent_app_module,
452
452
  "rebac_factory",
453
453
  lambda *args, **kwargs: SimpleNamespace(
454
- enforces_standing=enforces_standing,
454
+ requires_active_accounts=requires_active_accounts,
455
455
  enabled=True,
456
- validate_standing_model=AsyncMock(),
457
- is_standing_seed_ready=AsyncMock(return_value=standing_seed_ready),
458
- require_user_standing=AsyncMock(),
456
+ validate_account_status_model=AsyncMock(
457
+ side_effect=account_status_model_error
458
+ ),
459
+ require_active_account=AsyncMock(),
459
460
  ),
460
461
  )
461
462
  app = create_agent_app(
@@ -507,8 +508,9 @@ def test_a_role_holder_whose_token_is_not_addressed_to_delegation_is_refused(
507
508
  def test_a_service_bearer_without_the_caller_role_runs_as_itself_under_delegation(
508
509
  monkeypatch, tmp_path, _pod_inbound_security
509
510
  ) -> None:
510
- """The evaluator names no person: it runs on its own bearer, and a direct
511
- target skips the person's standing and team checks, as with delegation off."""
511
+ """The evaluator names no person: it runs on its own bearer. Its request's
512
+ account status check passes, and a direct target skips the team check, as with
513
+ delegation off."""
512
514
  app, delegation = _secured_pod(
513
515
  monkeypatch, tmp_path, caller_client_id="caller-1", accept_delegated_calls=False
514
516
  )
@@ -516,7 +518,7 @@ def test_a_service_bearer_without_the_caller_role_runs_as_itself_under_delegatio
516
518
  with TestClient(app) as client:
517
519
  engine = get_runtime_context().config.rebac_engine
518
520
  assert engine is not None
519
- engine.require_user_standing = AsyncMock(side_effect=AssertionError)
521
+ engine.require_active_account = AsyncMock()
520
522
  engine.check_user_team_permission_or_raise = AsyncMock(
521
523
  side_effect=AssertionError
522
524
  )
@@ -538,6 +540,7 @@ def test_a_service_bearer_without_the_caller_role_runs_as_itself_under_delegatio
538
540
  ]
539
541
 
540
542
  assert response.status_code == 200
543
+ engine.require_active_account.assert_awaited_once_with("workload-caller")
541
544
  assert admitted == []
542
545
  assert len(delegation.records) == 0
543
546
 
@@ -6677,19 +6680,51 @@ async def test_authorize_and_resolve_times_pod_authz_and_runtime_binding_phases(
6677
6680
  @pytest.mark.parametrize(
6678
6681
  "accept_delegated_calls", [False, True], ids=["acting-only", "also-accepting"]
6679
6682
  )
6680
- def test_delegation_refuses_to_start_before_standing_is_ready(
6683
+ def test_delegation_startup_validates_the_account_status_model_and_writes_nothing(
6684
+ monkeypatch, tmp_path, _pod_inbound_security, accept_delegated_calls: bool
6685
+ ) -> None:
6686
+ """Every person without a suspension has an active account, so startup only
6687
+ reads the model: the engine exposes no write, and nothing else is awaited."""
6688
+ app, _ = _secured_pod(
6689
+ monkeypatch,
6690
+ tmp_path,
6691
+ caller_client_id="caller-1",
6692
+ accept_delegated_calls=accept_delegated_calls,
6693
+ )
6694
+
6695
+ with TestClient(app):
6696
+ engine = get_runtime_context().config.rebac_engine
6697
+ assert engine is not None
6698
+ awaited = {
6699
+ name
6700
+ for name, value in vars(engine).items()
6701
+ if isinstance(value, AsyncMock) and value.await_count
6702
+ }
6703
+
6704
+ assert awaited == {"validate_account_status_model"}
6705
+ engine.validate_account_status_model.assert_awaited_once_with()
6706
+
6707
+
6708
+ @pytest.mark.parametrize(
6709
+ "accept_delegated_calls", [False, True], ids=["acting-only", "also-accepting"]
6710
+ )
6711
+ def test_delegation_refuses_to_start_on_a_model_without_the_suspended_relation(
6681
6712
  monkeypatch, tmp_path, _pod_inbound_security, accept_delegated_calls: bool
6682
6713
  ) -> None:
6683
- """Before default standing is granted the store answers "not active" for
6684
- everyone, which would refuse every person rather than enforce anything."""
6714
+ """A model that cannot record a suspension cannot refuse anyone, so the pod
6715
+ stops before serving instead of admitting every person."""
6685
6716
  app, _ = _secured_pod(
6686
6717
  monkeypatch,
6687
6718
  tmp_path,
6688
6719
  caller_client_id="caller-1",
6689
- standing_seed_ready=False,
6720
+ account_status_model_error=RuntimeError(
6721
+ "Account status authorization model is not available."
6722
+ ),
6690
6723
  accept_delegated_calls=accept_delegated_calls,
6691
6724
  )
6692
6725
 
6693
- with pytest.raises(ValueError, match="not ready"):
6726
+ with pytest.raises(
6727
+ RuntimeError, match="^Account status authorization model is not available.$"
6728
+ ):
6694
6729
  with TestClient(app):
6695
6730
  pass