# SPEC-P1 — Budget unification + journal contracts (LIVE engine)

audience: AI coding agents first.
Target: `modules/harness/v2` (flat layout on origin/main — the deployed engine). DO NOT touch `modules/harness/src` — abandoned WIP from a parked branch, never integrate it. Porting reference: `modules/harness/reference/sssf/` (semantics only).

Verified live state (2026-08-03, gpt-5.6-sol vs origin/main): reply parsing already schema-validated reply-shape mining (`wrappers/lib/verdict-status.py extract_json`); review verdict already BLOCKING (`v2/quality.js runQualityPhase` quarantines non-PASS/malformed); zero-diff already blocked (`v2/run.js runTrustedTask` → `empty-diff`). Do NOT re-implement those. Remaining defects below.

## B1 — ONE per-task dispatch budget (kills the overnight loop)

Defect: `v2/journal.js retryDisposition()` caps attempts per exact identity (`run/task/class/fingerprint/base/head`), `maxAttempts=3`, `maxParks=8`. A changed fingerprint or base mints a FRESH budget; quality adds three fix rungs — the product is the observed hours-long single-task loop.

Required:
- New task-level counter in the retry ledger projection: every provider invocation for a task (implement, fix rung, review, repair — any phase, any identity) increments `taskDispatchCount`.
- Hard cap `taskDispatchBudget` (default 8, runconfig-overridable) enforced at admission (`run.js trustedGate` / wherever `retryDisposition` verdicts are consumed): cap reached → task blocked, failureClass `budget-exceeded`, regardless of which sub-loop wanted more.
- Per-identity stop-loss stays (it is correct, just insufficient alone).
- Journal kind `budget.exhausted` `{ task, count, cap }` on trip.
- Crash/relaunch: count derives from journal projection (same pattern as `projectRetryLedger`) — survives restart by construction; test proves it.

## B2 — Journal kind schema enforcement

Defect: `v2/journal.js openJournal()` validates only object shape; arbitrary `kind` strings survive. `spec/events.schema.json` covers the normalized wrapper stream only.

Required:
- New `modules/harness/spec/journal-events.schema.json`: enumerate every kind the live engine emits (grep literal kinds across `v2/*.js`) + kinds added by this spec pack, payload contracts per kind.
- `openJournal` validates kind + payload against it; undeclared kind → throw (fail-closed). Migration guard: schema generated FROM the live grep so day-one enforcement is green.
- Test: appending a declared kind passes; undeclared kind throws; every kind emitted during the full engine test suite is declared (meta-test).

## Tests

Extend `modules/harness/v2/test/` (registered in `test/index.js`). Run: `node modules/harness/v2/test/index.js`. B1 fixture: task engineered to fail with a CHANGING fingerprint each attempt → blocked at `taskDispatchBudget`, not looping to stop-loss × identities.

## Exit criteria

- No sequence of fingerprint/base changes lets a single task exceed `taskDispatchBudget` provider invocations (test proves).
- `openJournal` rejects undeclared kinds; full suite green under enforcement.
