docs: archive shipped openspec changes, refresh roadmap + DEFERRED-WORK
Move openspec/changes/{contracts-ssot,orchestrator} → archived/ (both shipped,
v2.7.13 and v2.7.17). Mark the roadmap's "Write/edit robustness" and "Claude
provider SDK" milestones as shipped (fuzzy-match.ts + checkpoints.ts; the
claude-sdk backend is live via CLAUDE_SDK_BACKEND in .env.host) and add a
v2.7.12–v2.7.17 shipped summary. Flag DEFERRED-WORK.md as superseded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
openspec/changes/archived/contracts-ssot/proposal.md
Normal file
102
openspec/changes/archived/contracts-ssot/proposal.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# @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.
|
||||
109
openspec/changes/archived/contracts-ssot/tasks.md
Normal file
109
openspec/changes/archived/contracts-ssot/tasks.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Tasks — @boocode/contracts SSOT
|
||||
|
||||
Nine phases, each independently verifiable. Phases 1–7 are the migration units (one
|
||||
contract group each after a proven tracer); Phase 8 is the audit gate; Phase 9 is the
|
||||
human smoke test. All shipped 2026-06-02.
|
||||
|
||||
## Phase 1 — Tracer: scaffold package + build-order inversion + web proof ✅ SHIPPED
|
||||
|
||||
- [x] Create `packages/contracts` (`@boocode/contracts`): `declaration:true`, per-subpath
|
||||
exports map, zod `^3.23.8`. Placeholder `src/index.ts`.
|
||||
- [x] Add `packages/*` to `pnpm-workspace.yaml`.
|
||||
- [x] Invert build order everywhere: root `package.json` build script, `Dockerfile`
|
||||
(`COPY packages/*` + contracts build before web/server), coder deploy command.
|
||||
- [x] Regenerate `pnpm-lock.yaml` (Dockerfile uses `--frozen-lockfile`).
|
||||
- [x] Prove the web consumption path end-to-end: `tsc -b` (composite+Bundler), `vite dev`
|
||||
HMR, and `vite build` all resolve the built `.d.ts`+`.js` via the exports map.
|
||||
- [x] Verify all consumer builds + `docker compose build boocode` green.
|
||||
- [x] Remove Phase 1 probe artifacts before Phase 2.
|
||||
|
||||
## Phase 2a — Single-source the ws-frames runtime schema ✅ SHIPPED
|
||||
|
||||
- [x] Move ws-frames schema to `packages/contracts/src/ws-frames.ts` (`./ws-frames` subpath).
|
||||
- [x] Repoint 8 server/coder importers + 2 web validators to `@boocode/contracts/ws-frames`.
|
||||
- [x] Delete `apps/server/src/types/ws-frames.ts`; drop server `./ws-frames` exports subpath.
|
||||
- [x] Delete `apps/web/src/api/ws-frames.ts`.
|
||||
- [x] Move KNOWN_FRAME_TYPES drift + accept/reject tests into the package (11/11); delete
|
||||
byte-parity test; keep broker fail-closed tests in server importing from the package.
|
||||
- [x] Container smoke: `docker compose up`, `/api/health` 200, broker imports from package.
|
||||
|
||||
## Phase 3 — Single-source provider snapshot types ✅ SHIPPED
|
||||
|
||||
- [x] Move `ProviderSnapshotEntry`, `ProviderModel`, `ProviderMode`, `ThinkingOption`,
|
||||
`AgentCommand`, `ProviderSnapshotStatus` to `packages/contracts/src/provider-snapshot.ts`
|
||||
(`./provider-snapshot` subpath).
|
||||
- [x] `apps/coder/src/services/provider-types.ts` re-exports them (importers unchanged).
|
||||
- [x] Delete web mirror block; delete `provider-types-parity.test.ts` (coder −6 tests).
|
||||
- [x] All builds and typechecks green; server 543 / coder 293 unchanged.
|
||||
|
||||
## Phase 4 — Single-source the Zod provider-config schemas ✅ SHIPPED
|
||||
|
||||
- [x] Move `ProviderOverrideSchema`, `CoderProvidersFileSchema`, `ProviderConfigPatchSchema`
|
||||
+ `z.infer` types to `packages/contracts/src/provider-config.ts` (`./provider-config`
|
||||
subpath).
|
||||
- [x] `apps/coder/src/services/provider-config.ts` imports + re-exports (importers unchanged).
|
||||
- [x] Delete 17-line web hand-mirror.
|
||||
- [x] Coder provider-config tests 13/13; all builds green.
|
||||
|
||||
## Phase 5 — Single-source type-only contracts (MessageMetadata + AgentSessionConfig) ✅ SHIPPED
|
||||
|
||||
- [x] Move `MessageMetadata`, `ErrorReason`, `AgentSessionConfig` to
|
||||
`packages/contracts/src/message-metadata.ts` (`./message-metadata` subpath).
|
||||
- [x] Unify `AgentSessionConfig` to the web required/nullable shape; delete the coder's
|
||||
dead all-optional copy (zero live importers confirmed).
|
||||
- [x] Delete all duplicate `MessageMetadata` + `ErrorReason` copies (confirmed byte-identical).
|
||||
- [x] Repoint server `api.ts`, web types, and `MessageBubble.tsx` to the package.
|
||||
- [x] Server 543 / coder 293 unchanged; all builds green.
|
||||
|
||||
## Phase 6 — Single-source WorktreeRiskReport ✅ SHIPPED
|
||||
|
||||
- [x] Move `WorktreeRiskReport` to `packages/contracts/src/worktree-risk.ts`
|
||||
(`./worktree-risk` subpath); unify name (coder `RiskReport` → `WorktreeRiskReport`).
|
||||
- [x] Delete all three copies (shapes identical, names differed).
|
||||
- [x] Repoint `sessions.ts`, `ProjectSidebar.tsx`, `orphan-worktree-reaper.ts`,
|
||||
`worktree-safety.ts` via re-exports; `checkWorktreeWorkAtRisk` returns shared type.
|
||||
- [x] Server 543 / coder 293 unchanged; all builds green.
|
||||
|
||||
## Phase 7 — Migrate apps/coder/web (fallback SPA) ✅ SHIPPED
|
||||
|
||||
- [x] Add `@boocode/contracts` `workspace:*` dep to `apps/coder/web/package.json`.
|
||||
- [x] Delete hand-copied 9-arm `WsFrame` union; import canonical `WsFrame` from the package.
|
||||
- [x] Reconcile field conflicts: `tool_result.error` boolean→string; `tokens_used`
|
||||
number→number|null; `snapshot.messages` cast `as Message[]`; `chat_id ?? ''`.
|
||||
- [x] Delete dead `pending_change_added`/`pending_change_updated` reducer arms + the entire
|
||||
dead `onPendingChange` WS plumbing (`DiffPane` prop, `Session.tsx` listener).
|
||||
- [x] Confirm HTTP pending-change apply/reject path untouched.
|
||||
- [x] `apps/coder/web` Vite build green; root build + server 543 / coder 293 green.
|
||||
|
||||
## Phase 8 — Audit every requirement bullet ✅ PASSED (12/12)
|
||||
|
||||
- [x] Verify one definition per contract (file + line evidence for each).
|
||||
- [x] Verify `z.infer` for both Zod contracts (ws-frames, provider-config).
|
||||
- [x] Verify all four consumers wired via exports map (no project refs, no src imports).
|
||||
- [x] Verify all hand-copies + parity tests deleted; drift/broker tests preserved.
|
||||
- [x] Verify single zod version; build-order inverted in root + Dockerfile + deploy docs.
|
||||
- [x] Verify `packages/*` in workspace; dead `pending_change_*` arms gone.
|
||||
- [x] Verify web strict union preserved + coherent post-migration.
|
||||
- [x] Close gap G: update coder deploy command 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).
|
||||
- [x] Correct now-false byte-parity/duplication claims in CLAUDE.md conventions,
|
||||
apps/server/CLAUDE.md broker note, docs/coder-backends.md, and
|
||||
docs/coding-standards/cross-app-contract-parity.md (rewritten to describe the SSOT).
|
||||
- [x] Mark DEFERRED-WORK §3 + STALE-DEPRECATED item as shipped.
|
||||
- [x] Clean `docker compose build --no-cache boocode` green; server 543 / coder 293 /
|
||||
contracts 11 at exact baselines; nothing staged (HEAD e5ce01a).
|
||||
|
||||
## Phase 9 — Human smoke test ✅ PASSED (Sam, 2026-06-02)
|
||||
|
||||
- [x] Web dev HMR, web prod build at :9500, live WS stream rendering.
|
||||
- [x] Coder restart + a turn; fallback SPA; pending-change apply.
|
||||
|
||||
## Verify (all runs green)
|
||||
|
||||
- `pnpm -C packages/contracts build`
|
||||
- `pnpm -C apps/server test` (543)
|
||||
- `pnpm -C apps/coder test` (293)
|
||||
- `pnpm -C apps/server build && pnpm -C apps/coder build`
|
||||
- `npx tsc -p apps/web/tsconfig.app.json --noEmit`
|
||||
- `docker compose build --no-cache boocode`
|
||||
Reference in New Issue
Block a user