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
|
|
|
|
---
|
|
|
|
|
|
id: TEN-WP-0002
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Service skeleton, domain model, and the three boundary-contract APIs"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: tenant-engine
|
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
|
|
|
|
status: finished
|
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
|
|
|
|
owner: codex
|
|
|
|
|
|
topic_slug: netkingdom
|
|
|
|
|
|
created: "2026-07-23"
|
|
|
|
|
|
updated: "2026-07-23"
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_workstream_id: "f2fcafb4-edef-4e8e-9401-d781b4d0c585"
|
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
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Service skeleton, domain model, and the three boundary-contract APIs
|
|
|
|
|
|
|
|
|
|
|
|
First real implementation workplan. Builds the tenant-engine skeleton
|
|
|
|
|
|
against `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`:
|
|
|
|
|
|
tenant/grouping/capability-role/plan-grant domain model, an audited grant
|
|
|
|
|
|
trail, and the three API surfaces the contract defines — cache-read
|
|
|
|
|
|
(`key-cape`), live-lookup (`flex-auth`), and write (grant/revoke/plan
|
|
|
|
|
|
mutation). Guardrail/quota policy stays a reserved, unimplemented namespace
|
|
|
|
|
|
per ADR-0014 — not in scope here.
|
|
|
|
|
|
|
|
|
|
|
|
**Depends on:** `net-kingdom` canon — `iam-profile_v0.3.md`,
|
|
|
|
|
|
`tenant-engine-boundary-contract_v0.1.md`, `ADR-0013`, `ADR-0014` (all
|
|
|
|
|
|
ratified). **Non-goals:** payment processing, pricing-model definitions
|
|
|
|
|
|
(`adaptive-pricing`'s job), real `flex-auth` integration (the write/live
|
|
|
|
|
|
APIs get a policy-hook interface, not a working `flex-auth` client — that's
|
|
|
|
|
|
a follow-up once `flex-auth` has a reachable endpoint), guardrail/quota
|
|
|
|
|
|
enforcement.
|
|
|
|
|
|
|
|
|
|
|
|
## Task: Service skeleton
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T01
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: high
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "d22b0ce8-3f25-4c8d-b54f-ba44c65159c1"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Python 3.12 + FastAPI, matching `qonto-assistant`'s layout convention:
|
|
|
|
|
|
`pyproject.toml`, `Makefile` (`install-dev`, `test`, `lint`, `run`), package
|
|
|
|
|
|
layout separating `domain/` (pure), `store/` (persistence), `api/` (FastAPI
|
|
|
|
|
|
routers), from day one — not refactored in later.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: `make test` runs an empty/smoke suite; `make run` starts a bare
|
|
|
|
|
|
FastAPI app with a `/health` endpoint.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** `pyproject.toml` + `Makefile` land, mirroring
|
|
|
|
|
|
`qonto-assistant`'s exactly (`install-dev`/`test`/`lint`/`run`).
|
|
|
|
|
|
`src/tenant_engine/{domain,store,app,main}.py` scaffolded per the planned
|
|
|
|
|
|
layering (`domain.py` pure, `store.py` the persistence seam, `app.py` the
|
|
|
|
|
|
FastAPI factory). Verified live: `python -m tenant_engine.main` starts on
|
|
|
|
|
|
`127.0.0.1:8090`, `GET /health` returns
|
|
|
|
|
|
`{"status": "ok", "service": "tenant-engine", "version": "0.1.0"}`.
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Domain model — tenant, grouping, capability role, plan grant
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T02
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: high
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "cfceea30-f383-4781-a7e4-226653aafca9"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Pure domain types, no framework dependency:
|
|
|
|
|
|
|
|
|
|
|
|
- `Tenant`: id, identifier (`tenant:<grouping>:<name>` or reserved
|
|
|
|
|
|
`tenant:platform`/`tenant:coulomb`), grouping (ADR-0013 enum, nullable for
|
|
|
|
|
|
the two reserved identifiers).
|
|
|
|
|
|
- `CapabilityRole`: enum `PLTF`/`IAM`/`VEN`/`CUS`.
|
|
|
|
|
|
- `RoleGrant`: tenant_id, role, grant_reason (`plan_assignment` /
|
|
|
|
|
|
`manual_grant` / `platform_default`), plan_id (nullable), granted_by,
|
|
|
|
|
|
granted_at, revoked_at (nullable), correlation_id — the audited record
|
|
|
|
|
|
shape from the boundary contract's Tenant Role & Plan Grant Contract.
|
|
|
|
|
|
- `PlanAssignment`: tenant_id, plan_id (references an `adaptive-pricing`
|
|
|
|
|
|
plan id — stored as an opaque string, never resolved or duplicated
|
|
|
|
|
|
locally), assigned_at.
|
|
|
|
|
|
|
|
|
|
|
|
Validation rules encoded as domain invariants, not just API-layer checks:
|
|
|
|
|
|
|
|
|
|
|
|
- grouping must be one of ADR-0013's twelve values, or the tenant identifier
|
|
|
|
|
|
must be exactly `tenant:platform`/`tenant:coulomb` (grouping-less);
|
|
|
|
|
|
- a `trial`-grouped tenant may hold any role with `grant_reason:
|
|
|
|
|
|
platform_default` and no `plan_id` (ADR-0014) — this must be
|
|
|
|
|
|
representable, not blocked by a plan-required constraint;
|
|
|
|
|
|
- non-`trial` roles other than `platform_default` require a `plan_id` when
|
|
|
|
|
|
`grant_reason: plan_assignment`;
|
|
|
|
|
|
- revoking a grant sets `revoked_at`, never deletes the record (audit trail).
|
|
|
|
|
|
|
|
|
|
|
|
Done when: unit tests cover valid/invalid grouping values, the trial/
|
|
|
|
|
|
no-plan-required exception, and grant/revoke as append-only operations.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** `domain.py` implements `Tenant`, `CapabilityRole`,
|
|
|
|
|
|
`RoleGrant`, `PlanAssignment`, and `create_role_grant()` exactly as
|
|
|
|
|
|
specified. One refinement made while implementing, not pre-specified in the
|
|
|
|
|
|
task text: `platform_default` is valid for `trial`-grouped tenants **or**
|
|
|
|
|
|
the reserved, ungrouped `tenant:platform`/`tenant:coulomb` tenants (their
|
|
|
|
|
|
baseline roles were never purchased either) — the task only mentioned the
|
|
|
|
|
|
`trial` case. Also enforced `platform_default` grants must not carry a
|
|
|
|
|
|
`plan_id` (implied by "no `plan_id`" in the task text, made an explicit
|
|
|
|
|
|
invariant). `tests/test_domain.py` covers all listed cases plus the
|
|
|
|
|
|
reserved-tenant extension and double-revoke rejection. `pytest` →
|
|
|
|
|
|
29 passed (whole-repo total, includes T03's tests below).
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Storage layer
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T03
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: high
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "ade9374a-ed26-45da-a424-a0d21effe520"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
In-memory store behind a `TenantStore` protocol/interface (mirrors
|
|
|
|
|
|
`qonto_client.QontoClientProtocol`'s pattern in `qonto-assistant`), so a
|
|
|
|
|
|
real backend can be swapped in later without touching `domain/` or `api/`.
|
|
|
|
|
|
Every mutation emits a domain event (tenant created, role granted, role
|
|
|
|
|
|
revoked, plan assigned) per the boundary contract's Audit Correlation
|
|
|
|
|
|
Contract — the event bus itself can be a simple in-process list for now.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: unit tests cover create/read/grant/revoke/assign-plan through the
|
|
|
|
|
|
store interface, plus event emission for each mutation.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** `store.py`'s `TenantStore` Protocol +
|
|
|
|
|
|
`InMemoryTenantStore` implement create/get/grant/revoke/active_roles/
|
|
|
|
|
|
assign_plan, each emitting a `DomainEvent`. `tests/test_store.py` covers
|
|
|
|
|
|
the full lifecycle, non-exclusive role coexistence (`CUS` + `VEN`
|
|
|
|
|
|
simultaneously, proving ADR-0014's non-exclusivity isn't just a comment),
|
|
|
|
|
|
revoke-keeps-record-but-clears-active-set, and asserts the exact event
|
|
|
|
|
|
sequence for a full mutation chain. `python -m compileall src tests` clean.
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Cache-read API (for key-cape)
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T04
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: high
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "425cab98-ab7f-47fb-a8b6-ec0f52d62cd5"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`GET /tenants/{tenant_id}/roles` — returns current, non-revoked capability
|
|
|
|
|
|
roles for a tenant. This is the endpoint `key-cape` calls at token-issuance
|
|
|
|
|
|
time to source the cached `tenant_roles` claim (IAM Profile v0.3). No
|
|
|
|
|
|
authorization gate of its own beyond service-to-service auth (out of scope
|
|
|
|
|
|
here — see Non-Goals); this endpoint's whole purpose is to be cheap and
|
|
|
|
|
|
fast, per the boundary contract's performance model.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: integration test hits the endpoint against the in-memory store
|
|
|
|
|
|
and returns the expected role set for a seeded tenant.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** Implemented in `app.py`. Returns
|
|
|
|
|
|
`{"tenant_id": ..., "roles": [...]}`, sorted role-value strings; 404 for an
|
|
|
|
|
|
unknown tenant. Extended beyond the task's minimum: also fails closed (503)
|
|
|
|
|
|
on store unavailability (see T05) — there was no good reason for the two
|
|
|
|
|
|
read endpoints to behave differently on that axis, and keeping them
|
|
|
|
|
|
identical avoids a second, subtly-different error-handling path to drift
|
|
|
|
|
|
later. `tests/test_api_reads.py` covers the happy path and the 404 case;
|
|
|
|
|
|
verified live over real HTTP against the running service.
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Live-lookup API (for flex-auth) — fail closed
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T05
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: high
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "2a04ce4c-c967-4540-8873-70ac419378e6"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`GET /tenants/{tenant_id}/roles/live` — same data as the cache-read
|
|
|
|
|
|
endpoint, but explicitly documented and tested as the path `flex-auth` must
|
|
|
|
|
|
call before authorizing `aal2`-class actions. The distinction from T04 is
|
|
|
|
|
|
operational intent (freshness guarantee, called synchronously on a
|
|
|
|
|
|
privileged-decision path), not payload shape.
|
|
|
|
|
|
|
|
|
|
|
|
Per the boundary contract's performance model: **fail closed, never open**.
|
|
|
|
|
|
If the store is unavailable, this endpoint must return an error response
|
|
|
|
|
|
that a policy caller would treat as "deny", not a default-allow or an empty
|
|
|
|
|
|
role list indistinguishable from "no roles granted".
|
|
|
|
|
|
|
|
|
|
|
|
Done when: a test simulates store unavailability and asserts the endpoint
|
|
|
|
|
|
signals failure distinctly from "zero roles", not silently as 200 + `[]`.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** New `store.StoreUnavailableError`; both read endpoints
|
|
|
|
|
|
convert it to `503 {"detail": "tenant_roles_unavailable"}`. A `_BrokenStore`
|
|
|
|
|
|
test double (`tests/test_api_reads.py`) always raises it from
|
|
|
|
|
|
`active_roles()`, simulating an outage; the test asserts `503` and
|
|
|
|
|
|
explicitly asserts the response body is *not*
|
|
|
|
|
|
`{"tenant_id": ..., "roles": []}` — the exact ambiguity the task exists to
|
|
|
|
|
|
prevent. `TenantNotFoundError` stays a distinct `404`, so "tenant doesn't
|
|
|
|
|
|
exist," "store is down," and "tenant exists with zero roles" are three
|
|
|
|
|
|
different, distinguishable responses, never collapsed into one shape.
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Write API — grant, revoke, assign-plan
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T06
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: medium
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "3a2d1ee7-6e66-4080-9345-32ba457acf5f"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`POST /tenants` (create), `POST /tenants/{id}/roles/grant`,
|
|
|
|
|
|
`POST /tenants/{id}/roles/revoke`, `POST /tenants/{id}/plan` — all mutating
|
|
|
|
|
|
endpoints from the boundary contract's Write API. Per the contract,
|
|
|
|
|
|
`tenant-engine` does not self-authorize these writes; `flex-auth` is meant
|
|
|
|
|
|
to gate them. Since a reachable `flex-auth` integration is explicitly a
|
|
|
|
|
|
non-goal here, implement a `WriteAuthorizer` protocol/interface point (one
|
|
|
|
|
|
class, default-deny stub) so every mutation already flows through a single
|
|
|
|
|
|
seam — swapping in a real `flex-auth` client later touches one file, not
|
|
|
|
|
|
every endpoint.
|
|
|
|
|
|
|
|
|
|
|
|
Done when: unit tests confirm every write endpoint calls the
|
|
|
|
|
|
`WriteAuthorizer` seam and is denied by the default-deny stub; a
|
|
|
|
|
|
test-only authorizer override proves the seam actually gates the mutation
|
|
|
|
|
|
when swapped.
|
|
|
|
|
|
|
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
|
|
|
|
**Done 2026-07-23:** `authz.py`'s `WriteAuthorizer` Protocol +
|
|
|
|
|
|
`DefaultDenyWriteAuthorizer`; every write endpoint in `app.py` calls
|
|
|
|
|
|
`authorizer.authorize(...)` before touching the store, and a
|
|
|
|
|
|
`WriteAuthorizationDeniedError` exception handler maps denial to
|
|
|
|
|
|
`403 {"error_code": "write_denied", ...}`. `tests/test_api_writes.py`
|
|
|
|
|
|
asserts all four write endpoints are `403` under the default authorizer,
|
|
|
|
|
|
then swaps in an `_AllowAllAuthorizer` test double and exercises the full
|
|
|
|
|
|
create → grant → read → revoke → read → assign-plan lifecycle over real
|
|
|
|
|
|
HTTP requests (`TestClient`), confirming the seam actually gates rather
|
|
|
|
|
|
than just existing decoratively. Domain/store errors surfacing after
|
|
|
|
|
|
authorization passes get separate status codes: `400` invalid
|
|
|
|
|
|
identifier/grant, `404` unknown tenant, `409` duplicate tenant. Verified
|
|
|
|
|
|
live: unauthenticated `POST /tenants` over real HTTP returns `403` with the
|
|
|
|
|
|
expected body, matching the test suite. One known simplification, not
|
|
|
|
|
|
resolved here: `actor` is a request-body field rather than extracted from a
|
|
|
|
|
|
real auth context — there is no real auth context yet, since wiring one in
|
|
|
|
|
|
is exactly what a `flex-auth`-backed `WriteAuthorizer` will do; noted for
|
|
|
|
|
|
whoever picks up that follow-up.
|
|
|
|
|
|
|
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
|
|
|
|
## Task: Closure review
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: TEN-WP-0002-T07
|
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
|
|
|
|
status: done
|
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
|
|
|
|
priority: low
|
2026-07-23 21:56:43 +02:00
|
|
|
|
state_hub_task_id: "eecf4bc8-d20b-4b18-986a-6518f2f74d7b"
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Confirm T01–T06 done; run `make test`/`make lint`; verify the three API
|
|
|
|
|
|
surfaces match the boundary contract's Ownership Model and Source-of-Truth
|
|
|
|
|
|
Matrix with no drift. Note follow-ups: real `flex-auth` `WriteAuthorizer`
|
|
|
|
|
|
integration, `key-cape` wiring to actually call the cache-read endpoint at
|
|
|
|
|
|
issuance, guardrail/quota policy design (ADR-0014's reserved item), and
|
|
|
|
|
|
Binky Hedgehog GmbH as the first real tenant record once `key-cape`'s
|
|
|
|
|
|
`KEY-WP-0004` reaches that point. Run `statehub fix-consistency`.
|
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
|
|
|
|
|
|
|
|
|
|
**Closed 2026-07-23.** T01–T06 all done. `PYTHONPATH=src pytest` → `43
|
|
|
|
|
|
passed`; `python -m compileall src tests` clean; `ruff` unavailable in this
|
|
|
|
|
|
workstation's shared venv (same environment gap noted in `qonto-assistant`'s
|
|
|
|
|
|
own workplans) — `compileall` substituted, `make lint` untested against a
|
|
|
|
|
|
real `ruff` install. Verified live over real HTTP, not just `TestClient`:
|
|
|
|
|
|
`/health` 200, unauthenticated `POST /tenants` 403 with the expected error
|
|
|
|
|
|
body, `GET` on an unknown tenant 404.
|
|
|
|
|
|
|
|
|
|
|
|
**Ownership check against the boundary contract:** all three API surfaces
|
|
|
|
|
|
present (cache-read, live-lookup, write); every write routes through the
|
|
|
|
|
|
`WriteAuthorizer` seam, never self-authorized; every mutation emits a
|
|
|
|
|
|
`DomainEvent`; `plan_id` is stored and returned as an opaque string, never
|
|
|
|
|
|
resolved against `adaptive-pricing` locally; nothing here stores user data,
|
|
|
|
|
|
issues tokens, or makes an authorization decision. No drift found.
|
|
|
|
|
|
|
|
|
|
|
|
**Follow-ups, not started:**
|
|
|
|
|
|
- Real `flex-auth`-backed `WriteAuthorizer` (replaces the default-deny
|
|
|
|
|
|
stub) — also where a real `actor` identity would come from instead of a
|
|
|
|
|
|
request-body field.
|
|
|
|
|
|
- `key-cape` wiring to actually call the cache-read endpoint at token
|
|
|
|
|
|
issuance.
|
|
|
|
|
|
- Guardrail/quota policy design (ADR-0014's reserved item — spend limits,
|
|
|
|
|
|
entity/action counts).
|
|
|
|
|
|
- Binky Hedgehog GmbH as the first real tenant record, once `key-cape`'s
|
|
|
|
|
|
`KEY-WP-0004` reaches that point.
|
|
|
|
|
|
- Persistence beyond in-memory (`TenantStore` is already a swappable seam
|
|
|
|
|
|
for this).
|