Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
501112d2a4 | ||
|
|
3538d33106 |
@@ -0,0 +1,259 @@
|
||||
# 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.
|
||||
@@ -1,197 +0,0 @@
|
||||
# Tool↔Gateway Mapping Contract (D8)
|
||||
|
||||
Status: DRAFT — awaiting ratification (webui-audit S2, contract 5 of 9).
|
||||
Authority: PRD D8/D12 (Part I §8) — the webUI sits OVER official tooling:
|
||||
every webUI operation goes through the Gateway API backed by the same
|
||||
official framework tooling the CLI uses, and a webUI operation with no
|
||||
backing tool is scored **blocked on tooling** and the tool is built
|
||||
first. Measured input: the webui-audit A5 tooling baseline
|
||||
(operation-by-operation inventory of the current Gateway surface and the
|
||||
P1 gaps, cross-reviewed; `fleet/lanes/webui-audit/findings/
|
||||
A5-tooling-baseline.md` in the estate brain). The T10 ruling adopted the
|
||||
targeted-update plan including building the D8 tools in A5's rank order.
|
||||
|
||||
Revision 2 (GLM review F1–F5): the §2 table completed against an
|
||||
independent re-measurement of the live `apps/web` surface (mission
|
||||
reads, coordination status, capability-gated `turn:send` added); rank-6
|
||||
composition corrected to ranks 1 and 4; SOT citations corrected to §3
|
||||
invariant 11 / REQ-TASK-001 / §5+A1; the §3.2 retirement clause
|
||||
softened to match what the owning contracts actually schedule; §6.1
|
||||
scoped to outbound calls with an extractability lint, and §6.3 given
|
||||
static companions for §4.1 and §4.3.
|
||||
|
||||
This contract binds three things: the operation→tool mapping itself
|
||||
(§2–§3), the command envelope every mapped operation satisfies
|
||||
(§4), and the process rule that keeps the mapping closed (§5). Domain
|
||||
semantics stay with their owning contracts — hierarchy (contract 1,
|
||||
`hierarchy-schema.md`), grants (contract 2, `rbac-grant-model.md`),
|
||||
wizard (contract 3, `onboarding-wizard.md`), identity
|
||||
(`identity-lifecycle.md`), kanban lifecycle (`native-kanban-sot.md`
|
||||
§5 and Amendment A1), roll-up (contract 8), API artifact format
|
||||
(contract 9).
|
||||
|
||||
## 1. Definitions
|
||||
|
||||
1. **Official tool**: a command implemented in the framework packages and
|
||||
exposed through the Gateway API; the CLI remains the primary execution
|
||||
method for the same command (D8). The webUI is a Gateway client only.
|
||||
2. **Mapped operation**: a webUI operation with a named official path in
|
||||
§2 or §3. Anything else the webUI wants to do is unmapped and follows
|
||||
§5.
|
||||
3. **Legacy non-substitute**: an existing endpoint that resembles a P1
|
||||
need but is contractually barred from backing it (§3.2).
|
||||
|
||||
## 2. P0 mapping (current operations, ratified as-is)
|
||||
|
||||
This table is the complete measured P0 surface: every Gateway call the
|
||||
web app's production sources make at this revision's head appears as a
|
||||
row (independently re-measured at review; the three calls the first
|
||||
measurement missed — mission reads, coordination status, and the
|
||||
capability-gated `turn:send` emit — are rows below). The surface stays
|
||||
bound to these paths:
|
||||
|
||||
| WebUI operation | Official path |
|
||||
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Register / log in / log out / OIDC callback | better-auth mount `/api/auth/*`; `GET /api/sso/providers` |
|
||||
| List/show projects (legacy read) | `GET /api/projects`, `GET /api/projects/:id` |
|
||||
| List tasks / task detail (legacy read) | `GET /api/tasks`, `GET /api/tasks/:id` — with the filtered legacy project/mission reads the same surfaces use |
|
||||
| Mission list (legacy read) | `GET /api/missions` |
|
||||
| Coordination status (legacy read) | `GET /api/coord/status` |
|
||||
| Conversation CRUD/search/messages | `/api/conversations*` |
|
||||
| Chat turn / stop / thinking / command execute+approve / streaming | `/chat` socket events `message`, `abort`, `set:thinking`, `command:execute`, `command:approve`; `turn:send` (capability-gated — emitted only when the server advertises the pi turn-runtime capability, which the current Gateway does not) |
|
||||
| Harness/model selection | `GET /api/harnesses*`, `GET/PUT /api/chat/preferences/selection` |
|
||||
| Preferences; provider inspect/test | `/api/memory/preferences`, `GET /api/providers`, `POST /api/providers/test` |
|
||||
| Admin users / roles / ban / health | `/api/admin/users*`, `/api/admin/health` |
|
||||
|
||||
P0 rows inherit §4 obligations as their backing controllers are next
|
||||
touched; they are not required to be retrofitted in one sweep.
|
||||
|
||||
## 3. P1 mapping (bound to the build-first tools)
|
||||
|
||||
1. Every P1 operation maps to exactly one build-first command family, in
|
||||
the T10-ruled rank order:
|
||||
|
||||
| Rank | Command family (owning contract) | P1 webUI operations it backs |
|
||||
| ---- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Hierarchy command family (contract 1 §5; grants attach per contract 2) | Company/estate/platform-project/workspace CRUD, parentage and reparenting, hierarchy reads; the wizard's initial-hierarchy step (contract 3 §3.4) |
|
||||
| 2 | Hierarchy RBAC command/evaluator (contract 2) | Grant create/change/revoke at company/estate/platform-project; inherited evaluation down to workspace; authorization-safe hierarchy queries |
|
||||
| 3 | Typed kanban command/query surface (SOT §5, Amendment A1) | Workspace task lifecycle (create/edit/cancel/archive/move), board rank, typed queries |
|
||||
| 4 | Agent enrollment command | Enroll one agent: harness, credential reference/API-key intake (values never echoed), name/persona, assignment scope (contract 3 §3.5) |
|
||||
| 5 | Authorized roll-up query (contract 8) | Read-only aggregated task counts/statuses at every hierarchy level over readable workspaces only |
|
||||
| 6 | Onboarding orchestration (contract 3) | The re-runnable wizard flow, composing ranks 1 and 4 (its only grant write rides inside the rank-1 company-create command, contract 2 §4.3) |
|
||||
|
||||
2. **Legacy non-substitutes.** The following MUST NOT back any P1
|
||||
operation, matching the audit findings: legacy `/api/projects` and
|
||||
`/api/tasks` CRUD (planning-data records, not hierarchy nodes and not
|
||||
the typed kanban boundary); `POST /api/workspaces` (filesystem
|
||||
bootstrap, not audited hierarchy parentage); `/api/teams` reads (no
|
||||
grants, no inheritance); `POST /api/bootstrap/setup` (one-shot
|
||||
epoch transition, identity §3 — not the re-runnable wizard); the MCP
|
||||
`brain_*` task mutations (legacy Brain writes, not the typed kanban
|
||||
commands). These stay serving their existing P0/host consumers until
|
||||
the owning contract (or a successor amendment) schedules each
|
||||
retirement — no such migration is scheduled at this revision; the
|
||||
freeze stands on its own.
|
||||
3. New P1 mapping rows (operations this table does not list) are added by
|
||||
amending this contract, not ad hoc (§5).
|
||||
|
||||
## 4. Command envelope (request / result / error / audit)
|
||||
|
||||
Binding on every mapped operation the build-first families expose:
|
||||
|
||||
1. **Typed request and result.** Each command and query has an explicit
|
||||
request DTO and result DTO in the shared types package, validated at
|
||||
the Gateway boundary; unvalidated pass-through and `any`-typed
|
||||
payloads are non-conformant. Mutations on records with an
|
||||
expected-version rule in their owning contract carry the expected
|
||||
version in the request and fail on mismatch with the conflict error
|
||||
class (SOT §3 invariant 11 and REQ-TASK-001's concurrent-update
|
||||
conflict acceptance; hierarchy per contract 1).
|
||||
2. **Error taxonomy.** Every error result carries a stable
|
||||
machine-readable code from a closed per-family enum plus an HTTP
|
||||
status mapping, distinguishing at minimum: validation failure,
|
||||
authentication failure, authorization refusal, not-found, conflict
|
||||
(version/uniqueness), precondition/state refusal (e.g. bootstrap
|
||||
epoch, suspended team subjects), and internal fault. Where contract
|
||||
2's no-existence-oracle rule applies, authorization refusal and
|
||||
not-found are indistinguishable on the wire for unauthorized readers
|
||||
— same code, same status, same shape.
|
||||
3. **Audit linkage.** A mutating mapped operation emits exactly the
|
||||
audit events its owning contract defines (contract 1 §5.2, contract 2
|
||||
§4.4, identity §§2–4, SOT audit rules); the envelope contributes the
|
||||
correlation: every request accepts/generates a correlation id,
|
||||
carried into the audit events and returned in the result, so a UI
|
||||
action is traceable end to end. The mapping layer itself adds no
|
||||
second audit stream.
|
||||
4. **Fail-closed.** A mapped operation that cannot evaluate its
|
||||
authorization or reach its owning tool refuses (contract 2 §3.5); the
|
||||
envelope never degrades to an unauthorized fallback read or a direct
|
||||
data access.
|
||||
5. **CLI parity.** Each build-first family is invocable through the
|
||||
official CLI against the same Gateway commands with the same
|
||||
request/result/error contracts. No webUI-only command exists; a
|
||||
Gateway command without CLI exposure is a conformance gap tracked at
|
||||
the family's implementing issue.
|
||||
|
||||
## 5. Closure rule (blocked on tooling)
|
||||
|
||||
1. A webUI change that needs an operation with no mapping row is
|
||||
**blocked on tooling**: the backing tool is built and mapped first
|
||||
(D8). Scoring a gap "blocked on tooling" is mandatory, not
|
||||
discretionary; working around it in the UI (direct DB or filesystem
|
||||
access, calling a legacy non-substitute, embedding domain logic in
|
||||
the web app) is non-conformant.
|
||||
2. The mapping is enforced closed by §6.1's inventory witness: the web
|
||||
app's network surface must be a subset of the mapped paths.
|
||||
|
||||
## 6. Verification requirements
|
||||
|
||||
Binding on the implementing PRs:
|
||||
|
||||
1. **Network-surface inventory witness:** a CI assertion extracting the
|
||||
web app's outbound Gateway calls — route literals at request call
|
||||
sites and outbound socket emits in `apps/web` sources (inbound
|
||||
handler registrations are not calls and are out of scope) — and
|
||||
failing on any call outside the §2/§3 mapped paths. The inventory is
|
||||
closed like contract 1 §6.3's allowlist: a new call fails until a
|
||||
mapping row exists in the same PR. Dynamic route construction that
|
||||
evades extraction is resolved toward the witness, enforced by an
|
||||
extractability lint: every request call site takes a literal or
|
||||
template-literal path, and a call site that does not fails the
|
||||
assertion itself (the web-side analogue of contract 1's
|
||||
raw-execution prong), never an exemption for the caller.
|
||||
2. **Non-substitute witness:** the P1 surfaces (hierarchy, RBAC, kanban,
|
||||
enrollment, roll-up, wizard UI) make zero calls to the §3.2 legacy
|
||||
endpoints — asserted by the same inventory, scoped per surface.
|
||||
3. **Envelope witnesses per family:** for each build-first family — a
|
||||
request with an invalid DTO is refused with the validation code; a
|
||||
version-mismatch mutation returns the conflict code; an unauthorized
|
||||
read of an existing node and a read of a nonexistent node return
|
||||
indistinguishable results where the no-existence-oracle rule applies;
|
||||
a correlation id submitted on a mutation appears in its audit
|
||||
event(s) and result. Two static companions: a type-level assertion
|
||||
that the family's boundary accepts no `any`-typed or unvalidated
|
||||
pass-through payload (§4.1), and a single-emitter assertion that the
|
||||
mapped operation's audit events originate only from the owning
|
||||
contract's audit emitter (§4.3's no-second-audit-stream, made
|
||||
checkable).
|
||||
4. **CLI-parity witness:** for each family, a CLI smoke invocation of at
|
||||
least one command and one query against the Gateway succeeds with the
|
||||
same typed result the web client receives.
|
||||
5. **Fail-closed witness:** with the owning tool or grant state
|
||||
unreachable (fault injection), the mapped operation returns the
|
||||
internal-fault or authorization-refusal class and performs no
|
||||
fallback read/write (extends contract 2 §7.6 to the mapping layer).
|
||||
|
||||
## Ruling request
|
||||
|
||||
Ratify sections 1–6 as written, with one decision embedded:
|
||||
|
||||
- Decision (§3.2): the legacy endpoints named there are **frozen for new
|
||||
consumers** as of ratification — existing P0/host consumers keep
|
||||
working, new UI or tool code may not call them, and each is retired by
|
||||
the migration its owning contract schedules. Alternative if rejected:
|
||||
allow P1 surfaces to reuse legacy endpoints as interim backends —
|
||||
rejected by the audit's finding that they cannot satisfy the
|
||||
hierarchy/kanban/RBAC contracts, so the interim would ship
|
||||
non-conformant semantics.
|
||||
Reference in New Issue
Block a user