479 lines
29 KiB
Markdown
479 lines
29 KiB
Markdown
# 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.
|
||
|
||
Revision 2 (independent review, GLM 5.3): §1.1 consequence analysis
|
||
completed — the two existing platform-admin bypass code paths are named as
|
||
non-conformant and §7.4 retires them; team grant subjects suspended pending
|
||
a team contract (§1.4, §3.3–3.4, §7.5); no-self-escalation restated with
|
||
its true rationale and a constructible observable (§4.2, §7.7);
|
||
node-creation seeding scoped to the bootstrap path, resolving the §7.7/§4.3
|
||
contradiction; A1 quotation corrected; audit-field provenance corrected;
|
||
principal-position consequence named (§1.3); membership-row,
|
||
fail-closed-fault, and existence-oracle observables added (§7);
|
||
role-string namespacing rule added (§4.5); ruling request now names the
|
||
interpretive resolution of PRD "admins".
|
||
|
||
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 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 and to kanban SOT Amendment A2's
|
||
sole-disclosure rule — SOT Amendment A3 (native-kanban-sot.md §10, this
|
||
PR) expressly extends A2 by exactly this carve-out (§8.1). The holder
|
||
sees only a redacted actor receipt; the canonical audit event keeps
|
||
hierarchy schema §5.2's full immutable snapshot. 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; 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
|
||
`apps/gateway/src/hierarchy/hierarchy.repository.ts` implements it).
|
||
|
||
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), the team model (suspended here; see §1.4).
|
||
|
||
## 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 ("not a bypass of workspace authorization"). `AdminGuard`'s
|
||
`role === 'admin'` check on admin endpoints stays the platform role's
|
||
only meaning. **Two shipped code paths violate this rule today and are
|
||
implementation defects this contract makes non-conformant:** (a) the
|
||
command authorization service short-circuits every command scope to
|
||
allowed for platform admins
|
||
(`apps/gateway/src/commands/command-authorization.service.ts`,
|
||
`hasScope` returning true when `role === 'admin'`), and (b) the MCP
|
||
scope derivation maps platform `admin` to tenant-admin MCP scopes
|
||
including task create/update
|
||
(`apps/gateway/src/mcp/mcp.service.ts`,
|
||
`deriveMcpToolScopesForUser`). Ratifying this contract revokes both;
|
||
§7.4 names them as the surfaces the deny-by-default test retires.
|
||
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.
|
||
Consequence, stated so implementing PRs do not weaken REQ-TEN-001 to
|
||
remove the friction: a chain-granted actor who is not a workspace member
|
||
may issue the write commands their role implies but cannot occupy a
|
||
principal position — any command taking a principal argument must name
|
||
an ACTIVE member of the target workspace (§7.2 enumerates this cell).
|
||
4. **Team grant subjects are suspended.** Contract 1 §3.1 reserves a
|
||
`team_id` attachment point, but no ratified contract yet defines the
|
||
team it would bind: the only existing `teams` table is the legacy global
|
||
Brain table (own authority columns, no workspace binding, not
|
||
repurposed per contract 1 §1.3), while the SOT's teams are
|
||
workspace-bound (REQ-ID-001) — and a workspace-bound team holding a
|
||
company-level grant would be a cross-workspace authority group nothing
|
||
has ratified. Until a team contract defines the subject (which table,
|
||
which membership rows, and its relation to D2/REQ-ID-001), creating a
|
||
grant with a team subject MUST be refused at the command surface (the
|
||
schema column remains, per contract 1). §3's evaluation semantics for
|
||
team-conferred grants are specified now so the team contract activates
|
||
them without amending this one.
|
||
|
||
## 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, once the team contract activates team
|
||
subjects (§1.4), 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** (specified now, active only per
|
||
§1.4). 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 letting the chain become what A1
|
||
§8.1.3 forbids: "a bypass of workspace authorization".
|
||
|
||
## 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. Under the §2
|
||
vocabulary this rule is currently implied by §4.1 (managers are `owner`,
|
||
the top role — no constructible grant exceeds it); it is stated
|
||
explicitly so it survives any future amendment that decouples
|
||
grant-management authority from role height. Its observable is the §7.7
|
||
audit invariant, not a refusal test.
|
||
3. **Bootstrap of authority is explicit; inheritance covers the rest.**
|
||
Creating the first company (the wizard path, contract 3) and any
|
||
top-level company creation MUST name the initial `owner` grant in the
|
||
same audited operation — a top-level node has no ancestor to inherit
|
||
from, so without this the node would be unownable. Creating a child node
|
||
(estate, platform-project, workspace) requires effective `owner` on the
|
||
parent (§2.3) and confers no automatic grant; the creator's authority
|
||
over the new node already follows from §3.2 down-the-chain evaluation.
|
||
The creating command MAY additionally name an explicit initial grant for
|
||
a child node; it is not required to.
|
||
4. Every grant mutation is a semantic audit event under contract 1 §5.2's
|
||
guarantees, extended by this contract with two further fields: the event
|
||
carries actor, verb, target, **subject, and role** (subject and role are
|
||
this contract's addition; contract 1 §5.2 does not enumerate them).
|
||
5. **Role strings are namespaced.** `viewer`/`member` exist at hierarchy
|
||
level, `member`/`admin` on `users.role`, and the current command layer
|
||
uses a third `viewer|member|admin` vocabulary — same strings, different
|
||
meanings. Any serialized role string (audit events per §4.4, API
|
||
responses, logs) MUST identify its layer (e.g. `hierarchy:owner`,
|
||
`platform:admin`); a bare role string in a serialized artifact is
|
||
non-conformant.
|
||
|
||
## 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 (once team subjects activate, §1.4), or the cascade
|
||
deletion of a node's grants 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):
|
||
|
||
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). The
|
||
matrix includes: a chain grant creates zero workspace-membership rows
|
||
(assert row counts); a chain-granted non-member is refused as the
|
||
principal argument of any principal-taking command while their
|
||
non-principal writes succeed (§1.3); structure reads leak no existence
|
||
of nodes the reader holds no grant on (no cross-tenant existence
|
||
oracle, A1 §8.3 acceptance 3).
|
||
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 — asserted against the two §1.1 non-conformant surfaces after
|
||
their retirement: the command-authorization admin short-circuit and the
|
||
MCP tenant-admin scope derivation both gone (a platform admin with no
|
||
grant is refused workspace commands and receives no tenant MCP scopes);
|
||
workspace member with no chain grant gains nothing outside SOT
|
||
membership semantics; fresh account reaches nothing (identity contract
|
||
§1.4 cross-check).
|
||
5. Team subjects: while suspended (§1.4), creating a team-subject grant is
|
||
refused at the command surface. On activation by the team contract:
|
||
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). Fail-closed fault
|
||
witness for §3.5: with grant state unreadable (fault injection), the
|
||
decision denies.
|
||
7. Grant management: non-`owner` cannot mutate grants; top-level company
|
||
creation without the named initial `owner` grant is refused, while child
|
||
node creation under ancestor authority succeeds without one (§4.3 both
|
||
directions); every mutation produces its audit event with the §4.4
|
||
fields. Self-escalation observable: over the audit event stream, every
|
||
grant-create/change event's role is ≤ the acting user's effective role
|
||
on the target at event time (reconstructable invariant, not a refusal
|
||
test — see §4.2).
|
||
8. Transfer: both-sides `owner` accepted, each single-side case refused
|
||
(completing contract 1 §6.5).
|
||
|
||
## 8. Company-CRUD capability (Amendment 1)
|
||
|
||
Hierarchy schema §5.5 authorizes the company visibility mutation for
|
||
exactly two actor classes: platform admins and "subjects holding the
|
||
company-CRUD capability that a follow-up amendment to contract 2 will
|
||
define". This section is that definition. The name is historical — coined
|
||
in contract 1 Amendment 1 before the capability's content was fixed — and
|
||
confers nothing by connotation: the ratified content is exactly §8.1.
|
||
Company _creation_ is already ruled open to active users and always
|
||
private (contract 3 §5.2, Ruling 4); rename, delete, and transfer of
|
||
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 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, 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 holder-visible surface is the
|
||
command's **actor receipt** — the mutation result payload, carrying
|
||
exactly the target id, old visibility, and new visibility, and
|
||
**nothing else**: no name, slug, structure, content, grant, or
|
||
membership information. The actor receipt is a redacted projection
|
||
distinct from the **canonical audit event**, which is unchanged by
|
||
this amendment: it keeps hierarchy schema §5.2's deletion-safe
|
||
immutable target snapshot (id, slug, and parent chain at event time)
|
||
in full. The two never converge on the holder: the capability confers
|
||
no audit read (§8.5), so the canonical event — and with it the slug
|
||
and parent chain — is reachable only by subjects independently
|
||
authorized to read audit data, never through this capability. 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, kanban SOT Amendment
|
||
A3 (native-kanban-sot.md §10, this PR) expressly extends A2's
|
||
sole-disclosure enumeration by the same carve-out, and all three 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 whose column set is exactly (nothing
|
||
else, per the contract 1 §2.7 exhaustiveness discipline):
|
||
- `id` — uuid, primary key;
|
||
- `user_id` — text, NOT NULL, FK `users` **ON DELETE RESTRICT**;
|
||
- `capability` — text, NOT NULL, constraint-checked against exactly
|
||
`company_crud`;
|
||
- `granted_by` — text, NOT NULL, FK `users` **ON DELETE RESTRICT**;
|
||
- `created_at` — timestamptz, NOT NULL;
|
||
- UNIQUE (`user_id`, `capability`).
|
||
|
||
The user FKs are **text**, not uuid, because `users.id` is a BetterAuth
|
||
text key (`packages/db/src/schema.ts`; custody schema records the same)
|
||
— PostgreSQL cannot reference a text primary key with a uuid column.
|
||
This matches the shipped `hierarchy_grants` shape exactly: uuid
|
||
surrogate `id`, text FKs to `users`.
|
||
|
||
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
|
||
user, and capability; serialized capability strings are namespaced per
|
||
§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`). 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;
|
||
no grant management (§4.1 unchanged); no MCP scope; no other instance
|
||
administration (user management, system settings, provider
|
||
configuration remain platform-admin-only); no company create, rename,
|
||
delete, or transfer. Hierarchy schema §5.5's rule that a company
|
||
`owner` as such may NOT change visibility is unchanged — `owner` and
|
||
this capability are disjoint authorities that combine only by a
|
||
subject holding both.
|
||
6. **Verification requirements** (extends §7, binding on implementing
|
||
PRs):
|
||
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); 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, receipt and canonical
|
||
event separately): the mutation result for a private-valid target,
|
||
a directory-valid target, and a nonexistent id is exactly {success,
|
||
success, `not_found`}; the actor receipt for a success carries
|
||
exactly {target id, old visibility, new visibility} and no result
|
||
or error payload carries name, slug, structure, content, grant, or
|
||
membership data; the canonical audit event for the same mutation —
|
||
asserted directly against the hierarchy outbox, not through any
|
||
holder-facing surface — carries hierarchy schema §5.2's full
|
||
immutable snapshot (id, slug, parent chain); and the holder's
|
||
attempt to read audit data is refused (no audit read conferred,
|
||
§8.5), proving the receipt/event separation reaches the holder as
|
||
a redaction, not a weakened event.
|
||
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.
|
||
|
||
## Ruling request
|
||
|
||
Ratify sections 1–7 as written, with one decision embedded and one
|
||
interpretive resolution named:
|
||
|
||
- Decision: platform `admin` confers no implicit tenant access — operators
|
||
see tenant content only through explicit, audited grants (§1.1), which
|
||
retires the two existing admin bypass paths named there. Say "agreed" or
|
||
name the implicit access you want platform admins to keep.
|
||
- Interpretive resolution (for visibility, not a separate question): PRD
|
||
Part I §4 says "admins restrict access per company, estate, and project";
|
||
this contract resolves "admins" as hierarchy `owner`s (§4.1), not
|
||
platform admins. A1 §8.1.3 does not attribute grant declaration to
|
||
platform admins, and the §1.1 decision above is what makes this reading
|
||
binding.
|
||
|
||
## Ruling request (Amendment 1)
|
||
|
||
Ratify §8, the Amendment 1 header note, and kanban SOT Amendment A3
|
||
(native-kanban-sot.md §10 — the express A2 carve-out extension, which
|
||
binds only with this ratification) as written, with one decision
|
||
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 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.
|