Commit graph

4 commits

Author SHA1 Message Date
672cf4da6e 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
2026-08-29 13:02:51 +02:00
749461b97b Implement PostgreSQL production store path
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
Add the PostgreSQL backend, migration and stopped-write transfer tools, lease-aware deployment manifests, tenancy declarations, and shared conformance coverage. Persist grouping mutations in durable stores and separate process liveness from database readiness.
2026-08-19 14:43:08 +02:00
31e237cfa6 Fix: resolve tenants by identifier, not only internal tenant_id
Found via a real cross-service check while implementing key-cape's
KEY-WP-0005-T02: key-cape's Go adapter called GET /tenants/tenant:coulomb/roles
and got a genuine 404 for a tenant that existed. External callers
(key-cape, flex-auth) only ever have a tenant's profile identifier, never
tenant-engine's internal tenant_id (caller-chosen at creation, otherwise
opaque). Every existing test happened to use identical strings for both
fields, so this was invisible until a real, independent second caller
exercised the documented contract.

InMemoryTenantStore gained a _by_identifier index and a _resolve() helper
every method calls first; create_tenant now also rejects a duplicate
identifier under a different internal id (an oversight the same fix
surfaced). 5 new tests, including the exact HTTP-level scenario with colon
characters in the URL path. 65 total, all 60 pre-existing tests unaffected.

Re-verified end-to-end for real: fresh flex-auth + tenant-engine + key-cape's
actual adapter code, over real HTTP -- roles=[IAM] ok=true resolving by
identifier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:15:26 +02:00
adb74d2443 TEN-WP-0002 T04-T07: cache-read, live-lookup (fail-closed), write API, close
- authz.py: WriteAuthorizer Protocol + DefaultDenyWriteAuthorizer. Every
  write endpoint calls it before touching the store; denial maps to
  403 write_denied via an exception handler.
- app.py: GET /tenants/{id}/roles (cache-read, key-cape) and
  GET /tenants/{id}/roles/live (live-lookup, flex-auth) share one handler
  that fails closed (503) on StoreUnavailableError -- deliberately made
  identical rather than giving cache-read weaker guarantees than the task
  strictly required. POST /tenants, /roles/grant, /roles/revoke, /plan --
  all four gated by the WriteAuthorizer seam, domain/store errors mapped to
  400/404/409 after authorization passes.
- store.py: new StoreUnavailableError for the fail-closed test double.

43 tests passing: default-deny on every write endpoint, an
_AllowAllAuthorizer test double proving the seam actually gates (full
create->grant->read->revoke->read->assign-plan lifecycle over real HTTP),
and a _BrokenStore double proving outage never looks like "zero roles".
Verified live over real HTTP, not just TestClient.

TEN-WP-0002 closed: all 7 tasks done, boundary-contract ownership checked
against the implementation with no drift found. Follow-ups recorded in the
closure note (real flex-auth WriteAuthorizer, key-cape wiring, guardrail
policy design, Binky as first real tenant record, durable persistence).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:24:09 +02:00