docs: RBAC grant model contract revision 2 (GLM review findings 1-10)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- name the two shipped platform-admin bypass paths (command-authorization hasScope short-circuit, MCP tenant-admin scope derivation) as non-conformant; 7.4 retires them explicitly - suspend team grant subjects pending a team contract; command surface refuses team-subject grants; evaluation semantics pre-specified - no-self-escalation restated with future-decoupling rationale; observable is an audit invariant, not an unconstructible refusal test - bootstrap seeding scoped to top-level company creation; child nodes inherit via down-the-chain, resolving the 7.7/4.3 contradiction - exact A1 quote (not a bypass of workspace authorization) - subject/role audit fields attributed as this contract's addition - principal-position consequence named (1.3) with matrix cell - added observables: zero membership rows, fail-closed fault injection, no existence oracle - role-string namespacing rule (4.5) - ruling request names the PRD-admins interpretive resolution
This commit is contained in:
@@ -12,13 +12,25 @@ the meaning of "authority" here; the identity contract
|
|||||||
(`docs/requirements/identity-lifecycle.md` §1.4) pins that account creation
|
(`docs/requirements/identity-lifecycle.md` §1.4) pins that account creation
|
||||||
grants nothing.
|
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".
|
||||||
|
|
||||||
Scope: the roles that can appear in `hierarchy_grants.role`, what a grant at
|
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
|
each hierarchy level confers, how grants evaluate down the chain, how
|
||||||
revocation propagates, and who may manage grants. Out of scope: the hierarchy
|
revocation propagates, and who may manage grants. Out of scope: the hierarchy
|
||||||
tables themselves (contract 1), workspace-internal membership and its
|
tables themselves (contract 1), workspace-internal membership and its
|
||||||
role/capability vocabulary (native-kanban SOT REQ-ID-001 and its implementing
|
role/capability vocabulary (native-kanban SOT REQ-ID-001 and its implementing
|
||||||
schema), roll-up projection semantics (contract 8), wizard seeding
|
schema), roll-up projection semantics (contract 8), wizard seeding
|
||||||
(contract 3).
|
(contract 3), the team model (suspended here; see §1.4).
|
||||||
|
|
||||||
## 1. Three authority layers, none substitutable
|
## 1. Three authority layers, none substitutable
|
||||||
|
|
||||||
@@ -27,9 +39,20 @@ schema), roll-up projection semantics (contract 8), wizard seeding
|
|||||||
configuration. It is not tenancy authority: holding platform `admin`
|
configuration. It is not tenancy authority: holding platform `admin`
|
||||||
confers **no implicit hierarchy grant and no workspace authorization**.
|
confers **no implicit hierarchy grant and no workspace authorization**.
|
||||||
An operator who should see tenant content holds an explicit, audited
|
An operator who should see tenant content holds an explicit, audited
|
||||||
grant like anyone else. (This is the deny-by-default consequence of A1
|
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
|
§8.1.3 ("not a bypass of workspace authorization"). `AdminGuard`'s
|
||||||
`role === 'admin'` for admin endpoints and that stays its only meaning.)
|
`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
|
2. **Hierarchy grants** (`hierarchy_grants`, contract 1 §3) declare tenancy
|
||||||
authority at company, estate, or platform-project scope and evaluate down
|
authority at company, estate, or platform-project scope and evaluate down
|
||||||
the chain to workspace-scoped authorization (§3 below).
|
the chain to workspace-scoped authorization (§3 below).
|
||||||
@@ -38,6 +61,24 @@ schema), roll-up projection semantics (contract 8), wizard seeding
|
|||||||
it does not create membership rows, and row-level principal positions
|
it does not create membership rows, and row-level principal positions
|
||||||
(task owner, proposer, decision actor) still require ACTIVE workspace
|
(task owner, proposer, decision actor) still require ACTIVE workspace
|
||||||
membership exactly as REQ-TEN-001/REQ-ID-001 acceptance states.
|
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
|
## 2. Role vocabulary
|
||||||
|
|
||||||
@@ -72,13 +113,15 @@ contract amendment, not an implementation decision.
|
|||||||
entire descendant subtree. Nothing evaluates upward or sideways: a grant
|
entire descendant subtree. Nothing evaluates upward or sideways: a grant
|
||||||
on an estate says nothing about the parent company or sibling estates.
|
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
|
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
|
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
|
that node or any ancestor — and, once the team contract activates team
|
||||||
member of on that node or any ancestor. Roles never subtract — there is
|
subjects (§1.4), grants held by any team the user is a member of on that
|
||||||
no negative/deny grant in this model; revocation is deletion (§6).
|
node or any ancestor. Roles never subtract — there is no negative/deny
|
||||||
4. **Team grants follow live membership.** A team grant confers its role on
|
grant in this model; revocation is deletion (§6).
|
||||||
the team's current members, evaluated at decision time. Leaving the team
|
4. **Team grants follow live membership** (specified now, active only per
|
||||||
is loss of the grant with §6's propagation bound.
|
§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
|
5. **Live evaluation, fail closed.** Authorization decisions derive from the
|
||||||
live grant and team-membership rows (or from a cache that is invalidated
|
live grant and team-membership rows (or from a cache that is invalidated
|
||||||
in the same transaction as any grant/membership/hierarchy mutation). A
|
in the same transaction as any grant/membership/hierarchy mutation). A
|
||||||
@@ -87,23 +130,41 @@ contract amendment, not an implementation decision.
|
|||||||
6. **Tenant context stays derived from authenticated authority**
|
6. **Tenant context stays derived from authenticated authority**
|
||||||
(REQ-TEN-001). The chain adds where grants can be declared; a workspace
|
(REQ-TEN-001). The chain adds where grants can be declared; a workspace
|
||||||
request is still authorized against that workspace, with the chain
|
request is still authorized against that workspace, with the chain
|
||||||
contributing the effective role — never a bypass of workspace-scoped
|
contributing the effective role — never letting the chain become what A1
|
||||||
checks.
|
§8.1.3 forbids: "a bypass of workspace authorization".
|
||||||
|
|
||||||
## 4. Grant management
|
## 4. Grant management
|
||||||
|
|
||||||
1. Creating, changing, or revoking a grant on a node requires effective
|
1. Creating, changing, or revoking a grant on a node requires effective
|
||||||
`owner` on that node (directly or via any ancestor).
|
`owner` on that node (directly or via any ancestor).
|
||||||
2. **No self-escalation.** A grant manager cannot create a grant with a role
|
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
|
higher than their own effective role on the target node. Under the §2
|
||||||
vocabulary this only bites managers acting through team-conferred
|
vocabulary this rule is currently implied by §4.1 (managers are `owner`,
|
||||||
`owner`: the rule is stated so it survives vocabulary amendments.)
|
the top role — no constructible grant exceeds it); it is stated
|
||||||
3. Creating a hierarchy node confers no automatic grant. Bootstrap of
|
explicitly so it survives any future amendment that decouples
|
||||||
authority is explicit: the creating command names the initial `owner`
|
grant-management authority from role height. Its observable is the §7.7
|
||||||
grant in the same audited operation, and the wizard (contract 3) seeds
|
audit invariant, not a refusal test.
|
||||||
the first company's initial `owner` the same way.
|
3. **Bootstrap of authority is explicit; inheritance covers the rest.**
|
||||||
4. Every grant mutation is a semantic audit event per contract 1 §5.2
|
Creating the first company (the wizard path, contract 3) and any
|
||||||
(actor, verb, subject, target, role).
|
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)
|
## 5. Transfer authority (completes contract 1 §4.2)
|
||||||
|
|
||||||
@@ -116,9 +177,10 @@ half-authorized subjects are not a transfer protocol this contract defines.
|
|||||||
## 6. Revocation propagation
|
## 6. Revocation propagation
|
||||||
|
|
||||||
1. Revoking a grant (deleting the row), removing a user from a team that
|
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
|
carries a grant (once team subjects activate, §1.4), or the cascade
|
||||||
deletion (contract 1 §3.3) all propagate identically: the authority
|
deletion of a node's grants during node deletion (contract 1 §3.3) all
|
||||||
derived from that grant is gone for every descendant workspace.
|
propagate identically: the authority derived from that grant is gone for
|
||||||
|
every descendant workspace.
|
||||||
2. **Bound:** the next authorization decision on any affected transport
|
2. **Bound:** the next authorization decision on any affected transport
|
||||||
decides against the revoked grant. Concretely: no new HTTP/MCP command
|
decides against the revoked grant. Concretely: no new HTTP/MCP command
|
||||||
authorized by the revoked grant after the revoking transaction commits;
|
authorized by the revoked grant after the revoking transaction commits;
|
||||||
@@ -132,7 +194,7 @@ half-authorized subjects are not a transfer protocol this contract defines.
|
|||||||
## 7. Verification requirements
|
## 7. Verification requirements
|
||||||
|
|
||||||
Binding on the implementing PRs (extends A1 §8.3 acceptance 2–3 and
|
Binding on the implementing PRs (extends A1 §8.3 acceptance 2–3 and
|
||||||
contract 1 §6.4):
|
contract 1 §6):
|
||||||
|
|
||||||
1. Vocabulary: the role CHECK constraint rejects any value outside
|
1. Vocabulary: the role CHECK constraint rejects any value outside
|
||||||
`viewer|member|owner` (real-PostgreSQL witness, `ci-postgres` service in
|
`viewer|member|owner` (real-PostgreSQL witness, `ci-postgres` service in
|
||||||
@@ -140,30 +202,58 @@ contract 1 §6.4):
|
|||||||
2. Per-level conferral: for each of the three levels × three roles, a grant
|
2. Per-level conferral: for each of the three levels × three roles, a grant
|
||||||
yields exactly the implied workspace authorization in a descendant
|
yields exactly the implied workspace authorization in a descendant
|
||||||
workspace and nothing in a non-descendant workspace (the A1 §8.3
|
workspace and nothing in a non-descendant workspace (the A1 §8.3
|
||||||
"exactly the permissions the chain implies" matrix, enumerated).
|
"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
|
3. Ordering: `owner` ⊇ `member` ⊇ `viewer` behaviorally — each higher role
|
||||||
passes every lower role's positive cases.
|
passes every lower role's positive cases.
|
||||||
4. Deny-by-default: platform `admin` with no grant reaches no tenant
|
4. Deny-by-default: platform `admin` with no grant reaches no tenant
|
||||||
content; workspace member with no chain grant gains nothing outside SOT
|
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
|
membership semantics; fresh account reaches nothing (identity contract
|
||||||
§1.4 cross-check).
|
§1.4 cross-check).
|
||||||
5. Max-rule and teams: user-direct and team-conferred grants combine to the
|
5. Team subjects: while suspended (§1.4), creating a team-subject grant is
|
||||||
maximum; team-leave drops authority within the §6.2 bound; decision-time
|
refused at the command surface. On activation by the team contract:
|
||||||
evaluation witnessed (grant added → next decision allows; no restart or
|
user-direct and team-conferred grants combine to the maximum; team-leave
|
||||||
re-login required).
|
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
|
6. Revocation: each revocation path in §6.1 denies the next command on
|
||||||
every transport; the socket bound is measured; a cached-authorization
|
every transport; the socket bound is measured; a cached-authorization
|
||||||
implementation proves transactional invalidation (grant revoked and
|
implementation proves transactional invalidation (grant revoked and
|
||||||
decision made on two distinct physical connections).
|
decision made on two distinct physical connections). Fail-closed fault
|
||||||
7. Grant management: non-`owner` cannot mutate grants; self-escalation
|
witness for §3.5: with grant state unreadable (fault injection), the
|
||||||
attempt refused; node creation without the explicit initial grant
|
decision denies.
|
||||||
refused; every mutation produces its audit event.
|
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
|
8. Transfer: both-sides `owner` accepted, each single-side case refused
|
||||||
(completing contract 1 §6.2).
|
(completing contract 1 §6.5).
|
||||||
|
|
||||||
## Ruling request
|
## Ruling request
|
||||||
|
|
||||||
Ratify sections 1–7 as written, with one decision embedded: platform `admin`
|
Ratify sections 1–7 as written, with one decision embedded and one
|
||||||
confers no implicit tenant access — operators see tenant content only
|
interpretive resolution named:
|
||||||
through explicit, audited grants (§1.1) — say "agreed" or name the implicit
|
|
||||||
access you want platform admins to have.
|
- 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user