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:
@@ -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.
|
||||||
Reference in New Issue
Block a user