Commit graph

41 commits

Author SHA1 Message Date
6644ad8402 Onboard tenant-engine to the staged-promotion contract
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
TEN-WP-0008. railiance/app.toml declares criticality=high, empty secrets,
isolated canary, and the live PostgreSQL digest as previous_stable.
Manifests render through kustomize (deploy/ and deploy/canary/). Stage 1
passed. Stage 2/3 Helm-only CLI gap requested as RAIL-BS-IN-0001 rather
than a dummy chart.

Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
2026-08-29 14:51:27 +02:00
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
repo-manager
c722970aab chore(registrar): assign State Hub identifiers
Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
2026-08-29 12:02:53 +02:00
76677bffc5 Declare Engine/PIP and open TEN-WP-0011 for v0.7 conformance
The security layer model is accepted at v0.7. INTENT.md now declares
Engine / PIP in this repository's own voice (TEN-DEC-2026-001), SCOPE.md
is brought current with shipped APIs, and the intent/scope/implementation
review is history/2026-08-29-security-layer-intent-scope-review.md.

TEN-IN-0003 is absorbed by the declaration. Approvals stay with
approval-engine. TEN-IN-0001 and TEN-IN-0002 promote into TEN-WP-0011.

Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
2026-08-29 11:57:37 +02:00
f82017090b fix(workplans): qualify ad-hoc identifiers with the repository prefix
`ADHOC-YYYY-MM-DD` is unique per date but not per repository, so any two repos
opening an ad-hoc on the same day collide. The 2026-08-26 fleet projection
reset refused 9 records for exactly this reason.

Canon (work-record-types_v0.1, CUST-WP-0066) settled the form as
`{PREFIX}-WP-ADHOC-YYYY-MM-DD`, filename unchanged, and grandfathered existing
ids on the condition they are never *silently* re-derived. This is the explicit
migration that clause allows for.

The hub id is derived from the record id, so a changed id is a different
record: stale state_hub_*_id fields are dropped and fix-consistency re-derives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-28 00:28:24 +02:00
7a1725160f docs: finish PostgreSQL production cutover 2026-08-21 01:20:54 +02:00
custodian-sync
0a65a4a25c chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-08-18:
  - workplan status: ready → active
2026-08-18 07:29:03 +02:00
b22d4a7294 TEN-WP-0010-T01: mark proposed-and-recorded as done, ratification outstanding
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:53:52 +02:00
bdf9f5f643 Finish TEN-WP-0010: mutable grouping, contract corrected, handoffs sent
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:53:10 +02:00
b998ca2332 Finish TEN-WP-0010-T03/T04: audited grouping mutation
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m5s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:09:24 +02:00
1b2526910b TEN-WP-0010-T02 done, T01 proposed: no consumer parses the grouping segment
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:05:59 +02:00
20979040b2 Plan mutable grouping (TEN-WP-0010)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:41:05 +02:00
5a9e78f657 TEN-WP-0009-T01: record rapp-postgres acceptance, add postgres-client label
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 10:59:32 +02:00
05d12fe39f TEN-WP-0009-T01: target platform-pg via rapp-postgres consumer declaration
Corrects the credential assumption: shared-cluster access is a broker lease,
not a CNPG-minted secretKeyRef.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 10:44:59 +02:00
cf7ca1a692 TEN-WP-0009: make database placement portable rather than settled
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 18:59:30 +02:00
781359e7d4 Plan PostgreSQL production store (TEN-WP-0009)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 18:25:35 +02:00
c05cc704b2 Plan staged-promotion onboarding (TEN-WP-0008)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 18:17:39 +02:00
76196aa39f Finish TEN-WP-0007: guardrail surface live in production
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:34:44 +02:00
custodian-sync
1020758e83 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-08-16:
  - workplan status: ready → active
2026-08-16 10:02:49 +02:00
611431c2b7 TEN-WP-0007: pin guardrail image, record CI digest and flex-auth nine-action pin
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:02:34 +02:00
9f1e58a797 Plan guardrail production rollout (TEN-WP-0007)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:52:05 +02:00
955fe339fd Finish TEN-WP-0006: guardrail and quota policy
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 43s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:19:48 +02:00
d1d9c9a735 TEN-WP-0006-T05: cross-tenant denial and write-path outage conformance
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:18:52 +02:00
b6d016869f Finish TEN-WP-0006-T04: expose guardrail read and write APIs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:18:01 +02:00
f631224ab5 Finish TEN-WP-0006-T03: persist guardrails in both stores
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:14:31 +02:00
33ceb882ee Finish TEN-WP-0006-T02: implement guardrail domain model
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:10:38 +02:00
0d1435c2d2 Finish TEN-WP-0006-T01: specify guardrail model and boundary
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:07:42 +02:00
b8af02d74d Plan guardrail and quota policy (TEN-WP-0006)
Closes the guardrail/quota concern reserved since repo creation. Notably
ADR-0013 specifies trial tenants default to a zero spend budget; no such
default exists today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 01:50:08 +02:00
9ed391aec7 Finish TEN-WP-0005-T05: ship lifecycle image and verify production
Pin tenant-engine to the CI-built digest from 7e68cc8, record rollback
digests, and close T05 after live create/update/retire/replay/reactivate
evidence against a disposable production tenant. Hands the 0.1.0 contract
to USER-WP-0021.
2026-08-14 01:42:41 +02:00
7e68cc835e Start TEN-WP-0005-T05: recover deploy manifests and add CI image build
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 20s
Production still serves the TEN-WP-0004 image, which has no lifecycle
routes. Recover the live railiance01 objects into deploy/ so rollback
does not depend on a cluster annotation, and add the fleet CI image
workflow so the lifecycle image is built from a forge revision.
2026-08-14 01:37:44 +02:00
d6fd73bd42 Implement tenant update and reversible retirement API (TEN-WP-0005 T01-T04)
Add an explicit tenant lifecycle (active/retired), allow-listed mutable
metadata, record versioning, and lifecycle timestamps to the tenant authority.

