From 99a0c89c5ad081b7d18f80abe1263abc1def66c6 Mon Sep 17 00:00:00 2001 From: fred Date: Sat, 29 Aug 2026 13:49:47 -0500 Subject: [PATCH] =?UTF-8?q?docs:=20Amendment=201=20rev=202=20=E2=80=94=20t?= =?UTF-8?q?erra=20round-1=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existence disclosure stated as a bounded carve-out to hierarchy schema §6.7/§2.8 instead of denied (§8.1); platform_capabilities fully pinned (uuid PK, timestamptz, both user FKs ON DELETE RESTRICT mirroring contract 1 §3.3, column-set exhaustiveness) (§8.2); assignment rides the normal admin command surface, sole writer (§8.3); disjunct-independent fail-closed evaluation and capability deletion added to §6.1's revocation enumeration (§8.4); §8.6 rewritten as a capability-only command matrix with disclosure-bound, both-polarity assignment, and per-transport revocation witnesses. --- docs/requirements/rbac-grant-model.md | 179 +++++++++++++++++++------- 1 file changed, 129 insertions(+), 50 deletions(-) diff --git a/docs/requirements/rbac-grant-model.md b/docs/requirements/rbac-grant-model.md index 30ff3b94..7829014b 100644 --- a/docs/requirements/rbac-grant-model.md +++ b/docs/requirements/rbac-grant-model.md @@ -28,10 +28,13 @@ Amendment 1 (company-CRUD capability): defines the capability class that contract 1 Amendment 1 (Ruling 4b, 2026-08-28) and hierarchy schema §5.5 anticipate. §8 defines the capability as a platform-scoped, admin-assigned, audited delegation of exactly the hierarchy schema §5.5 company visibility -command — no read surface, no other company operation. The hierarchy role +command — no read command, no other company operation; the mutation's +inherent existence disclosure is ratified as a bounded carve-out to +hierarchy schema §6.7/§2.8 (§8.1). The hierarchy role vocabulary (§2), every evaluation rule (§3), and grant management (§4) are untouched: the capability is not a `hierarchy_grants.role` value and -evaluates outside the chain. Until this amendment ratifies, the capability +evaluates outside the chain; capability-row deletion joins §6.1's +revocation enumeration (§8.4). Until this amendment ratifies, the capability class is empty and the visibility command remains admin-only (hierarchy schema §5.5 states this fallback; the shipped gate at @@ -269,32 +272,66 @@ companies remain hierarchy `owner` operations (§2.3, §5); none of those is part of this capability, and widening it to any other operation is a further amendment, not an implementation decision. -1. **Content: exactly one command, no read surface.** Holding the - capability authorizes executing the hierarchy schema §5.5 visibility - command (`companies.visibility`, both directions: +1. **Content: exactly one command, no read command, disclosure stated.** + Holding the capability authorizes executing the hierarchy schema §5.5 + visibility command (`companies.visibility`, both directions: `private → directory` and `directory → private`) on any company in the - deployment. It authorizes nothing else — and in particular it carries - **no read surface**: no company enumeration, no structure read, no - existence probe beyond what every authenticated user already has (the - §2.8 directory listing). The practical flow this implies is - deliberate: to publish a private company, the holder is given the - target identifier by the requesting company `owner` out of band; to - unpublish, the target is already directory-listed. A curation listing - for capability holders, if ever wanted, is a further amendment with its - own disclosure analysis under hierarchy schema §6.7. + deployment, and no other command of any family. It confers **no read + command**: no company enumeration, no curation listing, no structure + read. The practical flow this implies is deliberate: to publish a + private company, the holder is given the target identifier by the + requesting company `owner` out of band; to unpublish, the target is + already directory-listed. A curation listing for capability holders, + if ever wanted, is a further amendment with its own disclosure + analysis under hierarchy schema §6.7. + + **Existence disclosure carve-out, stated rather than pretended away:** + exercising a mutation inherently discloses its target's existence. + The command's result distinguishes an existing company (success, for + private and directory targets alike) from a nonexistent id + (`not_found`), so a holder presenting candidate ids learns existence — + exactly as a platform admin already does through the same command. + This amendment ratifies that disclosure as part of the §5.5 curation + authority, bounded as follows: the mutation result and its audit event + disclose to the holder the target's existence and its old/new + visibility values, and **nothing else** — no name, slug, structure, + content, grant, or membership information. A successful publish + additionally makes the target directory-listed to every authenticated + user; that is the command's ratified purpose (hierarchy schema §5.5), + not a leak. Hierarchy schema §6.7's existence-oracle rule and §2.8's + directory-only disclosure are amended by exactly this carve-out for + capability holders and are otherwise untouched. Witnessed in §8.6.3. + 2. **Holding: platform-scoped assignment, user subjects only.** The capability is not a hierarchy grant: it attaches to no node, has no role, and never enters §3 chain evaluation. It is held via a - `platform_capabilities` table: `id`, `user_id` (FK `users`, NOT NULL), - `capability` (text, NOT NULL, constraint-checked against exactly - `company_crud`), `granted_by` (FK `users`, NOT NULL), `created_at`; - UNIQUE (`user_id`, `capability`). No team subjects (§1.4's suspension - reasoning applies with more force here — a workspace-bound team holding - deployment-wide curation authority has no ratified meaning), no expiry - column: revocation is row deletion, as everywhere in this contract. -3. **Assignment is instance administration.** Only platform admins - (`users.role = 'admin'`) may assign or revoke the capability — - assignment delegates a slice of instance administration and is itself + `platform_capabilities` table whose column set is exactly (nothing + else, per the contract 1 §2.7 exhaustiveness discipline): + - `id` — uuid, primary key; + - `user_id` — uuid, NOT NULL, FK `users` **ON DELETE RESTRICT**; + - `capability` — text, NOT NULL, constraint-checked against exactly + `company_crud`; + - `granted_by` — uuid, NOT NULL, FK `users` **ON DELETE RESTRICT**; + - `created_at` — timestamptz, NOT NULL; + - UNIQUE (`user_id`, `capability`). + + Both user FKs are RESTRICT for the same reason contract 1 §3.3 pins + RESTRICT on principal FKs: the identity contract (§7.3) gates user + deletion, and a cascade here could silently destroy a capability + without its §8.3 revocation audit event. Revocation is row deletion + through the §8.3 command — there is no other removal path, no expiry + column, and no tombstone. A deactivated holder confers nothing while + deactivated: identity contract §7.1 denies all authorization to + deactivated accounts, and the §8.4 predicate evaluates on the + authenticated live user. No team subjects (§1.4's suspension reasoning + applies with more force here — a workspace-bound team holding + deployment-wide curation authority has no ratified meaning). + +3. **Assignment is instance administration on the normal admin surface.** + Only platform admins (`users.role = 'admin'`) may assign or revoke the + capability, through an ordinary admin command (the same command class + `AdminGuard` governs, §1.1) — not through direct table writes. + Assignment delegates a slice of instance administration and is itself an instance-administration act under §1.1. A capability holder as such may NOT assign or revoke it (no self-propagation). Every assignment and revocation is a semantic audit event carrying actor, verb, subject @@ -302,11 +339,19 @@ further amendment, not an implementation decision. §4.5 (`platform-capability:company-crud` — a bare `company_crud` in any serialized artifact is non-conformant). 4. **Evaluation and revocation follow this contract's existing rules.** - The hierarchy schema §5.5 command's authorization predicate is: `users.role = 'admin'` - OR a live `platform_capabilities` row (`user_id`, `company_crud`) — - evaluated live and fail-closed per §3.5 (unreadable capability state - denies). Revocation propagates per §6: the next authorization decision - on any transport decides against the deleted row, within §6.2's bound. + The hierarchy schema §5.5 command's authorization predicate is: + `users.role = 'admin'` OR a live `platform_capabilities` row + (`user_id`, `company_crud`). Both disjuncts are evaluated live and + fail closed per §3.5 — **independently**: with capability state + unreadable (fault), the capability disjunct denies, but a platform + admin whose `users.role` is readable remains authorized through the + admin disjunct; with role state unreadable, the admin disjunct denies + likewise. A decision that can read neither denies. Capability-row + deletion is hereby added to §6.1's enumerated revocation paths: + it propagates identically, under §6.2's bound, on every transport — + no new HTTP/MCP command authorized by the deleted row after the + revoking transaction commits, and any cached authorization is + invalidated in the revoking transaction (§3.5). 5. **What it does not confer**, stated so implementing PRs cannot drift: no hierarchy grant or effective role at any node; no workspace authorization or membership; no content, structure, or roll-up read; @@ -319,24 +364,58 @@ further amendment, not an implementation decision. subject holding both. 6. **Verification requirements** (extends §7, binding on implementing PRs): - 1. Vocabulary: the `capability` CHECK constraint rejects any value - outside `company_crud` (real-PostgreSQL witness, `ci-postgres` - service in the `test` CI step). - 2. Conferral exactness: a non-admin holder succeeds on the visibility - command in both directions with contract 1 §5.2's audit event - (old and new values as semantic content); the same holder, with no - other grants, is refused a workspace command, receives the + 1. Schema witnesses (real PostgreSQL, `ci-postgres` service in the + `test` CI step): the `capability` CHECK constraint rejects any + value outside `company_crud`; NOT NULL enforced on every declared + NOT NULL column; UNIQUE (`user_id`, `capability`) rejects a + duplicate; both user FKs reject a dangling reference AND deleting a + referenced user is refused (RESTRICT witnessed in both directions); + the table's column set is exactly the §8.2 declared set (contract 1 + §6.2 discipline). + 2. Capability-only command matrix — the witness that proves "exactly + one command", not merely "at least one": a non-admin holder with no + other grants succeeds on the visibility command in **both** + directions with contract 1 §5.2's audit event (old and new values + as semantic content), and the **same** actor is refused, case by + enumerated case: every hierarchy mutation family (company/child + create under another's node, rename, delete, transfer); grant + create/change/revoke; the workspace read and write command + families; roll-up reads; structure reads — including the not-found-indistinguishable refusal on a structure read of the very - company they just mutated (hierarchy schema §6.7), cannot create a - grant, and derives no MCP scope — the §7.4 deny-by-default matrix - gains this cell. - 3. Assignment authority: a non-admin — including a current capability - holder — is refused assign and revoke; every assign/revoke produces - its audit event with the namespaced string (§8.3). - 4. Revocation and fail-closed: after row deletion, the ex-holder's - next visibility command is refused; with capability state - unreadable (fault injection), the decision denies (§3.5). - 5. Owner-as-such refusal re-witnessed: hierarchy schema §6.9's + company they just mutated (hierarchy schema §6.7); every + instance-administration surface other than the visibility command + (user management, system settings, provider configuration, and + capability assign/revoke itself); and MCP scope derivation yields + nothing — the §7.4 deny-by-default matrix gains this row. Company + creation compares against an eligible-user baseline: the holder's + create behaves exactly as any active user's — always `private`, + and a creation request carrying a visibility argument is refused + for holder and baseline alike (contract 3 §5.2). + 3. Disclosure bound (§8.1 carve-out witnessed): the mutation result + for a private-valid target, a directory-valid target, and a + nonexistent id is exactly {success, success, `not_found`}; no + result or error payload carries name, slug, structure, content, + grant, or membership data; the holder's audit-visible surface + carries only existence and old/new visibility. + 4. Assignment path, both polarities: a platform admin assigns and + revokes through the normal admin command (positive witnesses — + assign then observe the §8.6.2 allow, revoke then observe deny); a + non-admin — including a current capability holder — is refused + assign and revoke; every assign/revoke produces its audit event + with the namespaced string (§8.3); a direct-write path that skips + the command surface is non-conformant (the §8.3 command is the only + writer of `platform_capabilities`). + 5. Revocation joins the §7.6 matrix: assignment is decision-time-live + (capability assigned → the holder's next visibility command allows, + no re-login); after row deletion, the ex-holder's next visibility + command is refused **on every exposed transport**, measured with + the revocation and the decision on distinct physical connections; a + cached-authorization implementation proves transactional + invalidation (§3.5). Fail-closed fault witnesses, both disjuncts + (§8.4): with `platform_capabilities` unreadable, a non-admin holder + is denied while a platform admin remains authorized; with role + state unreadable, the admin disjunct denies. + 6. Owner-as-such refusal re-witnessed: hierarchy schema §6.9's owner-cannot-publish witness re-asserted with the `platform_capabilities` table present and empty for that owner. @@ -363,7 +442,7 @@ embedded: - Decision: the company-CRUD capability is a platform-scoped, admin-assigned, audited delegation of exactly the hierarchy schema §5.5 - visibility command — no read surface, no other company operation. Say - "agreed" or - name the additional operations (or the curation listing) you want it to - carry. + visibility command — no read command, no other company operation, with + the mutation's inherent existence disclosure ratified as a bounded + carve-out (§8.1). Say "agreed" or name the additional operations (or + the curation listing) you want it to carry.