fred-core 3.4.0__tar.gz → 3.4.2__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_core-3.4.0 → fred_core-3.4.2}/PKG-INFO +4 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/__init__.py +9 -15
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/__init__.py +2 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/fastapi_handlers.py +5 -4
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/structures.py +5 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/document_store.py +0 -10
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/filesystem/gcs_filesystem.py +2 -3
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/filesystem/local_filesystem.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/filesystem/minio_filesystem.py +2 -3
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_phase_metric.py +1 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_writer.py +1 -3
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_writer_structures.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/opensearch_kpi_store.py +88 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/log_setup.py +1 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/model/factory.py +59 -4
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/model/http_clients.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/model/models.py +1 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/portable/observability.py +0 -4
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/scheduler/temporal_client_provider.py +19 -2
- fred_core-3.4.2/fred_core/security/authorization.py +43 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/keycloak/keycloack_admin_client.py +0 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/models.py +0 -18
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/oidc.py +0 -3
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/rebac/noop_engine.py +11 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/rebac/openfga_engine.py +82 -18
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/rebac/rebac_engine.py +113 -111
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/rebac/rebac_factory.py +2 -2
- fred_core-3.4.2/fred_core/security/rebac/schema.fga +215 -0
- fred_core-3.4.2/fred_core/security/rebac/schema.fga.json +1 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/structure.py +34 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/opensearch_mapping_validator.py +0 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/__init__.py +8 -0
- fred_core-3.4.2/fred_core/tasks/authz.py +143 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/bus.py +80 -22
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/models.py +58 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/orm_models.py +7 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/service.py +55 -13
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/sse.py +40 -16
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/store.py +17 -3
- fred_core-3.4.2/fred_core/teams/metadata_store.py +335 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/teams/team_metatada_models.py +16 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/cli/test_auth.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_config_loader.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_fastapi_handlers.py +1 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_structures.py +1 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/filesystem/test_gcs_filesystem.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/filesystem/test_local_filesystem.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/integration/test_rebac.py +514 -141
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/kpi/test_http_middleware.py +0 -1
- fred_core-3.4.2/fred_core/tests/kpi/test_opensearch_kpi_store_anonymise.py +199 -0
- fred_core-3.4.2/fred_core/tests/kpi/test_opensearch_kpi_store_ensure_ready.py +104 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/logs/test_memory_log_store.py +0 -1
- fred_core-3.4.2/fred_core/tests/model/test_anthropic_factory.py +177 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/model/test_http_clients.py +1 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/model/test_vertex_model_garden_auth.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/portable/test_observability.py +1 -4
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/scheduler/test_backend.py +0 -1
- fred_core-3.4.2/fred_core/tests/scheduler/test_temporal_client_provider.py +84 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/test_oidc_strict.py +27 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/test_rebac_engine_team_helpers.py +99 -10
- fred_core-3.4.2/fred_core/tests/security/test_rebac_schema_authz05.py +186 -0
- fred_core-3.4.2/fred_core/tests/security/test_service_agent.py +57 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/session/test_postgres_json_session_store_sqlite.py +0 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/store/test_local_content_store.py +0 -1
- fred_core-3.4.2/fred_core/tests/tasks/test_authz.py +307 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/tasks/test_bus.py +32 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/tasks/test_reconcile.py +36 -0
- fred_core-3.4.2/fred_core/tests/tasks/test_scheduling.py +185 -0
- fred_core-3.4.2/fred_core/tests/tasks/test_sse.py +136 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core.egg-info/PKG-INFO +4 -2
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core.egg-info/SOURCES.txt +10 -4
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core.egg-info/requires.txt +3 -1
- {fred_core-3.4.0 → fred_core-3.4.2}/pyproject.toml +7 -2
- fred_core-3.4.0/fred_core/security/authorization.py +0 -82
- fred_core-3.4.0/fred_core/security/authorization_decorator.py +0 -66
- fred_core-3.4.0/fred_core/security/rbac.py +0 -134
- fred_core-3.4.0/fred_core/security/rebac/schema.fga +0 -146
- fred_core-3.4.0/fred_core/security/rebac/schema.fga.json +0 -1
- fred_core-3.4.0/fred_core/teams/metadata_store.py +0 -189
- fred_core-3.4.0/fred_core/tests/security/test_authorization.py +0 -176
- fred_core-3.4.0/fred_core/tests/security/test_authorization_decorator.py +0 -136
- {fred_core-3.4.0 → fred_core-3.4.2}/README.md +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/cli/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/cli/auth.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/cli/ui.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/config_files.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/config_loader.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/env.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/lru_cache.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/team_id.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/common/utils.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/document_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/document_structures.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/postgres_document_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/documents/tag_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/filesystem/structures.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/history/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/history/base_history_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/history/history_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/history/history_schema.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/history/postgres_history_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/base_kpi_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/base_kpi_writer.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/http_middleware.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_factory.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_process.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/kpi_reader_structures.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/log_kpi_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/noop_kpi_writer.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/kpi/prometheus_kpi_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/base_log_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/log_structures.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/memory_log_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/null_log_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/logs/opensearch_log_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/models/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/models/base.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/portable/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/py.typed +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/scheduler/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/scheduler/backend.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/scheduler/scheduler_structures.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/backend_to_backend_auth.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/keycloak/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/outbound.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/rebac/openfga_schema.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/security/whitelist_access_control/access_control.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/session_schema.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/stores/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/stores/base_session_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/stores/postgres_session_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/session/stores/session_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/sql/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/sql/alembic_env.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/sql/async_session.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/sql/base_sql.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/sql/mixin.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/base_content_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/local_content_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/minio_content_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/store/vector_search.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tasks/workflow_control.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/teams/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/cli/test_ui.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_env.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_log_setup.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/common/test_lru_cache.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/integration/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/kpi/test_noop_kpi_writer.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/model/test_embedding_factory.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/test_rebac_config_defaults.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/test_security_profile.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/security/test_whitelist_access_control.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/tasks/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/tasks/test_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/test_log_kpi_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/tests/test_prometheus_kpi_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/users/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/users/store/__init__.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/users/store/base_user_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/users/store/postgres_user_store.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core/users/user_models.py +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core.egg-info/dependency_links.txt +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/fred_core.egg-info/top_level.txt +0 -0
- {fred_core-3.4.0 → fred_core-3.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fred-core
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: Core shared utilities for Fred backends (config, storage, security, and runtime helpers).
|
|
5
5
|
Author-email: Thales <noreply@thalesgroup.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,7 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Requires-Python: >=3.12
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: fastapi==0.
|
|
15
|
+
Requires-Dist: fastapi==0.139.0
|
|
16
|
+
Requires-Dist: starlette>=1.3.1
|
|
16
17
|
Requires-Dist: prometheus-client>=0.20.5
|
|
17
18
|
Requires-Dist: pyjwt<3.0.0,>=2.10.1
|
|
18
19
|
Requires-Dist: psycopg[binary]>=3.1.0
|
|
@@ -28,6 +29,7 @@ Requires-Dist: langchain-google-genai>=4.2.1
|
|
|
28
29
|
Requires-Dist: langchain-ollama>=1.0.1
|
|
29
30
|
Requires-Dist: langchain-mistralai>=1.1.2
|
|
30
31
|
Requires-Dist: langchain-google-vertexai>=3.2.2
|
|
32
|
+
Requires-Dist: langchain-anthropic>=0.3.0
|
|
31
33
|
Requires-Dist: langchain-openai>=1.1.14
|
|
32
34
|
Requires-Dist: azure-identity>=1.25.0
|
|
33
35
|
Requires-Dist: authzed>=1.22.1
|
|
@@ -61,15 +61,7 @@ from fred_core.model.models import ModelProvider
|
|
|
61
61
|
from fred_core.security.authorization import (
|
|
62
62
|
NO_AUTHZ_CHECK_USER,
|
|
63
63
|
TODO_PASS_REAL_USER,
|
|
64
|
-
Action,
|
|
65
|
-
AuthorizationError,
|
|
66
|
-
Resource,
|
|
67
|
-
authorize_or_raise,
|
|
68
|
-
is_authorized,
|
|
69
|
-
require_admin,
|
|
70
|
-
require_task_access,
|
|
71
64
|
)
|
|
72
|
-
from fred_core.security.authorization_decorator import authorize
|
|
73
65
|
from fred_core.security.backend_to_backend_auth import (
|
|
74
66
|
M2MAuthConfig,
|
|
75
67
|
M2MBearerAuth,
|
|
@@ -80,6 +72,11 @@ from fred_core.security.keycloak.keycloack_admin_client import (
|
|
|
80
72
|
KeycloackDisabled,
|
|
81
73
|
create_keycloak_admin,
|
|
82
74
|
)
|
|
75
|
+
from fred_core.security.models import (
|
|
76
|
+
Action,
|
|
77
|
+
AuthorizationError,
|
|
78
|
+
Resource,
|
|
79
|
+
)
|
|
83
80
|
from fred_core.security.oidc import (
|
|
84
81
|
decode_jwt,
|
|
85
82
|
get_current_user,
|
|
@@ -91,10 +88,10 @@ from fred_core.security.oidc import (
|
|
|
91
88
|
split_realm_url,
|
|
92
89
|
)
|
|
93
90
|
from fred_core.security.outbound import BearerAuth, ClientCredentialsProvider
|
|
94
|
-
from fred_core.security.rbac import RBACProvider
|
|
95
91
|
from fred_core.security.rebac.openfga_engine import OpenFgaRebacEngine
|
|
96
92
|
from fred_core.security.rebac.rebac_engine import (
|
|
97
93
|
ORGANIZATION_ID,
|
|
94
|
+
SERVICE_AGENT_ALLOWED_TEAM_PERMISSIONS,
|
|
98
95
|
AgentPermission,
|
|
99
96
|
DocumentPermission,
|
|
100
97
|
OrganizationPermission,
|
|
@@ -115,6 +112,7 @@ from fred_core.security.structure import (
|
|
|
115
112
|
RebacConfiguration,
|
|
116
113
|
SecurityConfiguration,
|
|
117
114
|
UserSecurity,
|
|
115
|
+
is_service_agent,
|
|
118
116
|
)
|
|
119
117
|
from fred_core.session import SessionSchema
|
|
120
118
|
from fred_core.session.stores import BaseSessionStore, PostgresSessionStore
|
|
@@ -164,15 +162,11 @@ __all__ = [
|
|
|
164
162
|
"GcsFilesystem",
|
|
165
163
|
"FilesystemResourceInfoResult",
|
|
166
164
|
"FilesystemResourceInfo",
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"require_task_access",
|
|
165
|
+
"is_service_agent",
|
|
166
|
+
"SERVICE_AGENT_ALLOWED_TEAM_PERMISSIONS",
|
|
170
167
|
"Action",
|
|
171
168
|
"Resource",
|
|
172
169
|
"AuthorizationError",
|
|
173
|
-
"is_authorized",
|
|
174
|
-
"authorize_or_raise",
|
|
175
|
-
"authorize",
|
|
176
170
|
"oauth2_scheme",
|
|
177
171
|
"ClientCredentialsProvider",
|
|
178
172
|
"BearerAuth",
|
|
@@ -38,7 +38,7 @@ from .structures import (
|
|
|
38
38
|
StoreConfig,
|
|
39
39
|
TemporalSchedulerConfig,
|
|
40
40
|
)
|
|
41
|
-
from .team_id import TeamId, personal_team_id
|
|
41
|
+
from .team_id import TeamId, is_personal_team_id, personal_team_id
|
|
42
42
|
from .utils import raise_internal_error
|
|
43
43
|
|
|
44
44
|
__all__ = [
|
|
@@ -58,6 +58,7 @@ __all__ = [
|
|
|
58
58
|
"PostgresTableConfig",
|
|
59
59
|
"StoreConfig",
|
|
60
60
|
"TeamId",
|
|
61
|
+
"is_personal_team_id",
|
|
61
62
|
"personal_team_id",
|
|
62
63
|
"TemporalSchedulerConfig",
|
|
63
64
|
"ThreadSafeLRUCache",
|
|
@@ -22,11 +22,12 @@ from fred_core.security.models import AuthorizationError, Resource
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
24
|
_TEAM_PERMISSION_MESSAGES: dict[str, str] = {
|
|
25
|
-
"can_update_resources": "You are not allowed to manage resources in this team. Ask a team
|
|
26
|
-
"can_update_agents": "You are not allowed to manage agents in this team. Ask a team
|
|
25
|
+
"can_update_resources": "You are not allowed to manage resources in this team. Ask a team admin or editor.",
|
|
26
|
+
"can_update_agents": "You are not allowed to manage agents in this team. Ask a team admin or editor.",
|
|
27
27
|
"can_administer_members": "You are not allowed to manage members in this team.",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
28
|
+
"can_administer_editors": "You are not allowed to manage editors in this team.",
|
|
29
|
+
"can_administer_analysts": "You are not allowed to manage analysts in this team.",
|
|
30
|
+
"can_administer_admins": "You are not allowed to manage admins in this team.",
|
|
30
31
|
"can_read_members": "You are not allowed to view team members.",
|
|
31
32
|
"can_update_info": "You are not allowed to update this team.",
|
|
32
33
|
}
|
|
@@ -40,6 +40,11 @@ class TemporalSchedulerConfig(BaseModel):
|
|
|
40
40
|
task_queue: str = "default"
|
|
41
41
|
workflow_id_prefix: str = "task"
|
|
42
42
|
connect_timeout_seconds: Optional[int] = 5
|
|
43
|
+
rpc_timeout_seconds: Optional[int] = Field(
|
|
44
|
+
default=10,
|
|
45
|
+
description="Deadline applied to individual Temporal RPC calls (start_workflow, describe) "
|
|
46
|
+
"so a stuck Temporal frontend cannot hang the caller indefinitely.",
|
|
47
|
+
)
|
|
43
48
|
ingestion_workflow_parallelism: int = Field(
|
|
44
49
|
default=3,
|
|
45
50
|
ge=1,
|
|
@@ -24,8 +24,6 @@ from fred_core.documents.document_structures import DocumentMetadata
|
|
|
24
24
|
class DocumentMetadataDeserializationError(Exception):
|
|
25
25
|
"""Raised when document metadata cannot be parsed correctly due to invalid fields or enum mismatches."""
|
|
26
26
|
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
27
|
|
|
30
28
|
class BaseDocumentMetadataStore:
|
|
31
29
|
"""
|
|
@@ -37,7 +35,6 @@ class BaseDocumentMetadataStore:
|
|
|
37
35
|
@abstractmethod
|
|
38
36
|
async def count_all(self, session: AsyncSession | None = None) -> int:
|
|
39
37
|
"""Return the total number of document metadata records in the store."""
|
|
40
|
-
pass
|
|
41
38
|
|
|
42
39
|
@abstractmethod
|
|
43
40
|
async def get_all_metadata(
|
|
@@ -50,7 +47,6 @@ class BaseDocumentMetadataStore:
|
|
|
50
47
|
- Keys are metadata field names (e.g., "source_tag", "tags")
|
|
51
48
|
- Values are filter values (exact match). Lists are interpreted as 'terms'.
|
|
52
49
|
"""
|
|
53
|
-
pass
|
|
54
50
|
|
|
55
51
|
@abstractmethod
|
|
56
52
|
async def get_metadata_by_uid(
|
|
@@ -61,7 +57,6 @@ class BaseDocumentMetadataStore:
|
|
|
61
57
|
|
|
62
58
|
:raises DocumentMetadataDeserializationError: if stored data is malformed.
|
|
63
59
|
"""
|
|
64
|
-
pass
|
|
65
60
|
|
|
66
61
|
async def get_metadata_by_uids(
|
|
67
62
|
self, document_uids: list[str], session: AsyncSession | None = None
|
|
@@ -88,7 +83,6 @@ class BaseDocumentMetadataStore:
|
|
|
88
83
|
self, tag_id: str, session: AsyncSession | None = None
|
|
89
84
|
) -> List[DocumentMetadata]:
|
|
90
85
|
"""Return all metadata entries that are tagged with a specific tag ID."""
|
|
91
|
-
pass
|
|
92
86
|
|
|
93
87
|
async def browse_metadata_in_tag(
|
|
94
88
|
self,
|
|
@@ -107,7 +101,6 @@ class BaseDocumentMetadataStore:
|
|
|
107
101
|
self, source_tag: str, session: AsyncSession | None = None
|
|
108
102
|
) -> List[DocumentMetadata]:
|
|
109
103
|
"""Return all metadata entries originating from a specific pull source."""
|
|
110
|
-
pass
|
|
111
104
|
|
|
112
105
|
@abstractmethod
|
|
113
106
|
async def save_metadata(
|
|
@@ -121,7 +114,6 @@ class BaseDocumentMetadataStore:
|
|
|
121
114
|
|
|
122
115
|
:raises ValueError: if 'document_uid' is missing.
|
|
123
116
|
"""
|
|
124
|
-
pass
|
|
125
117
|
|
|
126
118
|
@abstractmethod
|
|
127
119
|
async def delete_metadata(
|
|
@@ -132,9 +124,7 @@ class BaseDocumentMetadataStore:
|
|
|
132
124
|
|
|
133
125
|
:raises ValueError: if 'document_uid' is missing.
|
|
134
126
|
"""
|
|
135
|
-
pass
|
|
136
127
|
|
|
137
128
|
@abstractmethod
|
|
138
129
|
async def clear(self, session: AsyncSession | None = None) -> None:
|
|
139
130
|
"""Delete all metadata records from the store. Destructive — dev/test only."""
|
|
140
|
-
pass
|
|
@@ -17,14 +17,13 @@ import re
|
|
|
17
17
|
import time
|
|
18
18
|
from typing import List, Optional, Set
|
|
19
19
|
|
|
20
|
-
from google.cloud import storage
|
|
21
|
-
from google.cloud.exceptions import NotFound
|
|
22
|
-
|
|
23
20
|
from fred_core.filesystem.structures import (
|
|
24
21
|
BaseFilesystem,
|
|
25
22
|
FilesystemResourceInfo,
|
|
26
23
|
FilesystemResourceInfoResult,
|
|
27
24
|
)
|
|
25
|
+
from google.cloud import storage
|
|
26
|
+
from google.cloud.exceptions import NotFound
|
|
28
27
|
|
|
29
28
|
logger = logging.getLogger(__name__)
|
|
30
29
|
|
|
@@ -18,14 +18,13 @@ from io import BytesIO
|
|
|
18
18
|
from typing import List, Set
|
|
19
19
|
from urllib.parse import urlparse
|
|
20
20
|
|
|
21
|
-
from minio import Minio
|
|
22
|
-
from minio.deleteobjects import DeleteObject
|
|
23
|
-
|
|
24
21
|
from fred_core.filesystem.structures import (
|
|
25
22
|
BaseFilesystem,
|
|
26
23
|
FilesystemResourceInfo,
|
|
27
24
|
FilesystemResourceInfoResult,
|
|
28
25
|
)
|
|
26
|
+
from minio import Minio
|
|
27
|
+
from minio.deleteobjects import DeleteObject
|
|
29
28
|
|
|
30
29
|
logger = logging.getLogger(__name__)
|
|
31
30
|
|
|
@@ -17,7 +17,7 @@ from contextlib import asynccontextmanager
|
|
|
17
17
|
from fred_core.kpi.base_kpi_writer import BaseKPIWriter
|
|
18
18
|
from fred_core.kpi.kpi_writer_structures import Dims, KPIActor
|
|
19
19
|
|
|
20
|
-
PHASE_METRIC_ACTOR = KPIActor(type="system", user_id=None
|
|
20
|
+
PHASE_METRIC_ACTOR = KPIActor(type="system", user_id=None)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def record_phase_metric(
|
|
@@ -123,7 +123,7 @@ def to_kpi_actor(user: KeycloakUser) -> KPIActor:
|
|
|
123
123
|
- Fred’s KPI model is actor-centric (human/agent/system). We always attribute
|
|
124
124
|
costs and usage to an actor to support per-user chargeback and audits.
|
|
125
125
|
"""
|
|
126
|
-
return KPIActor(type="human", user_id=user.uid
|
|
126
|
+
return KPIActor(type="human", user_id=user.uid)
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
# -------------------------
|
|
@@ -196,8 +196,6 @@ class KPIWriter(BaseKPIWriter):
|
|
|
196
196
|
_dims["actor_type"] = actor.type
|
|
197
197
|
if actor.user_id:
|
|
198
198
|
_dims["user_id"] = actor.user_id
|
|
199
|
-
if "groups" not in _dims and actor.groups:
|
|
200
|
-
_dims["groups"] = ",".join(sorted(actor.groups))
|
|
201
199
|
merged_dims = _merge_dims(self.defaults, _dims)
|
|
202
200
|
|
|
203
201
|
event = KPIEvent(
|
|
@@ -64,6 +64,10 @@ KPI_INDEX_MAPPING: Dict[str, Any] = {
|
|
|
64
64
|
"env": {"type": "keyword"},
|
|
65
65
|
"cluster": {"type": "keyword"},
|
|
66
66
|
"user_id": {"type": "keyword"},
|
|
67
|
+
# Conversation id carried by every session-scoped KPI row
|
|
68
|
+
# (react/graph phase, tool). Explicit keyword so the RGPD
|
|
69
|
+
# erasure `update_by_query` can `term`-match it (CTRLP-12 A3).
|
|
70
|
+
"session_id": {"type": "keyword"},
|
|
67
71
|
"exchange_id": {"type": "keyword"},
|
|
68
72
|
"agent_id": {"type": "keyword"},
|
|
69
73
|
"agent_step": {"type": "keyword"},
|
|
@@ -99,7 +103,6 @@ KPI_INDEX_MAPPING: Dict[str, Any] = {
|
|
|
99
103
|
"system_prompt_chars": {"type": "keyword"},
|
|
100
104
|
"template_id": {"type": "keyword"},
|
|
101
105
|
"source_runtime_id": {"type": "keyword"},
|
|
102
|
-
"groups": {"type": "keyword"},
|
|
103
106
|
}
|
|
104
107
|
},
|
|
105
108
|
"cost": {
|
|
@@ -116,6 +119,8 @@ KPI_INDEX_MAPPING: Dict[str, Any] = {
|
|
|
116
119
|
"bytes_out": {"type": "long"},
|
|
117
120
|
"chunks": {"type": "integer"},
|
|
118
121
|
"vectors": {"type": "integer"},
|
|
122
|
+
"input_tokens": {"type": "long"},
|
|
123
|
+
"output_tokens": {"type": "long"},
|
|
119
124
|
}
|
|
120
125
|
},
|
|
121
126
|
"labels": {"type": "keyword"},
|
|
@@ -170,6 +175,11 @@ class OpenSearchKPIStore(BaseKPIStore):
|
|
|
170
175
|
logger.info("[OPENSEARCH][KPI] created index '%s'", self.index)
|
|
171
176
|
else:
|
|
172
177
|
logger.info("[OPENSEARCH][KPI] index '%s' already exists.", self.index)
|
|
178
|
+
# CTRLP-12: session_id was added to KPI_INDEX_MAPPING for RGPD
|
|
179
|
+
# conversation erasure. Existing indices predate it, so add it
|
|
180
|
+
# additively (put_mapping) before validation — otherwise startup
|
|
181
|
+
# fails with "Missing nested field: 'dims.session_id'".
|
|
182
|
+
self._ensure_dim_mapping("session_id", {"type": "keyword"})
|
|
173
183
|
self._ensure_dim_mapping("service", {"type": "keyword"})
|
|
174
184
|
self._ensure_dim_mapping("team_id", {"type": "keyword"})
|
|
175
185
|
self._ensure_dim_mapping("agent_instance_id", {"type": "keyword"})
|
|
@@ -181,7 +191,11 @@ class OpenSearchKPIStore(BaseKPIStore):
|
|
|
181
191
|
self._ensure_dim_mapping("system_prompt_chars", {"type": "keyword"})
|
|
182
192
|
self._ensure_dim_mapping("template_id", {"type": "keyword"})
|
|
183
193
|
self._ensure_dim_mapping("source_runtime_id", {"type": "keyword"})
|
|
184
|
-
|
|
194
|
+
# OBSERV-02: quantities.input_tokens/output_tokens were written by
|
|
195
|
+
# agent.turn_completed since Phase 7 but never mapped, so they were
|
|
196
|
+
# unaggregatable. Added additively for existing indices.
|
|
197
|
+
self._ensure_quantities_mapping("input_tokens", {"type": "long"})
|
|
198
|
+
self._ensure_quantities_mapping("output_tokens", {"type": "long"})
|
|
185
199
|
# Validate existing mapping matches expected mapping
|
|
186
200
|
validate_index_mapping(self.client, self.index, KPI_INDEX_MAPPING)
|
|
187
201
|
except OpenSearchException as e:
|
|
@@ -209,6 +223,27 @@ class OpenSearchKPIStore(BaseKPIStore):
|
|
|
209
223
|
"[OPENSEARCH][KPI] failed to add dims.%s mapping: %s", name, e
|
|
210
224
|
)
|
|
211
225
|
|
|
226
|
+
def _ensure_quantities_mapping(self, name: str, mapping: Dict[str, Any]) -> None:
|
|
227
|
+
try:
|
|
228
|
+
current_mapping_resp = self.client.indices.get_mapping(index=self.index)
|
|
229
|
+
current_mapping = current_mapping_resp.get(self.index, {}).get(
|
|
230
|
+
"mappings", {}
|
|
231
|
+
)
|
|
232
|
+
quantities_props = (
|
|
233
|
+
current_mapping.get("properties", {})
|
|
234
|
+
.get("quantities", {})
|
|
235
|
+
.get("properties", {})
|
|
236
|
+
)
|
|
237
|
+
if name in quantities_props:
|
|
238
|
+
return
|
|
239
|
+
body = {"properties": {"quantities": {"properties": {name: mapping}}}}
|
|
240
|
+
self.client.indices.put_mapping(index=self.index, body=body)
|
|
241
|
+
logger.info("[OPENSEARCH][KPI] added quantities.%s mapping", name)
|
|
242
|
+
except OpenSearchException as e:
|
|
243
|
+
logger.warning(
|
|
244
|
+
"[OPENSEARCH][KPI] failed to add quantities.%s mapping: %s", name, e
|
|
245
|
+
)
|
|
246
|
+
|
|
212
247
|
# -- writes ----------------------------------------------------------------
|
|
213
248
|
def index_event(self, event: KPIEvent) -> None:
|
|
214
249
|
try:
|
|
@@ -234,6 +269,57 @@ class OpenSearchKPIStore(BaseKPIStore):
|
|
|
234
269
|
logger.error("[OPENSEARCH][KPI] bulk_index failed: %s", e)
|
|
235
270
|
raise
|
|
236
271
|
|
|
272
|
+
# -- erasure (RGPD) --------------------------------------------------------
|
|
273
|
+
def anonymise_for_session(self, session_id: str) -> int:
|
|
274
|
+
"""Anonymise — not delete — every KPI row of one conversation (CTRLP-12 A3).
|
|
275
|
+
|
|
276
|
+
RGPD default per RFC §3.3: KPI events are an analytics *aggregate*, not
|
|
277
|
+
conversation content, so erasure severs the link to a person rather than
|
|
278
|
+
dropping the rows. An OpenSearch `update_by_query` (the same primitive the
|
|
279
|
+
vector store uses for `delete_by_query`) nulls the direct identifiers on
|
|
280
|
+
the session's rows: `dims.session_id`, `dims.user_id`, `dims.exchange_id`
|
|
281
|
+
and — for any generic session-scoped analytics row — `dims.scope_id`.
|
|
282
|
+
Aggregate counts are untouched — the rows survive, now anonymous. Returns
|
|
283
|
+
the number of rows anonymised.
|
|
284
|
+
|
|
285
|
+
A session's rows are matched by `dims.session_id == session_id`, the dim
|
|
286
|
+
every session-scoped KPI row actually carries (react/graph `phase_latency`
|
|
287
|
+
and `tool_*` all set `dims.session_id`; see the runtime KPI emitters). A
|
|
288
|
+
pure aggregate with no `session_id` (e.g. `llm.call_latency_ms` emitted
|
|
289
|
+
under a system actor) carries no personal identifier and is correctly out
|
|
290
|
+
of per-conversation scope. Idempotent: once `session_id` is nulled the row
|
|
291
|
+
no longer matches, so a second erase updates zero.
|
|
292
|
+
"""
|
|
293
|
+
body: Dict[str, Any] = {
|
|
294
|
+
"query": {"bool": {"filter": [{"term": {"dims.session_id": session_id}}]}},
|
|
295
|
+
"script": {
|
|
296
|
+
"lang": "painless",
|
|
297
|
+
"source": (
|
|
298
|
+
"if (ctx._source.dims != null) "
|
|
299
|
+
"{ for (f in params.fields) { ctx._source.dims.remove(f); } }"
|
|
300
|
+
),
|
|
301
|
+
"params": {
|
|
302
|
+
"fields": ["session_id", "scope_id", "user_id", "exchange_id"]
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
try:
|
|
307
|
+
resp = self.client.update_by_query(
|
|
308
|
+
index=self.index,
|
|
309
|
+
body=body,
|
|
310
|
+
params={"conflicts": "proceed", "refresh": "true"},
|
|
311
|
+
)
|
|
312
|
+
updated = int(resp.get("updated", 0))
|
|
313
|
+
logger.info(
|
|
314
|
+
"[OPENSEARCH][KPI] anonymised %s row(s) for session_id=%s",
|
|
315
|
+
updated,
|
|
316
|
+
session_id,
|
|
317
|
+
)
|
|
318
|
+
return updated
|
|
319
|
+
except OpenSearchException as e:
|
|
320
|
+
logger.error("[OPENSEARCH][KPI] anonymise_for_session failed: %s", e)
|
|
321
|
+
raise
|
|
322
|
+
|
|
237
323
|
# -- reads -----------------------------------------------------------------
|
|
238
324
|
def query(self, q: KPIQuery) -> KPIQueryResult:
|
|
239
325
|
logger.info(
|
|
@@ -81,7 +81,7 @@ class StoreEmitHandler(logging.Handler):
|
|
|
81
81
|
payload = json.loads(raw)
|
|
82
82
|
except Exception:
|
|
83
83
|
print("Log record is not JSON: %s", raw)
|
|
84
|
-
|
|
84
|
+
# formatter should be JSON, but we tolerate plain text
|
|
85
85
|
|
|
86
86
|
if payload:
|
|
87
87
|
mapped_level = LEVEL_MAP.get(payload.get("level"), record.levelname)
|
|
@@ -13,6 +13,9 @@ from collections.abc import AsyncGenerator, Generator
|
|
|
13
13
|
from typing import Any, Dict, Iterable, Optional, Protocol, Sequence, Type, cast
|
|
14
14
|
|
|
15
15
|
import httpx
|
|
16
|
+
from fred_core.common import ModelConfiguration
|
|
17
|
+
from fred_core.model.http_clients import get_shared_stack, strip_transport_settings
|
|
18
|
+
from fred_core.model.models import ModelProvider
|
|
16
19
|
from langchain_core.embeddings import Embeddings as LCEmbeddings
|
|
17
20
|
from langchain_core.language_models.chat_models import BaseChatModel
|
|
18
21
|
from langchain_core.output_parsers import PydanticOutputParser
|
|
@@ -26,10 +29,6 @@ from langchain_openai import (
|
|
|
26
29
|
)
|
|
27
30
|
from pydantic import BaseModel
|
|
28
31
|
|
|
29
|
-
from fred_core.common import ModelConfiguration
|
|
30
|
-
from fred_core.model.http_clients import get_shared_stack, strip_transport_settings
|
|
31
|
-
from fred_core.model.models import ModelProvider
|
|
32
|
-
|
|
33
32
|
logger = logging.getLogger(__name__)
|
|
34
33
|
|
|
35
34
|
# ---------------------------------------------------------------------------
|
|
@@ -572,6 +571,61 @@ def get_model(cfg: Optional[ModelConfiguration]) -> BaseChatModel:
|
|
|
572
571
|
**settings,
|
|
573
572
|
)
|
|
574
573
|
|
|
574
|
+
# --- Provider: Anthropic (native + Synapse Gateway) ---
|
|
575
|
+
if provider == ModelProvider.ANTHROPIC.value:
|
|
576
|
+
if not cfg.name:
|
|
577
|
+
raise ValueError(
|
|
578
|
+
"Anthropic chat requires 'name' (e.g., claude-sonnet-4-5)."
|
|
579
|
+
)
|
|
580
|
+
# Accept both ANTHROPIC_API_KEY (standard) and ANTHROPIC_AUTH_TOKEN (legacy/Thales env)
|
|
581
|
+
api_key = os.getenv("ANTHROPIC_API_KEY") or os.getenv("ANTHROPIC_AUTH_TOKEN")
|
|
582
|
+
if not api_key:
|
|
583
|
+
raise ValueError(
|
|
584
|
+
"Missing required environment variable: ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN)"
|
|
585
|
+
)
|
|
586
|
+
base_url = settings.pop("base_url", None)
|
|
587
|
+
|
|
588
|
+
# --- Proxy bypass patch (WSL / corporate proxy environments) ---
|
|
589
|
+
# WSL sets lowercase no_proxy in the shell before the process starts; python-dotenv
|
|
590
|
+
# does not override already-set env vars by default, so our .env value is ignored.
|
|
591
|
+
# httpx prefers lowercase no_proxy over NO_PROXY, so we must patch it here at the
|
|
592
|
+
# Python level before the Anthropic SDK constructs its internal httpx client.
|
|
593
|
+
_effective_base = base_url or os.getenv("ANTHROPIC_BASE_URL") or ""
|
|
594
|
+
if _effective_base:
|
|
595
|
+
try:
|
|
596
|
+
from urllib.parse import urlparse as _urlparse
|
|
597
|
+
|
|
598
|
+
_host = _urlparse(_effective_base).hostname or ""
|
|
599
|
+
if _host:
|
|
600
|
+
_no_proxy = os.environ.get("no_proxy", "")
|
|
601
|
+
_parts = {h.strip() for h in _no_proxy.split(",") if h.strip()}
|
|
602
|
+
if _host not in _parts:
|
|
603
|
+
_parts.add(_host)
|
|
604
|
+
os.environ["no_proxy"] = ",".join(sorted(_parts))
|
|
605
|
+
logger.info(
|
|
606
|
+
"[MODEL][ANTHROPIC] Patched no_proxy: added %s for direct connection",
|
|
607
|
+
_host,
|
|
608
|
+
)
|
|
609
|
+
except Exception: # nosec B110 — best-effort proxy patch; must not block model construction
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
_info_provider(cfg, settings)
|
|
613
|
+
logger.info(
|
|
614
|
+
"[MODEL][ANTHROPIC] Constructing ChatAnthropic model=%s base_url=%s",
|
|
615
|
+
cfg.name,
|
|
616
|
+
base_url or "(default, from ANTHROPIC_BASE_URL env)",
|
|
617
|
+
)
|
|
618
|
+
try:
|
|
619
|
+
from langchain_anthropic import ChatAnthropic
|
|
620
|
+
except ImportError as e:
|
|
621
|
+
raise ImportError(
|
|
622
|
+
"Provider 'anthropic' requires package 'langchain-anthropic'."
|
|
623
|
+
) from e
|
|
624
|
+
ctor_kwargs: Dict[str, Any] = {"model": cfg.name, "anthropic_api_key": api_key}
|
|
625
|
+
if base_url:
|
|
626
|
+
ctor_kwargs["base_url"] = base_url
|
|
627
|
+
return ChatAnthropic(**ctor_kwargs, **settings)
|
|
628
|
+
|
|
575
629
|
raise ValueError(f"Unsupported chat provider: {provider}")
|
|
576
630
|
|
|
577
631
|
|
|
@@ -777,6 +831,7 @@ def get_structured_chain(schema: Type[BaseModel], model_config: ModelConfigurati
|
|
|
777
831
|
ModelProvider.AZURE_APIM.value,
|
|
778
832
|
ModelProvider.VERTEX_AI.value,
|
|
779
833
|
ModelProvider.VERTEX_AI_MODEL_GARDEN.value,
|
|
834
|
+
ModelProvider.ANTHROPIC.value,
|
|
780
835
|
}:
|
|
781
836
|
try:
|
|
782
837
|
structured = model.with_structured_output(schema, method="function_calling")
|
|
@@ -209,7 +209,6 @@ class MetricsProvider:
|
|
|
209
209
|
name: str,
|
|
210
210
|
*,
|
|
211
211
|
dims: dict[str, str | None] | None = None,
|
|
212
|
-
groups: list[str] | None = None,
|
|
213
212
|
) -> Generator[dict[str, str | None], None, None]:
|
|
214
213
|
"""Time a block. Yields a mutable dims dict callers can annotate."""
|
|
215
214
|
yield dict(dims) if dims else {}
|
|
@@ -238,7 +237,6 @@ class LoggingMetricsProvider(MetricsProvider):
|
|
|
238
237
|
name: str,
|
|
239
238
|
*,
|
|
240
239
|
dims: dict[str, str | None] | None = None,
|
|
241
|
-
groups: list[str] | None = None,
|
|
242
240
|
) -> Generator[dict[str, str | None], None, None]:
|
|
243
241
|
import time as _time
|
|
244
242
|
|
|
@@ -258,7 +256,6 @@ class LoggingMetricsProvider(MetricsProvider):
|
|
|
258
256
|
"name": name,
|
|
259
257
|
"dims": d,
|
|
260
258
|
"duration_ms": round((_time.perf_counter() - start) * 1000, 2),
|
|
261
|
-
"groups": groups,
|
|
262
259
|
}
|
|
263
260
|
},
|
|
264
261
|
)
|
|
@@ -305,7 +302,6 @@ class InMemoryMetricsProvider(MetricsProvider):
|
|
|
305
302
|
name: str,
|
|
306
303
|
*,
|
|
307
304
|
dims: dict[str, str | None] | None = None,
|
|
308
|
-
groups: list[str] | None = None,
|
|
309
305
|
) -> Generator[dict[str, str | None], None, None]:
|
|
310
306
|
import time as _time
|
|
311
307
|
|
|
@@ -54,10 +54,27 @@ class TemporalClientProvider:
|
|
|
54
54
|
self._config.host,
|
|
55
55
|
self._config.namespace,
|
|
56
56
|
)
|
|
57
|
-
# temporalio Client.connect is async
|
|
58
|
-
|
|
57
|
+
# temporalio Client.connect is async and exposes no per-call rpc_timeout of
|
|
58
|
+
# its own — bound it ourselves so an unreachable/slow Temporal frontend
|
|
59
|
+
# cannot hang every caller of get_client() indefinitely.
|
|
60
|
+
connect_call = Client.connect(
|
|
59
61
|
self._config.host,
|
|
60
62
|
namespace=self._config.namespace,
|
|
61
63
|
data_converter=pydantic_data_converter,
|
|
62
64
|
)
|
|
65
|
+
timeout = self._config.connect_timeout_seconds
|
|
66
|
+
try:
|
|
67
|
+
self._client = (
|
|
68
|
+
await asyncio.wait_for(connect_call, timeout=timeout)
|
|
69
|
+
if timeout
|
|
70
|
+
else await connect_call
|
|
71
|
+
)
|
|
72
|
+
except TimeoutError:
|
|
73
|
+
logger.error(
|
|
74
|
+
"[TEMPORAL] Connect to host=%s namespace=%s timed out after %ss",
|
|
75
|
+
self._config.host,
|
|
76
|
+
self._config.namespace,
|
|
77
|
+
timeout,
|
|
78
|
+
)
|
|
79
|
+
raise
|
|
63
80
|
return self._client
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright Thales 2025
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Residual authorization helpers after the RBAC→ReBAC migration (AUTHZ-01).
|
|
16
|
+
|
|
17
|
+
Role-based authorization (RBACProvider / @authorize / authorize_or_raise /
|
|
18
|
+
is_authorized / require_admin) has been removed — all endpoint authorization is now
|
|
19
|
+
ReBAC (OpenFGA). Task access authorization (list / stream / cancel) lives in its own
|
|
20
|
+
canonical owner, ``fred_core.tasks.authz`` (CTRLP-12), and is ReBAC-based; this module
|
|
21
|
+
only retains the internal service-principal users below.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from fred_core.security.structure import KeycloakUser
|
|
25
|
+
|
|
26
|
+
# Fake user to use when a function requires a user but we don't have one yet
|
|
27
|
+
# but should have one when authorization is added to the service.
|
|
28
|
+
# This is a temporary workaround and should be replaced with a real user.
|
|
29
|
+
TODO_PASS_REAL_USER = KeycloakUser(
|
|
30
|
+
uid="internal-admin-todo",
|
|
31
|
+
username="internal admin - todo",
|
|
32
|
+
email="internal-admin-todo@localhost",
|
|
33
|
+
roles=["admin"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Internal admin user to use for system operations that must always succeed
|
|
37
|
+
# and should not be subject to authorization checks.
|
|
38
|
+
NO_AUTHZ_CHECK_USER = KeycloakUser(
|
|
39
|
+
uid="internal-admin",
|
|
40
|
+
username="internal admin",
|
|
41
|
+
email="internal-admin@localhost",
|
|
42
|
+
roles=["admin"],
|
|
43
|
+
)
|