- domain: TenantLifecycle, with_metadata/retire/reactivate, immutability and
  transition invariants. Identifier stays immutable -- it is the IAM Profile
  `tenant` claim key-cape mints into tokens.
- store: mutate_tenant() commits idempotency replay, version CAS, mutation,
  and audit event together; durable receipts survive restart. Retired tenants
  refuse new grants and plan changes but keep their history.
- sqlite: forward-only idempotent migration; existing rows default to active
  at version 1. Reads now take the write lock -- the concurrent-writer test
  caught unguarded reads on the shared connection observing mid-transaction
  state as a spurious tenant_not_found.
- api: GET/PATCH /tenants/{id}, POST retire|reactivate. Idempotency-Key and
  If-Match required, distinct flex-auth actions per operation, stable error
  schema, redacted 503s.
- docs/tenant-lifecycle-api.md: consumer contract for user-engine.

Implemented against SQLite, not PostgreSQL as the workplan assumed --
TEN-WP-0004 shipped SQLite on a PVC as the production store.

124 tests pass (was 66); no breaking change to existing endpoints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 20:00:43 +02:00
7dcccafc03 Plan tenant update and retirement APIs 2026-08-10 18:54:28 +02:00
a48d3711c9 Sync tenant runtime workplan state 2026-08-09 01:49:11 +02:00
1e44cace1f Deploy durable tenant authority runtime 2026-08-09 01:35:00 +02:00
20bda2f0e5 chore(consistency): register ADHOC-2026-07-24 in State Hub [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:16:21 +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
5d57c7d488 TEN-WP-0003: FlexAuthWriteAuthorizer -- gate writes through flex-auth
flex_auth.py: CheckRequest + FlexAuthCheckClient against flex-auth's real
POST /v1/check contract (schemas/check_request.schema.json,
decision_envelope.schema.json, read directly from the flex-auth repo, not
guessed). Fail-closed by construction: only effect=="allow" authorizes;
every other effect, non-200, malformed body, or transport failure resolves
to deny, nothing raises past is_allowed().

authz.FlexAuthWriteAuthorizer implements the existing WriteAuthorizer
Protocol. Action -> resource-type mapping coordinated with FLEX-WP-0008's
planned vocabulary (both repos reference the same table).
DefaultDenyWriteAuthorizer stays the fallback when no flex-auth URL is
configured.

config.py: Settings.from_env(), mirroring qonto-assistant's pattern.
docs/flex-auth-integration.md documents the contract, fail-closed rule,
and current real state (denies everything until FLEX-WP-0008 lands).

60 tests passing. Verified live twice over real HTTP between separate
processes (not just MockTransport): a deny-returning flex-auth double
produces 403 from POST /tenants, an allow-returning one produces 201.

Also registered (not implemented) the two workplans this depends on for a
complete picture: flex-auth/FLEX-WP-0008 (protected-system registration --
what makes allow reachable) and key-cape/KEY-WP-0005 (discovered key-cape
emits none of iam-profile_v0.3.md's core claims yet, not just missing
tenant_roles -- a bigger, security-sensitive gap flagged rather than
quietly worked around).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:54:44 +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
0770ce82d9 TEN-WP-0002 T01-T03: service skeleton, domain model, storage layer
Python 3.12 + FastAPI, pyproject.toml + Makefile mirroring
qonto-assistant's exactly. src/tenant_engine/{domain,store,app,main}.py:

- domain.py: Tenant, CapabilityRole (PLTF/IAM/VEN/CUS), RoleGrant,
  PlanAssignment, create_role_grant() enforcing ADR-0014's invariants.
  Refinement made while implementing: platform_default grants are valid
  for trial-grouped tenants OR the reserved tenant:platform/tenant:coulomb
  tenants (their baseline roles were never purchased either) -- the task
  spec only named the trial case.
- store.py: TenantStore Protocol + InMemoryTenantStore, every mutation
  emits a DomainEvent per the boundary contract's Audit Correlation
  Contract.
- app.py/main.py: FastAPI factory + /health endpoint, verified live on
  127.0.0.1:8090.

29 tests passing, including non-exclusive role coexistence (CUS+VEN
simultaneously) and append-only revoke semantics.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:01:23 +02:00
7eb21c05b8 chore(consistency): register TEN-WP-0001/0002 in State Hub [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 21:56:43 +02:00
9370348d54 Bootstrap repo: State Hub registration, agent docs, TEN-WP-0001/0002
statehub register + repo-seed template scaffold (CLAUDE.md, .claude/rules/,
registry/). INTENT.md and SCOPE.md rewritten from the generated stub to
match net-kingdom's ratified tenant-engine-boundary-contract_v0.1.md
(Purpose, Responsibility Boundary, Non-Goals). topic_slug corrected from
the auto-assigned custodian default to netkingdom, matching key-cape and
user-engine.

TEN-WP-0001 (bootstrap) complete: files reviewed/refined, stack decided
(Python 3.12 + FastAPI, matching qonto-assistant's convention), first real
workplan seeded.

TEN-WP-0002 drafted: service skeleton, domain model (tenant/grouping/
capability-role/plan-grant), storage layer, and the three boundary-contract
API surfaces (cache-read for key-cape, live-lookup for flex-auth with an
explicit fail-closed requirement, write API behind a WriteAuthorizer seam
since real flex-auth integration is a declared non-goal for this pass).

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