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.
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/PKG-INFO +3 -3
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/agent_app.py +64 -38
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_base_client.py +5 -4
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_interceptors.py +7 -12
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_utils.py +6 -10
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/outbound_credentials.py +14 -42
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/v2_runtime/adapters.py +2 -4
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/tool_execution.py +19 -6
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/PKG-INFO +3 -3
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/requires.txt +2 -2
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/pyproject.toml +3 -3
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_agent_app.py +49 -14
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_admission.py +397 -31
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_foreground_route.py +18 -12
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegated_mcp.py +273 -118
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_delegation_receiver_integration.py +22 -4
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_openai_compat_router.py +8 -5
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_outbound_credential_paths.py +252 -219
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_outbound_credentials.py +119 -55
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_observability_middleware.py +138 -29
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/README.md +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/__main__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/chat_input_limit.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/config.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/container.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/context.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/execution_diagnostics.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/filesystem_factory.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/app/platform_sql.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/assembly.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/assets.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/errors.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/mcp.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/openapi_dump.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/capabilities/registry.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/background_tasks.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_document_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_tag_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/table_hits.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/conversation_filesystem.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/concat_files.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/conversation_backend.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/conversation_port.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/deep/deep_runtime.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/execution_errors.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/graph_executor.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/graph/node_context.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/inprocess_toolkit_registry.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/kf_vector_search/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/kf_vector_search/toolkit.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/env.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/script.py.mako +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/a1e2f3c4d5b6_create_session_history.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/b2f3a4e5c6d7_add_exchange_id.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/c3d4b5a6f7e8_add_team_and_instance.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/migrations/versions/d4e5c6b7a8f9_drop_demo_echo_tables.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/authz.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/checkpoint_hygiene.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/dynamic_prompt.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/frame.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/hitl.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/rate_limit_retry.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/shared.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tool_call_recovery.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tool_observability.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/middleware/tracing_kpi.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_prompting.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_runtime.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_stream_adapter.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/authority.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/run_scope.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/tool_approval.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/trace_payloads.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/thinking.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/SOURCES.txt +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/setup.cfg +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_agent_config_assets_path.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_agent_1973.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_chat_controls_1976.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_chat_parts_1977.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_endpoints_1974.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_registry_1973.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_routes_tables_1979.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_capability_selection_1974.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_chat_input_limit.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_checkpoint_namespace.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_config_loader.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_context.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_context_aware_tool.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_conversation_filesystem.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_conversational_memory.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_dispatch.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_hitl_integration.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_agent_middleware.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_compiled_filesystem_tools.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_concat_files.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_conversation_filesystem.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_deep_internal_filesystem.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_port_error_redaction.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_read_scope.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_document_similarity_adapter.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_eval_collector.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_eval_trace.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_execution_diagnostics.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_filesystem_factory.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_fred_workspace_fs.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_capability_bridge.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_capability_hitl.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_checkpoint_namespace.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_executor_storage.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_invoke_agent.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_graph_runtime_token_usage_totals.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_history.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_history_display.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_hitl_resume_langgraph_integration.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_base_client_refresh_shapes.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_vector_search_toolkit.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kf_workspace_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_knowledge_search_table_ordering.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_knowledge_search_tool_invoker_sources.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_kpi_display.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_langfuse_tracing.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_langgraph_interrupt_id_semantics.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_mcp_config.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_migrations.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_enforcement.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_metadata_cache_tokens.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_reasoning_enablement.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_routing.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_model_routing_provider.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_models_catalog_projection.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_platform_sql.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_platform_sql_postgres_integration.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_pod_client.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_rate_limit_retry_middleware.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_checkpoint_namespace.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_loop_regressions_1972.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_message_codec_resume.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_middleware_frame.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_prompting.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_thinking.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_token_usage_totals.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_binding_span_status.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_error_final_2244.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_react_tool_resolution.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_repl_helpers.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_run_scope.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_run_stop_authority.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_runtime_context_prompt_injection.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_similarity_search_tool_invoker.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_smoke.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_spike_capability_state_1971.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_hitl_claim.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_owner.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_sql_checkpointer_session_postgres.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_table_repair_wiring.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_team_wiki_adapter.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_token_expiry.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_call_recovery.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_carrier_middleware_naming.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tool_loop_trim.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_trace_payloads.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_tracing_kpi_content_confinement.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_url_helpers.py +0 -0
- {fred_runtime-4.3.0 → fred_runtime-4.4.0}/tests/test_user_token_refresher.py +0 -0
- {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
|
+
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.
|
|
16
|
-
Requires-Dist: fred-sdk[agents]>=4.
|
|
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
|
|
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
|
-
|
|
270
|
-
|
|
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":
|
|
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)
|
|
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
|
|
1760
|
-
headers={DENIAL_CAUSE_HEADER:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2004
|
-
|
|
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
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
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
|
-
|
|
5862
|
-
|
|
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
|
-
#
|
|
241
|
-
#
|
|
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)
|
|
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:
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
==
|
|
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
|
-
==
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
==
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
await rebac.require_user_standing(user_id)
|
|
91
|
+
if delegated:
|
|
92
|
+
await rebac.require_active_account(user_id)
|
|
92
93
|
return
|
|
93
|
-
|
|
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
|
-
|
|
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
|
+
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.
|
|
16
|
-
Requires-Dist: fred-sdk[agents]>=4.
|
|
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,14 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fred-runtime"
|
|
3
|
-
version = "4.
|
|
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.
|
|
11
|
-
"fred-sdk[agents]>=4.
|
|
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
|
-
|
|
425
|
-
|
|
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
|
-
|
|
454
|
+
requires_active_accounts=requires_active_accounts,
|
|
455
455
|
enabled=True,
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
|
511
|
-
target skips the
|
|
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.
|
|
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
|
|
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
|
-
"""
|
|
6684
|
-
|
|
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
|
-
|
|
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(
|
|
6726
|
+
with pytest.raises(
|
|
6727
|
+
RuntimeError, match="^Account status authorization model is not available.$"
|
|
6728
|
+
):
|
|
6694
6729
|
with TestClient(app):
|
|
6695
6730
|
pass
|