Move all hand-synced cross-app wire contracts into one built workspace package, @boocode/contracts, consumed by server/web/coder/coder-web via workspace:* + a per-subpath exports map. The ws-frames and provider-config Zod schemas are schema-first (z.infer); MessageMetadata, ErrorReason, AgentSessionConfig, the provider snapshot types, and WorktreeRiskReport are each single-sourced. Deletes the byte-identical copies and their parity tests, fixes a live AgentSessionConfig drift (coder dead copy removed, unified to the web required/nullable shape), removes the dead pending_change WS arms in the fallback SPA, and inverts the build order (contracts builds first) across root build, Dockerfile, and the coder deploy docs. Reverses the shared-package decision declined in v2.5.12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
5.1 KiB
Markdown
103 lines
5.1 KiB
Markdown
# @boocode/contracts — cross-app wire contract SSOT
|
|
|
|
**Status:** shipped (2026-06-02)
|
|
|
|
Eliminate BooCode's hand-synced, duplicated cross-app TypeScript wire contracts by
|
|
creating one workspace package, `@boocode/contracts`, that every consumer imports.
|
|
Each contract is defined exactly once; the two Zod-backed contracts (`ws-frames`,
|
|
`provider-config`) use `z.infer` so validator and type derive from the same definition
|
|
and cannot drift independently.
|
|
|
|
## Why
|
|
|
|
BooCode maintained hand-synced copies of cross-app contracts across up to four
|
|
locations (`apps/server`, `apps/web`, `apps/coder`, `apps/coder/web`), verified only
|
|
by byte-parity tests — `provider-types-parity.test.ts` and the ws-frames byte-parity
|
|
assertion in `ws-frames.test.ts`. A live drift had appeared: `AgentSessionConfig`
|
|
existed in two incompatible shapes — `apps/coder` held an all-optional dead copy
|
|
(zero live importers) while `apps/web` held the real required/nullable shape — making
|
|
the parity-test regime insufficient for type-only contracts.
|
|
|
|
`v2.5.12-provider-lifecycle-phase4` had explicitly deferred a shared types package as
|
|
"not worth the Docker/build-order risk at solo scale"; the observed drift made the
|
|
investment worth taking.
|
|
|
|
## What shipped
|
|
|
|
**Package:** `packages/contracts` (`@boocode/contracts`), `declaration:true`, zod
|
|
pinned `^3.23.8`, per-subpath exports map with `types`-then-`default` conditions.
|
|
`packages/*` added to `pnpm-workspace.yaml`; `pnpm-lock.yaml` regenerated.
|
|
|
|
**Six contracts single-sourced:**
|
|
|
|
- `./ws-frames` — `WsFrameSchema` (Zod runtime), `KNOWN_FRAME_TYPES`, `WsFrame`
|
|
(`z.infer` from the schema).
|
|
- `./provider-snapshot` — `ProviderSnapshotEntry`, `ProviderModel`, `ProviderMode`,
|
|
`ThinkingOption`, `AgentCommand`, `ProviderSnapshotStatus` (plain TS; the coder's
|
|
`provider-types.ts` re-exports them so internal importers are unchanged).
|
|
- `./provider-config` — `ProviderOverrideSchema`, `CoderProvidersFileSchema`,
|
|
`ProviderConfigPatchSchema` and their `z.infer` types.
|
|
- `./message-metadata` — `MessageMetadata`, `ErrorReason`, `AgentSessionConfig`.
|
|
- `./worktree-risk` — `WorktreeRiskReport` (unified from three copies that differed
|
|
only in name; the coder called it `RiskReport`).
|
|
|
|
**Four consumers** import via `workspace:*` through the exports map: `apps/server`,
|
|
`apps/web`, `apps/coder`, and the fallback SPA `apps/coder/web`. No tsconfig project
|
|
references; built dist only.
|
|
|
|
**Deleted:**
|
|
- `apps/server/src/types/ws-frames.ts` (server `./ws-frames` exports subpath dropped)
|
|
- `apps/web/src/api/ws-frames.ts`
|
|
- Provider-snapshot mirror block in web (`apps/web/src/api/types.ts`)
|
|
- Provider-config 17-line hand-mirror in web
|
|
- `apps/coder/src/services/__tests__/provider-types-parity.test.ts` (6 parity tests)
|
|
- ws-frames byte-parity assertion (server test suite)
|
|
- All duplicate `MessageMetadata`, `ErrorReason`, `AgentSessionConfig` copies
|
|
- `WorktreeRiskReport` / `RiskReport` duplicates (3 copies)
|
|
- `apps/coder/web` dead `pending_change_added`/`pending_change_updated` reducer arms
|
|
and associated WS plumbing (`DiffPane` prop, `Session.tsx` listener)
|
|
|
|
**Preserved:**
|
|
- KNOWN_FRAME_TYPES drift test — moved into the package (11/11)
|
|
- Broker fail-closed tests — kept in `apps/server`, importing from the package (4/4)
|
|
- Web strict `SessionFrame`/`UserFrame` discriminated union — web-local, untouched
|
|
|
|
## Key decisions
|
|
|
|
**F1 (ws-frames):** repoint all 8 server/coder importers + 2 web validators to the
|
|
package; drop the server `./ws-frames` re-export subpath — one path per contract, no
|
|
shim.
|
|
|
|
**F2 (web strict union):** the package exports the runtime schema only (`WsFrameSchema`,
|
|
`KNOWN_FRAME_TYPES`, loose `WsFrame`). The web's rich discriminated union stays
|
|
web-local — it references entity types (`Message`, `ToolCall`, etc.) that are
|
|
intentionally web-local and not cross-app duplicated. Zero entity-type scope expansion.
|
|
|
|
**AgentSessionConfig drift:** unified to the web required/nullable shape; the coder's
|
|
all-optional copy confirmed dead (zero live importers) and deleted.
|
|
|
|
**`apps/coder/web` (fallback SPA):** its hand-copied 9-arm `WsFrame` union replaced by
|
|
the canonical import; dead `pending_change_*` arms removed (no publisher exists for
|
|
these frames anywhere in the codebase — they were HTTP-delivered, not WS); field
|
|
conflicts reconciled per-field (`tool_result.error` boolean→string, `tokens_used`
|
|
number→number|null, `snapshot.messages` cast).
|
|
|
|
**Two `ErrorReason` concepts (intentional, not duplication):**
|
|
`message-metadata`'s `ErrorReason` is the DB-persisted 3-value set; the ws-frames
|
|
frame-level `reason` is the wire 5-value set. Different value sets, different
|
|
semantics, confirmed during audit.
|
|
|
|
## Build-order inversion
|
|
|
|
Contracts builds before all consumers in:
|
|
- Root `package.json` build script
|
|
- `Dockerfile` — new `COPY packages/*` block + contracts build step before web/server
|
|
- Coder deploy command — updated in all 7 doc sites (CLAUDE.md, apps/coder/CLAUDE.md,
|
|
BOOCODER.md, docs/ARCHITECTURE.md, docs/project-discovery.md, README.md,
|
|
docs/coder-backends.md)
|
|
|
|
## Test counts at ship
|
|
|
|
Server 543 / coder 293 / contracts 11. Clean `docker compose build --no-cache boocode`
|
|
green. Human smoke verified 2026-06-02.
|