diff --git a/requirements.txt b/requirements.txt index ee8835b5..5a78c60a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ rich>=14.0.0 typing_extensions>=4.14.0 google-genai>=2.12.1 +google-cloud-datastore==2.26.0 diff --git a/services/guardian/execution/production_composition.py b/services/guardian/execution/production_composition.py new file mode 100644 index 00000000..8595fc41 --- /dev/null +++ b/services/guardian/execution/production_composition.py @@ -0,0 +1,36 @@ +"""Production composition for Guardian execution authority. + +Production execution authority consumption must use persistent, +transactional storage. Failure to construct the Datastore client is +intentionally propagated: production must never silently fall back to +process-local in-memory consumption state. +""" + +from google.cloud import datastore + +from services.guardian.execution.authorization_consumption_store import ( + DatastoreAuthorizationConsumptionStore, +) +from services.guardian.execution.execution_layer import ( + AutonomousExecutionLayer, +) + + +def build_authorization_consumption_store(): + """Build the production authorization-consumption store.""" + + client = datastore.Client() + + return DatastoreAuthorizationConsumptionStore( + client=client, + ) + + +def build_execution_layer(): + """Build the production Guardian execution layer.""" + + store = build_authorization_consumption_store() + + return AutonomousExecutionLayer( + authorization_consumption=store, + ) diff --git a/services/guardian/tests/execution/test_production_authorization_composition.py b/services/guardian/tests/execution/test_production_authorization_composition.py new file mode 100644 index 00000000..bac3b8b8 --- /dev/null +++ b/services/guardian/tests/execution/test_production_authorization_composition.py @@ -0,0 +1,74 @@ +import pytest + +from services.guardian.execution.authorization_consumption_store import ( + DatastoreAuthorizationConsumptionStore, +) + + +def test_production_composition_uses_datastore_store(monkeypatch): + from services.guardian.execution import production_composition + + sentinel_client = object() + + monkeypatch.setattr( + production_composition.datastore, + "Client", + lambda: sentinel_client, + ) + + store = ( + production_composition + .build_authorization_consumption_store() + ) + + assert isinstance( + store, + DatastoreAuthorizationConsumptionStore, + ) + assert store._client is sentinel_client + + +def test_production_execution_layer_receives_datastore_store( + monkeypatch, +): + from services.guardian.execution import production_composition + + sentinel_client = object() + + monkeypatch.setattr( + production_composition.datastore, + "Client", + lambda: sentinel_client, + ) + + engine = production_composition.build_execution_layer() + + assert isinstance( + engine.authorization_consumption, + DatastoreAuthorizationConsumptionStore, + ) + assert ( + engine.authorization_consumption._client + is sentinel_client + ) + + +def test_production_composition_does_not_fallback_to_memory( + monkeypatch, +): + from services.guardian.execution import production_composition + + def fail_client(): + raise RuntimeError("DATASTORE_UNAVAILABLE") + + monkeypatch.setattr( + production_composition.datastore, + "Client", + fail_client, + ) + + with pytest.raises( + RuntimeError, + match="DATASTORE_UNAVAILABLE", + ): + production_composition.build_execution_layer()