From 7ef620cda7da056886a3aa5aa050db17a64fc64f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:10:45 -0500 Subject: [PATCH 1/9] docs: hierarchy schema contract (S2 contract 1, D2) Turns PRD D2/D13 and kanban-SOT Amendment A1 into a concrete schema contract: companies/estates/platform_projects/workspaces with NOT NULL RESTRICT parent chains (chain uniqueness by construction), per-parent slug scoping, hierarchy_grants attachment shape (vocabulary deferred to contract 2), transfer as single audited parent-FK update requiring both-sides authority, no owner columns (ownership = grants), fail-closed bottom-up deletion, Gateway-only mutation path, and a verification matrix with real-Postgres constraint witnesses. --- docs/requirements/hierarchy-schema.md | 163 ++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 docs/requirements/hierarchy-schema.md diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md new file mode 100644 index 00000000..b82497d8 --- /dev/null +++ b/docs/requirements/hierarchy-schema.md @@ -0,0 +1,163 @@ +# Hierarchy Schema Contract (D2) + +Status: DRAFT — awaiting ratification (webui-audit S2, contract 1 of 9). +Authority: PRD D2/D9/D13 (Part I §4) and the native-kanban SOT Amendment A1 +(`docs/requirements/native-kanban-sot.md` §8, ratified 2026-08-25). This +document turns the ratified hierarchy into a concrete schema contract: +tables, cardinalities, constraints, and ownership/transfer semantics. It is +the prerequisite for the D8 rank-1 tool (hierarchy command family) and for +the RBAC grant model (contract 2). + +Scope: the tenancy/authorization structure record class — companies, +estates, platform-projects, workspaces, their parentage, and the grant +attachment points. Out of scope: the RBAC grant vocabulary and evaluation +semantics (contract 2), roll-up projection semantics (contract 8), kanban +planning entities inside workspaces (SOT §5), migration of legacy flat data +(SOT REQ-MIG-001). + +## 1. Record class and placement + +1. The four tables below form the **tenancy/authorization structure record + class** defined by Amendment A1 §8.1.2. They carry parentage, naming, and + grant data only — never task, plan, or any business/orchestration + payload. No business/orchestration row may reference a hierarchy record + as a dependency, claim target, or work subject. +2. Hierarchy records are NOT workspace-scoped rows: REQ-TEN-001's + `workspace_id` obligation binds business/orchestration rows and does not + apply to this class (A1 §8.1.2). The `workspaces` table itself is the + anchor the obligation points at. +3. The legacy flat tables (`teams`, `projects` in `packages/db/src/schema.ts` + — the Brain planning table, not the hierarchy level) are not part of this + class and are not repurposed. The SOT forbids reusing the flat model for + the hierarchy; their eventual migration or retirement is REQ-MIG-001 + work, out of scope here. + +## 2. Tables and cardinalities + +Naming: the level above workspaces is `platform_projects`, per A1 §8.1.4 — +the existing `projects` table already means workspace-internal planning +entities, and the schema MUST NOT merge the two. (A rename of either +remains an implementation-PR decision under A1; this contract pins only that +they stay distinct tables.) + +1. `companies` — id (uuid pk), name, slug (unique per deployment), + created_at, updated_at. N per deployment (D2). +2. `estates` — id, name, slug, `company_id` NOT NULL → + `companies.id` ON DELETE RESTRICT. Exactly one company per estate. +3. `platform_projects` — id, name, slug, `estate_id` NOT NULL → + `estates.id` ON DELETE RESTRICT. Exactly one estate per + platform-project. +4. `workspaces` — id, name, slug, `platform_project_id` NOT NULL → + `platform_projects.id` ON DELETE RESTRICT. Exactly one platform-project + per workspace. This table is the referent of every `workspace_id` column + the SOT requires on canonical rows. +5. **Chain resolution is by construction.** Because every parent FK is NOT + NULL and single-valued, each workspace resolves to exactly one + platform-project → estate → company chain (A1 §8.3 acceptance 1). There + are no parentage edge tables, no multi-parent forms, and no nullable + "detached" states. +6. **Slug scoping.** `estates.slug` is unique within its company, + `platform_projects.slug` within its estate, `workspaces.slug` within its + platform-project (composite unique constraints). Display names are + unconstrained. +7. No hierarchy table carries a `metadata` jsonb column or any + free-form payload field. Parentage, naming, timestamps, and the audit + linkage of §5 — nothing else (A1 §8.1.2). + +## 3. Grant attachment points + +The grant vocabulary (which roles exist, what each permits, how inheritance +and revocation evaluate) is contract 2. This contract pins only the schema +shape contract 2 attaches to: + +1. `hierarchy_grants` — id, subject (exactly one of `user_id` → `users.id`, + `team_id` → `teams.id`; CHECK-enforced exactly-one-of), target (exactly + one of `company_id`, `estate_id`, `platform_project_id`; + CHECK-enforced exactly-one-of), `role` (text; vocabulary owned by + contract 2), granted_by → `users.id`, created_at. +2. Uniqueness: at most one grant row per (subject, target, role). +3. Deleting a hierarchy record deletes its grants (the one permitted + cascade in this class — a grant on a deleted node is meaningless and + fail-open if retained). Deleting a user or team follows the platform + rule for principal deletion (identity contract §7; deletion is currently + gated). +4. Workspace-level access is evaluated, not stored here: a grant at any of + the three levels evaluates down the chain to workspace-scoped + authorization (A1 §8.1.3). No `workspace_id` column exists on + `hierarchy_grants` — workspace membership (REQ-ID-001) remains its own + mechanism inside the SOT schema, and the chain adds where grants can be + declared, never a bypass. + +## 4. Ownership and transfer + +"Assets are transferable subject to the structure" (PRD Part I §4): + +1. A transfer is an audited UPDATE of exactly one parent FK on exactly one + hierarchy row: workspace → new platform-project, platform-project → new + estate, estate → new company. Nothing else changes: business and + orchestration rows inside affected workspaces are untouched, keep their + `workspace_id`, and never cross a workspace boundary (A1 §8.1.3 "chain + maintenance"). +2. Transfer authorization requires authority over BOTH the source and the + destination parent (the vocabulary for "authority" is contract 2; the + both-sides requirement is structural and binds here). +3. A transfer is a single-row, single-statement mutation; 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 + grant structure (§3) — a "company owner" is a subject with the top role + on that company, not a column. This avoids the dual-write failure mode + of the legacy `teams.owner_id`/`teams.manager_id` columns, which encode + authority outside any evaluable grant model. + +## 5. Mutation path, audit, and deletion + +1. All hierarchy mutations flow through the same sole-writable-SOT, + fail-closed, audited Gateway command path as everything else (A1 §8.2.3, + REQ-API-001). No direct-DB writers, no raw CRUD endpoints. +2. Every create, rename, transfer, grant change, and delete emits a + semantic audit event carrying actor, verb, target record, and (for + transfers) source and destination parents. Hierarchy audit events + reference hierarchy records; they are not workspace-scoped rows and do + not ride the workspace outbox (REQ-AUD-001 binds business rows; the + implementing PR defines the hierarchy audit store under the same + append-only rules). +3. Deletion is fail-closed bottom-up: a hierarchy record with children + cannot be deleted (RESTRICT FKs, §2). Deleting a workspace is a SOT-side + operation subject to the kanban SOT's own rules and is not granted any + new semantics by this contract. +4. Roll-up reads (A1 §8.1.3) touch none of these tables' write paths and + are specified by contract 8; this contract only guarantees the chain + they aggregate over is unique and non-null. + +## 6. Verification requirements + +Binding on the implementing PRs (extends A1 §8.3): + +1. Schema tests: chain uniqueness by construction (insert attempts with + null/duplicate parents fail); composite slug uniqueness per parent; + CHECK-enforced exactly-one-of on grant subject and target; grant + uniqueness per (subject, target, role). +2. Transfer tests: parent-FK update moves the subtree resolution and + modifies zero business/orchestration rows (row-count and content + assertions on workspace contents before/after); transfer without + authority on either side is refused. +3. Deletion tests: delete with children refused at the database level; + delete of a leaf cascades its grants and nothing else. +4. Authorization tests (with contract 2): a grant at each level yields + exactly the workspace permissions the chain implies; revocation up the + chain propagates (A1 §8.3 acceptance 2). +5. Negative tests: no business/orchestration table accepts a hierarchy + record id in any dependency/reference position; roll-up endpoints cannot + mutate hierarchy state; readers see aggregates only over workspaces they + are authorized on, with no cross-tenant existence oracles (A1 §8.3 + acceptance 3). +6. Real-PostgreSQL coverage for the constraint-witness tests (unique/CHECK/ + RESTRICT behavior), using the `ci-postgres` service in the `test` CI + step; mocked specs cannot witness database constraints. + +## Ruling request + +Ratify sections 1–6 as written, with one decision embedded: hierarchy +records carry no owner column — ownership is expressed solely through +grants (§4.4) — say "agreed" or name the ownership model you want. -- 2.54.0 From ee8c59385820ca95aa6ce96e56e6f726359702d1 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:29:53 -0500 Subject: [PATCH 2/9] docs: hierarchy schema contract revision 2 (terra review findings 1-9) - tenancy-FK exemption: canonical workspace_id is the one permitted business->class reference; all other forms forbidden with control test - record class = 4 node tables + hierarchy_grants per A1 8.1.2 - grant uniqueness via UNIQUE NULLS NOT DISTINCT (or 6 partial indexes); NOT NULL on role/granted_by/name/slug - grant FK delete actions split: target cascade, principal RESTRICT - audit parity: REQ-AUD-001 guarantees, same-transaction outbox, deletion-safe snapshot linkage, cascade coverage, rollback witness - roll-up never-a-write restored to full A1 strength (no canonical writes) - ownership invariant completed via contract 2; both-sides transfer rule marked new policy; legacy premise corrected - corrected cardinality witness (one-parent-per-child + sibling control) - provenance fixes: REQ-MIG-001 attribution dropped, projects table described accurately, D8-rank-1 claim removed --- docs/requirements/hierarchy-schema.md | 261 +++++++++++++++++--------- 1 file changed, 175 insertions(+), 86 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index b82497d8..e3f517ca 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -5,82 +5,122 @@ Authority: PRD D2/D9/D13 (Part I §4) and the native-kanban SOT Amendment A1 (`docs/requirements/native-kanban-sot.md` §8, ratified 2026-08-25). This document turns the ratified hierarchy into a concrete schema contract: tables, cardinalities, constraints, and ownership/transfer semantics. It is -the prerequisite for the D8 rank-1 tool (hierarchy command family) and for -the RBAC grant model (contract 2). +the prerequisite for the hierarchy command family and for the RBAC grant +model (contract 2, `docs/requirements/rbac-grant-model.md`). + +Revision 2 (independent review, GPT-5.6 terra): tenancy-FK exemption made +explicit (§1.1); record class extended to include `hierarchy_grants` +(§1.1); provenance corrections on legacy tables and the planning `projects` +table (§1.3, §2 naming note); NOT NULL and `NULLS NOT DISTINCT` grant +uniqueness (§2.6, §3.2); grant FK delete actions split cascade/restrict +(§3.3); transfer transaction includes its audit write (§4.3); ownership +invariant completed via contract 2 with the both-sides rule marked as new +policy (§4.2, §4.4); hierarchy audit brought under REQ-AUD-001-equivalent +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). Scope: the tenancy/authorization structure record class — companies, -estates, platform-projects, workspaces, their parentage, and the grant -attachment points. Out of scope: the RBAC grant vocabulary and evaluation +estates, platform-projects, workspaces, hierarchy grants, their parentage, +and constraints. Out of scope: the RBAC grant vocabulary and evaluation semantics (contract 2), roll-up projection semantics (contract 8), kanban -planning entities inside workspaces (SOT §5), migration of legacy flat data -(SOT REQ-MIG-001). +planning entities inside workspaces (SOT §5), migration or retirement of +legacy flat data (future work; see §1.3). ## 1. Record class and placement -1. The four tables below form the **tenancy/authorization structure record - class** defined by Amendment A1 §8.1.2. They carry parentage, naming, and - grant data only — never task, plan, or any business/orchestration - payload. No business/orchestration row may reference a hierarchy record - as a dependency, claim target, or work subject. +1. The **tenancy/authorization structure record class** defined by + Amendment A1 §8.1.2 comprises five tables: the four node tables of §2 + AND `hierarchy_grants` (§3) — A1 includes hierarchy-level access grants + in the class. Every rule addressed to "the class" in this contract + (payload prohibition, mutation path, audit) binds all five tables. Class + rows carry parentage, naming, grant, and audit-linkage data only — never + task, plan, or any business/orchestration payload. + References from business/orchestration rows into the class are limited + to exactly one form: the canonical `workspace_id` tenancy column that + REQ-TEN-001 requires on every canonical row, referencing + `workspaces.id`. No business/orchestration row may reference a company, + estate, platform-project, or grant id in any position, and no + business/orchestration row may reference a workspace id in any + non-tenancy position (dependency, claim target, work subject). 2. Hierarchy records are NOT workspace-scoped rows: REQ-TEN-001's `workspace_id` obligation binds business/orchestration rows and does not apply to this class (A1 §8.1.2). The `workspaces` table itself is the anchor the obligation points at. -3. The legacy flat tables (`teams`, `projects` in `packages/db/src/schema.ts` - — the Brain planning table, not the hierarchy level) are not part of this - class and are not repurposed. The SOT forbids reusing the flat model for - the hierarchy; their eventual migration or retirement is REQ-MIG-001 - work, out of scope here. +3. The legacy flat tables (`teams`, and the Brain planning `projects` table + in `packages/db/src/schema.ts`) are not part of this class. What A1 + §8.1.4 pins is narrower: the planning `projects` table and + `platform_projects` stay distinct tables. This contract adds, as new + policy ratified here: neither `teams` nor `projects` is repurposed as a + hierarchy table. Their eventual migration or retirement is future work + that no existing REQ assigns; it is out of scope here. ## 2. Tables and cardinalities -Naming: the level above workspaces is `platform_projects`, per A1 §8.1.4 — -the existing `projects` table already means workspace-internal planning -entities, and the schema MUST NOT merge the two. (A rename of either -remains an implementation-PR decision under A1; this contract pins only that -they stay distinct tables.) +Naming: the level above workspaces is `platform_projects`, per A1 §8.1.4. +The existing `projects` table is Brain planning data (so labeled in +`packages/db/src/schema.ts`; it carries no `workspace_id`), and the schema +MUST NOT merge the two. (A rename of either remains an implementation-PR +decision under A1; this contract pins only that they stay distinct tables.) 1. `companies` — id (uuid pk), name, slug (unique per deployment), created_at, updated_at. N per deployment (D2). 2. `estates` — id, name, slug, `company_id` NOT NULL → - `companies.id` ON DELETE RESTRICT. Exactly one company per estate. + `companies.id` ON DELETE RESTRICT. Exactly one company per estate; a + company holds any number of estates. 3. `platform_projects` — id, name, slug, `estate_id` NOT NULL → `estates.id` ON DELETE RESTRICT. Exactly one estate per - platform-project. + platform-project; an estate holds any number of platform-projects. 4. `workspaces` — id, name, slug, `platform_project_id` NOT NULL → `platform_projects.id` ON DELETE RESTRICT. Exactly one platform-project per workspace. This table is the referent of every `workspace_id` column the SOT requires on canonical rows. 5. **Chain resolution is by construction.** Because every parent FK is NOT - NULL and single-valued, each workspace resolves to exactly one - platform-project → estate → company chain (A1 §8.3 acceptance 1). There - are no parentage edge tables, no multi-parent forms, and no nullable - "detached" states. -6. **Slug scoping.** `estates.slug` is unique within its company, - `platform_projects.slug` within its estate, `workspaces.slug` within its - platform-project (composite unique constraints). Display names are - unconstrained. + NULL and single-valued (one FK column, no parentage edge tables, no + multi-parent forms, no nullable "detached" states), each workspace + resolves to exactly one platform-project → estate → company chain (A1 + §8.3 acceptance 1). One-parent-per-child is the constrained direction; + many children per parent is valid data. +6. **Slug scoping.** All `name` and `slug` columns are NOT NULL. + `estates.slug` is unique within its company, `platform_projects.slug` + within its estate, `workspaces.slug` within its platform-project + (composite unique constraints). Display names are unconstrained beyond + NOT NULL. 7. No hierarchy table carries a `metadata` jsonb column or any - free-form payload field. Parentage, naming, timestamps, and the audit - linkage of §5 — nothing else (A1 §8.1.2). + free-form payload field. The columns declared in this section and §3 + are exhaustive: a class table's column set is exactly its declared set + (verified per §6.2) — nothing else (A1 §8.1.2). ## 3. Grant attachment points -The grant vocabulary (which roles exist, what each permits, how inheritance -and revocation evaluate) is contract 2. This contract pins only the schema +The grant vocabulary (which roles exist, what each permits, how evaluation +and revocation work) is contract 2. This contract pins only the schema shape contract 2 attaches to: 1. `hierarchy_grants` — id, subject (exactly one of `user_id` → `users.id`, `team_id` → `teams.id`; CHECK-enforced exactly-one-of), target (exactly one of `company_id`, `estate_id`, `platform_project_id`; - CHECK-enforced exactly-one-of), `role` (text; vocabulary owned by - contract 2), granted_by → `users.id`, created_at. -2. Uniqueness: at most one grant row per (subject, target, role). -3. Deleting a hierarchy record deletes its grants (the one permitted - cascade in this class — a grant on a deleted node is meaningless and - fail-open if retained). Deleting a user or team follows the platform - rule for principal deletion (identity contract §7; deletion is currently - gated). + CHECK-enforced exactly-one-of), `role` (text NOT NULL; vocabulary and + its CHECK constraint owned by contract 2 §2), `granted_by` NOT NULL → + `users.id`, created_at. +2. Uniqueness: at most one grant row per (subject, target, role). Because + the subject and target columns are nullable by design, ordinary + PostgreSQL composite uniqueness treats NULLs as distinct and would not + enforce this. The implementation MUST use a single + `UNIQUE NULLS NOT DISTINCT` constraint across (`user_id`, `team_id`, + `company_id`, `estate_id`, `platform_project_id`, `role`) or six + equivalent partial unique indexes (one per subject×target form). The + pinned Drizzle ORM supports `nullsNotDistinct()`. +3. Delete actions are split by column class: + - Target FKs (`company_id`, `estate_id`, `platform_project_id`): + ON DELETE CASCADE — the one permitted cascade in this class. A grant + on a deleted node is meaningless and fail-open if retained. Cascaded + grant deletions are audited per §5.2. + - Principal FKs (`user_id`, `team_id`, `granted_by`): ON DELETE + RESTRICT. The identity contract (§7.3) gates user deletion today and + defines no team-deletion rule; this contract does not invent one. + These FKs stay RESTRICT until an explicit deletion-and-retention + contract ratifies otherwise. 4. Workspace-level access is evaluated, not stored here: a grant at any of the three levels evaluates down the chain to workspace-scoped authorization (A1 §8.1.3). No `workspace_id` column exists on @@ -92,69 +132,118 @@ shape contract 2 attaches to: "Assets are transferable subject to the structure" (PRD Part I §4): -1. A transfer is an audited UPDATE of exactly one parent FK on exactly one - hierarchy row: workspace → new platform-project, platform-project → new - estate, estate → new company. Nothing else changes: business and - orchestration rows inside affected workspaces are untouched, keep their - `workspace_id`, and never cross a workspace boundary (A1 §8.1.3 "chain - maintenance"). +1. A transfer changes exactly one parent FK on exactly one hierarchy row: + workspace → new platform-project, platform-project → new estate, estate + → new company. Nothing else in the class or the SOT changes: business + and orchestration rows inside affected workspaces are untouched, keep + their `workspace_id`, and never cross a workspace boundary (A1 §8.1.3 + "chain maintenance"). 2. Transfer authorization requires authority over BOTH the source and the - destination parent (the vocabulary for "authority" is contract 2; the - both-sides requirement is structural and binds here). -3. A transfer is a single-row, single-statement mutation; there are no - multi-row transfer batches at the schema level. Bulk moves are N audited + destination parent. This both-sides predicate is **new policy + 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 + 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 - grant structure (§3) — a "company owner" is a subject with the top role - on that company, not a column. This avoids the dual-write failure mode - of the legacy `teams.owner_id`/`teams.manager_id` columns, which encode - authority outside any evaluable grant model. + grant structure: a "company owner" is a subject with an `owner` grant + on that company or an ancestor (contract 2 §2), not a column. The + ownership invariant across the contract pair: a node may hold zero + direct owner grants (authority can derive from an ancestor grant); node + creation names the initial `owner` grant in the same audited operation + and the wizard seeds the first company's owner the same way (contract 2 + §4.3); transfer and revocation semantics are contract 2 §§5–6. This + avoids column-encoded authority of the kind the legacy schema carries + (`teams.owner_id` and `teams.manager_id` are required user FKs, and + `team_members.role` is a further authority field — none of them + evaluable under a grant model). ## 5. Mutation path, audit, and deletion 1. All hierarchy mutations flow through the same sole-writable-SOT, fail-closed, audited Gateway command path as everything else (A1 §8.2.3, REQ-API-001). No direct-DB writers, no raw CRUD endpoints. -2. Every create, rename, transfer, grant change, and delete emits a - semantic audit event carrying actor, verb, target record, and (for - transfers) source and destination parents. Hierarchy audit events - reference hierarchy records; they are not workspace-scoped rows and do - not ride the workspace outbox (REQ-AUD-001 binds business rows; the - implementing PR defines the hierarchy audit store under the same - append-only rules). +2. **Audit parity.** A1 §8.2 leaves every pre-existing REQ binding, so + hierarchy mutations get REQ-AUD-001's guarantees, not a weakened + substitute. Concretely: + - Every create, rename, transfer, grant create/change/revoke, and + delete — including every grant deletion cascaded by a node delete — + emits a semantic audit event carrying actor, verb, target, and (for + transfers) source and destination parents, with the correlation, + causation, idempotency, and per-target ordering guarantees REQ-AUD-001 + defines. + - The state change and its audit event(s) commit in the same + transaction, delivered through a transactional outbox. Hierarchy + events are not workspace-scoped rows and do not ride the workspace + outbox; they get an equivalent hierarchy outbox under the same + append-only, same-transaction rules. + - **Deletion-safe linkage:** audit events reference their target by an + immutable snapshot (id, slug, and parent chain at event time), never + by a foreign key into the class tables, so append-only events survive + the deletion of their target. 3. Deletion is fail-closed bottom-up: a hierarchy record with children cannot be deleted (RESTRICT FKs, §2). Deleting a workspace is a SOT-side operation subject to the kanban SOT's own rules and is not granted any new semantics by this contract. -4. Roll-up reads (A1 §8.1.3) touch none of these tables' write paths and - are specified by contract 8; this contract only guarantees the chain - they aggregate over is unique and non-null. +4. **Roll-up is never a write** (A1 §8.2.2, preserved at full strength). A + roll-up read mutates nothing — not hierarchy state, and not business or + orchestration state: it must not mutate, claim, order, or gate + workspace work. Contract 8 owns projection details but cannot narrow + this rule. This contract additionally guarantees the chain roll-ups + aggregate over is unique and non-null (§2.5). ## 6. Verification requirements Binding on the implementing PRs (extends A1 §8.3): -1. Schema tests: chain uniqueness by construction (insert attempts with - null/duplicate parents fail); composite slug uniqueness per parent; - CHECK-enforced exactly-one-of on grant subject and target; grant - uniqueness per (subject, target, role). -2. Transfer tests: parent-FK update moves the subtree resolution and +1. Schema witnesses (real PostgreSQL, §6.8): chain construction — insert + with a null parent FK refused; insert with one valid parent accepted; + two siblings under one parent accepted (the control proving the + constraint rejects only what §2.5 forbids); catalog assertion that each + child table has exactly one parent-FK column and no parentage edge + table exists. Composite slug uniqueness per parent (duplicate slug + under same parent refused; same slug under different parents accepted). + Grant CHECKs: exactly-one-of subject and exactly-one-of target each + witnessed (zero and two set → refused). Grant uniqueness: a duplicate + (subject, target, role) row refused for each of the six subject×target + forms, proving NULLS-NOT-DISTINCT semantics; NOT NULL on `role`, + `granted_by`, and all `name`/`slug` columns witnessed. +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). +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 + mutation leaves no event (rollback witness); a node delete's cascaded + grant deletions are each covered by events; events survive deletion of + their target (query the events of a deleted node). +5. Transfer tests: parent-FK update moves the subtree resolution and modifies zero business/orchestration rows (row-count and content assertions on workspace contents before/after); transfer without - authority on either side is refused. -3. Deletion tests: delete with children refused at the database level; - delete of a leaf cascades its grants and nothing else. -4. Authorization tests (with contract 2): a grant at each level yields - exactly the workspace permissions the chain implies; revocation up the - chain propagates (A1 §8.3 acceptance 2). -5. Negative tests: no business/orchestration table accepts a hierarchy - record id in any dependency/reference position; roll-up endpoints cannot - mutate hierarchy state; readers see aggregates only over workspaces they - are authorized on, with no cross-tenant existence oracles (A1 §8.3 - acceptance 3). -6. Real-PostgreSQL coverage for the constraint-witness tests (unique/CHECK/ - RESTRICT behavior), using the `ci-postgres` service in the `test` CI - step; mocked specs cannot witness database constraints. + authority on the source or on the destination side is refused (with + contract 2 §7.8). +6. Deletion tests: delete with children refused at the database level; + delete of a leaf cascades its grants and nothing else; deleting a user + or team that is a grant subject (or `granted_by` referent) is refused + (RESTRICT witnesses for §3.3). +7. Negative tests: no business/orchestration table accepts a company, + estate, platform-project, or grant id in any reference position, and + none accepts a workspace id in any non-tenancy position; the canonical + tenancy FK control — a business row inserted with a valid + `workspace_id` succeeds, with an invalid one is refused; roll-up + endpoints mutate no canonical state anywhere (assert zero writes across + hierarchy AND workspace tables, not hierarchy only); readers see + aggregates only over workspaces they are authorized on, with no + cross-tenant existence oracles (A1 §8.3 acceptance 3). +8. Real-PostgreSQL coverage for every constraint witness (unique/CHECK/ + RESTRICT/NULLS NOT DISTINCT behavior), using the `ci-postgres` service + in the `test` CI step; mocked specs cannot witness database constraints. ## Ruling request -- 2.54.0 From f5e3165ea53893bafe2f2d48cdd5463886df4231 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:43:00 -0500 Subject: [PATCH 3/9] docs: hierarchy schema contract revision 3 (terra r2 residuals F4, F7) --- docs/requirements/hierarchy-schema.md | 33 ++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) 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 -- 2.54.0 From dcf42208232ffc905fc4e633e0470ad8189b561c Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:48:13 -0500 Subject: [PATCH 4/9] docs: hierarchy schema contract revision 4 (terra r3 residual F7: raw-SQL literal prong) --- docs/requirements/hierarchy-schema.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index 6d5e54b5..e18a817a 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -30,6 +30,11 @@ 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`.) +Revision 4 (terra r3 residual F7): the §6.3(b) writer-coverage assertion +extended to raw SQL — it now also fails on class-table name literals +inside SQL strings or tagged SQL templates outside the allowlist, so a +raw-SQL writer that touches no schema symbol is still caught. + 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 @@ -232,12 +237,19 @@ Binding on the implementing PRs (extends A1 §8.3): 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. + with two prongs, both bound to one explicitly enumerated allowlist of + hierarchy command/repository modules. (i) Symbol prong: write + references to the class-table schema symbols (insert, update, delete) + occur only in allowlisted modules. (ii) Literal prong: a class-table + name appearing inside a SQL string or tagged SQL template outside the + allowlist fails the assertion — this is what catches a raw-SQL writer + that references no schema symbol. Schema definitions and generated + migrations are excluded from the literal prong; a false positive + (e.g. a table name in a non-write query string) is resolved by + extending the allowlist in the same PR, never by weakening the + assertion. The allowlist is closed: any new writer, ORM or raw, + fails CI until the allowlist names it, so the write path is surfaced + in 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 -- 2.54.0 From e32d77e5a14ff5b3285435779f6d99b5f21e22af Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:55:08 -0500 Subject: [PATCH 5/9] =?UTF-8?q?docs:=20hierarchy=20contract=20revision=205?= =?UTF-8?q?=20=E2=80=94=20raw-execution=20prong=20closes=20dynamic-identif?= =?UTF-8?q?ier=20evasion=20(terra=20r4=20F7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements/hierarchy-schema.md | 31 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index e18a817a..a1332b30 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -35,6 +35,13 @@ extended to raw SQL — it now also fails on class-table name literals inside SQL strings or tagged SQL templates outside the allowlist, so a raw-SQL writer that touches no schema symbol is still caught. +Revision 5 (terra r4 residual F7): §6.3(b) gains a third prong — any +raw-SQL execution primitive outside the allowlist fails the assertion +regardless of its SQL content, closing the evasion where a +dynamically constructed table name carries neither a schema symbol nor +a class-table literal. The detection claim is now coextensive with +what the three prongs statically see. + 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 @@ -243,13 +250,23 @@ Binding on the implementing PRs (extends A1 §8.3): occur only in allowlisted modules. (ii) Literal prong: a class-table name appearing inside a SQL string or tagged SQL template outside the allowlist fails the assertion — this is what catches a raw-SQL writer - that references no schema symbol. Schema definitions and generated - migrations are excluded from the literal prong; a false positive - (e.g. a table name in a non-write query string) is resolved by - extending the allowlist in the same PR, never by weakening the - assertion. The allowlist is closed: any new writer, ORM or raw, - fails CI until the allowlist names it, so the write path is surfaced - in review rather than passing unnoticed. + that references no schema symbol. (iii) Raw-execution prong: any call + to a raw-SQL execution primitive (the ORM's raw/unsafe constructors, + driver-level query/execute) outside the allowlist fails the + assertion, regardless of what the SQL string contains or how it is + constructed — the call site is statically detectable even when a + dynamically assembled table name is not, so a raw writer with a + runtime-built identifier is caught by its primitive, not its + payload. Schema definitions and generated migrations are excluded + from the literal prong; a false positive (e.g. a table name in a + non-write query string, or a legitimate raw query elsewhere in the + codebase) is resolved by extending the allowlist in the same PR, + never by weakening the assertion. The allowlist is closed: a symbol + writer fails prong (i), a raw writer naming a class table fails + prong (ii), and any raw execution at all outside the allowlist fails + prong (iii), so every new writer — ORM, raw-literal, or + dynamically constructed — fails CI until the allowlist names it and + the write path is surfaced in 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 -- 2.54.0 From 5c1e8d290e0a4124abce6a7280c8443d894ee61a Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:02:11 -0500 Subject: [PATCH 6/9] =?UTF-8?q?docs:=20hierarchy=20contract=20revision=206?= =?UTF-8?q?=20=E2=80=94=20three-prong=20wording,=20allowlist=20composition?= =?UTF-8?q?=20rules,=20honest=20detection=20scope=20(terra=20r5=20F7/F8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements/hierarchy-schema.md | 43 +++++++++++++++++++-------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index a1332b30..3df5015d 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -42,6 +42,14 @@ dynamically constructed table name carries neither a schema symbol nor a class-table literal. The detection claim is now coextensive with what the three prongs statically see. +Revision 6 (terra r5 residual F7 + new F8): the "two prongs" wording +corrected to three (F8); §6.3(b) gains the allowlist composition rules +(no generic raw-SQL helper is allowlisted; an allowlisted module may +not export caller-supplied-SQL execution) and fails outright on +runtime code-construction primitives; the detection claim is scoped +honestly to the stated syntactic forms, with evasions beyond static +reach assigned to §5.1 review/audit rather than claimed for CI. + 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 @@ -244,8 +252,8 @@ Binding on the implementing PRs (extends A1 §8.3): 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 - with two prongs, both bound to one explicitly enumerated allowlist of - hierarchy command/repository modules. (i) Symbol prong: write + with three prongs, each bound to one explicitly enumerated allowlist + of hierarchy command/repository modules. (i) Symbol prong: write references to the class-table schema symbols (insert, update, delete) occur only in allowlisted modules. (ii) Literal prong: a class-table name appearing inside a SQL string or tagged SQL template outside the @@ -257,16 +265,27 @@ Binding on the implementing PRs (extends A1 §8.3): constructed — the call site is statically detectable even when a dynamically assembled table name is not, so a raw writer with a runtime-built identifier is caught by its primitive, not its - payload. Schema definitions and generated migrations are excluded - from the literal prong; a false positive (e.g. a table name in a - non-write query string, or a legitimate raw query elsewhere in the - codebase) is resolved by extending the allowlist in the same PR, - never by weakening the assertion. The allowlist is closed: a symbol - writer fails prong (i), a raw writer naming a class table fails - prong (ii), and any raw execution at all outside the allowlist fails - prong (iii), so every new writer — ORM, raw-literal, or - dynamically constructed — fails CI until the allowlist names it and - the write path is surfaced in review rather than passing unnoticed. + payload. Two composition rules keep prong (iii) meaningful: the + allowlist names hierarchy command/repository modules only — a + generic raw-SQL helper or database-utility module is never + allowlisted; and an allowlisted module MUST NOT export a function + that executes caller-supplied SQL (such an export is itself a + raw-execution primitive, and the exporting module is treated as + unallowlisted for prong (iii) if it does). Runtime code-construction + primitives (`eval`, `new Function`) anywhere in the scanned sources + fail the assertion outright, allowlist or not. Schema definitions + and generated migrations are excluded from the literal prong; a + false positive (e.g. a table name in a non-write query string, or a + legitimate raw query elsewhere in the codebase) is resolved by + extending the allowlist in the same PR, never by weakening the + assertion. The allowlist is closed, and the assertion's detection + claim is exactly its prongs: it statically surfaces every writer + expressed as a schema-symbol reference, a class-table SQL literal, a + raw-execution call site, or runtime code construction. An evasion + engineered outside those syntactic forms is a §5.1 violation that + review and audit own — the witness does not claim to catch what + static analysis cannot see, and any such evasion found later is + corrected as a conformance defect, not grandfathered. 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 -- 2.54.0 From bf3bd6c477d30470e7313fd8436656bd43271b63 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:09:00 -0500 Subject: [PATCH 7/9] =?UTF-8?q?docs:=20hierarchy=20schema=20revision=207?= =?UTF-8?q?=20=E2=80=94=20infrastructure=20register=20for=20legitimate=20n?= =?UTF-8?q?on-hierarchy=20raw=20execution=20(terra=20F9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements/hierarchy-schema.md | 32 ++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index 3df5015d..68425f7c 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -50,6 +50,14 @@ runtime code-construction primitives; the detection claim is scoped honestly to the stated syntactic forms, with evasions beyond static reach assigned to §5.1 review/audit rather than claimed for CI. +Revision 7 (terra r6 new F9): the false-positive remedy no longer +contradicts the composition rules — legitimate non-hierarchy raw +execution (e.g. the db package's migration runner) is dispositioned +onto a second closed enumerated list, the infrastructure register, +exempt from prong (iii) only, still bound by prongs (i)/(ii), barred +from the writer allowlist, and importable only by registered modules +or the operational entry points. + 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 @@ -271,14 +279,28 @@ Binding on the implementing PRs (extends A1 §8.3): allowlisted; and an allowlisted module MUST NOT export a function that executes caller-supplied SQL (such an export is itself a raw-execution primitive, and the exporting module is treated as - unallowlisted for prong (iii) if it does). Runtime code-construction + unallowlisted for prong (iii) if it does). Legitimate raw execution + that is not a hierarchy writer — e.g. the migration runner in the + db package — lives on a second, separately enumerated + **infrastructure register**, distinct from the writer allowlist and + equally closed. A registered module is exempt from prong (iii) only: + prongs (i) and (ii) apply to it with no exemption, so it can hold no + class-table schema symbol or class-table SQL literal, and it can + never appear on the writer allowlist. To close the laundering path, + the same assertion checks imports: a module on the infrastructure + register may be imported only by other registered modules or by the + enumerated operational entry points (migration/bootstrap CLI), never + by Gateway or application sources — an import from anywhere else + fails the assertion. Runtime code-construction primitives (`eval`, `new Function`) anywhere in the scanned sources fail the assertion outright, allowlist or not. Schema definitions and generated migrations are excluded from the literal prong; a - false positive (e.g. a table name in a non-write query string, or a - legitimate raw query elsewhere in the codebase) is resolved by - extending the allowlist in the same PR, never by weakening the - assertion. The allowlist is closed, and the assertion's detection + false positive is resolved in the same PR by adding the module to + the one enumerated list its role permits — the writer allowlist for + a hierarchy command/repository module, the infrastructure register + for non-hierarchy raw execution — never by weakening the assertion, + and neither list may take a module the composition rules bar from + it. Both lists are closed, and the assertion's detection claim is exactly its prongs: it statically surfaces every writer expressed as a schema-symbol reference, a class-table SQL literal, a raw-execution call site, or runtime code construction. An evasion -- 2.54.0 From f07b4c73b18f6247418f2ca5fd7e9ab7ff07638e Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:18:11 -0500 Subject: [PATCH 8/9] =?UTF-8?q?docs:=20hierarchy=20schema=20revision=208?= =?UTF-8?q?=20=E2=80=94=20re-export-aware=20import=20analysis,=20per-modul?= =?UTF-8?q?e=20importer=20enumeration=20(terra=20F9=20residual)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements/hierarchy-schema.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index 68425f7c..339cba66 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -58,6 +58,13 @@ exempt from prong (iii) only, still bound by prongs (i)/(ii), barred from the writer allowlist, and importable only by registered modules or the operational entry points. +Revision 8 (terra r7 residual F9): the register's import rule made +satisfiable by the live tree — imports are checked re-export-aware +(package barrels followed), and each registered module carries its own +closed importer enumeration, which may name operational entry points +such as the Gateway's startup migration hook; named importers stay +subject to prongs (i)/(ii) and gain no writer standing. + 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 @@ -287,11 +294,21 @@ Binding on the implementing PRs (extends A1 §8.3): prongs (i) and (ii) apply to it with no exemption, so it can hold no class-table schema symbol or class-table SQL literal, and it can never appear on the writer allowlist. To close the laundering path, - the same assertion checks imports: a module on the infrastructure - register may be imported only by other registered modules or by the - enumerated operational entry points (migration/bootstrap CLI), never - by Gateway or application sources — an import from anywhere else - fails the assertion. Runtime code-construction + the same assertion checks imports, and the import analysis is + **re-export-aware**: it follows package barrels and re-exports, so a + route hidden behind an index module is still a route. A registered + module may be imported only by other registered modules or by + importers named on that module's own closed importer enumeration in + the register — operational entry points such as the + migration/bootstrap CLI or the Gateway's startup migration hook + (the runner's one live importer today, the Gateway database module + reached through the db package barrel, is such an entry point and + is named in the enumeration, not exempted from analysis). Any + import route outside the enumeration fails the assertion. Being a + named importer confers nothing else: the importer stays fully + subject to prongs (i) and (ii), gains no writer-allowlist standing, + and whether it uses the registered module beyond its operational + purpose is a §5.1 review question, not a static claim. Runtime code-construction primitives (`eval`, `new Function`) anywhere in the scanned sources fail the assertion outright, allowlist or not. Schema definitions and generated migrations are excluded from the literal prong; a -- 2.54.0 From e3b0375211d7e09d2dff03b83c31ae56f57e29fc Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:31:09 -0500 Subject: [PATCH 9/9] =?UTF-8?q?docs(hierarchy):=20revision=209=20=E2=80=94?= =?UTF-8?q?=20complete=20the=20runner's=20importer=20enumeration=20(terra?= =?UTF-8?q?=20r8=20F10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'one live importer' claim was false: the measured production importer set of the db migration runner has four members (Gateway database module via the barrel, storage Postgres adapter, fleet-backlog and gateway-verify CLI commands via literal dynamic import). The enumeration example now names all four, the import analysis resolves literal dynamic import() routes, and a non-literal dynamic import of the db package fails the assertion. Injection-only consumers (schema-check) have no import edge. --- docs/requirements/hierarchy-schema.md | 38 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index 339cba66..5287133d 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -65,6 +65,13 @@ closed importer enumeration, which may name operational entry points such as the Gateway's startup migration hook; named importers stay subject to prongs (i)/(ii) and gain no writer standing. +Revision 9 (terra r8 F10): revision 8 called the Gateway database +module the runner's "one live importer today". That was false — the +measured production importer set has four members. The enumeration +example now lists the complete measured set, and the import analysis +is extended to resolve literal dynamic `import()` routes, which two of +the four members use. + 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 @@ -296,15 +303,28 @@ Binding on the implementing PRs (extends A1 §8.3): never appear on the writer allowlist. To close the laundering path, the same assertion checks imports, and the import analysis is **re-export-aware**: it follows package barrels and re-exports, so a - route hidden behind an index module is still a route. A registered - module may be imported only by other registered modules or by - importers named on that module's own closed importer enumeration in - the register — operational entry points such as the - migration/bootstrap CLI or the Gateway's startup migration hook - (the runner's one live importer today, the Gateway database module - reached through the db package barrel, is such an entry point and - is named in the enumeration, not exempted from analysis). Any - import route outside the enumeration fails the assertion. Being a + route hidden behind an index module is still a route — and it + resolves literal dynamic imports the same way: an + `await import('')` is an import edge like any + static import, not an evasion of the analysis (a dynamic import of + the db package whose specifier is not a literal fails the assertion + outright, because it makes the import graph unanalyzable). A + registered module may be imported only by other registered modules + or by importers named on that module's own closed importer + enumeration in the register — operational entry points such as the + migration/bootstrap CLI or the Gateway's startup migration hook. + The enumeration names the complete permitted production consumer + set, and completeness is measured, not asserted: the migration + runner's measured production importer set today has four members — + the Gateway database module (reached through the db package + barrel), the storage package's Postgres adapter, and two mosaic CLI + commands, the fleet-backlog command and the gateway verify command, + both routed through literal dynamic imports of the db package — so + its enumeration names those four. A module that only receives the + runner's functions by parameter injection (the gateway schema-check + module takes them as arguments from the verify command) has no + import edge of its own and is not enumerated. Any import route + outside the enumeration fails the assertion. Being a named importer confers nothing else: the importer stays fully subject to prongs (i) and (ii), gains no writer-allowlist standing, and whether it uses the registered module beyond its operational -- 2.54.0