Files
stack/docs/requirements/hierarchy-schema.md
T
fred 7ef620cda7
ci/woodpecker/pr/ci Pipeline was canceled
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.
2026-08-26 18:10:45 -05:00

8.7 KiB
Raw Blame History

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_idusers.id, team_idteams.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.