diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index e3f517ca..6d5e54b5 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -20,6 +20,16 @@ guarantees with deletion-safe linkage (§5.2); roll-up never-a-write restored to full A1 strength (§5.4); §6 rebuilt with bounded observables for every MUST (allowlist, command surface, audit, corrected cardinality witness). +Revision 3 (terra re-review residuals): §4.3 transfer write inventory +reconciled with §5.2 — the transaction's writes are the single class-row +mutation plus that mutation's §5.2 audit writes (event + outbox record), +not "exactly two writes"; §6.3 extended with a closed writer-coverage +witness so an unregistered internal writer cannot pass a registered-route +inventory. (Terra's finding-8 residual — a stale contract 2 §7.8 backlink +to contract 1 §6.2 — was already fixed in contract 2 revision 2, which +cites §6.5; measured against `origin/contract/rbac-grants` head +`501112d2`.) + Scope: the tenancy/authorization structure record class — companies, estates, platform-projects, workspaces, hierarchy grants, their parentage, and constraints. Out of scope: the RBAC grant vocabulary and evaluation @@ -143,8 +153,11 @@ shape contract 2 attaches to: introduced by this contract pair** (D2/A1 do not state it); its evaluation semantics are contract 2 §5. The structural half — that the transfer command evaluates it before mutating — binds here. -3. A transfer transaction contains exactly two writes: the single-row - parent-FK update and its audit event (§5.2). There are no multi-row +3. A transfer transaction mutates exactly one class-table row — the + single-row parent-FK update — and contains, beyond that, only the + §5.2 audit writes for that mutation (the audit event and its + hierarchy-outbox record, committing in the same transaction). No other + class, business, or orchestration row changes. There are no multi-row transfer batches at the schema level; bulk moves are N audited transfers. 4. Hierarchy records have no `owner_id`. Ownership in the hierarchy IS the @@ -213,10 +226,18 @@ Binding on the implementing PRs (extends A1 §8.3): 2. Column allowlist: an information_schema assertion that each class table's column set is exactly the set declared in §2/§3 — the bounded observable for no-payload (§2.7) and no-`owner_id` (§4.4). -3. Command surface: an inventory assertion over the Gateway's registered - hierarchy routes/commands proving the mutation surface is exactly the - declared hierarchy command family — no generic CRUD endpoint and no - non-command write path touches class tables (§5.1). +3. Command surface: two witnesses, both required (§5.1). (a) Route + inventory: an assertion over the Gateway's registered hierarchy + routes/commands proving the registered mutation surface is exactly the + declared hierarchy command family — no generic CRUD endpoint. (b) + Writer coverage — the closed allowlist a route inventory cannot + provide: a static CI assertion over the Gateway and package sources + that write references to the class-table schema symbols (insert, + update, delete) occur only in an explicitly enumerated allowlist of + hierarchy command/repository modules. The allowlist is closed: a new + writer anywhere else fails the assertion until the allowlist is + extended in the same PR, so an unregistered internal or raw-SQL write + path is surfaced in CI and review rather than passing unnoticed. 4. Audit witnesses: for each mutation class (create, rename, transfer, grant create/change/revoke, delete) — the event exists after commit with actor/verb/target and same-transaction atomicity; a rolled-back