Implement TEN-WP-0011 security layer conformance
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s

Engine/PIP declaration is now checkable (layer.yaml plus a Tooling-client
scan). Writes persist a decision record or the published fail-closed
stance, live-lookup freshness is published, events_for is tenant-scoped,
and mutation evidence drains to audit-core from a local outbox without
blocking the mutation.

Sender registration is requested as AUDIT-IN-0002. Boundary-contract
amendment is requested as NET-IN-0002.

Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
This commit is contained in:
tegwick 2026-08-29 13:02:51 +02:00
parent 80961af91e
commit 672cf4da6e
40 changed files with 2285 additions and 361 deletions

View file

@ -140,7 +140,7 @@ def test_the_audit_trail_is_append_only(store, tenant):
def test_a_guardrail_change_emits_a_domain_event(store, tenant):
set_override(store, tenant, eur(9_000))
events = [e for e in store.events() if e.event_type == "guardrail_changed"]
events = [e for e in store.events_for(tenant.tenant_id) if e.event_type == "guardrail_changed"]
assert len(events) == 1
assert events[0].payload["limit_key"] == KEY
assert events[0].payload["correlation_id"] == "corr-1"
@ -161,9 +161,11 @@ def test_unlimited_survives_a_round_trip_as_an_explicit_value(store, tenant):
entity = LimitValue(kind=LimitKind.ENTITY_COUNT, amount=UNLIMITED)
# spend.monthly is the only registered key, so use it to prove the
# sentinel serialises; the kind check lives in the domain tests
set_override(store, tenant, LimitValue(
kind=LimitKind.SPEND, amount=UNLIMITED, currency="EUR", period="P1M"
))
set_override(
store,
tenant,
LimitValue(kind=LimitKind.SPEND, amount=UNLIMITED, currency="EUR", period="P1M"),
)
stored = store.guardrail_overrides(tenant.tenant_id)[KEY]
assert stored.is_unlimited
assert entity.is_unlimited
@ -175,8 +177,9 @@ def test_unlimited_survives_a_round_trip_as_an_explicit_value(store, tenant):
def test_a_stale_version_conflicts(store, tenant):
set_override(store, tenant, eur(9_000))
with pytest.raises(VersionConflictError):
set_override(store, tenant, eur(1_000), version=1, change_id="c-2",
idempotency_key="idem-2")
set_override(
store, tenant, eur(1_000), version=1, change_id="c-2", idempotency_key="idem-2"
)
def test_replay_returns_the_original_result_without_reapplying(store, tenant):
@ -204,8 +207,7 @@ def test_an_unregistered_key_is_rejected_before_anything_is_written(store, tenan
def test_a_failed_write_leaves_no_audit_record_and_no_version_bump(store, tenant):
set_override(store, tenant, eur(9_000))
with pytest.raises(VersionConflictError):
set_override(store, tenant, eur(1), version=99, change_id="c-2",
idempotency_key="idem-2")
set_override(store, tenant, eur(1), version=99, change_id="c-2", idempotency_key="idem-2")
assert store.get_tenant(tenant.tenant_id).version == 2
assert len(store.guardrail_changes(tenant.tenant_id)) == 1
assert store.guardrail_overrides(tenant.tenant_id)[KEY].amount == 9_000
@ -265,6 +267,5 @@ def test_clearing_an_override_that_would_loosen_is_refused_while_retired(store,
)
# clearing would fall back to small's 25_000 default -- a loosening
with pytest.raises(TenantRetiredError):
set_override(store, tenant, None, version=3, change_id="c-3",
idempotency_key="idem-3")
set_override(store, tenant, None, version=3, change_id="c-3", idempotency_key="idem-3")
assert store.guardrail_overrides(tenant.tenant_id)[KEY].amount == 100