feat: single-source cross-app wire contracts in @boocode/contracts (v2.7.13)

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>
This commit is contained in:
2026-06-02 21:00:00 +00:00
parent 2a05d2f9fe
commit 649ce71eff
55 changed files with 804 additions and 1108 deletions

View File

@@ -1,7 +1,8 @@
---
paths:
- "apps/server/src/types/ws-frames.ts"
- "apps/web/src/api/ws-frames.ts"
- "packages/contracts/src/ws-frames.ts"
- "packages/contracts/src/provider-snapshot.ts"
- "packages/contracts/src/message-metadata.ts"
- "apps/server/src/types/api.ts"
- "apps/web/src/api/types.ts"
- "apps/coder/src/services/provider-types.ts"
@@ -11,7 +12,7 @@ paths:
# Cross-App Contract Parity
*Reach for this when a parity test goes red (`ws-frames.test.ts`, `provider-types-parity.test.ts`), a reviewer flags a "half-synced" type, or a frame/sentinel "does nothing" at runtime — i.e. one copy of a duplicated cross-app contract drifted from the other. The fix-it path is [When to Apply](#when-to-apply) + its Verification step.*
*Reach for this when adding a new WS frame type, a new sentinel `kind`, or touching the server's `InferenceFrame` loose union or the web's strict `WsFrame` union — contracts whose primary definition now lives in `@boocode/contracts` but that still have split secondary representations in the apps. The fix-it path is [When to Apply](#when-to-apply) + its Verification step.*
- **Status:** proposed
- **Date Created:** 2026-06-02 00:00
@@ -20,19 +21,19 @@ paths:
- indifferentketchup (samkintop@gmail.com)
- **Reviewers:**
- **Applies To:**
- Every hand-synced type/schema contract that crosses the `apps/server``apps/web``apps/coder` boundary in the files under `paths:`. The primary examples are the WS-frame Zod schema, the provider-snapshot types, and the sentinel `MessageMetadata` union plus its `MessageBubble` render arm — but the same rule governs the other duplicated pairs in these files (`WorktreeRiskReport`, the provider-config wire types, and the interface-typed `WsFrame` union that mirrors the Zod schema).
- The remaining split contracts that cross app boundaries: the server's `InferenceFrame` loose publish union (`services/inference/turn.ts`), the web's strict `WsFrame` discriminated union (`apps/web/src/api/types.ts`), and the `MessageBubble` sentinel render arm (`apps/web/src/components/MessageBubble.tsx`). The primary contracts (WS-frame Zod schema, provider-snapshot types, `MessageMetadata`, `WorktreeRiskReport`, provider-config schemas) are now **single-sourced in `@boocode/contracts`** — those are governed by editing the package, not by this sync-both-copies rule.
## Introduction
Several wire contracts in BooCode exist as **two or three hand-synced copies** in different apps, because the apps have separate `tsconfig`s with no shared path alias and a composite-project restriction (TS6307) that structurally blocks importing one app's types from another. There is no shared workspace package for these types yet. This standard governs what you must do when you touch one of those copies: **change every copy in the same commit** — and, where the contract has no compile-time consumer guarantee (the sentinel render arm), the consumer too.
The primary wire contracts (WS-frame Zod schema, provider-snapshot types, provider-config schemas, `MessageMetadata`, `WorktreeRiskReport`, `AgentSessionConfig`) are now **single-sourced in `@boocode/contracts`** — edit `packages/contracts/src/<subpath>.ts` and rebuild the package. There is no second copy to sync for those contracts.
The three families in [Coding Standard](#coding-standard) are the primary examples, but the rule applies to **every** hand-synced pair in the files under `paths:`, each of which carries its own in-code `edit both copies` / `Mirror of …` / `KEEP IN SYNC` marker. Beyond the three: `WorktreeRiskReport` (`apps/server/src/types/api.ts``apps/web/src/api/types.ts`), the provider-config wire types (`ProviderOverride` / `CoderProvidersFile`, web mirror of the coder's Zod-inferred shapes), and — note this one — a **second** representation of the WS wire shape: the interface-typed `WsFrame` union in `apps/web/src/api/types.ts` plus the `*Frame` interfaces in `apps/server/src/types/api.ts`, which is distinct from the byte-identical Zod `ws-frames.ts` pair and is **not** covered by the byte-parity test. A WS frame's shape therefore lives in more than one place; treat all of them as one contract.
What this standard still governs: adding a new WS frame type or a new sentinel `kind` touches contracts whose primary definition is in the package but that also have split secondary representations in the apps. The server's `InferenceFrame` loose publish union (`services/inference/turn.ts`) and the web's strict `WsFrame` discriminated union (`apps/web/src/api/types.ts`) both still exist separately from the canonical `WsFrameSchema` in the package, and must be updated together with it. The sentinel `MessageBubble` render arm (`apps/web/src/components/MessageBubble.tsx`) has no compile-time guard and still needs updating when a new `MessageMetadata` kind is added to the package.
### Purpose
- **Primary:** prevent *silent runtime* contract breakage. Nothing at compile time links the copies — each app type-checks against its own copy, so `tsc` stays green when they drift. The failure surfaces only at runtime, and silently: a WS frame whose `type` exists on one side but not the other is **dropped at JSON-parse** with no error; a sentinel `kind` added without a render arm shows nothing. Editing every copy in lockstep is the only thing that keeps the contract whole.
- **Secondary:** two of the three contracts have runtime parity tests (`ws-frames.test.ts`, `provider-types-parity.test.ts`) that catch drift in the test run — but they are a backstop, not the mechanism, and the sentinel triple has no test at all.
- **Side effect:** keeping the copies byte- or text-identical makes a contract change reviewable as a matched diff across files.
- **Secondary:** the `@boocode/contracts` package's `ws-frames.test.ts` tests schema correctness (accept/reject behavior) and the `KNOWN_FRAME_TYPES` drift probe. `provider-types-parity.test.ts` was deleted when the provider-snapshot types moved to the package. The sentinel render arm still has no automated test.
- **Side effect:** for the remaining split representations (server `InferenceFrame`, web `WsFrame`), updating them together with the package source in a single commit makes the change reviewable as a matched diff across files.
### Scope
@@ -43,33 +44,33 @@ The specific duplicated contracts listed in `paths:` above, inside the `apps/ser
Walk this before editing a type, schema, enum, or metadata union:
1. **Does this shape exist as a copy in another app?** — Check: `grep -rn "<TypeOrFieldName>" apps/*/src`. If it appears under two or more of `apps/server`, `apps/web`, `apps/coder` → continue. If it lives in exactly one app → see "When NOT to Apply".
2. **Are you changing its wire shape?** — adding, removing, renaming, or re-typing a field; adding/removing a frame `type`; adding an enum value or a sentinel `kind`. If yes → apply this standard: edit **every** copy, plus every consumer that switches on the shape, in the **same commit**. If no (a comment or formatting change that the contract's parity test normalizes away) → see "When NOT to Apply".
2. **Are you changing its wire shape?** — adding, removing, renaming, or re-typing a field; adding/removing a frame `type`; adding an enum value or a sentinel `kind`. If yes → apply this standard: update the `@boocode/contracts` package source, rebuild, and also update every secondary app-side representation (the server `InferenceFrame` loose union, the web `WsFrame` strict union, and the `MessageBubble` render arm for sentinel kinds), in the **same commit**. If no (a comment or formatting change) → see "When NOT to Apply".
**Exception — the sentinel/consumer triple:** `MessageMetadata` (`apps/server/src/types/api.ts``apps/web/src/api/types.ts`) has **no parity test**, and a new `kind` is inert until it gets a render branch in `apps/web/src/components/MessageBubble.tsx`. When the shape you are editing is `MessageMetadata`, "every copy" includes that render arm — there is no test to remind you.
**Exception — the sentinel render arm:** `MessageMetadata` is single-sourced in `@boocode/contracts/message-metadata` (one definition, no second copy). However, a new `kind` is inert until it gets a render branch in `apps/web/src/components/MessageBubble.tsx`. There is no test to catch a missing render arm — when adding a `kind`, the render branch in `MessageBubble.tsx` is the one consumer you must update manually.
**Verification step:** run the guards that exist *now*, before you commit:
```bash
# The trailing arg is a FILE-PATH substring filter for `vitest run` (not a test
# name). A typo matches zero files and still exits 0 — a false green — so confirm
# the run actually executed the file (look for "1 passed" on the named file).
pnpm -C apps/server test ws-frames.test # WS-frame byte-parity + KNOWN_FRAME_TYPES drift
pnpm -C apps/coder test provider-types-parity # provider-snapshot text-parity (incl. nested blocks)
# Sentinel triple has no test — grep all copies for a NEW rendering kind:
grep -rn "<new-kind>" apps/server/src/types/api.ts apps/web/src/api/types.ts apps/web/src/components/MessageBubble.tsx
# Build the contracts package first, then run its tests.
pnpm -C packages/contracts build
pnpm -C packages/contracts test ws-frames.test # WS-frame schema correctness + KNOWN_FRAME_TYPES drift
# provider-types-parity.test.ts was deleted — provider snapshot types are in @boocode/contracts
# Sentinel render arm has no test — grep for the new kind in the package definition and MessageBubble:
grep -rn "<new-kind>" packages/contracts/src/message-metadata.ts apps/web/src/components/MessageBubble.tsx
```
For a **rendering** sentinel kind (`cap_hit` / `doom_loop` / `mistake_recovery`) the new `kind` must appear in all three files. The non-rendering `error` arm of `MessageMetadata` lives in the two type copies only — it has no `MessageBubble` branch — so for it the grep should match the two `api.ts`/`types.ts` copies, not `MessageBubble.tsx`.
For a **rendering** sentinel kind (`cap_hit` / `doom_loop` / `mistake_recovery`) the new `kind` must appear in `packages/contracts/src/message-metadata.ts` and have a render branch in `MessageBubble.tsx`. The non-rendering `error` arm has no `MessageBubble` branch — for it, only the package definition needs updating.
## When NOT to Apply
- **The type lives in a single app.** Internal server types, web-only view models, coder-only helpers — there is no second copy, so there is nothing to sync. Edit the one definition directly; do **not** manufacture a duplicate in another app "for symmetry." A new cross-app contract should prefer the eventual shared package or, at minimum, ship with its own parity test — not a third hand-synced copy.
- **A comment- or whitespace-only edit to a *text-parity* file.** `provider-types-parity.test.ts` strips comments and blank lines before comparing, so a comment-only change to one provider-types copy is tolerated and you needn't chase the other. (This relief does **not** apply to `ws-frames.ts`, which is compared **byte-for-byte** — every character, including comments, must match.)
- **The shared workspace package lands.** This standard exists *only* because the single source of truth was deferred (a Tier-2 follow-up noted in `provider-types-parity.test.ts`). Once these types move into one shared package, delete the hand-syncing rule rather than keep paying it — the SSOT supersedes this standard.
- **A comment- or whitespace-only edit to a `@boocode/contracts` source file.** The package's `ws-frames.test.ts` tests behavior and structure, not formatting, so a comment-only change does not require secondary app-side updates.
- **The type lives in `@boocode/contracts` and has no split secondary representations.** For the migrated contracts (WS-frame Zod schema, provider-snapshot, provider-config, `MessageMetadata`, `WorktreeRiskReport`) the package IS the single source of truth. Edit the package source; there is nothing to sync in the apps. The split representations that still require multi-file changes are the server `InferenceFrame` loose union, the web `WsFrame` strict union, and the `MessageBubble` render arm — all documented in [When to Apply](#when-to-apply).
## Background
The duplication is deliberate, not accidental. A compile-time bidirectional-assignability check was attempted first — a web-side file importing the coder's import-free `provider-types.ts` — but `apps/web/tsconfig.app.json` is a composite project and rejects out-of-include files with **TS6307**, so cross-project type import is structurally blocked. The team chose hand-synced copies guarded by runtime tests over a premature shared package. The WS-frame copies go further and are kept **byte-identical** so a single `readFileSync` equality test can guard them; the provider-snapshot copies are kept **text-identical per named type block** (comments normalized away) because they sit among unrelated types. The cost of this choice is exactly what this standard manages: a copy can drift, and because each app compiles independently, only a runtime test — or a runtime bug — reveals it.
*The history below explains why the duplication existed, not the current state.* The primary contracts (WS-frame Zod schema, provider-snapshot types, provider-config schemas, `MessageMetadata`, `WorktreeRiskReport`) were formerly hand-synced copies guarded by runtime parity tests — because `apps/web/tsconfig.app.json` is a composite project and rejects out-of-include files with **TS6307**, blocking cross-project type import. The WS-frame copies were kept **byte-identical** so a single `readFileSync` equality test could guard them; provider-snapshot copies were kept **text-identical per named block**. These contracts have since moved to `@boocode/contracts` (the `packages/contracts` workspace package), the hand-sync discipline and the byte-parity test are retired, and drift is prevented by there being exactly one definition. The split representations that remain (server `InferenceFrame` loose union, web `WsFrame` strict union, `MessageBubble` render arm) still require lockstep edits when frame types or sentinel kinds are added — that is what this standard now governs.
## Coding Standard
@@ -77,41 +78,35 @@ The duplication is deliberate, not accidental. A compile-time bidirectional-assi
When you change one copy of a duplicated contract, change the others in the same commit. Each contract family has its own home files and its own (or no) guard.
**WS frame schema — `apps/server/src/types/ws-frames.ts` ↔ `apps/web/src/api/ws-frames.ts` (byte-identical):**
**WS frame schema — single-sourced at `packages/contracts/src/ws-frames.ts` (imported as `@boocode/contracts/ws-frames`):**
```typescript
// PRIMARY: no compile-time link exists across apps (separate tsconfigs, TS6307
// blocks cross-import). A frame type added to one copy but not the other breaks
// silently at runtime — the frontend drops the frame at JSON-parse. So this file
// and apps/web/src/api/ws-frames.ts MUST stay byte-identical, in the same commit.
//
// IMPORTANT: This file is duplicated byte-identical at
// apps/web/src/api/ws-frames.ts. ... If you change one, change the other.
//
// Adding a frame also means adding its `type` to KNOWN_FRAME_TYPES (a drift test
// probes every entry for a discriminated branch).
// Single source of truth: WsFrameSchema (Zod), WsFrame (z.infer), KNOWN_FRAME_TYPES.
// No second copy to sync. When adding a frame type:
// 1. Add it here (the canonical schema definition) and rebuild the package.
// 2. Also add to the server's InferenceFrame loose union (services/inference/turn.ts).
// 3. Also add to the web's strict WsFrame discriminated union (apps/web/src/api/types.ts).
// Adding a frame also means adding its `type` to KNOWN_FRAME_TYPES — the package
// test probes every entry for a discriminated branch.
```
**Provider snapshot types — `apps/coder/src/services/provider-types.ts` ↔ `apps/web/src/api/types.ts`, text-identical per block.** By convention you author on the coder side and mirror to web (the in-code `KEEP IN SYNC` markers point that way), but the parity test is **symmetric** — it fails on drift in *either* file and names no authoritative copy, so "fix the red test" means re-sync the two, not edit one in particular:
**Provider snapshot types — single-sourced at `packages/contracts/src/provider-snapshot.ts` (imported as `@boocode/contracts/provider-snapshot`):**
```typescript
// PRIMARY: nothing links these two copies at compile time — a field added here
// but not in apps/web/src/api/types.ts breaks silently at runtime (the web side
// drops or mis-reads the snapshot). The in-file marker, with its test backstop:
// KEEP IN SYNC with apps/web/src/api/types.ts ProviderSnapshotEntry — parity
// is enforced by __tests__/provider-types-parity.test.ts (fails on field drift).
// Applies to the nested ProviderModel / ProviderMode / ThinkingOption /
// AgentCommand / ProviderSnapshotStatus blocks the entry references, too.
// Single source of truth for ProviderSnapshotEntry, ProviderModel, ProviderMode,
// ThinkingOption, AgentCommand, ProviderSnapshotStatus. No second copy to sync.
// apps/coder/src/services/provider-types.ts re-exports from this package.
// provider-types-parity.test.ts was deleted — drift is prevented by the single definition.
export interface ProviderSnapshotEntry { /* ...fields... */ }
```
**Sentinel metadata — `apps/server/src/types/api.ts` ↔ `apps/web/src/api/types.ts`, plus the render arm in `apps/web/src/components/MessageBubble.tsx` (no parity test):**
**Sentinel metadata — `packages/contracts/src/message-metadata.ts` (single source) plus the render arm in `apps/web/src/components/MessageBubble.tsx` (no automated test):**
```typescript
// A new *rendering* sentinel kind is a THREE-file change with NO test to catch a miss:
// 1. apps/server/src/types/api.ts — add the arm to MessageMetadata
// 2. apps/web/src/api/types.ts — add the identical arm
// 3. MessageBubble.tsx — add the render branch, else it shows nothing
// MessageMetadata is single-sourced in @boocode/contracts/message-metadata.
// A new *rendering* sentinel kind is a TWO-step change with NO test to catch a miss:
// 1. packages/contracts/src/message-metadata.ts — add the arm to MessageMetadata, rebuild
// 2. MessageBubble.tsx — add the render branch, else it shows nothing
// The real union has FOUR arms; show it whole so nobody reads two as the full set:
export type MessageMetadata =
| { kind: 'cap_hit'; /* used, limit, agent_name, can_continue */ }
@@ -129,37 +124,37 @@ export type MessageMetadata =
**What to avoid:**
```typescript
// ANTI-PATTERN: editing one copy only.
// Add a new frame type to apps/web/src/api/ws-frames.ts but not the server copy
// (or vice versa): tsc stays green — they're separate projects — but the parity
// test fails, and had it not existed, the server would publish a frame the
// frontend silently discards at JSON-parse. A half-edited contract is invisible
// to the type-checker; never land one.
// ANTI-PATTERN: editing only the package schema without updating the app-side representations.
// Add a new frame type to packages/contracts/src/ws-frames.ts but not to the
// web's strict WsFrame union (apps/web/src/api/types.ts): tsc stays green because
// they're separate projects, but the frontend silently discards the frame at
// JSON-parse. A half-edited contract is invisible to the type-checker; never land one.
```
**Project references:**
- `apps/server/src/types/ws-frames.ts` — the byte-identical sync comment (top of file) and `KNOWN_FRAME_TYPES`.
- `apps/web/src/api/ws-frames.ts` the web copy that must match it byte-for-byte.
- `apps/coder/src/services/provider-types.ts` — the `KEEP IN SYNC` comment above `ProviderSnapshotEntry`.
- `apps/web/src/api/types.ts` — the provider-snapshot wire copy and the `MessageMetadata` copy.
- `packages/contracts/src/ws-frames.ts``WsFrameSchema`, `WsFrame`, `KNOWN_FRAME_TYPES` (via `@boocode/contracts/ws-frames`). The two former app-level `ws-frames.ts` copies are deleted.
- `packages/contracts/src/provider-snapshot.ts` — provider snapshot types (via `@boocode/contracts/provider-snapshot`). The former web mirror block in `apps/web/src/api/types.ts` and the former coder copy in `provider-types.ts` are deleted; `provider-types.ts` now re-exports from the package.
- `packages/contracts/src/message-metadata.ts``MessageMetadata`, `ErrorReason` (via `@boocode/contracts/message-metadata`). The former copies in `apps/server/src/types/api.ts` and `apps/web/src/api/types.ts` are deleted.
- `apps/web/src/api/types.ts` — the web-local strict `WsFrame` discriminated union (still maintained separately from the canonical schema in the package).
- `apps/web/src/components/MessageBubble.tsx` — the sentinel render arms (`metadata?.kind` branches).
### A wire-shape change passes through the gate, then a consumer
A frame is published by the server's permissive `InferenceFrame` union (`apps/server/src/services/inference/turn.ts`) but only reaches the UI if the strict schema/union accepts it — permissive publish, strict receive. Keep the **type/schema copies** (this standard's scope) in lockstep so the frame survives validation; then make sure something consumes it.
> **Where consumer-wiring fits.** This standard governs the duplicated *type/schema* copies and the one consumer with no compile-time guard — the sentinel `MessageBubble` render arm. A new WS frame additionally needs a runtime handler to *do* anything: `applyFrame` in `apps/web/src/hooks/useSessionStream.ts` (per-session frames) and `useUserEvents` (user-channel frames), plus the sidebar reducer. That wiring — and the event-dedup discipline around it — is governed by `apps/web/CLAUDE.md`, not by this parity standard. A frame that passes the byte-parity test but has no reducer `case` validates and is then silently ignored.
> **Where consumer-wiring fits.** This standard governs the duplicated *type/schema* copies and the one consumer with no compile-time guard — the sentinel `MessageBubble` render arm. A new WS frame additionally needs a runtime handler to *do* anything: `applyFrame` in `apps/web/src/hooks/useSessionStream.ts` (per-session frames) and `useUserEvents` (user-channel frames), plus the sidebar reducer. That wiring — and the event-dedup discipline around it — is governed by `apps/web/CLAUDE.md`, not by this parity standard. A frame that passes `WsFrameSchema` validation but has no reducer `case` is then silently ignored.
**Correct usage:**
```typescript
// Adding a WS frame type, all in one commit:
// - packages/contracts/src/ws-frames.ts — WsFrameSchema + WsFrame + KNOWN_FRAME_TYPES (rebuild package)
// - apps/server/src/services/inference/turn.ts — loose InferenceFrame publish union (+ optional fields)
// - apps/server/src/types/ws-frames.ts — strict WsFrameSchema + WsFrame + KNOWN_FRAME_TYPES
// - apps/web/src/api/ws-frames.ts — byte-identical copy of the strict gate
// The strict web-side type is the wire-format gate: a frame whose type isn't in
// - apps/web/src/api/types.ts — strict WsFrame discriminated union (the web gate)
// The web strict WsFrame is the wire-format gate: a frame whose type isn't in
// it is dropped at JSON-parse. The loose publish union and the strict gate are
// BOTH required — permissive publish, strict receive.
// BOTH required — permissive publish, strict receive. The canonical schema in the
// package is validated by the broker fail-closed (Zod) on every publish.
```
**What to avoid:**
@@ -172,25 +167,26 @@ A frame is published by the server's permissive `InferenceFrame` union (`apps/se
```
**Project references:**
- `packages/contracts/src/ws-frames.ts``WsFrameSchema` (the broker's fail-closed validation gate) + `WsFrame` + `KNOWN_FRAME_TYPES`.
- `apps/server/src/services/inference/turn.ts` — the loose `InferenceFrame` publish union.
- `apps/server/src/types/ws-frames.ts``WsFrameSchema` (the broker's fail-closed validation gate) + `KNOWN_FRAME_TYPES`.
- `apps/web/src/api/types.ts` — the web-local strict `WsFrame` discriminated union.
- `apps/web/src/components/MessageBubble.tsx` — the consumer for sentinel `MessageMetadata` kinds.
### Sync the copies; never weaken the parity test
### Keep the package tests; never weaken them
When a parity test fails, the fix is to make the copies match — not to make the test stop checking. The corollary also holds: when you add a **new** nested type that `ProviderSnapshotEntry` references, add its name to the `names` array in `provider-types-parity.test.ts`, or the new type is hand-synced but **unguarded**.
When a package test fails, the fix is to update the package source — not to delete the assertion or skip the test. When adding a **new** nested type to `ProviderSnapshotEntry`, add it to `packages/contracts/src/provider-snapshot.ts` — there is no `names` array to update since `provider-types-parity.test.ts` was deleted; the single definition is the guard.
**What to avoid:**
```typescript
// ANTI-PATTERN: a red parity test "fixed" by deleting the assertion, skipping
// the it(), or trimming a type out of the compared `names` list. That converts a
// caught drift into a shipped, silent contract break. Re-sync the copies instead.
// ANTI-PATTERN: "fixing" a red package test by deleting the assertion, skipping
// the it(), or removing a frame type from the tested set. That converts caught
// drift into a shipped, silent contract break. Fix the package source instead.
```
**Project references:**
- `apps/server/src/services/__tests__/ws-frames.test.ts``ws-frames.ts file mirror parity` (byte-identical) and the `KNOWN_FRAME_TYPES` drift probe.
- `apps/coder/src/services/__tests__/provider-types-parity.test.ts`text-identity of each shared block across the coder ↔ web copies.
- `packages/contracts/src/__tests__/ws-frames.test.ts` schema correctness (accept/reject) and the `KNOWN_FRAME_TYPES` drift probe. (The former server-side `ws-frames.ts file mirror parity` byte-identical test was deleted when the schema moved to the package.)
- `apps/coder/src/services/__tests__/provider-types-parity.test.ts`**deleted**: provider snapshot types moved to `@boocode/contracts/provider-snapshot`.
## Additional Resources
@@ -198,8 +194,8 @@ When a parity test fails, the fix is to make the copies match — not to make th
- [BooCoder Dispatch Backends](../coder-backends.md) — the provider-snapshot contract and the WS-frame mapping in their runtime context (see "Core Types" and the parity notes).
- [Architecture overview](../ARCHITECTURE.md) — the three surfaces and the shared database the contracts cross.
- Root `CLAUDE.md` → "Conventions" — the cross-app contract rules (WS frame, sentinels, provider-type parity, JSONB) this standard formalizes.
- `apps/server/CLAUDE.md` (`services/broker.ts`) and `apps/coder/CLAUDE.md` — per-app notes on the broker validation and the provider-type mirror.
- Root `CLAUDE.md` → "Conventions" — the cross-app contract rules (WS frame schema, sentinels, provider snapshot types, JSONB) this standard formalizes; updated to reflect `@boocode/contracts` SSOT.
- `apps/server/CLAUDE.md` (`services/broker.ts`) — broker validation against the `@boocode/contracts` schema. `apps/coder/CLAUDE.md``provider-types.ts` re-exports from the package.
### External Resources