17 KiB
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.
Revision 2 (terra review F1–F7): membership-only readability is now workspace-local — it contributes at the workspace node only and never promotes ancestor visibility; upward aggregation requires an effective chain role, and §1 defines direct vs effective grants in contract 2's terms (F1). The no-oracle rule gains a defined equivalence predicate (normalized byte equality with an enumerated volatile-field set) and a partial-scope hidden-sibling witness (F2). §2.5 enumerates the closed semantic result and denial schemas field-by-field, including the explicit-zero representation (F3). Cache invalidation, when a cache exists, is witnessed per invalidator class (F4). Non-authoritative and never-gate rules gain an import-graph/data-flow witness, and the mutation check is aligned to contract 1 §6.7's both-table zero-write assertion (F5). The fixture gains a second estate with distinct counts and explicit company-, estate-, project-grant, and membership cases (F6). The §5.3 legacy-row exclusion and pre-rank no-obligation rules are disclosed as drafting additions (F7).
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
- 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).
- Effective chain role (at a node, for a reader): the role contract 2 §3 evaluation yields at that node — from a grant on the node itself (a direct grant) or from a grant on an ancestor whose domain covers it (an inherited grant, contract 2 §3.2). The role vocabulary is contract 2 §2's; this contract adds no role and no new authority source.
- Chain-readable workspace (for a reader): a workspace where the reader's effective chain role permits reading task state.
- Member-readable workspace (for a reader): a workspace readable only through workspace membership under the SOT's own membership rules (REQ-ID-001), with no effective chain role. Membership confers workspace-local semantics only (contract 2 §3.1, §7.4): it never contributes authority, visibility, or aggregation upward.
- Aggregation scope (of a hierarchy node, for a reader): the set of chain-readable workspaces in that node's descendant subtree; plus, when the node is itself a workspace, that workspace if it is chain-readable or member-readable. A member-readable workspace therefore contributes to exactly one node's aggregation scope: its own.
- 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
- 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. - 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.
- 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 (§2.5; §6.7 witness): adding any field is an amendment to this contract. - 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 or deny work (witness §6.8 — both the write-path and the decision-path prohibitions are witnessed).
- Closed semantic schema. The successful result consists of
exactly these semantic fields, and no others:
node: the queried node — exactlyid,type(one of the four contract 1 levels),name.totals: one entry per status value of the typed lifecycle — every status key present, a count of zero represented explicitly as0, never by key absence.children: the reader-visible (§3.2) child nodes ofnode, each carrying exactly the sameid/type/name/totalsshape, recursively down to the workspaces in the reader's aggregation scope. A node outside §3.2 visibility never appears at any depth. There are no optional result fields. The denial/nonexistent response is the contract 5 §4.2 not-found-class error envelope with no fields beyond that envelope. The wire DTO is expressed under contract 5 §4.1, and MUST be a faithful serialization of exactly this semantic field set: a wire field with no corresponding semantic field above is a conformance defect.
3. Reader authorization semantics
- Scope rule. A reader's roll-up over any node aggregates ONLY the reader's aggregation scope (§1.5). An unreadable workspace contributes nothing to any total — not a count, not a row, not a presence marker. A member-readable workspace contributes only at the workspace node itself (§1.4–§1.5): querying it directly succeeds; it never appears in, and never adds to, any ancestor's response for that reader.
- 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 an effective chain role at the node (§1.2 — direct or inherited; contract 2 §3.2 makes a grant's domain the node and its subtree, so an ancestor grant makes empty descendants visible per the ruling). Per the ruling below, a node with an effective chain role but an empty aggregation scope appears with zero counts. Workspace membership alone never makes any non-workspace node visible. A node where the reader has neither an effective chain role nor a non-empty aggregation scope does not appear at all.
- 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 MUST satisfy the §3.4 response
equivalence with a query naming a nonexistent node (fail closed,
rbac-grant-model.md§3.5 pattern: a decision path that cannot read grant state denies). - Response equivalence predicate. Two responses are equivalent when they carry the identical HTTP status, the identical contract 5 §4.2 error code, and byte-identical bodies after normalizing exactly the declared volatile envelope fields — correlation id and response timestamp, and nothing else. The implementing PR declares that volatile-field list in the witness; any additional normalization is a conformance defect. This is contract 5 §4.2's same code/status/shape rule made executable.
- 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
- 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.
- 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.
- 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 (each invalidator class witnessed, §6.5), and it is never authoritative (§1.6).
5. Dependencies and phase timing
- 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.
- 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 (§2.5 defines the semantic fields the contract 5 §4.1 DTO serializes), not its wire encoding.
- 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. Both rules are drafting additions disclosed in §7 (they trace to no §8 sentence).
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 two estates with distinct, non-identical count profiles: estate A1 with two platform-projects and three workspaces, estate A2 with one platform-project and one workspace, all with known task counts across at least three statuses; under company B one workspace.
- Correctness witnesses: for a reader holding a direct company-A grant, roll-up totals at every level equal the seeded sums — each workspace, each platform-project, estate A1 and estate A2 separately (their distinct profiles asserted distinct), and the company total equal to A1+A2 — keyed by the typed status values, with no double count across the chain. For a reader holding a direct estate-A1 grant, the estate-A1 result equals the A1 sum and a company-A query returns company A with exactly A1's contribution (estate A2 invisible). Each of the four grant levels — company, estate, platform-project (below), workspace — has an explicit case; none is simulated by unioning workspace access.
- Scope witnesses: a reader with a direct grant on one platform-project sees exactly its subtree's counts at every queryable level; for a reader authorized on two of three sibling workspaces, the parent totals equal the two readable workspaces' sum. Membership locality (§1.4): a member-only reader queries the workspace directly and receives its counts; the same reader querying the workspace's parent (or any ancestor) receives the §3.4-equivalent nonexistent-node response, and no ancestor response for any other reader changes because of that membership.
- 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). Partial-scope hidden node: the reader who sees the parent and two siblings queries the hidden third workspace by its real id — the response satisfies the §3.4 equivalence predicate against the same query naming a nonexistent id, under one fixed request context with the declared volatile-field normalization. Cross-tenant: an unauthorized reader naming company B receives a response §3.4-equivalent to naming a nonexistent id. Each equivalence check is executable byte comparison after the declared normalization, not a shape judgment.
- Empty-vs-hidden witness (ruling): a reader granted (direct
chain grant) on an empty platform-project receives it with zero
counts — every status key present at
0(§2.5); with the grant deleted, the same query returns the §3.4-equivalent nonexistent-node response. An inherited-grant case: a company grant makes an empty descendant platform-project visible with zero counts. - Cache-invalidation witnesses (conditional): bound only if the implementation caches — for EACH invalidator class, prime the cache, commit one mutation of that class, and assert the next query reflects it: a task status change, a task creation, a membership removal (the member-readable workspace disappears from its own node's next query), a workspace reparenting (both old and new parent totals correct), and a grant revocation. A live (cacheless) v1 implementation records that fact and the witnesses bind at the PR that introduces a cache.
- 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, the row diff is empty across BOTH the workspace tables and the hierarchy tables (contract 1 §6.7's both-table zero-write assertion).
- Closed-schema witness: the response is asserted field-exact
against the §2.5 enumeration —
node(id/type/name),totals(every typed status present, explicit zeros),children(recursive, visible only) — and a response carrying any field outside the enumeration fails the assertion (carve-out boundary, §2.3). The denial envelope is asserted field-exact against contract 5 §4.2's envelope. - Non-authoritative and never-gate witnesses: (a) a static
production import-graph inventory (hierarchy contract §6.3 style,
production code over
apps/andpackages/, tests excluded) shows no production module imports the roll-up query module or its result DTO into any SOT write path, any authorization/gating decision path, or any persistence beyond the response lifetime — asserted in both directions (the roll-up module's consumers are enumerated and each is a presentation surface); (b) a behavioral probe: with roll-up output artificially perturbed (test double), no authorization outcome and no work-gating decision anywhere in the fixture suite changes — proving no gate consumes it. - Revocation witness: after revoking the grant that made a subtree readable, the next roll-up query excludes it (contract 2 §6.2 bound).
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:
- The §3.2 node-visibility rule and the granted-but-empty behavior (the ruling below).
- The §3.3–§3.4 nonexistent-node response equivalence, with its normalized-byte-equality predicate, as the concrete no-oracle mechanism.
- The §4.2 read-only-transaction mechanical enforcement.
- The §4.3 cache option with transactional invalidation and the §6.5 per-invalidator witnesses.
- The §2.5 closed response schema as an amendment boundary.
- The §1.4 membership-locality rule — membership-only readability
contributes at the workspace node only (this contract's
reconciliation of
native-kanban-sot.md§8.1.3 "authorized on" with contract 2 §3.1/§7.4's workspace-local membership). - The §5.3 legacy-row exclusion and the §5.3 pre-rank no-obligation rule.
Ruling request
Ruling requested (one decision): shall a node the reader holds an effective chain role on (direct or inherited, §1.2) 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?