Compare commits

..
Author SHA1 Message Date
fred 8a03d99e31 contract(custody-schema): revision 13 — withdraw wrong §4.5 repair attribution (sol r12 F20 residual)
ci/woodpecker/pr/ci Pipeline was successful
Sol's r12 confirm closed F18/F19 and found one MINOR residual on F20:
rev 12 assigned the terminated ordinary writer's committed-but-unpointed
content to §4.5 repair, but §4.5 enumerates pointers and repairs the
opposite state (pointer present, content absent) — unpointed content is
outside its input, so the §7.10 assertion had no conforming transition.

Per the prescribed correction: the §4.5 repair assertion is removed from
§3.7 and §7.10; the no-upsert assertion stays; the state is what §3.7
and §7.16 already define — inert unpointed content (no pointer, no read
path, no repair transition; a §4.5 cycle run against it changes
nothing), never acknowledged, converging only through the client's
retry as a fresh lock acquisition under a new token that recommits and
completes the pointer upsert. Preamble Revision 13 paragraph added.
2026-08-27 04:48:07 -05:00
fred 8085982396 contract(custody-schema): revision 12 — exact token comparison base case, split stale database-half boundary
ci/woodpecker/pr/ci Pipeline was successful
sol r11 APPROVE minors:
- F19: strictly-less-than refusal made explicit; absent recorded token
  compares lower than every minted token, first acceptance creates the
  record; §7.10 virgin-locator control + delayed-first-grant race.
- F18 residual closed: §7.10 managed-deletion fault predicate restated
  as the exact §4.7 predicate (advance's epoch mint and token record
  permitted).
- F20: pre-advance linearization boundary distinguishes database
  halves — pair-conditioned step 2 for deletion/repair; terminated
  ordinary writer performs no pointer upsert, retry is a fresh
  acquisition; §7.10 control runs both branches.
- §8 item 16 revision-12 clause; preamble Revision 12 paragraph.
2026-08-27 04:19:19 -05:00
fred 2c02631324 custody contract revision 11: acquisition-token guard on the fence-advance (sol r10 F16 residual, F18)
ci/woodpecker/pr/ci Pipeline was successful
The fence-advance is itself fenced against an older acquisition: a
per-locator acquisition token minted by the lock authority at every
lock grant, carried unchanged across the acquisition's brain
operations and swap-loop retries, compared and recorded by the brain
store atomically with every brain mutation (advance included). A
superseded acquisition's delayed advance is refused, mints nothing,
and yields no usable fence (mandatory abort-release-surface,
expected on deadline supersession). The 'refused unconditionally'
claim is precisified to the exact linearization boundary; the
pre-step-1 interruption predicates are restated over exact
content/database state, expressly permitting the advance's
epoch/token change (F18). New §7.10 stale-advance refusal and
pre-advance linearization controls; token invariants added to the
fence static controls (§7.10, §7.16); §8 item 16 and preamble
extended.
2026-08-27 03:47:21 -05:00
fred b10cfb1e69 docs: custody-schema contract revision 10 (sol r9 F16 residual + F17: ABA-resistant per-locator fence epoch — fresh mint on every mutation incl. byte-identical and absence tombstone, fence-advance at lock acquisition invalidating stale tails before first mutation, repair advances before observing; distinct-locator no-contention narrowed to locator locks)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-27 03:04:52 -05:00
fred c6a2237dde docs: custody-schema contract revision 9 (sol r8 F15 residual + F16: independent deadline supervisor, brain fence via conditional application, reservation-at-skip, computed fixed cycle maximum with explicit exceedance carry-over)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-27 02:22:17 -05:00
fred f8ac64f58a docs: custody-schema contract revision 8 (sol r7 residual F15: declared hold deadline + arrival-order grants make the one-cycle bound wall-clock provable)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-26 23:08:12 -05:00
fred 482400d93c docs: custody-schema contract revision 7 (sol r6 F15: within-cycle blocking retry preserves the one-cycle orphan bound under lock contention)
ci/woodpecker/pr/ci Pipeline failed
2026-08-26 22:38:20 -05:00
fred 8ca4564b5e docs: custody-schema contract revision 6 (sol r5 residuals F9/F12 + F14)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-26 22:06:00 -05:00
fred a8111998c7 docs: custody-schema contract revision 5 (sol r4 residuals F9/F11 + F12/F13)
ci/woodpecker/pr/ci Pipeline failed
2026-08-26 21:25:53 -05:00
fred 269aeda193 docs: custody-schema contract revision 4 (sol r3 residuals F1/F2/F4/F6/F7/F9 + F10/F11)
ci/woodpecker/pr/ci Pipeline failed
2026-08-26 21:02:12 -05:00
fred 671b872ec9 docs: custody-schema contract revision 3 (sol re-review residuals F1-F9)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-26 20:33:21 -05:00
fred 8079d8dc91 docs(custody): revision 2 — classifier registry, grantee model, exact schemas, witness closure (sol r1 F1-F6)
ci/woodpecker/pr/ci Pipeline is running
2026-08-26 19:39:04 -05:00
fred caa5b04096 docs: custody pointer and consent schema contract (S2 contract 7)
ci/woodpecker/pr/ci Pipeline is running
2026-08-26 19:10:12 -05:00
2 changed files with 1742 additions and 259 deletions
File diff suppressed because it is too large Load Diff
-259
View File
@@ -1,259 +0,0 @@
# 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".
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).
## 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.