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

164 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.