# 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).

Revision 3 (terra re-review residuals): the partial-scope witnesses are
reconstructed at levels where chain grants can actually differ —
platform-project siblings under one estate and estate siblings under
one company — because contract 1 §3.1/§3.4 defines no workspace-level
grant target, so no reader can hold a chain grant on two of three
sibling workspaces (F2). §2.5 now defines one field-exact recursive
record — every node, including the queried node and every leaf, is the
same five-field shape with a required, deterministically ordered
`children` array that is empty at workspaces — and the whole-result
rules (no optional fields, denial envelope, wire faithfulness) are
their own §2.6 at section scope (F3). The fixture assigns workspaces
to named platform-projects, and §6.1's grant-level cases are the three
levels contract 1 defines, with workspace-level access covered by the
membership case and stated as having no direct chain grant (F6).

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. **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.
3. **Chain-readable workspace** (for a reader): a workspace where the
   reader's effective chain role permits reading task state.
4. **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.
5. **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.
6. **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 (§2.5; §6.7 witness):
   adding any field 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 or deny work (witness §6.8 — both the write-path and the
   decision-path prohibitions are witnessed).
5. **Closed semantic schema.** The successful result is exactly one
   **roll-up node record**, a single recursive shape used at every
   depth. A roll-up node record consists of exactly these five
   fields, and no others:
   - `id`: the node's identifier.
   - `type`: one of the four contract 1 levels.
   - `name`: the node's name.
   - `totals`: one entry per status value of the typed lifecycle —
     every status key present, a count of zero represented explicitly
     as `0`, never by key absence. At a workspace node, `totals` is
     that workspace's own counts; at any other node, `totals` is the
     sum over the node's aggregation scope (§2.2). This is how §2.1's
     "per workspace and as subtree totals" content is carried:
     per-workspace counts are the leaf records' `totals`, subtree
     totals are the interior records' `totals`.
   - `children`: a required array, present on EVERY node record. Its
     elements are the reader-visible (§3.2) child nodes of this node,
     each itself a complete roll-up node record, recursing down to
     the workspaces in the reader's aggregation scope. At a workspace
     node the array is exactly `[]` — a workspace record never has
     children. The array is ordered deterministically, ascending by
     `id`; the implementing PR asserts that ordering. A node outside
     §3.2 visibility never appears at any depth.

   The queried node's record IS the whole result — there is no
   wrapper field around it.

6. **Whole-result rules.** There are no optional result fields at any
   depth. 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 the §2.5 recursive
   record: a wire field with no corresponding semantic field is a
   conformance defect.

## 3. Reader authorization semantics

1. **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.
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 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.
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 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).
4. **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.
5. **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 (each invalidator class
   witnessed, §6.5), and it is never authoritative (§1.6).

## 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 (§2.5 defines the semantic
   fields the contract 5 §4.1 DTO serializes), not its wire encoding.
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. 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 — P1 holding workspaces W1 and W2, P2 holding
workspace W3 — and estate A2 with one platform-project P3 holding one
workspace W4, all with known task counts across at least three
statuses; under company B one workspace.

1. **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 three chain grant levels
   contract 1 §3.1 defines — company, estate, platform-project
   (below, §6.2) — has an explicit direct-grant case, none simulated
   by unioning lower access. Workspace-level access has NO direct
   chain grant (contract 1 §3.1/§3.4 define no workspace grant
   target) and is covered by the §6.2 membership case.
2. **Scope witnesses:** a reader with a direct grant on
   platform-project P1 only sees exactly P1's subtree counts
   (W1+W2): a P1 query returns W1+W2; an estate-A1 query returns the
   estate node with exactly P1's contribution, sibling project P2 and
   its workspace W3 absent at every depth; a company-A query likewise
   carries only P1's contribution. An estate-sibling case: a reader
   with a direct grant on estate A1 only queries company A and
   receives exactly A1's contribution, estate A2 absent. (Chain
   grants exist only at company, estate, and platform-project —
   contract 1 §3.1 — so partial scope among SIBLING WORKSPACES of
   one project is not constructible by grants and is not witnessed;
   the constructible partial-scope cases are the project- and
   estate-sibling ones above.) **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.
3. **No-oracle witnesses:** the P1-only reader's estate-A1 response
   above contains no field disclosing P2's or W3's existence
   (closed-schema comparison against an estate-A1-granted reader's
   response: identical field set, differing only in counts and
   visible nodes). **Partial-scope hidden node:** the P1-only reader
   — who sees estate A1 and the P1 subtree — queries hidden sibling
   project P2 by its real id, and separately hidden workspace W3 by
   its real id; each 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.
4. **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.
5. **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.
6. **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).
7. **Closed-schema witness:** the response is asserted field-exact
   against the §2.5 recursive record at every depth — exactly
   `id`/`type`/`name`/`totals`/`children` on every node, every typed
   status present with explicit zeros, `children: []` at every
   workspace record, the declared ascending-`id` ordering, no
   wrapper field — and a response carrying any field outside the
   record at any depth fails the assertion (carve-out boundary,
   §2.3). The denial envelope is asserted field-exact against
   contract 5 §4.2's envelope (§2.6).
8. **Non-authoritative and never-gate witnesses:** (a) a static
   production import-graph inventory (hierarchy contract §6.3 style,
   production code over `apps/` and `packages/`, 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.
9. **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:

1. The §3.2 node-visibility rule and the granted-but-empty behavior
   (the ruling below).
2. The §3.3–§3.4 nonexistent-node response equivalence, with its
   normalized-byte-equality predicate, as the concrete no-oracle
   mechanism.
3. The §4.2 read-only-transaction mechanical enforcement.
4. The §4.3 cache option with transactional invalidation and the
   §6.5 per-invalidator witnesses.
5. The §2.5 closed response schema as an amendment boundary.
6. 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).
7. 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?
