# Shared contracts package — one wire-type and vocabulary source for collector, controller, web

status: PROPOSED
task IDs: (none assigned)
source request: 2026-08-13 owner: "create a full spec and plan for all of your recommendations, do not continue to execution yet" — deliverable is documents only; launch is explicit owner-gated.

## Goal

Land `@overdeck/incident-contract` (pattern: packages/activity-contract): the incident wire types (sourced from the collector producer shapes), the dispatch state vocabulary, dispatch error codes, offload verbs, and the token helper. Collapse the three hand-maintained mirrors. Owner decision: docs only for now — plan registered PROPOSED, not launched.

## Source design

Normative: docs/specs/2026-08-13-arch-shared-contracts-design.md (D1-D10, §3, §4, §5, §6, §8). This plan adds execution order and file pins only; it MUST NOT contradict the spec.

## Context

- Collector's producer types are canonical (incident-service.ts: `IncidentDispatch` :34, `ListIncidentsQuery` :80, `ListIncidentsResult` :96, `FileIncidentRequest` :102 incl. `wrapperModel?: string` :114). The web mirror (apps/web/src/lib/incident-types.ts, 113 lines, "Mirrored rather than imported" :2) has already drifted: missing `wrapperModel`, non-canonical names (`ListIncidentsParams`, `IncidentsResponse`). The contract is sourced from the PRODUCER, and the web mirror is corrected.
- Precedent package: packages/activity-contract (`@overdeck/activity-contract`, private, type module, `exports: { ".": "./src/index.ts" }`); collector dep `file:../packages/activity-contract`, web dep `workspace:*`; pnpm-workspace.yaml includes `packages/*`.
- Seams (verified at origin/main): status-writer.ts:6-14 state unions; incident-service.ts:211 `DISPATCH_STATE_BY_METADATA` (incl. "starting", written at :722/:725/:1008); server.ts incident error literals at :236/:243/:246/:253/:802 (+ `unauthorized` :216, `harness-unreachable` :231); actions.ts `OFFLOAD_ACTION_VERBS` :53-62; controller/store.ts:75-84 `TransitionVerb`; controller transitions.ts:28 `TRANSITION_VERBS` + :39 `VerbSchema`; token.ts identical logic in collector and controller.
- apps/web/src/lib/collector-types.ts is a SEPARATE protocol mirror (schema.ts + state.ts, not incidents) — NOT absorbed by this plan.

## Files

- NEW packages/incident-contract/{package.json, tsconfig.json, src/index.ts, src/incidents.ts, src/vocabulary.ts, src/errors.ts, src/verbs.ts, src/token.ts}
- collector/package.json (add `@overdeck/incident-contract: file:../packages/incident-contract`)
- collector/src/incidents/provider.ts (retype `IncidentsProvider` D9)
- collector/src/incidents/status-writer.ts (unions import, no `export type`)
- collector/src/incidents/incident-service.ts (typed metadata map D4)
- collector/src/actions.ts (OFFLOAD_ACTION_VERBS import)
- collector/src/server.ts (DISPATCH_ERROR_CODES constants)
- collector/src/token.ts (re-export contract helper)
- controller/package.json, controller/src/store.ts (verb extension D6), controller/src/token.ts (helper)
- apps/web/package.json (`@overdeck/incident-contract: workspace:*`)
- apps/web/src/lib/incident-types.ts (re-export only)
- apps/web/src/lib/{collector-client.ts, collector-queries.ts, page-mappers.ts, overview-mappers.ts, panel-data.ts}, apps/web/src/components/incidents/* (import path updates)

## Contracts

- Wire shape: byte-identical; producer types unchanged (D3).
- Vocabulary: `DispatchMetadataState = IncidentStatusState | "starting"`; metadata map `Record<DispatchMetadataState, IncidentState>` (D4).
- Error codes: `DISPATCH_ERROR_CODES` values identical to today's literals (D5).
- Verbs: 8 canonical + controller extension `"delivery-feature-reconcile"` (D6).
- Token: `loadOrCreateToken(tokenPath)` — dir 0o700, file 0o600, 32-hex, `token file ${path} exists but is empty`, error class per process (D7).
- Interface: provider methods typed by contract types; service satisfies structurally; `IncidentsProviderLike` slice kept (D9).

## Behavior

Per spec §4. No user-visible behavior change; wire bytes and token file bytes identical.

## Out of scope

Kanboard/mutation-store/status internals; the already-landed candidates (C1, C2, C6, C14, C20); collector-types.ts protocol mirror; any wire-shape change; anything in docs/specs/2026-08-13-arch-module-deepenings-design.md.

## Acceptance criteria

Per spec §6, all of:

- `pnpm --filter @overdeck/incident-contract typecheck` green.
- collector: `bun test` + `tsc --noEmit` green (actions.test.ts, incidents/routes.test.ts).
- controller: `bun test` + `tsc --noEmit` green (store.test.ts, transitions.test.ts).
- web: `pnpm --filter web build` + `pnpm --filter web typecheck` green; sparkline tests green (collector-client/collector-queries/overview-mappers/panel-freshness).
- Six deletion tests (spec §6): incident-types.ts re-export only; OFFLOAD_ACTION_VERBS imported not declared; `Parameters<IncidentService` = 0 in collector; status-writer unions import-only; DISPATCH_ERROR_CODES present in server.ts; zero `randomBytes` in both token.ts.

## Preserved WIP

- Worktree /home/user/Projects/overdeck/.worktrees/arch-deepening (branch wt/arch-deepening at 7158544cf = origin/main). Main checkout is STALE — never use it as the implementation base.
- Review report: /home/user/.cache/agent-tmp/architecture-review-20260813-102418.html (baseline note: 5 candidates already landed at origin/main; do not re-specify).

## Constraints

- Delivery scope is documentation only until the owner lifts it: "do not continue to execution yet" (2026-08-13). MUST NOT launch.
- modules/harness/** and packaging/** untouched; the mandatory factory pytest suite is out of scope.
- UI edits under apps/web/src/** governed by .claude/skills/od-ui-dev/SKILL.md (deck-ui barrel, ui-primitives, tokens, gallery).
- Contract package MUST NOT depend on zod or on collector/controller/web code.

## Execution steps

1. (Owner gate) Owner approves → plan flips ACTIVE/IDLE with a named worker.
2. Create packages/incident-contract from activity-contract clone; move D3-D7 content.
3. Collector wiring (package.json, provider.ts, status-writer.ts, incident-service.ts, actions.ts, server.ts, token.ts).
4. Controller wiring (package.json, store.ts, token.ts).
5. Web wiring (package.json, incident-types.ts → re-export, import updates).
6. Run acceptance incl. the six deletion tests; fix to green.
7. Land on a branch via .claude/scripts/ship.sh; deploy via packaging/deploy-local.sh.
8. Register follow-up: ADR-0001 file creation belongs to plan 2026-08-13-arch-domain-glossary.md.

## Current receipt

2026-08-13: Spec 2026-08-13-arch-shared-contracts-design.md written in worktree, externally reviewed (APPROVE-WITH-FIXES), all blocker/minor/nits corrected (D3 sourced from producer types; collector-types.ts excluded; deletion tests for D4/D5/D7 added; status-writer :6-14; routes.test.ts named). Three plans registered PROPOSED in docs/plans/INDEX.md. Nothing implemented, nothing launched.

## Next executable action

Owner approval to launch; then execution step 2. Until then: none.