diff --git a/docs/requirements/rbac-grant-model.md b/docs/requirements/rbac-grant-model.md new file mode 100644 index 00000000..43359202 --- /dev/null +++ b/docs/requirements/rbac-grant-model.md @@ -0,0 +1,169 @@ +# RBAC Grant Model Contract + +Status: DRAFT — awaiting ratification (webui-audit S2, contract 2 of 9). +Authority: PRD Part I §4 ("Granular RBAC: admins restrict access per company, +estate, and project; grants are evaluated down the chain") and the +native-kanban SOT Amendment A1 (§8.1.3 RBAC evaluation, §8.3 acceptance 2). +This document defines the grant vocabulary, evaluation semantics, and +revocation propagation that the hierarchy schema contract +(`docs/requirements/hierarchy-schema.md`, contract 1) attaches to. Contract 1 +pins the `hierarchy_grants` table shape and defers the `role` vocabulary and +the meaning of "authority" here; the identity contract +(`docs/requirements/identity-lifecycle.md` §1.4) pins that account creation +grants nothing. + +Scope: the roles that can appear in `hierarchy_grants.role`, what a grant at +each hierarchy level confers, how grants evaluate down the chain, how +revocation propagates, and who may manage grants. Out of scope: the hierarchy +tables themselves (contract 1), workspace-internal membership and its +role/capability vocabulary (native-kanban SOT REQ-ID-001 and its implementing +schema), roll-up projection semantics (contract 8), wizard seeding +(contract 3). + +## 1. Three authority layers, none substitutable + +1. **Platform role** (`users.role`, better-auth: `member` | `admin`) governs + instance administration — user management, system settings, provider + configuration. It is not tenancy authority: holding platform `admin` + confers **no implicit hierarchy grant and no workspace authorization**. + An operator who should see tenant content holds an explicit, audited + grant like anyone else. (This is the deny-by-default consequence of A1 + §8.1.3's "never a bypass" rule; today's `AdminGuard` checks + `role === 'admin'` for admin endpoints and that stays its only meaning.) +2. **Hierarchy grants** (`hierarchy_grants`, contract 1 §3) declare tenancy + authority at company, estate, or platform-project scope and evaluate down + the chain to workspace-scoped authorization (§3 below). +3. **Workspace membership** (SOT REQ-ID-001) remains its own mechanism. + A chain grant confers command authorization over descendant workspaces; + it does not create membership rows, and row-level principal positions + (task owner, proposer, decision actor) still require ACTIVE workspace + membership exactly as REQ-TEN-001/REQ-ID-001 acceptance states. + +## 2. Role vocabulary + +One vocabulary at every hierarchy level, totally ordered — a higher role +includes everything below it: + +1. `viewer` — read: sees the node, its subtree structure, and the roll-up + aggregates over descendant workspaces (within contract 8's carve-out + bounds); read access to descendant workspace content per the SOT's read + command families. No mutation of anything. +2. `member` — work: everything `viewer` has, plus write authorization for + business/orchestration command families in descendant workspaces (the + concrete command-family mapping is implementation work under SOT + REQ-ID-001; this contract pins that `member` maps to the workspace write + families and nothing structural). +3. `owner` — structure: everything `member` has, plus hierarchy mutations on + the subtree (create/rename/delete child nodes, transfers per §5), and + grant management on the node and its subtree (§4). + +No other value is valid in `hierarchy_grants.role`; the column is +constraint-checked against exactly these three. Extending the vocabulary is a +contract amendment, not an implementation decision. + +## 3. Evaluation semantics + +1. **Deny by default.** No grant on any ancestor → no authority. There are + no implicit grants: not from platform role (§1.1), not from creating a + node (§4.3), not from workspace membership (membership without a chain + grant confers exactly what the SOT's own membership rules confer inside + that workspace, nothing up the chain). +2. **Down-the-chain only.** A grant on a node applies to that node and its + entire descendant subtree. Nothing evaluates upward or sideways: a grant + on an estate says nothing about the parent company or sibling estates. +3. **Effective role = maximum.** A subject's effective role at any node is + the highest role among: grants held directly by the subject's user on + that node or any ancestor, and grants held by any team the user is a + member of on that node or any ancestor. Roles never subtract — there is + no negative/deny grant in this model; revocation is deletion (§6). +4. **Team grants follow live membership.** A team grant confers its role on + the team's current members, evaluated at decision time. Leaving the team + is loss of the grant with §6's propagation bound. +5. **Live evaluation, fail closed.** Authorization decisions derive from the + live grant and team-membership rows (or from a cache that is invalidated + in the same transaction as any grant/membership/hierarchy mutation). A + decision path that cannot read grant state denies. No materialized ACL is + ever authoritative. +6. **Tenant context stays derived from authenticated authority** + (REQ-TEN-001). The chain adds where grants can be declared; a workspace + request is still authorized against that workspace, with the chain + contributing the effective role — never a bypass of workspace-scoped + checks. + +## 4. Grant management + +1. Creating, changing, or revoking a grant on a node requires effective + `owner` on that node (directly or via any ancestor). +2. **No self-escalation.** A grant manager cannot create a grant with a role + higher than their own effective role on the target node. (With the §2 + vocabulary this only bites managers acting through team-conferred + `owner`: the rule is stated so it survives vocabulary amendments.) +3. Creating a hierarchy node confers no automatic grant. Bootstrap of + authority is explicit: the creating command names the initial `owner` + grant in the same audited operation, and the wizard (contract 3) seeds + the first company's initial `owner` the same way. +4. Every grant mutation is a semantic audit event per contract 1 §5.2 + (actor, verb, subject, target, role). + +## 5. Transfer authority (completes contract 1 §4.2) + +"Authority over BOTH the source and the destination parent" means: effective +`owner` on the current parent node (or an ancestor) AND effective `owner` on +the destination parent node (or an ancestor), evaluated at transfer time in +the transfer's own transaction. One subject must hold both; two cooperating +half-authorized subjects are not a transfer protocol this contract defines. + +## 6. Revocation propagation + +1. Revoking a grant (deleting the row), removing a user from a team that + carries a grant, or deleting an ancestor node's grant during node + deletion (contract 1 §3.3) all propagate identically: the authority + derived from that grant is gone for every descendant workspace. +2. **Bound:** the next authorization decision on any affected transport + decides against the revoked grant. Concretely: no new HTTP/MCP command + authorized by the revoked grant after the revoking transaction commits; + an open Socket.IO connection whose subscriptions depend on the revoked + grant is re-evaluated within 30 seconds or at its next inbound message, + whichever comes first (same bound as the identity contract's §7.1 + deactivation rule; same mechanism may serve both). +3. Revocation is subtractive only in effect, not in representation: the + evaluator never needs tombstones; deletion of the row is the revocation. + +## 7. Verification requirements + +Binding on the implementing PRs (extends A1 §8.3 acceptance 2–3 and +contract 1 §6.4): + +1. Vocabulary: the role CHECK constraint rejects any value outside + `viewer|member|owner` (real-PostgreSQL witness, `ci-postgres` service in + the `test` CI step). +2. Per-level conferral: for each of the three levels × three roles, a grant + yields exactly the implied workspace authorization in a descendant + workspace and nothing in a non-descendant workspace (the A1 §8.3 + "exactly the permissions the chain implies" matrix, enumerated). +3. Ordering: `owner` ⊇ `member` ⊇ `viewer` behaviorally — each higher role + passes every lower role's positive cases. +4. Deny-by-default: platform `admin` with no grant reaches no tenant + content; workspace member with no chain grant gains nothing outside SOT + membership semantics; fresh account reaches nothing (identity contract + §1.4 cross-check). +5. Max-rule and teams: user-direct and team-conferred grants combine to the + maximum; team-leave drops authority within the §6.2 bound; decision-time + evaluation witnessed (grant added → next decision allows; no restart or + re-login required). +6. Revocation: each revocation path in §6.1 denies the next command on + every transport; the socket bound is measured; a cached-authorization + implementation proves transactional invalidation (grant revoked and + decision made on two distinct physical connections). +7. Grant management: non-`owner` cannot mutate grants; self-escalation + attempt refused; node creation without the explicit initial grant + refused; every mutation produces its audit event. +8. Transfer: both-sides `owner` accepted, each single-side case refused + (completing contract 1 §6.2). + +## Ruling request + +Ratify sections 1–7 as written, with one decision embedded: platform `admin` +confers no implicit tenant access — operators see tenant content only +through explicit, audited grants (§1.1) — say "agreed" or name the implicit +access you want platform admins to have.