From 61c495dd633b21694ff1d48aa04126651eb1f53f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:41:47 -0500 Subject: [PATCH] docs: roll-up projection contract (S2 contract 8) --- docs/requirements/rollup-projection.md | 191 +++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 docs/requirements/rollup-projection.md diff --git a/docs/requirements/rollup-projection.md b/docs/requirements/rollup-projection.md new file mode 100644 index 00000000..e35761d4 --- /dev/null +++ b/docs/requirements/rollup-projection.md @@ -0,0 +1,191 @@ +# Roll-up Projection Contract (S2 contract 8) + +Status: DRAFT — awaiting ratification (webui-audit S2, contract 8 of 9). +Authority: `native-kanban-sot.md` §8 (A1 amendment) — "task and status +visualization bubbles up the hierarchy as aggregation over workspaces +the reader is authorized on" (§8.1.3); roll-up is never a write and +bubble-up views are generated projections, non-authoritative and never +import sources (§8.2.2); the express, narrow carve-out from the +portfolio-analytics non-goal covers per-workspace task counts and +statuses aggregated up the parent chain over readable workspaces, and +nothing beyond that boundary (§8.2.4); acceptance requires that roll-up +endpoints cannot mutate state and that a reader sees aggregates only +over workspaces they are authorized on, with no cross-tenant existence +oracles (§8.3). A5 rank 5 names the deliverable: an authorized +read-only roll-up query over only readable workspaces, at every +hierarchy level, as its own non-mutating query tool, dependent on ranks +1–3. + +This contract binds the projection semantics (§2), reader authorization +semantics (§3), read-only enforcement (§4), dependencies and phase +timing (§5), witnesses (§6), and disclosed drafting additions (§7). It +defines the roll-up only: hierarchy shape stays with contract 1 +(`hierarchy-schema.md`), grant vocabulary and evaluation with contract 2 +(`rbac-grant-model.md`), the task lifecycle and status taxonomy with +`native-kanban-sot.md`'s typed surface, and the tool↔Gateway mapping +row with contract 5 (`tool-gateway-mapping.md`). + +## 1. Definitions + +1. **Roll-up**: the read-only projection of per-workspace task counts + by status, aggregated up the contract 1 parent chain (workspace → + platform-project → estate → company). +2. **Readable workspace** (for a given reader): a workspace where the + reader's authorization permits reading task state — either + workspace membership under the SOT's own membership rules + (REQ-ID-001), or an effective chain role at that workspace per + contract 2 §3 evaluation. The role vocabulary is contract 2 §2's; + this contract adds no role and no new authority source. +3. **Aggregation scope** (of a hierarchy node, for a given reader): + the set of readable workspaces in that node's descendant subtree + (for a workspace node, the workspace itself if readable). +4. **Projection**: a generated, non-authoritative view in the sense of + `native-kanban-sot.md` §3 invariant 5 — derived from SOT rows, + never an import source, never authoritative. + +## 2. Projection semantics + +1. **Aggregate content.** The roll-up for a node reports, per + workspace in the reader's aggregation scope and as subtree totals: + task counts keyed by the typed lifecycle's status values (owned by + `native-kanban-sot.md`; this contract introduces no status), and + nothing else. Direct count/status aggregation is the entire + surface. +2. **Every level.** The roll-up is queryable at workspace, + platform-project, estate, and company level. A node's totals equal + the sum over its aggregation scope; chain resolution is contract 1 + §2.5's (every workspace resolves to exactly one chain), so no + workspace is counted twice and none is orphaned. +3. **Carve-out boundary.** Everything beyond direct count/status + aggregation — metrics, trends, forecasting, scoring, velocity, + cross-workspace derived analytics, dashboards computed across + workspaces — remains a `native-kanban-sot.md` §6 non-goal + (§8.2.4). The response schema is closed (§6.7 witness): adding any + field beyond identifiers, names of visible nodes, and status + counts is an amendment to this contract. +4. **Non-authoritative.** No consumer may treat roll-up output as a + source of record; it is recomputable at any time from SOT rows and + is never imported, persisted as authoritative state, or used to + gate work. + +## 3. Reader authorization semantics + +1. **Scope rule.** A reader's roll-up over any node aggregates ONLY + the reader's aggregation scope (§1.3). An unreadable workspace + contributes nothing to any total — not a count, not a row, not a + presence marker. +2. **Node visibility.** A node appears in a roll-up response iff the + reader's aggregation scope at that node is non-empty, or the + reader holds a grant on the node itself (contract 2 §3.2 makes the + node and its subtree the grant's domain). Per the ruling below, a + granted node whose aggregation scope is empty appears with zero + counts; a node the reader has no authority on does not appear at + all. +3. **No existence oracle.** The response MUST NOT disclose the + existence, count, name, or any property of unreadable workspaces + or of nodes outside §3.2 visibility — no "N workspaces hidden" + fields, no total-vs-visible discrepancy fields. A query naming a + node outside §3.2 visibility returns the same response as a query + naming a nonexistent node (fail closed, `rbac-grant-model.md` §3.5 + pattern: a decision path that cannot read grant state denies). +4. **Live evaluation.** Readability is evaluated per contract 2 §3.5 + (live rows or transactionally-invalidated cache). Revocation + propagates per contract 2 §6: the next roll-up query decided after + the revoking transaction commits excludes the revoked scope. + +## 4. Read-only enforcement + +1. **Never a write.** No roll-up path may mutate, claim, order, or + gate work in any workspace (§8.2.2). The roll-up ships as a + non-mutating query tool (A5 rank 5) — a query surface with no + command counterpart. +2. **Mechanical enforcement.** The implementing PR executes roll-up + database work inside read-only transactions (or an equivalently + privilege-restricted path), so a mutation attempt fails at the + database boundary, not only by convention. +3. **Freshness.** v1 computes the roll-up live from SOT rows at query + time. A cache is an implementation option only if it is + invalidated in the same transaction as any task, hierarchy, grant, + or membership mutation that affects it, and it is never + authoritative (§1.4). + +## 5. Dependencies and phase timing + +1. The roll-up depends on A5 ranks 1–3: contract 1's hierarchy tables + (the parent chain), contract 2's evaluator (readability), and the + typed Kanban lifecycle (the task state being counted). It ships + after them and reads their surfaces; it defines none of them. +2. The roll-up query is one tool with one Gateway mapping row under + contract 5's regime (request/result/error/audit contracts there); + this contract binds its semantics, not its wire shape. +3. Legacy task rows outside the typed lifecycle are not aggregated; + the roll-up begins counting a workspace's tasks when they exist in + the typed surface. No roll-up obligation attaches to v1 before + ranks 1–3 exist. + +## 6. Verification requirements + +Binding on the implementing PRs. Every witness names, in its +implementation, the exact endpoints/tools, tables, and fixtures it +exercises. The base fixture seeds two companies; under company A an +estate with two platform-projects and three workspaces with known task +counts across at least three statuses; under company B one workspace. + +1. **Correctness witness:** for a reader authorized on all of company + A, roll-up totals at workspace, platform-project, estate, and + company level equal the seeded counts, keyed by the typed status + values, with no double count across the chain. +2. **Scope witnesses:** a reader with a grant on one platform-project + sees exactly its subtree's counts at every queryable level; a + workspace-member-only reader sees exactly that workspace; for a + reader authorized on two of three sibling workspaces, the parent + totals equal the two readable workspaces' sum. +3. **No-oracle witnesses:** the sibling-workspace response above + contains no field disclosing the third workspace's existence + (closed-schema comparison against a reader authorized on all + three: identical field set, differing only in counts and visible + nodes); a query by an unauthorized reader naming company B returns + a response byte-equivalent in shape and status to the same query + naming a random nonexistent id. +4. **Empty-vs-hidden witness (ruling):** a reader granted on an empty + platform-project receives it with zero counts; with the grant + deleted, the same query returns the nonexistent-node response. +5. **Mutation witnesses:** the roll-up surface rejects every mutating + verb/command; a crafted attempt to issue a write through the + roll-up's database path fails at the read-only boundary (§4.2); + after any roll-up query, an SOT row diff is empty. +6. **Revocation witness:** after revoking the grant that made a + subtree readable, the next roll-up query excludes it (contract 2 + §6.2 bound). +7. **Closed-schema witness:** the response schema is asserted + field-exact against §2.1/§3.2 (identifiers, visible-node names, + status counts) — a field outside the closed set fails the + assertion (carve-out boundary, §2.3). +8. **Non-authoritative witness:** no production module imports roll-up + output into SOT writes; a static route inventory (hierarchy + contract §6.3 style, production code over `apps/` and `packages/`, + tests excluded) shows the roll-up query modules perform no SOT + writes and no module feeds roll-up responses into a write path. + +## 7. Drafting additions (PRD §12.1 disclosure) + +Proposed drafting additions, visible here for ratification, each +severable; the aggregation itself, its authorization scope, its +read-only nature, and the no-oracle acceptance are traced to +`native-kanban-sot.md` §8 and are not additions: + +1. The §3.2 node-visibility rule and the granted-but-empty behavior + (the ruling below). +2. The §3.3 nonexistent-node response equivalence as the concrete + no-oracle mechanism. +3. The §4.2 read-only-transaction mechanical enforcement. +4. The §4.3 cache option with transactional invalidation. +5. The §2.3 closed response schema as an amendment boundary. + +## Ruling request + +Ruling requested (one decision): shall a node the reader is granted on +but whose aggregation scope is empty appear in the roll-up with zero +counts (recommended — it lets the UI show a granted-but-empty subtree +honestly) — or, as the alternative, be indistinguishable from a +nonexistent node until it contains a readable workspace?