Files
stack/docs/requirements/tool-gateway-mapping.md
T
fred 19ebc422aa
ci/woodpecker/push/publish Pipeline was successful
docs: tool-gateway mapping contract (S2 contract 5) (#1438)
2026-08-28 22:04:03 +00:00

198 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 F1F5): 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 §§24, 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 16 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.