From 7ef620cda7da056886a3aa5aa050db17a64fc64f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 18:10:45 -0500 Subject: [PATCH] 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.