docs: hierarchy schema contract (S2 contract 1, D2)
ci/woodpecker/pr/ci Pipeline was canceled

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.
This commit is contained in:
fred
2026-08-26 18:10:45 -05:00
parent 49b7943420
commit 7ef620cda7
+163
View File
@@ -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 16 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.