# Extension Subsystem

Compiled TS → JS lives in `extension/services/`, `extension/bin/`, `extension/hooks/`, `extension/types/`. Source in `extension/src/`. Tests in `extension/tests/` via `node --test`. Rebuild with `npx tsc` from `extension/`.

## ⛔ Worker Forbidden Ops (R-WSRC)

| Forbidden write | Override flag | Runtime check |
|---|---|---|
| tsc errors at commit time | `allow_tsc_failed_reason` (manager-only) | `tsc-gate.ts` hook |

## R-TSPF

R-TSPF stabilization spans five admitted flakes across four race classes. `load-dependent-timeout` covers `extension/tests/auto-resume-stop-conditions.test.js` warn-banner progression (depends on retry-state transitions, not extra waiting). `subprocess-spawn-timing` covers `extension/tests/council-publish.test.js` hung-`gh` regressions and `extension/tests/mux-runner-relaunch.test.js` delayed fake-`tmux` writes (both require deterministic barriers, not incidental timing luck). `process-global-state` covers `extension/tests/ensure-monitor-window.test.js` path-shim helpers (must serialize `process.env.PATH` mutation across concurrent tests). `file-existence` scope was merged: R-TSPF-5 collapsed into the R-TSPF-4 `ensure-monitor-window` fix, so the missing-`tmux-monitor.sh` case inherits the same shared PATH-serialization trap door.

Use `## Trap Doors` as the source of truth for the per-file invariant text, race class, and `PATTERN_SHAPE` anchors. For the pre-merge external proof of AC-1', operators run `gh workflow run stability-gate.yml -f run_count=30` once per bundle; worker AC-7 keeps the single-pass `test:fast` path.

## R-TFP

R-TFP serialization precedent: subprocess-heavy `@tier: fast` tests promoted to `@tier: integration` + added to `tests/integration/.serial-tests.json` to run at `--test-concurrency=1`. Precedent set in v1.76.0 (commit `cf600408`) for B-FLAKE class: `council-publish.test.js`, `mux-runner-claude-max-turns-relaunch.test.js`, `install-script-real.test.js`, `mux-runner.output-stall.test.js`, `check-update.test.js`. R-TFP-C1 (2026-05-28) extends the same pattern to `auto-resume-stop-conditions.test.js` (load-dependent-timeout: auto-resume.sh subprocess starvation at c=8; also covered by R-TSPF-2 trap door) and `microverse.test.js` (subprocess-spawn-timing: spawnSync/execSync orphan-tmp recovery at c=8). R-TFP-C2 (2026-05-28) adds forward-protection: `extension/scripts/audit-subprocess-heavy-tests.sh` flags any new test with a subprocess-heavy spawn + short timeout that lacks serialization (see trap-door entry). R-TFP-C3 (2026-05-28) closes R-TFP with a 3× consecutive regression loop: `extension/scripts/regression-test-fast-integration-3x.sh` (gated by `RUN_REGRESSION_3X=1`, wired as `test:regression-3x`) runs `test:fast` + `test:integration` 3× in sequence at gate concurrency, appending JSONL run records to `~/.claude/pickle-rick/r-tfp-regression-log.jsonl`; 3 consecutive greens are required to move R-TFP from watch-item to closed (see trap-door entry).

### Serial-manifest hygiene principle (AC-R-ITIH-4)

A subprocess `timeout` is a hang-guard (≥30s), not a perf-assertion; serialize via the manifest when concurrency is the real constraint. Never shrink a test's spawn timeout to make a load-starved test "pass" — that converts a deadlock guard into a flaky stopwatch. If a test flakes only under parallel load, add it to `extension/tests/integration/.serial-tests.json` (runs at `--test-concurrency=1`) instead. Each serial entry's root-cause class is annotated 1:1 in the sidecar `extension/tests/integration/.serial-tests.reasons.json` — one of exactly five classes: `real-repo-isolation`, `subprocess-timeout-coupling`, `process-global-state`, `subprocess-spawn-timing`, `load-dependent-timeout`. The sidecar is human/operator-only (no runtime consumer reads it); `extension/tests/serial-tests-reasons-coverage.test.js` enforces 1:1 coverage and the five-class allowlist.

## Architectural Vocabulary

Single source of truth for the Pocock LANGUAGE.md vocabulary (`mattpocock/skills:improve-codebase-architecture`). Use these terms in `research_*.md` and `code_review_*.md` phases and in `/death-crystal` reports. Per AC-DC-11 this section is the vocabulary's only home — do NOT add a `CONTEXT.md`.

### Terms (the 8 canonical LANGUAGE.md terms)

- **Module** — anything with an interface and an implementation; deliberately scale-agnostic. _Avoid: component._
- **Interface** — everything a caller must know to use the module correctly: the type signature plus invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid: API._
- **Implementation** — the code inside a module; the part the interface hides. _Avoid: unit._
- **Depth** — the behaviour a caller (or test) can exercise per unit of interface they have to learn; a deep module is a simple interface hiding substantial implementation. _Avoid: equating depth with line count — see "Depth is leverage, not line count" below._
- **Seam** — a place where you can alter behaviour without editing in that place; where a module's interface lives. _Avoid: boundary._
- **Adapter** — a concrete thing that satisfies an interface at a seam; names the role (which slot it fills), not the substance. _Avoid: service._
- **Leverage** — what callers get from depth: more capability per unit of interface they have to learn.
- **Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate at one place.

### Avoid (banned substitutions)

Never substitute these for the terms above:

- **component** → use **Module**
- **service** → use **Adapter**
- **boundary** → use **Seam**
- **API** → use **Interface**

### Principles

- **Deletion test** — evaluate whether a module earns its existence by imagining its removal; a pure pass-through that hides no complexity fails the test.
- **Interface-as-test-surface** — callers and tests cross the same seam, so a test that must reach past the interface signals a design problem.
- **One-adapter-rule** — one adapter means a hypothetical seam; two adapters means a real one. Don't introduce a seam until a second adapter justifies it.
- **Depth-as-leverage** — measure depth by caller benefit (leverage and locality), not by an implementation-to-interface line ratio.

### Depth is leverage, not line count

Reject the Ousterhout line-ratio reading of "deep module." Depth is not "many implementation lines behind a few interface lines" — it is leverage: how much capability a caller gets per unit of interface they must learn. A short implementation can be deep if it concentrates real decisions; a long one can be shallow if it merely forwards calls.

## Trap Doors

- `extension/scripts/regression-test-fast-integration-3x.sh` (R-TFP-C3 3× regression loop) — INVARIANT: the script MUST (a) skip with exit 0 when `RUN_REGRESSION_3X` is unset/empty, (b) loop exactly 3 times running `npm run test:fast` then `npm run test:integration` in sequence, (c) exit non-zero immediately on any failure, and (d) append a JSONL record `{"ts","run","status","fast_exit","integration_exit","duration_ms"}` to `~/.claude/pickle-rick/r-tfp-regression-log.jsonl` after each run. BREAKS: removing the skip-guard makes the script run unconditionally and adds ~30-45 min to the test:fast/integration tier; removing the JSONL append loses the consecutive-green count needed to close R-TFP; adding a `test:regression-3x` npm script without the `RUN_REGRESSION_3X` guard causes accidental invocation from `test:expensive`. ENFORCE: extension/tests/regression-test-fast-integration-3x.test.js (covers syntax, executable bit, npm script wiring, skip-guard, loop count, JSONL fields). PATTERN_SHAPE: `RUN_REGRESSION_3X` guard present, `r-tfp-regression-log.jsonl` append present, loop count = 3.

- `extension/scripts/audit-subprocess-heavy-tests.sh` (R-TFP-C2 forward-protection) — INVARIANT: the script MUST flag any test file containing `spawnSync('bash'|'sh', [scriptArg, ...])` with an explicit `timeout: N` where N ≤ `SUBPROCESS_HEAVY_TIMEOUT_MS` (5000) AND the first array argument does NOT start with `'-'`, UNLESS the file is listed in `tests/integration/.serial-tests.json` OR the file contains a `// SERIAL: <reason>` comment; `@tier: expensive` files are always excluded (not part of the c=8 flake surface). A `SUBPROCESS_HEAVY_WARN_MS=15000` load-sensitive WARN tier emits a NON-failing warning for non-serialized subprocess spawns in the 6000–15000ms band (closes the 10s blind spot — e.g. the pntr-pickle-deprecated ~10004ms-under-c=8 class) without hard-failing the gate. BREAKS: adding a new subprocess-heavy test without serializing it reintroduces the R-TFP flake class without detection; removing the `'-'` first-arg exclusion causes false positives on `bash -c`/`bash -lc` inline commands; removing the `tests/fixtures/` exclusion from the full-scan path causes fixture files to trigger false failures. ENFORCE: extension/tests/audit-subprocess-heavy-tests.test.js. PATTERN_SHAPE: `SUBPROCESS_HEAVY_TIMEOUT_MS=5000` constant in `extension/scripts/audit-subprocess-heavy-tests.sh` AND `bash scripts/audit-subprocess-heavy-tests.sh` present in the gate command in `CLAUDE.md`, `ci.yml`, `release.yml`.

- `src/bin/mux-runner.ts` (R-WTB-A1 artifact-progress-aware timeout) — INVARIANT: the `if (counterNext.halt)` block in `runMuxRunnerMain` MUST call `detectArtifactProgress(ticketDir, lastArtifactProgressSnapshot, ...)` BEFORE calling `executeTimeoutHalt`; if `pResult.progressed`, reset `timeoutCount = 1` and emit `ticket_timeout_progress_extension` WITHOUT halting; only emit `ticket_timeout_halted_no_progress` + call `executeTimeoutHalt` when progress is absent. `PICKLE_TIMEOUT_NO_PROGRESS_WINDOW_SECONDS` (default 1500) controls the silence window. BREAKS: reverting the check to a bare `executeTimeoutHalt` call restores the original aggressive halt on 2 consecutive timeouts regardless of worker productivity (live incident b04f41d6 in v1.81.0: sound 40-min implement halted). ENFORCE: extension/tests/timeout-progress-aware.test.js, extension/tests/timeout-progress-fixtures.test.js. PATTERN_SHAPE: `detectArtifactProgress(ticketDir, lastArtifactProgressSnapshot` inside `if (counterNext.halt)` block.

- `extension/tests/auto-resume-stop-conditions.test.js` (R-TSPF-2 warn banner progression) — INVARIANT: `prints [warn] banner past retry 3` must prove warn-banner emission from retry-state progression and the retry-4 no-progress halt, not from waiting for a fifth incomplete launch to exhaust `MAX_RETRIES`. BREAKS: retry accounting regressions can fake the warning by over-waiting for a nonexistent fifth launch instead of proving the retry-3/4 state transition. ENFORCE: extension/tests/auto-resume-stop-conditions.test.js.
  Race-Class: `load-dependent-timeout`
  PATTERN_SHAPE: `const nextTicket = n === 3 ? 'ticket_2' : 'ticket_' + n;`
- `extension/tests/council-publish.test.js` (R-TSPF-3 hung gh * class-wide) — INVARIANT: the `hung gh pr list` and `hung gh pr comment` regressions must exercise the timeout-classification path without any PATH-resolved helper binaries inside the shell `gh` mock; branch lookup and comment-call state tracking stay builtin-only so the healthy sibling branch still completes after the timed-out one. BREAKS: `base64`/`tr`/`cat` helper spawns reintroduce load-sensitive or PATH-sensitive secondary failures, so the second branch flips from `posted` to `failed` even though only the first branch was configured to hang. ENFORCE: extension/tests/council-publish.test.js. PATTERN_SHAPE: `await withPathEnv\\(emptyPathDir`. Race-Class: `subprocess-spawn-timing`
- `extension/tests/ensure-monitor-window.test.js` (R-TSPF-4 withPath pane-2 flake) — INVARIANT: every helper PATH shim for `ensureMonitorWindow` / `restartDeadWatcherPanes` must enter through the shared serialized `withPath` path so concurrent test callbacks cannot clobber `process.env.PATH` mid-assertion. BREAKS: mode-specific pane-2 recovery flakes, or sibling watcher-pane tests read another fixture's shimmed `tmux` binary / restored host PATH. ENFORCE: extension/tests/ensure-monitor-window.test.js. PATTERN_SHAPE: `let withPathQueue = Promise\.resolve\(\)|return withSerializedPath\(shimDir, fn\)`. Race-Class: `process-global-state`
- `src/bin/audit-ticket-bundle.ts` (R-TAQ-2 disposition exemption) — INVARIANT: `loadDispositions` merges the three disposition tables (`bundle-disposition-2026-05-04.json`, `bundle-disposition-2026-05-07-deferred-slots.json`, `bundle-disposition-2026-05-08-mega.json`) in order; tickets whose every `mapped_requirements` entry resolves to `REGRESSION-TEST-ONLY`, `DROP`, `IMPLEMENT-but-no-source-PRD-for-K-L`, or `DIAGNOSE` are EXEMPT from the `hallucinated-premise` check; absent tables collapse to no-exemption. BREAKS: missing or mistyped disposition tables reintroduce the bootstrap failure where shipped requirements are flagged as `hallucinated-premise` and the bundle gate kills the bundle. ENFORCE: extension/tests/audit-ticket-bundle-schema.test.js. PATTERN_SHAPE: `loadDispositions\\(`.
- `src/bin/audit-ticket-bundle.ts` (R-RTRC-7 path annotation parity) — INVARIANT: `checkPathDrift` accepts the same path-level forward-reference annotations as the analyst prompt and readiness gate: ``<path>`` `(forward-created)` or ``<path>`` `(created|introduced) by ticket <6-12-char-hash>`. BREAKS: bundle-authored forward-created paths survive prompt and readiness validation, then die as fatal `path-drift` during ticket audit. ENFORCE: extension/tests/audit-ticket-bundle.test.js. PATTERN_SHAPE: `checkPathDrift[\\s\\S]*forward-created|created\\|introduced`.
- `src/bin/check-flake-budget.ts`; `scripts/check-flake-budget.sh`; `tests/flake-budget.test.js` (Stability-gate budget) — INVARIANT: the flake-budget guard reruns the fast test surface under `node --test --test-concurrency=8` with defaults `--runs=5` and `--fail-budget=2`; it fails closed when the child target is missing or exits before emitting node:test results, exits `0` when failures are less than or equal to budget, and emits `FAIL_BUDGET_EXCEEDED` on stderr only when failures exceed budget. BREAKS: a future flaky test can re-open the local fast-suite instability surface, a harness/bootstrap error can be misreported as an admitted flake, or an over-strict boundary check rejects healthy bundles when failures equal the admitted budget. ENFORCE: extension/tests/flake-budget.test.js. PATTERN_SHAPE: `basename\\(process\\.argv\\[1\\]\\) === 'check-flake-budget\\.js'|FAIL_BUDGET_EXCEEDED|--fail-budget=2|--runs=5|Flake-budget child failed before reporting test results`.
- `src/bin/council-publish.ts` — INVARIANT: every `execFileSync('gh', …)` passes `timeout` from `PublishOptions.ghTimeoutMs`. BREAKS: network hangs stall publish forever. ENFORCE: extension/tests/council-publish.test.js, extension/tests/council-publish-cli.test.js.
- `src/services/scope-resolver.ts` — INVARIANT: importer walks time out, diff refs/targets clamp, and refresh promotes dead tmp without base. BREAKS: hangs or wrong scope. ENFORCE: extension/tests/scope-one-hop-hang-guard.test.js, extension/tests/scope-pipeline.test.js, extension/tests/scope-refresh.test.js.
- `src/services/scope-resolver.ts` (R-SRGT Finding #50 one-hop walk caps) — INVARIANT: `computeOneHop` returns `[]` immediately for an empty seed set (no grep subprocess), and the export-name walk loop is bounded by an aggregate wall-clock cap (`walkDeadline` / `ONE_HOP_WALK_WALL_MS`, default 60s) — on exceed it logs and returns the partial importer set. The per-grep `FIND_IMPORTERS_TIMEOUT_MS` default is 5s. BREAKS: dropping the empty-seed short-circuit or the wall-clock cap re-opens the `N × per-grep-timeout` unbounded walk that stalled the readiness gate 67s on a large repo. ENFORCE: extension/tests/scope-srgt.test.js. PATTERN_SHAPE: `walkDeadline|ONE_HOP_WALK_WALL_MS`.
- `src/services/scope-resolver.ts` (HS-8 rg-empty-vs-failure) — INVARIANT: `_runRgImportWalk` returns `null` ONLY on rg failure/missing and `string[]` (possibly empty) on a successful run; `findImporters` falls back to `_runGrepImportWalk` only when rg returns `null`, never on an empty-but-successful rg result. BREAKS: collapsing the return to `[]` makes a zero-match rg fall through to the gitignore-blind `grep -rl … .` fallback, double-spawning one grep per importerless export name and pulling `node_modules/`/`dist/` importers into the one-hop set. ENFORCE: extension/tests/scope-resolver-import-walks.test.js (`rg success with zero matches does NOT fall through to grep`). PATTERN_SHAPE: `_runRgImportWalk(...): string[] | null` AND `if (rgMatches !== null) return rgMatches` in `findImporters`.
- `src/services/scope-resolver.ts` (rg/grep extension parity) — INVARIANT: `_runRgImportWalk` rg `--glob` and `_runGrepImportWalk` grep `--include` flags cover the same extension set (`ts,tsx,js,jsx,mjs,cjs`). BREAKS: on rg-absent hosts the grep fallback drops `.mjs`/`.cjs` importers → under-inclusive one-hop scope. ENFORCE: extension/tests/scope-resolver-import-walks.test.js (`grep fallback honors .mjs/.cjs`). PATTERN_SHAPE: grep `--include=*.<ext>` set equals the rg `--glob '*.{...}'` extension set.
- `src/services/scope-resolver.ts` (R-SRAA Finding #53 archive rotation) — INVARIANT: `writeScopeArchive` rotates a pre-existing `archive/scope.<phase>.json` to a timestamped `<file>.<epochMs>.bak` sibling instead of throwing the (now-retired) `SCOPE_ARCHIVE_EXISTS` error; `ScopeErrorCode` MUST NOT re-introduce `SCOPE_ARCHIVE_EXISTS`. BREAKS: re-introducing the throw makes every pipeline relaunch FATAL whenever launch #1 crashed in the window between writing the archive and updating `state.phases_entered`, forcing operators to `rm -f $SESSION/archive/scope.*.json` before each relaunch. ENFORCE: extension/tests/scope-refresh.test.js (`R-SRAA #53` rotation test), extension/tests/scope-errors-doc-parity.test.js (phantom-type guard). PATTERN_SHAPE: `rotatedPath|\\.bak`.
- `src/bin/init-microverse.ts` — INVARIANT: convergence mode and flag values are runtime-validated; allowed-path scope reads promote dead tmp. BREAKS: typo falls into metric mode or scoped runners persist stale scope. ENFORCE: extension/tests/init-microverse.test.js, extension/tests/scope-refresh.test.js.
- `src/bin/init-microverse.ts` (LLM metric) — INVARIANT: default LLM metric omits `judge_model`; backend-specific defaults stay in runner selection. BREAKS: codex szechuan judge gets Claude-only `-m`. ENFORCE: extension/tests/szechuan-sauce.test.js. PATTERN_SHAPE: `DEFAULT_METRIC[\s\S]*judge_model`.
- `src/bin/init-microverse.ts` (SCJM-T5) — INVARIANT: `DEFAULT_METRIC` MUST NOT pin `judge_model`; the judge always spawns via the claude binary (never codex) regardless of `state.backend`, using `DEFAULT_JUDGE_MODEL` ('claude-sonnet-4-6') when no explicit model is provided. Worker iteration spawns continue to honor `state.backend`. BREAKS: routing the judge through codex causes silent false-convergence (BestScore: 0) because codex on ChatGPT accounts rejects claude-sonnet-4-6. ENFORCE: extension/tests/integration/microverse-runner-judge-failure.test.js, extension/tests/microverse-codex.test.js. PATTERN_SHAPE: `judge_model.*claude-` in `DEFAULT_METRIC`.
- `src/bin/microverse-runner.ts` (R-SCJM-5 judge claude-only) — INVARIANT: the LLM judge spawn (`measureLlmMetricAttempt` + `probeJudgeBackendAvailability('claude', ...)`) MUST always go through the claude binary regardless of `state.backend`; `backendEnvOverrides` MUST be called with `'claude'` (not `state.backend`) at the judge exec site, and no backend ternary may exist on the judge spawn path. `resolveJudgeBackend` / `getMicroverseSettings` are allowed only for WORKER-ITERATION fallback state (`worker_backend` + `judge_backend_resolved` breadcrumbs) after typed judge failures; they MUST NOT choose the judge binary, and worker fallback persistence may set `worker_backend` only, never overwrite `judge_backend_resolved`. Worker iteration spawns continue to honor the resolved worker backend. BREAKS: a future refactor reintroduces a backend ternary on the judge spawn path or rewrites the sticky judge breadcrumb; codex on a ChatGPT account rejects `claude-sonnet-4-6` with "model is not supported", producing silent `score=null` history that the R-SCJM-3 guard must then catch. ENFORCE: extension/tests/microverse-codex.test.js, extension/tests/integration/microverse-runner-judge-failure.test.js, extension/tests/microverse-runner.worker-backend-fallback.test.js. PATTERN_SHAPE: `buildJudgeInvocation\('claude'` in `microverse-runner.ts`|`persistWorkerIterationFallback\(`.
- `src/bin/microverse-runner.ts` — INVARIANT: worker-mode subprocess-error contract changes must rebuild deployed `extension/bin/microverse-runner.js` and `extension/types/index.js` in the same change. BREAKS: runtime drops `last_error`/`subprocess_error` telemetry while source/tests claim coverage. ENFORCE: extension/tests/microverse.test.js. PATTERN_SHAPE: `subprocess_error|last_subprocess_error|last_error` touched in `src/bin/microverse-runner.ts` or `src/types/index.ts` without matching compiled-mirror diff.
- `src/bin/microverse-runner.ts` (R-MBLE-1 aggregator 3-way switch) — INVARIANT: the all-attempts-exhausted aggregator MUST remain a 3-way switch over `measured.exitReason`, mapping `'cli_missing'` to `'judge_cli_missing'`, `'timeout'` to `'judge_timeout'`, and the default branch to `'baseline_unmeasurable_unrecoverable'`; no binary ternary may collapse the non-timeout path back to bare `'baseline_unmeasurable'`. BREAKS: a future refactor can silently reintroduce the binary ternary fallback, collapsing exhausted generic failures away from `'baseline_unmeasurable_unrecoverable'` and weakening the gate to only test-time detection. ENFORCE: extension/tests/microverse-baseline-aggregator.test.js. PATTERN_SHAPE: `switch\s*\(\s*measured\.exitReason\s*\)` present and `exitReason:\s*ExitReason\s*=\s*['"]baseline_unmeasurable['"]` absent.
- `src/bin/microverse-runner.ts` (R-MBLE-7 timeout telemetry parity) — INVARIANT: every LLM callsite of `measureLlmMetricWithBackoff` MUST pass `{session,iteration}` so each ETIMEDOUT retry emits `baseline_attempt_timeout` before the aggregate `judge_timeout`. BREAKS: iteration retries go dark and only terminal timeout telemetry survives. ENFORCE: extension/tests/baseline-attempt-timeout.test.js, extension/tests/microverse-baseline-classification.test.js. PATTERN_SHAPE: `measureLlmMetricWithBackoff\(` callsites lacking adjacent `session:\s*path\.basename\(ctx\.sessionDir\).*iteration:\s*ctx\.iteration`.
- `src/bin/microverse-runner.ts` (R-MBLE-8 iteration failure parity) — INVARIANT: `measureLlmBaseline` and `measureLlmIteration` MUST map exhausted judge failures through the same helper so non-timeout exhaustion stays `baseline_unmeasurable_unrecoverable`, never `judge_timeout`. BREAKS: fatal judge failures fall into pipeline finalize-gate recovery. ENFORCE: extension/tests/microverse-baseline-classification.test.js. PATTERN_SHAPE: `mapJudgeMeasurementFailure|measureLlm(Baseline|Iteration)`.
- `src/bin/microverse-runner.ts` (probe path) — INVARIANT: `probeJudgeBackendAvailability('claude', ...)` MUST classify ENOENT, ETIMEDOUT, and other errors via `classifyJudgeError`; only ENOENT-class produces `judge_cli_missing`. BREAKS: cold-start probe timeouts on macOS misclassified as missing CLI, killing pipelines that have completed converged phases. ENFORCE: extension/tests/bin/microverse-judge-probe.test.js.
- `src/bin/check-gate.ts` — INVARIANT: value flags require non-flag values and `--allowed-paths-file` promotes dead scope tmp. BREAKS: false-green gates or stale scoped failures. ENFORCE: extension/tests/bin/check-gate.test.js.
- `src/bin/check-readiness.ts` (skip) — INVARIANT: `--skip-readiness` REQUIRES a non-empty reason argument; missing/blank/`--`-prefixed arg exits 64. When honored, the gate emits a `readiness_skipped` activity event with `gate_payload.reason` + ISO timestamp, prints `{status:"skipped",reason,elapsed_ms:0}`, and exits 0 BEFORE any PRD/manifest/contract validation runs. The flag is reached only via `mux-runner.runMuxReadinessGate` reading `state.flags.skip_readiness_reason` — empty/absent reason MUST NOT forward `--skip-readiness`. BREAKS: silent gate bypass without audit (no event), implicit defaulting that disarms readiness for unrelated sessions, or reason-less skip that callers misread as "passed". ENFORCE: extension/tests/check-readiness-skip.test.js.
- `src/bin/check-readiness.ts` — INVARIANT: readiness state/snapshot reads and writes must promote recoverable `state.json.tmp.*` / `readiness_snapshot.json.tmp.*` before history or delta logic runs. BREAKS: stale readiness deltas or reset cycle-history on tmp-only sessions. ENFORCE: extension/tests/check-readiness.test.js. PATTERN_SHAPE: `readState|writeState|readiness_snapshot.json`.
- `src/bin/check-readiness.ts` (hang guard) — INVARIANT: contract-resolution subprocesses pass finite timeouts. BREAKS: wedged readiness gate. ENFORCE: extension/tests/check-readiness-hang-guard.test.js. PATTERN_SHAPE: `spawnSync('git', ['ls-files']|computeOneHop(`.
- `src/bin/check-update.ts` — INVARIANT: active-session downgrade guards must read each session `state.json` through `readRecoverableJsonObject(statePath)` before inspecting `active`. BREAKS: a newer recoverable `state.json.tmp.<pid>` can hide a live session and permit in-place upgrade/downgrade over the running pipeline. ENFORCE: extension/tests/check-update.test.js. PATTERN_SHAPE: `JSON\\.parse\\(fs\\.readFileSync\\(statePath`.
- `src/bin/spawn-refinement-team.ts` (R-RTRC-1 forward-reference hygiene) — INVARIANT: `PATH_VERIFICATION_PROMPT_SECTION` instructs analysts that (a) every backticked path/symbol MUST be verified at HEAD before being cited, (b) stdlib/external-package APIs MUST NEVER be backticked because the readiness contract resolver treats them as in-repo refs, (c) forward-created artifacts MUST carry the R-RTRC-7 annotation `\`<token>\` (created|introduced) by ticket <8-12-char-hash>` with EXACTLY one ASCII space separator. The grep-able anchor `Forward-reference hygiene` MUST appear at least once in the source. BREAKS: removing the hygiene language re-opens the false-positive class where analysts cite paths that don't exist at HEAD or backtick stdlib APIs, and the readiness gate fails sessions on non-bugs. ENFORCE: extension/tests/spawn-refinement-team-forward-ref-hygiene.test.js. PATTERN_SHAPE: `Forward-reference hygiene`.
- `src/bin/spawn-refinement-team.ts` (R-RTRC-1 path-not-verified annotations) — INVARIANT: `checkAnalystOutputPaths` suppresses `path_not_verified` ONLY for exact path annotations accepted by readiness: `(forward-created)` or `(created|introduced) by ticket <6-12-char-hash>`. BREAKS: malformed ticket hashes clear authoring and fail later at readiness. ENFORCE: extension/tests/spawn-refinement-team-path-verification.test.js. PATTERN_SHAPE: `PATH_FORWARD_REF_ANNOTATION_RE|checkAnalystOutputPaths`.
- `src/bin/spawn-refinement-team.ts` (R-SAOV-7 forward-create audit affordance) — INVARIANT: symbol audit MUST accept `(forward-created)`, `(created|introduced) by ticket <6-12-char-hash>`, and `(created by R-<CODE>-N)` for activity events and helpers/sentinels, while trigger-phrase lines that also describe enum/state prose remain reviewable for false-positive backticked enum values. The source MUST retain the grep-able trap-door anchor `TRAP DOOR: R-SAOV-7 forward-create annotation acceptance`. BREAKS: removing a ticket or requirement alias turns readiness-valid sibling symbols into phantom audit findings, or widening the enum-value false-positive filter without reviewing true-positive event wording on wrapper trigger-phrase lines. ENFORCE: extension/tests/spawn-refinement-team-symbol-audit-annotations.test.js, bash extension/scripts/audit-trap-door-enforcement.sh. PATTERN_SHAPE: `TRAP DOOR: R-SAOV-7 forward-create annotation acceptance|(?:created|introduced) by ticket|created by R-[A-Z0-9]+(?:-[A-Z0-9]+)*-\\d+`.
- `.claude/commands/pickle-refine-prd.md` (R-FRA-1 Step 7c forward-reference hygiene reminder) — INVARIANT: Step 7c of `.claude/commands/pickle-refine-prd.md` MUST contain the literal token `🚦 Forward-reference hygiene` and one worked example per canonical annotation form: `(forward-created)`, `(created by ticket <hash>)`, `(introduced by ticket <hash>)`. The reminder structurally aligns with `PATH_VERIFICATION_PROMPT_SECTION` in `spawn-refinement-team.ts` (R-RTRC-1 trap door) so ticket authors see the same grammar at authoring time as analysts see at refinement time. BREAKS: removing the reminder leaves ticket authors without the annotation grammar, causing creation-heavy bundles to routinely halt at the readiness gate with `path-drift` errors on forward-created artifacts. ENFORCE: extension/tests/spawn-refinement-team-step7c-annotation-reminder.test.js. PATTERN_SHAPE: `🚦 Forward-reference hygiene|Forward-reference hygiene`.
- `src/services/forward-ref-annotation.ts`; `src/bin/check-readiness.ts`; `src/bin/audit-ticket-bundle.ts` (R-FRA-6 shared forward-ref annotation predicate) — INVARIANT: `FORWARD_REF_ANNOTATION_RE` MUST be defined ONLY in `extension/src/services/forward-ref-annotation.ts`; both `check-readiness.ts` and `audit-ticket-bundle.ts` MUST import from `../services/forward-ref-annotation.js` and MUST NOT contain an inline `FORWARD_REF_ANNOTATION_RE = /…/` literal. BREAKS: divergent inline copies re-open the silent gate-parity bug where check-readiness and audit-ticket-bundle apply different forward-ref predicates. ENFORCE: extension/tests/forward-ref-annotation-shared-predicate.test.js. PATTERN_SHAPE: `import.*forward-ref-annotation` in both consumer files; `FORWARD_REF_ANNOTATION_RE\s*=\s*/` absent from both consumers.
- `extension/scripts/audit-ticket-forward-refs.sh`; `extension/tests/audit-ticket-forward-refs.test.js` (R-FRA-2 pre-flight forward-ref audit script) — INVARIANT: `extension/scripts/audit-ticket-forward-refs.sh` MUST delegate annotation grammar to `extension/services/forward-ref-annotation.js` (R-FRA-6) via a Node helper; NO inline `FORWARD_REF_ANNOTATION_RE` literal in the shell script; running against a fixture bundle with ≥3 fully-annotated forward-creating paths exits 0; running against a bundle with ≥1 unannotated forward-creating path exits 2. BREAKS: adding an inline regex copy to the shell script re-opens the silent gate-parity bug (divergent annotation predicates between check-readiness and the pre-flight audit); exit-code drift from 2 breaks the `check-readiness.ts` contract expected by callers. ENFORCE: extension/tests/audit-ticket-forward-refs.test.js. PATTERN_SHAPE: `\((forward-created|created by ticket [a-f0-9]+|introduced by ticket [a-f0-9]+)\)`
- `src/bin/check-readiness.ts` (R-RTRC-2 / R-RTRC-7 annotated refs) — INVARIANT: `extractContractReferences` filters out tokens whose backticked form in the same content is followed by `(forward-created)`, `(created|introduced) by ticket <hash>`, or the symbol-audit compatibility alias `(created by R-<CODE>-N)` ONLY for symbol contracts, never file paths; ticket hashes still require exactly one ASCII space and `/^[A-Za-z0-9]{6,12}$/`, while requirement codes match `/^R-[A-Z0-9]+(?:-[A-Z0-9]+)*-\\d+$/`. Mismatches surface as `annotation_format`. BREAKS: forward-created bundle artifacts or sibling-helper contracts produce false `contract`/`file_path` findings, OR malformed annotations silently pass and readiness green-lights a path that ticket-audit later kills as `path-drift`. ENFORCE: extension/tests/check-readiness-forward-ref-annotation.test.js, extension/tests/check-readiness-forward-ref-fixture.test.js. PATTERN_SHAPE: `extractForwardRefAnnotations|FORWARD_REF_ANNOTATION_RE|created by R-<CODE>-N`.
- `src/bin/check-readiness.ts` (R-RTRC-3 tests/ in resolver scope) — INVARIANT: `createResolverCache` includes test source files in `trackedSourceFiles` (no `tests/` exclusion); the extension allowlist remains `(ts|tsx|js|jsx|mjs|cjs)`. Symbols defined in test fixtures, helpers, and integration tests are valid resolution targets. BREAKS: re-introducing the tests/ exclusion produces false-positive `contract` findings whenever a ticket cites a test-defined helper or assertion contract. ENFORCE: extension/tests/check-readiness-forward-ref-fixture.test.js (RC-2). PATTERN_SHAPE: `trackedSourceFiles\\s*=\\s*gitTrackedFiles`.
- `src/bin/check-readiness.ts` (R-RHFP Finding #64 advisory findings) — INVARIANT: `runReadiness` excludes `kind:'performance'` findings from `blockingFindings`; the exit code and `status:fail` key on `blockingFindings`, never the raw `findings` array. A `performance` finding (contract-resolution wall budget exceeded) is the checker reporting its own incompleteness, not a ticket defect — it stays in the JSON `findings`/report as a coverage signal but cannot fail the gate. `extractContractReferences` drops `isEventNameLiteral` refs (3+ dotted segments, every segment all-lowercase) so ticket-introduced telemetry-event names are never extracted as symbol contracts. BREAKS: re-keying the exit code on `findings` makes a large/slow target repo fail readiness for the checker's own slowness; dropping the `isEventNameLiteral` filter re-flags by-design event names as unresolved `contract` findings. ENFORCE: extension/tests/check-readiness-rhfp.test.js. PATTERN_SHAPE: `blockingFindings|isEventNameLiteral`.
- `src/bin/check-readiness.ts` (R-RCEX Finding #65 external SDK resolution) — INVARIANT: `resolveSymbolRef` falls back to `resolveExternalSymbolRef` when in-repo resolution yields zero candidates; the external resolver grep-matches the ref's parts against the `.d.ts` files of the target repo's DECLARED dependencies (`collectExternalDtsFiles`, scoped to `node_modules/<dep>/`, `@types/*` excluded, file/byte caps bound the scan). BREAKS: removing the fallback re-flags every legitimate third-party SDK symbol (e.g. `JobGetResponse.result`) as an unresolved `contract` finding; descending into nested `node_modules` or dropping the caps reopens an unbounded scan. ENFORCE: extension/tests/check-readiness.test.js. PATTERN_SHAPE: `resolveExternalSymbolRef|collectExternalDtsFiles`.
- `src/bin/check-readiness.ts` (R-RTRC-4 suffix-match path fallback) — INVARIANT: `resolvePathRef` falls back to `git ls-files` suffix-match (`^|/<ref>$`) AFTER its base-dir checks miss. The git output is cached on `ResolverCache.trackedAllFiles` to avoid re-spawning git per ticket. BREAKS: bare-basename or near-root path refs in deep monorepos (e.g. `nested-deep.ts` under `packages/core/src/modules/...`) become spurious `file_path` findings. ENFORCE: extension/tests/check-readiness-forward-ref-fixture.test.js (RC-3). PATTERN_SHAPE: `cache\\.trackedAllFiles|suffixRe\\.test`.
- `src/bin/check-readiness.ts` (R-RTRC-5 readiness allowlist) — INVARIANT: `loadReadinessAllowlist(repoRoot)` reads `extension/.readiness-allowlist.json` and silently drops entries whose `ref` or `source` field is missing/empty. The allowlist short-circuits both path and symbol resolution: refs in the set never produce `contract` or `file_path` findings. The schema invariant — every entry MUST have a non-empty `source:` justification — is enforced at CI time by `extension/scripts/audit-readiness-allowlist.sh`. BREAKS: removing the allowlist load disarms the long-tail stdlib/external-package suppression list, OR removing the audit lint lets unjustified entries accumulate without review. ENFORCE: extension/tests/check-readiness-forward-ref-fixture.test.js, extension/tests/check-readiness-forward-ref-annotation.test.js, extension/scripts/audit-readiness-allowlist.sh. PATTERN_SHAPE: `loadReadinessAllowlist|ALLOWLIST_FILE_REL`.
- `src/bin/check-readiness.ts` (R-RTRC-6 fixture coverage) — INVARIANT: `extension/tests/check-readiness-forward-ref-fixture.test.js` ships THREE fixture tickets (RC-1 forward-ref-annotated bundle artifact, RC-2 test-defined helper, RC-3 deep repo path) and asserts contract-only `check-readiness.js` exit 0 on each. BREAKS: removing any fixture lets one of the four false-positive root causes regress without trip-wire. ENFORCE: extension/tests/check-readiness-forward-ref-fixture.test.js.
- `src/bin/check-readiness.ts` (R-RTRC-7 annotation schema) — INVARIANT: the canonical forward-reference annotation `\`<token>\` (created|introduced) by ticket <hash>` requires (a) position OUTSIDE backticks, (b) EXACTLY one ASCII space between closing backtick and opening paren, (c) hash matching `/^[A-Za-z0-9]{6,12}$/` (8-char short SHA OR ticket-dir basename are both accepted). Compatibility aliases `\`<token>\` (forward-created)` and `\`<token>\` (created by R-<CODE>-N)` remain accepted so readiness stays aligned with upstream audit/path-verification and symbol-audit affordances. Separator/hash/requirement-code mismatches emit `annotation-format-error`. BREAKS: dropping an alias reintroduces false readiness failures for bundle-authored paths or sibling helper contracts; relaxing separator/hash rules lets typos silently pass. ENFORCE: extension/tests/check-readiness-forward-ref-annotation.test.js. PATTERN_SHAPE: `FORWARD_REF_ANNOTATION_HASH_RE|FORWARD_REF_ANNOTATION_RE`.
- `src/bin/plumbus-frame-analyzer.ts` — INVARIANT: every `spawnSync('bun', …)` in `parseDotViaBun` passes `timeout` (default `BUN_TIMEOUT_MS = 30_000`). BREAKS: wedged bun stalls generative audit. ENFORCE: extension/tests/plumbus-frame-analyzer-hang-guard.test.js.
- `src/bin/spawn-morty.ts` — INVARIANT: state and settings reads are crash-recovered before timeout, cwd, backend, and model routing. BREAKS: stale budgets, repo/backend, or model. ENFORCE: extension/tests/spawn-morty.test.js, extension/tests/spawn-morty-helpers.test.js.
- `src/bin/spawn-morty.ts` (timeout CLI) — INVARIANT: `--timeout` is a strict positive integer. BREAKS: malformed worker budgets alter timeout behavior. ENFORCE: extension/tests/spawn-morty.test.js.
- `src/bin/spawn-morty.ts` (R-SMTEST early-exit invariant) — INVARIANT: spawn-morty MUST exit non-zero within 1 second when `--ticket-path` resolves outside `getDataRoot()` or to a non-existent session directory. BREAKS: the silent wedge ate 5 release-gate tests in v1.80.2 (finding #79); test-runners that SIGTERM-kill spawn-morty leave session state half-written. ENFORCE: extension/tests/spawn-morty.test.js. PATTERN_SHAPE: `spawn_morty_invalid_ticket_path` event emission + `process.exit(1)` in early-exit branch of `main()` in `src/bin/spawn-morty.ts`.
- `tests/process-iteration-outcome.test.js` — INVARIANT: fixture replays resolve from `import.meta.url`, not cwd. BREAKS: package-root test runs prepend `extension/` twice and kill rate-limit replay coverage. ENFORCE: extension/tests/process-iteration-outcome.test.js. PATTERN_SHAPE: `path\.resolve\('extension/tests/fixtures/`.
- `src/bin/spawn-morty.ts`; `src/bin/spawn-refinement-team.ts`; `src/bin/microverse-runner.ts`; `src/bin/mux-runner.ts` (R-XBL-2 backend SoT) — INVARIANT: manager spawns continue to read `state.backend` as their exec-time source of truth, while worker spawns apply `PICKLE_REFINEMENT_LOCK=1 -> state.worker_backend -> state.backend` using `StateManager.read(statePath)` immediately before exec-time resolution. `spawn-refinement-team.ts` remains refinement-only (hardcoded invocation path and env lock still force claude). The `--backend <name>` CLI flag on `spawn-morty.js` overrides the actual worker spawn and emits a `worker_spawn_backend_override` activity event, but does not redefine the persisted `worker_backend` precedence layer. spawn-gate-remediator.ts has zero backend code and inherits from caller. BREAKS: stale param backends or dropped `worker_backend` precedence drive cross-backend leaks or block the intended claude-manager/codex-worker hybrid. ENFORCE: extension/tests/integration/spawn-morty-backend-resolution.test.js, extension/tests/integration/worker-backend-split.test.js, extension/tests/integration/spawn-morty-actual-session-bug.test.js. PATTERN_SHAPE: `StateManager.read` immediately before worker/manager invocation at the real spawn sites.
- `src/bin/spawn-morty.ts (worker_backend resolution)` — INVARIANT: worker backend resolution emits `worker_backend_resolved` and follows `PICKLE_REFINEMENT_LOCK=1 -> state.worker_backend -> state.backend`; `worker_spawn_backend_resolved` continues to describe the final exec-time worker backend after any allowed CLI/settings routing. Manager spawn stays on `state.backend` alone. BREAKS: legitimate worker/backend splits are misclassified as mismatches, or worker spawns silently ignore the override field. ENFORCE: extension/tests/integration/spawn-morty-backend-resolution.test.js, extension/tests/integration/worker-backend-split.test.js, extension/tests/backend-spawn.test.js.
- `src/bin/spawn-morty.ts` (worker lint gate) — INVARIANT: worker MUST run lint + tsc before completion-commit. Non-`small` tickets MUST then run `npm run test:fast`; `full` gate tier additionally runs `npm run test:integration`. `small` tickets intentionally skip test commands and MUST emit `tier_phase_skipped`. One auto-fix retry only for lint and tsc; one targeted-fix retry for test failures. Worker invocations MUST reuse extension/node_modules; no per-worker `npm ci` or `npm install` may run here or in `mux-runner.ts`. PATTERN_SHAPE: `runWorkerGate(changedFiles, ...)` executes before any `git commit`, and `test:fast` runs only after clean eslint + tsc except for the explicit `small`-tier skip path. BREAKS: regressing this contract re-opens R-PTG-2 by letting workers ship failures, skip the required test-triage loop without telemetry, or reintroduce per-worker install overhead. ENFORCE: extension/tests/integration/worker-lint-gate.test.js, extension/tests/integration/worker-lint-gate-forensic.test.js, extension/tests/spawn-morty-worker-gate.test.js, extension/tests/node-modules-reuse.test.js, bash extension/scripts/audit-trap-door-enforcement.sh.
- `src/services/pickle-utils.ts` (R-WTFT worker test:fast gate timeout) — INVARIANT: `DEFAULT_WORKER_TEST_GATE_TIMEOUT_MS` is 600_000 ms (10 min) — the per-gate-phase cap for `npm run test:fast` / `test:integration` inside the worker lint gate (distinct from the per-ticket umbrella `WORKER_TIMEOUT_SECONDS` R-WTB). `resolveWorkerTestGateTimeoutMs(...)` honors the `PICKLE_WORKER_TEST_FAST_TIMEOUT_MS` env override (strict positive integer, clamped to a 60_000 ms floor, falls back to settings/default on parse failure or sub-floor value). The env override wins over `pickle_settings.json:worker_test_gate_timeout_ms`. BREAKS: shrinking the default back below ~5 min reopens the R-WUWC-1 class (4994-test fast suite SIGTERMs mid-validation, rolls back worker artifacts, flips ticket Failed); dropping the env override path forces operators to redeploy to tune per-machine. ENFORCE: extension/tests/worker-test-fast-timeout.test.js. PATTERN_SHAPE: `DEFAULT_WORKER_TEST_GATE_TIMEOUT_MS = 600_000` and `WORKER_TEST_GATE_TIMEOUT_ENV_VAR = 'PICKLE_WORKER_TEST_FAST_TIMEOUT_MS'` in `pickle-utils.ts`.
- `src/services/git-utils.ts` — INVARIANT: any transition writing ticket `status: "Failed"` with `completion_commit: null` MUST also clear `completion_commit_inferred`. BREAKS: stale completion evidence can auto-promote a failed ticket back to Done. ENFORCE: `extension/tests/git-utils-ticket-frontmatter.test.js`. PATTERN_SHAPE: `updateTicketFrontmatter(... completion_commit: null)` without deleting `completion_commit_inferred`.
- `src/bin/spawn-refinement-team.ts` — INVARIANT: recovered state/settings drive timeout/backend/cwd; manifest writes use `.tmp.<pid>`; readiness gate path includes `extension/bin`. BREAKS: wrong repo/budget, unrecoverable manifest, or silent gate skip. ENFORCE: extension/tests/spawn-refinement-team.test.js, extension/tests/refinement-manifest-atomic.test.js. PATTERN_SHAPE: `getExtensionRoot(), 'bin'`.
- `src/bin/microverse-runner.ts` (auto-commit staging) — INVARIANT: auto-commit rescue stages tracked and untracked files via the shared staging helper while honoring `docs/`/`prds/` exclusions. BREAKS: `git add -u` drops new tests or first-time `CLAUDE.md` and misclassifies the iteration as stalled. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/microverse-runner.ts` (baseline capture) — INVARIANT: per-iteration gates capture `gate/baseline.json` from the clean pre-iteration tree before the first worker commit. BREAKS: the first regression becomes the accepted baseline. ENFORCE: extension/tests/integration/anatomy-park-baseline-gate.test.js.
- `src/services/convergence-gate.ts` (baseline write verification) — INVARIANT: `runGate({mode:'baseline'})` post-write disk verification is mandatory; after writing `baselinePath`, the gate must prove the file exists on disk and must not return a success result without `fs.existsSync(baselinePath)` or an equivalent access/stat probe. BREAKS: callers log or act on a green baseline while `gate/baseline.json` is absent. ENFORCE: extension/tests/services/convergence-gate-baseline-write-verify.test.js, extension/tests/test-registration-hygiene.test.js. PATTERN_SHAPE: `rg -U --pcre2 "opts\\.mode === 'baseline'[\\s\\S]*writeStateFile\\(baselinePath,[\\s\\S]*(fs\\.promises\\.access\\(baselinePath\\)|fs\\.existsSync\\(baselinePath\\))" extension/src/services/convergence-gate.ts`.
- `src/services/convergence-gate.ts` (R-APBN-1 baseline-mode no-project-type write) — INVARIANT: when `runGate({mode:'baseline', baselinePath, ...})` enters its early-return paths because `detectProjectType(workingDir) === null` OR the detected project type has no `cmdMap` entry, the function MUST still write `baselinePath` with a valid `GateBaselineFile` (empty `checks`, empty `failures`, `project_type: <detected|null>`, `captured_at: now`) BEFORE returning, then emit `gate_skipped` with the existing reason (`no_project_type_detected` or `project_type_low_confidence`). Non-baseline modes still skip without writing. PATTERN_SHAPE: both `if (!projectType)` and `if (!cmdMap)` branches in `runGate` MUST call the empty-baseline writer when `opts.mode === 'baseline'` AND `opts.baselinePath` is set. BREAKS: anatomy-park / szechuan-sauce phases targeted at a workingDir with no project marker (e.g. monorepo whose package lives in a subdir like `extension/`) silently fail at iter 1 with `gate_baseline_init_failed` because the trap-door at `microverse-runner.ts:capturePerIterationGateBaseline` cannot distinguish "intentionally skipped" from "forgot to write". ENFORCE: extension/tests/services/convergence-gate-baseline-no-project-type.test.js, extension/tests/services/convergence-gate-baseline-write-verify.test.js.
- `src/bin/microverse-runner.ts` (baseline-init log gating) — INVARIANT: baseline-init success logs fire only after the expected `gate/baseline.json` exists on disk; a successful baseline-mode `runGate` return with no baseline file must log failure, emit `gate_baseline_init_failed`, and throw. BREAKS: operators see "initialized per-iteration gate baseline" for a missing baseline and later strict-mode regressions look like valid comparisons. ENFORCE: extension/tests/microverse-runner-baseline-init.test.js, extension/tests/test-registration-hygiene.test.js. PATTERN_SHAPE: `rg -U --pcre2 "capturePerIterationGateBaseline[\\s\\S]*pathExists\\(opts\\.baselinePath\\)[\\s\\S]*successMessage" extension/src/bin/microverse-runner.ts`.
- `src/bin/microverse-runner.ts` (stale baseline) — INVARIANT: stale baselines are recaptured from the clean loop-start tree before reuse, honoring age-by-iteration and age-by-seconds controls. BREAKS: resumed sessions subtract obsolete failures forever. ENFORCE: extension/tests/integration/anatomy-park-baseline-gate.test.js.
- `src/bin/microverse-runner.ts` (tests in gate) — INVARIANT: per-iteration gates baseline and replay `tests` with `typecheck` and `lint`. BREAKS: pure test regressions slip through green. ENFORCE: extension/tests/integration/anatomy-park-baseline-gate.test.js.
- `src/bin/microverse-runner.ts` (worker convergence) — INVARIANT: worker-managed convergence signals must pass the per-iteration gate, and red final-iteration gates defer convergence. BREAKS: unresolved toolchain failures can still exit as converged. ENFORCE: extension/tests/integration/anatomy-park-baseline-gate.test.js.
- `src/bin/microverse-runner.ts` (worker subprocess error cap) — INVARIANT: worker-mode iteration timeouts arrive as `{ completion: 'error', timedOut: true, stallReason: 'wall_clock'|'output_stall' }`; `handleWorkerSubprocessError` increments `consecutive_subprocess_errors`, returns `continue` below `Defaults.WORKER_CONSECUTIVE_ERROR_CAP` (default 3) and `error` at the cap; surrounding plumbing populates `state.last_error` and `state.last_subprocess_error` plus a `subprocess_error` activity event before cap logic. BREAKS: worker iteration errors regress to the manager-mode error path and skip the cap. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/microverse-runner.ts` (state reads) — INVARIANT: normal `state.json` control-flow reads use `StateManager.read()`, never raw `JSON.parse(fs.readFileSync(...))`. BREAKS: orphan tmp state can keep cancelled loops running or choose the wrong backend. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/microverse-runner.ts` (ownership) — INVARIANT: runner ownership claims stamp `pid = process.pid` when setting the session active. BREAKS: crashed active sessions cannot be demoted by dead-pid recovery. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/microverse-runner.ts` (deactivation) — INVARIANT: clean-success finalize uses `finalizeTerminalState({ step: 'completed', runnerIteration, exitReason })`; forensic paths (signal handler, fatal top-level catch, gap-analysis failure, finalize fallback) use `safeDeactivate(statePath)` plus `recordExitReason` to preserve step/current_ticket for postmortem. BREAKS: terminal-success exits leak stale step/current_ticket, OR lock-acquisition failures collapse state to `{ active: false }` and erase resume pointers. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/microverse-runner.ts` (worker/fatal state) — INVARIANT: convergence and fatal `microverse.json` reads promote newer dead `.tmp.<pid>` snapshots. BREAKS: stale convergence or regression counters. ENFORCE: extension/tests/microverse.test.js, extension/tests/microverse-convergence.test.js.
- `src/bin/microverse-runner.ts` (final reports) — INVARIANT: report filenames use local-day keys. BREAKS: evening US runs file reports under tomorrow's UTC date. ENFORCE: extension/tests/microverse-final-report.test.js.
- `src/bin/microverse-runner.ts` (rate limits) — INVARIANT: wait state preserves API reset metadata and emits `rate_limit_wait`. BREAKS: monitor/activity consumers lose wait source/type. ENFORCE: extension/tests/rate-limit.test.js.
- `src/bin/microverse-runner.ts` (startup settings) — INVARIANT: convergence-gate and failure-classification settings promote newer dead tmp; numeric gate controls stay positive integers; `enabled_convergence_files` stays a non-empty trimmed string allowlist. BREAKS: worker gates use stale config, warped budgets, or silently disable anatomy gating. ENFORCE: extension/tests/microverse.test.js, extension/tests/failure-classification.test.js, extension/tests/integration/anatomy-park-baseline-gate.test.js.
- `src/services/convergence-gate.ts` — INVARIANT: per-iteration baseline freshness uses `captured_iteration` age, not absolute session iteration. BREAKS: long-lived anatomy sessions recapture full toolchain baselines every loop after the age ceiling. ENFORCE: extension/tests/services/convergence-gate-baseline-freshness.test.js, extension/tests/integration/anatomy-park-baseline-gate.test.js. PATTERN_SHAPE: `captured_iteration|baseline iteration age`.
- `src/bin/microverse-runner.ts` (remediation results) — INVARIANT: per-iteration remediation result reads promote dead tmp artifacts. BREAKS: successful remediation increments `iteration_regressions`. ENFORCE: extension/tests/microverse.test.js.
- `src/bin/council-publish.ts` (directive/stack parity) — INVARIANT: stack/directive tmp snapshots are recovered before parity checks, and every non-trunk stack branch has a directive entry. BREAKS: stale publish plans or silent empty-findings comments. ENFORCE: extension/tests/council-publish.test.js.
- `src/bin/pipeline-runner.ts` — INVARIANT: startup reuses recoverable `scope.json`/`pipeline.json`; startup and every phase boundary call `claimPipelineRunnerActive()` to set `state.active=true`, stamp `pid=process.pid`, and clear stale completed/failed `exit_reason` before watchers can poll; finalize is a three-case contract: (a) when `phaseIncomplete || handoffStop`, calls `finalizeTerminalState({ step: 'completed' })` WITHOUT `exitReason` to preserve the already-stamped handoff/phaseIncomplete exit_reason (R-PRH), (b) normal success calls `finalizeTerminalState({ step: 'completed', exitReason: 'completed' })`, (c) normal failure calls `finalizeTerminalState({ step: 'completed', exitReason: 'failed' })`; forensic paths (signal handler, top-level fatal catch) use `safeDeactivate` plus `recordExitReason`. The fatal-catch path MUST deactivate — without it `active: true` strands forever on pipeline crash. BREAKS: stale/widened resumes, wrong phase dispatch, watchers terminating between phases, or stuck active sessions on crash; collapsing the three-case finalize to a uniform two-arg call overwrites the already-stamped handoff exit_reason with the generic `failed` label (R-PRH regression). ENFORCE: extension/tests/anatomy-park-scope.test.js, extension/tests/szechuan-scope.test.js, extension/tests/pipeline-runner-dispatch.test.js, extension/tests/pipeline-runner.test.js. PATTERN_SHAPE: `claimPipelineRunnerActive`.
- `src/bin/pipeline-runner.ts` (phase-step write) — INVARIANT: phase entry writes `state.step` to the phase name before dispatch. BREAKS: monitors, resume logic, and forensic state can report stale lifecycle phases. ENFORCE: extension/tests/integration/pipeline-state-coherence.test.js, extension/tests/test-registration-hygiene.test.js. PATTERN_SHAPE: `state.step = '<phase>'` at phase boundary.
- `src/bin/pipeline-runner.ts` (anatomy config) — INVARIANT: `anatomy-park.json` initializes every subsystem map key to `0` or `[]`. BREAKS: first worker `+= 1` writes `NaN` and corrupts convergence. ENFORCE: extension/tests/anatomy-park-scope.test.js. PATTERN_SHAPE: `pass_counts|consecutive_clean|stall_counts|findings_history`.
- `src/bin/pipeline-runner.ts` (status) — INVARIANT: `pipeline-status.json` uses tmp-rename writes and monitor recoverable reads. BREAKS: inactive completed pipelines can keep monitor panes alive. ENFORCE: extension/tests/monitor.test.js.
- `src/bin/pipeline-runner.ts` (dirty-tree guard) — INVARIANT: launch filtering starts from git dirty paths, then exempts `prds/`, `docs/`, `.gitignore` matches, and entries listed in `extension/.pipeline-runner-dirty-allowed.json`; fatal stderr lists each remaining blocking file on its own line. BREAKS: pipeline launch deadlocks on runtime-mutated evidence files or hides the real blocker behind a generic dirty-tree message. ENFORCE: `extension/tests/integration/pipeline-runner-dirty-tree-guard.test.js` (created) by ticket 1e821336. PATTERN_SHAPE: `DIRTY_ALLOWED_FILE_REL|isGitIgnoredPath|Dirty files:`.
- `src/bin/pipeline-runner.ts` (R-CCR-3 stale-handoff clearance) — INVARIANT: in `runPhaseIteration`, when `readHandoffExitReason` returns a handoff reason but the phase runner exits non-zero, the stale reason MUST be cleared via `clearExitReason(runtime.statePath)` BEFORE the normal `shouldHaltAfterPhase` path runs; ONLY a zero exit may return `{ action: 'break' }` for a clean handoff stop. BREAKS: removing the `exitCode === 0` guard causes the "twin-read leak" — a failed phase runner that leaves a stale `manager_handoff_pending` or `closer_handoff_terminal` in `state.exit_reason` is mistaken for a deliberate clean handoff stop; `finalizePipeline` reads `handoffStop=true`, calls `finalizeTerminalState` without exitReason, and the failure exits 0 instead of non-zero. ENFORCE: extension/tests/pipeline-runner.test.js. PATTERN_SHAPE: `clearExitReason` call inside `runPhaseIteration` guarded by `handoffReason` truthy and `exitCode !== 0`.
- `src/bin/mux-runner.ts` (R-ICP-1 cap-exit code 3) — INVARIANT: `iteration_cap_exhausted` exit MUST map to exit code 3 (not 1 or 0); the code path `if (exitReason === 'iteration_cap_exhausted') exitCode = 3;` MUST appear before the `isFailedExit` fallback at the end of `main()`. Code 3 is the same value as `PipelineRunnerExitCode.PhaseIncomplete` so `pipeline-runner` can call `reportPhaseIncomplete` (stamps `pipeline_phase_incomplete` + logs unfinished list) rather than `logPhaseHaltReason`. BREAKS: changing `iteration_cap_exhausted` to exit code 1 makes pipeline-runner fall through to `shouldHaltAfterPhase` (also halts, but silently omits the unfinished-ticket list and stamps `exit_reason = 'failed'` instead of `pipeline_phase_incomplete` — auto-resume.sh R-CNAR-4(c) stop condition then does not fire). ENFORCE: extension/tests/mux-runner-iteration-cap-exit.test.js. PATTERN_SHAPE: `if (exitReason === 'iteration_cap_exhausted') exitCode = 3`.
- `src/bin/mux-runner.ts` (R-WUWC SOFT-variant auto-promote) — INVARIANT: `guardCompletionCommitBeforeDone` MUST, when post-backoff evidence is `{ source: 'inferred', sha: <SHA> }`, invoke `autoFillCompletionCommit({ sessionDir, workingDir, ticketId, statePath: null })` to persist the SHA into the ticket frontmatter, then re-probe `hasCompletionCommit` before returning. This is the runtime equivalent of the documented operator workaround `edit ticket frontmatter to include completion_commit: <sha>`. Best-effort: try/catch around the auto-fill MUST fall through to the existing `ok: false` classification on failure. BREAKS: removing the auto-promotion re-opens the R-WUWC SOFT-variant pipeline halt: a worker that commits with the ticket-id in the message but forgets to stamp `completion_commit:` flips the ticket frontmatter to Done; the guard refuses with `done_without_commit_evidence`, and the pipeline halts at the first such ticket (live incident 2026-05-23 session `2026-05-23-17b2f716`, tickets `be5a047d`, `aec0cda1`, near-miss `8fefc179`). ENFORCE: extension/tests/guard-completion-commit-auto-promote.test.js. PATTERN_SHAPE: `autoFillCompletionCommit\(` inside `guardCompletionCommitBeforeDone` guarded by `evidence.source === 'inferred' && evidence.sha`.
- `src/services/pickle-utils.ts` (R-CCQF quoted-form completion_commit parser) — INVARIANT: `hasCompletionCommit` MUST normalize the frontmatter `completion_commit` / `completion_commit_inferred` field via `normalizeCompletionCommitField(raw)` before SHA validation. The normalizer strips leading/trailing single OR double quotes (paired or unpaired) and trims whitespace, then validates `^[0-9a-f]{7,40}$/i`. Auto-promote helper writes unquoted short SHAs (e.g. `4b38893c`); codex tool calls and human edits naturally write quoted full SHAs (e.g. `"724f69d4db8aae9b5f8e4ab7f3abfa0a72c5f6c8"`); both shapes MUST classify as `source: 'explicit'`. Parse failure (corrupt SHA, non-hex chars) MUST classify as `'absent'`, not `'inferred'` — a fall-through to `inferred` would conflate parser failure with git-log scan miss. BREAKS: closer workers that write quoted full SHAs (a legitimate, codex-natural shape) are mis-classified as `inferred`, the gate refuses the Done flip, and the pipeline halts with `done_without_commit_evidence` even though the commit landed in git (live incident 2026-05-23 session `2026-05-23-48e6309a` closer ticket `26301c6a`). ENFORCE: extension/tests/has-completion-commit-quoted-form.test.js, extension/tests/has-completion-commit.test.js. PATTERN_SHAPE: `normalizeCompletionCommitField\(readFrontmatterField\(content, 'completion_commit(?:_inferred)?'\)\)` at every SHA-extracting callsite inside `hasCompletionCommit`.
- `src/services/pickle-utils.ts` (R-CCRC-1 ref-code fallback in hasCompletionCommit) — INVARIANT: `hasCompletionCommit` MUST extend its git-log scan to also grep for the ticket's `r_code:` value (read from frontmatter) in addition to the ticket-id. Scan precedence: exact ticket-id match first, then exact `r_code:` match second (word-boundary). Most-recent matching commit wins. BREAKS: commits using R-code in the subject (e.g. `test(R-APWS-7): ...`) or recovery commits citing a different SHA (`fix(<dropped-sha>): recover R-CODE ...`) are missed; gate classifies `absent`; downstream Done-flip flows trip without evidence (3x recurrence on B-APWS, 1x on B-WSRC-GR). ENFORCE: extension/tests/has-completion-commit-ref-code.test.js. PATTERN_SHAPE: `hasCompletionCommit` reads `r_code:` from ticket frontmatter AND includes it in the git-log grep precedence.
- `src/bin/mux-runner.ts` (R-CCRC-2 done-flip guard routing) — INVARIANT: every Done-flip code path in `mux-runner.ts` MUST route through `guardCompletionCommitBeforeDone` (and `clearStaleDoneWithoutCommitEvidence`) BEFORE persisting `status: Done`. The 5th callsite (`applyAutoTicketCompletionValidation` at `mux-runner.ts:1742`) routes via the `allow_inferred_completion_commit: true` flag because the manager drift path enters with `status: 'In Progress'` (guard's internal auto-fill requires status=Done). After `markTicketDone` flips the status, `autoFillCompletionCommit` runs best-effort to persist the SHA. Guard-call count MUST equal `clearStaleDoneWithoutCommitEvidence`-call count (R-PEDC parity). BREAKS: removing the routing re-opens the silent Done-without-completion_commit class — worker commits with ticket-id in message, manager drifts to next ticket, `applyAutoTicketCompletionValidation` flips the old ticket Done WITHOUT firing the R-WUWC SOFT-variant auto-fill; `completion_commit:` field stays empty (live incident 2026-05-24 session `pickle-01199cf9` ticket `eee90f16`). ENFORCE: extension/tests/done-flip-paths-call-guard.test.js. PATTERN_SHAPE: `applyAutoTicketCompletionValidation` body contains `guardCompletionCommitBeforeDone(` AND `clearStaleDoneWithoutCommitEvidence(` AND `autoFillCompletionCommit(` in that order before/around the `markTicketDone(` call.
- `src/bin/mux-runner.ts` (R-PEDC clear-on-recovery exit_reason) — INVARIANT: `clearStaleDoneWithoutCommitEvidence(statePath)` MUST be called immediately AFTER every `guardCompletionCommitBeforeDone({...})` callsite that returns `ok: true` and BEFORE the subsequent `markTicketDone` / `markTicketSkipped` / `Marked ticket … as Done` log line. The helper reads `state.exit_reason` via `readRecoverableJsonObject` and calls `clearExitReason(statePath)` ONLY when the prior stamp is exactly `done_without_commit_evidence`; unrelated exit_reasons (`circuit_open`, `rate_limit_exhausted`, handoffs, etc.) MUST be preserved untouched. Best-effort try/catch so a transient state read/write does not block the happy-path Done flip. Mirrors the R-CCR-3 stale-handoff clearance pattern in `pipeline-runner.ts:runPhaseIteration`. BREAKS: removing the clear lets a prior iteration's `done_without_commit_evidence` stamp survive into `finalizePipeline`, which mislabels `pipeline-status.json:status === "failed"` even when all tickets shipped Done (live incident 2026-05-23 session `2026-05-23-48e6309a`: 3/3 tickets Done but pipeline reported failed). ENFORCE: extension/tests/exit-reason-clears-on-recovery.test.js. PATTERN_SHAPE: `clearStaleDoneWithoutCommitEvidence\(` count MUST equal the count of `guardCompletionCommitBeforeDone\(` callsites in mux-runner.ts (one clear per guard-pass branch).
- `src/bin/mux-runner.ts` (R-MUXQG test-reset contract) — INVARIANT: `qualityGateLegacyWarningLogged` is a process-local flag reset ONLY by `_resetQualityGateSkipDeprecation`; prod code MUST NOT call the reset. BREAKS: R-QGSK-3 deprecation warning must fire once per process; resetting in prod would re-warn on every state read, flooding logs. ENFORCE: extension/tests/mux-runner.test.js. PATTERN_SHAPE: `_resetQualityGateSkipDeprecation` callers outside `extension/tests/` detected by `audit-trap-door-enforcement.sh`.
- `src/bin/pipeline-runner.ts` (R-ICP-2 phase-incomplete halt) — INVARIANT: when a phase runner exits with `PipelineRunnerExitCode.PhaseIncomplete` (= 3), `runPhaseIteration` MUST call `reportPhaseIncomplete` to stamp `state.exit_reason = 'pipeline_phase_incomplete'` and log the unfinished ticket list BEFORE returning `{ action: 'break', phaseIncomplete: true }`; `finalizePipeline` then exits with `PipelineRunnerExitCode.PhaseIncomplete` (3). BREAKS: removing the `exitCode === PipelineRunnerExitCode.PhaseIncomplete` check causes cap-hit to fall through to `shouldHaltAfterPhase` (also halts on non-zero exit code), but silently omits the unfinished-ticket list and stamps `exit_reason = 'failed'` instead of `pipeline_phase_incomplete` — auto-resume.sh R-CNAR-4(c) stop condition then does not trigger. ENFORCE: extension/tests/pipeline-runner-halt-on-incomplete.test.js, extension/tests/iteration-cap-e2e.test.js. PATTERN_SHAPE: `exitCode === PipelineRunnerExitCode.PhaseIncomplete`.
- `src/bin/pipeline-runner.ts` (R-PHC-6 continue-by-default phase-halt invariant) — INVARIANT: `shouldHaltAfterPhase` MUST return `false` for non-fatal `pickle`, `anatomy-park`, and `szechuan-sauce` non-zero exits when downstream remediation phases are queued; citadel severity-threshold halt behavior remains unchanged; strict halt is opt-in only via `--strict-phases` or persisted `state.pipeline_continue_on_phase_fail: false`. BREAKS: if this contract regresses, bundles whose pickle phase exits non-zero lose 100% of downstream remediation work, operator cost doubles, and the operator must file a remediation bundle and re-run the pipeline. ENFORCE: extension/tests/pipeline-runner-phase-fail-continue.test.js cases 1, 3, 7.
- `src/bin/finalize-gate.ts` — INVARIANT: synthetic failures stay in-scope, working_dir/backend share one recovered state snapshot, and settings promote dead tmp with positive numeric caps/timeouts. BREAKS: scoped gates false-green, run in stale repos, or skip/warp remediation. ENFORCE: extension/tests/bin/finalize-gate.test.js.
- `src/bin/finalize-gate.ts`; `src/bin/microverse-runner.ts` (gate result) — INVARIANT: remediation gate results use tmp-rename writes. BREAKS: interrupted writes leave malformed handoffs. ENFORCE: extension/tests/process-iteration-outcome.test.js, extension/tests/bin/finalize-gate.test.js.
- `src/bin/spawn-gate-remediator.ts` — INVARIANT: `--gate-result` promotes newer dead tmp snapshots. BREAKS: brief prep rejects valid interrupted handoffs. ENFORCE: extension/tests/bin/spawn-gate-remediator.test.js.
- `src/services/backend-spawn.ts` — INVARIANT: state-file-backed backend lookups read through `StateManager.read()`. BREAKS: orphan tmp backend flips can route workers/remediators through stale backend. ENFORCE: extension/tests/backend-spawn.test.js, extension/tests/spawn-morty.test.js.
- `src/bin/spawn-morty.ts` (codex model resolution) — INVARIANT: codex worker spawns resolve `-m <model>` via `resolveCodexModel(state)` precedence: `state.codex_model` (trimmed, non-empty) → `pickle_settings.default_codex_model` → undefined; `--ignore-user-config` strips `~/.codex/config.toml`, so absent settings yield no flag and codex CLI uses its compiled default. BREAKS: codex spawns silently use the wrong tier when `default_codex_model` is unset, or per-session `state.codex_model` overrides are dropped, leaving `gpt-5.3-codex-spark` budget unused. ENFORCE: extension/tests/backend-spawn.test.js, extension/tests/spawn-morty.test.js.
- `src/bin/jar-runner.ts` — INVARIANT: state/meta/settings reads recover tmp; timeout and manager-turn budgets stay positive; activation stamps pid; per-task success uses `finalizeTaskSession` (finalizeTerminalState wrapper) while task failure / enoent / shutdown use `safeDeactivate` + `recordExitReason` so failed-task forensics survive. BREAKS: consumed reruns, warped budgets/backend, dead active sessions, OR successful task leaks stale current_ticket into the next jar run. ENFORCE: extension/tests/jar-runner.test.js.
- `src/bin/log-commit.ts` — INVARIANT: commit attribution resolves current cwd from `current_sessions.json` first, then active same-cwd sessions; never pick first active session globally. BREAKS: concurrent loops stamp commits onto the wrong session. ENFORCE: extension/tests/log-commit.test.js.
- `src/bin/cancel.ts` — INVARIANT: cancel resolves only live sessions; same-cwd fallback requires `active === true`. BREAKS: a completed session can hide the real no-active-session condition. ENFORCE: extension/tests/cancel.test.js.
- `src/bin/cancel.ts` (session map) — INVARIANT: map cleanup promotes newer dead `current_sessions.json.tmp.<pid>` before deleting cwd. BREAKS: cancel drops other live map entries. ENFORCE: extension/tests/cancel.test.js.
- `src/bin/log-watcher.ts` — INVARIANT: liveness probes read `state.json` through `StateManager.read()`. BREAKS: monitor feeds tail dead sessions forever. ENFORCE: extension/tests/log-watcher.test.js.
- `src/bin/log-watcher.ts` (R-MWR-4 EOF resilience; AC-MWR-07; ticket 584c5617) — INVARIANT: every poll iteration calls `detectLogTruncation(currentLog, offset, lineBuf)` BEFORE `drainStreamJsonLines`; on `size < offset` the offset/lineBuf reset to `{0, ''}` and exactly one dim `(reconnecting...)` line is printed. The watcher never exits on EOF — only when `StateManager.read()` reports `state.active !== true`. BREAKS: post-truncate content is silently skipped (the `size <= offset` early-return in drainStreamJsonLines), or the `(reconnecting...)` line is replaced/duplicated. ENFORCE: extension/tests/log-watcher.test.js (`R-MWR-4 + R-MWR-6 + R-MWR-8` block).
- `src/bin/log-watcher.ts` (R-MWR-6 banner reservation) — INVARIANT: `◤ FEED TERMINATED ◢` is emitted ONLY from the inactive-state liveness branch (`state.active !== true`). EOF/truncation paths emit at most one dim `(reconnecting...)` line and never the banner. BREAKS: operators see a terminal banner for a transient log truncation and assume the session ended. ENFORCE: extension/tests/log-watcher.test.js (`truncation prints (reconnecting...) NOT FEED TERMINATED`).
- `src/bin/monitor.ts` — INVARIANT: dashboard render and liveness reads recover state; microverse, circuit breaker, and rate-limit JSON promote dead tmp. BREAKS: stale panes, obsolete convergence, hidden API waits. ENFORCE: extension/tests/monitor.test.js.
- `src/bin/monitor.ts` (R-MWR-1 startRespawnWatchdog) — INVARIANT: `main()` arms `startRespawnWatchdog({ sessionDir })` BEFORE entering the render loop; the helper schedules `setInterval(restartDeadWatcherPanes, RESPAWN_WATCHDOG_INTERVAL_MS=30_000)` with `.unref()`, wrapped in best-effort try/catch so a transient tmux failure cannot crash the dashboard. The interval handle is `unref()`'d so the timer never blocks process exit. R-MWR-rename: `RESPAWN_WATCHDOG_INTERVAL_MS` is a DIFFERENT symbol from `MONITOR_STDOUT_WATCHDOG_MS` — never share their values. BREAKS: watcher panes that crash mid-iteration stay dead until the next mux-runner phase boundary calls `ensureMonitorWindow` again. ENFORCE: extension/tests/monitor-watchdog.test.js.
- `src/bin/monitor.ts` (R-MWR-2 kill-switch) — INVARIANT: `process.env.PICKLE_MONITOR_WATCHDOG === 'off'` short-circuits `startRespawnWatchdog` BEFORE scheduling the timer — no tick fires, no tmux probe runs, no log line is written. The function returns `null` so callers can distinguish "watchdog not running" from "tick failed". Off-by-default; only the literal lowercase string `'off'` disables. BREAKS: operators debugging a frozen pane can't suppress the watchdog reviving the pane underfoot, OR a typo (`OFF`/`0`/`false`) silently disables the watchdog. ENFORCE: extension/tests/monitor-watchdog.test.js.
- `src/bin/monitor.ts` (R-MWR-3 log tag) — INVARIANT: watchdog tick passes `'monitor-watchdog'` as the 5th argument to `restartDeadWatcherPanes`, so respawn lines surface in `mux-runner.log` as `monitor-watchdog: respawned <name> in pane <N>` distinct from boundary-driven `restartDeadWatcherPanes:` lines (AC-MWR-05). BREAKS: operators cannot tell whether a respawn was driven by phase-boundary `ensureMonitorWindow` or the continuous heartbeat, so root-causing flapping panes is harder. ENFORCE: extension/tests/monitor-watchdog.test.js (`respawn lines are tagged "monitor-watchdog:"`).
- `src/bin/refinement-watcher.ts` — INVARIANT: fallback reads recover state and manifest tmp before failure. BREAKS: hangs or false refinement-failed warnings. ENFORCE: extension/tests/refinement-watcher.test.js.
- `src/bin/refinement-watcher.ts` (R-MWR-5 manifest-rewrite resilience) — INVARIANT: the watcher polls indefinitely until `shouldStopForInactiveSession` returns true; manifest sightings render the summary BUT do NOT exit. Content-changing manifest rewrites re-render via the `lastManifestKey` JSON-stringified comparison. `shouldStopForInactiveSession` returns true when `state.json` is missing/unreadable OR (`state.active === false` AND `state.step !== 'prd'`) — there is no longer a 3-second sleep + manifest re-check (relic of the pre-R-MWR-5 break-on-manifest design). The R-MWR-6 file-tail banner reservation does NOT apply — refinement-watcher's terminal banners are "🥒 Refinement Complete" and "Session inactive with no manifest". BREAKS: cycle 1 manifest is overwritten by cycle 2 manifest mid-stream and the watcher either races to exit on first sighting (dropping cycle 2's render) OR loops forever after refinement completes (because the legacy 3s manifest re-check never fires). ENFORCE: extension/tests/refinement-watcher-manifest-rewrite.test.js.
- `src/bin/metrics.ts` — INVARIANT: report ranges display the last represented day, CLI dates round-trip exact `YYYY-MM-DD`, and `--json` always emits a `MetricsReport`. BREAKS: future-looking ranges, normalized impossible dates, or prose empty JSON output. ENFORCE: extension/tests/metrics.test.js.
- `src/bin/morty-watcher.ts` — INVARIANT: liveness probes read `state.json` through `StateManager.read()`. BREAKS: worker feeds tail dead sessions forever. ENFORCE: extension/tests/morty-watcher.test.js.
- `src/bin/morty-watcher.ts` (R-MWR-4 EOF resilience) — INVARIANT: every poll iteration calls `detectLogTruncation(currentLog, offset, '')` BEFORE `drainLog`; on `size < offset` the offset resets to 0 and exactly one dim `(reconnecting...)` line is printed. The watcher never exits on EOF — only when `StateManager.read()` reports `state.active !== true`. BREAKS: post-truncate worker_session log content is silently skipped (the `size <= offset` early-return in drainLog). ENFORCE: extension/tests/log-watcher.test.js (`morty-watcher: truncate → reset offset` parametrized).
- `src/bin/raw-morty.ts` — INVARIANT: liveness probes read `state.json` through `StateManager.read()`. BREAKS: raw feeds tail dead sessions forever. ENFORCE: extension/tests/raw-morty.test.js.
- `src/bin/raw-morty.ts` (R-MWR-4 EOF resilience) — INVARIANT: every poll iteration calls `detectLogTruncation(currentLog, offset, lineBuf)` BEFORE `drainStreamJsonLines`; on `size < offset` the offset/lineBuf reset to `{0, ''}` and exactly one dim `(reconnecting...)` line is printed. The watcher never exits on EOF — only when `StateManager.read()` reports `state.active !== true`. R-MWR-6 banner reservation: `◤ FEED TERMINATED ◢` fires ONLY from the inactive branch. BREAKS: post-truncate content is silently skipped, or transient truncation looks like session termination. ENFORCE: extension/tests/log-watcher.test.js (`raw-morty: truncate → reset offset` parametrized).
- `src/services/pickle-utils.ts` (R-MWR-4 detectLogTruncation) — INVARIANT: `detectLogTruncation(logPath, offset, lineBuf)` is the single shared truncation primitive consumed by the three file-tail watchers (log-watcher, morty-watcher, raw-morty). On `fs.statSync(logPath).size < offset` it returns `{ offset: 0, lineBuf: '', truncated: true }`; on missing/unreadable file or `size >= offset` it returns the inputs unchanged with `truncated: false`. Caller uses the boolean to print exactly one dim `(reconnecting...)` line per truncation. BREAKS: a per-watcher local truncation check drifts from the shared contract (e.g., resets offset on missing file, or fails to reset on small truncation), causing post-truncate content drops or reconnect-line floods. ENFORCE: extension/tests/log-watcher.test.js (`detectLogTruncation:` direct tests).
- `src/services/pickle-utils.ts` (R-MWR-3 restartDeadWatcherPanes log tag) — INVARIANT: `restartDeadWatcherPanes` 5th argument `logTag` defaults to `'restartDeadWatcherPanes'` for boundary-driven invocations and is overridden to `'monitor-watchdog'` only by `startRespawnWatchdog` in monitor.ts. The tag is interpolated into every appendWatcherRestartLog line so AC-MWR-05 grep can distinguish phase-boundary respawns from continuous heartbeat respawns. BREAKS: a caller forgets the tag and operators see a uniform log shape that hides which path revived a pane. ENFORCE: extension/tests/monitor-watchdog.test.js (`monitor-watchdog: respawned ...` matches), extension/tests/ensure-monitor-window.test.js (`restartDeadWatcherPanes: respawned ...` still matches default tag).
- `src/services/metrics-utils.ts` — INVARIANT: report windows use local-day keys; git/session slugs match; repo discovery handles nested repos/worktrees; cache reads recover timezone and dead tmp. BREAKS: wrong dates, missing/misattributed LOC, stale token totals. ENFORCE: extension/tests/metrics.test.js.
- `src/services/state-manager.ts` — INVARIANT: tmp snapshots recover before schema migration/mutation and same-iteration mtimes are compared. BREAKS: stale state overwrites cancellation/backend metadata. ENFORCE: extension/tests/state-manager.test.js, extension/tests/crash-recovery.test.js, extension/tests/backend-spawn.test.js.
- `src/services/state-manager.ts` (AC-RVN-08 deploy/source parity) — INVARIANT: deployed `extension/types/index.js`, `services/state-manager.js`, and `bin/check-update.js` must reflect compiled output of their source TS at all times; `bash install.sh` is the only sanctioned deploy path; runtime `assertSchemaVersionDeployParity()` is the safety net. BREAKS: schema-mismatch errors on every fresh-process state read; auto-update can re-deliver stale tarballs in a propagating loop. ENFORCE: extension/tests/state-schema-version-deploy-parity.test.js, extension/tests/state-manager.test.js, extension/tests/install-script.test.js.
- `src/services/state-manager.ts` (paused-orphan demotion) — INVARIANT: `recoverStaleActiveFlag` demotes a session whose `active=true` but PID is dead AND no current claim is found, recording `exit_reason='orphan-paused-no-claim'` and emitting a `paused_session_orphan_demoted` activity event. BREAKS: paused or crashed sessions stay `active=true` forever, blocking the stop-hook and shadowing live sessions in same-cwd resolution. ENFORCE: extension/tests/services/resolve-state-paused-orphan.test.js, extension/tests/bin/setup-paused-orphan.test.js.
- `src/services/state-manager.ts` (mapped-orphan + phantom demotion) — INVARIANT: `recoverStaleActiveFlag` also demotes `active=true` sessions with `pid=null` when the cwd's `current_sessions.json` PID is dead, and prunes missing session-dir/state map entries by recording `exit_reason='orphan-session-dir-missing'` plus `phantom_session_demoted`. BREAKS: mapped orphans keep shadowing live same-cwd sessions, and phantom map entries survive forever. ENFORCE: extension/tests/services/recover-stale-active-flag-mapped-orphan.test.js, extension/tests/integration/phantom-session-prune.test.js.
- `src/services/state-manager.ts` (R-WSRC-1) — INVARIANT: `update()` and `forceWrite()` validate `state.schema_version <= LATEST_SCHEMA_VERSION` AFTER mutator returns AND BEFORE any disk write, throwing `SchemaVersionAheadError` and emitting `state_write_schema_version_violation`. EXEMPTION: `_internalSchemaBump: true` flag in opts bypasses (used by legitimate `migrateSchema` only). BREAKS: a worker writes a forward-schema state and the running runtime cannot parse it; mux-runner wedges in a 1-warn/sec loop (R-QGSK-3 incident 2026-05-16). ENFORCE: extension/tests/state-manager-schema-write-ceiling.test.js. PATTERN_SHAPE: `update` AND `forceWrite` bodies MUST contain `if (state.schema_version > LATEST_SCHEMA_VERSION) throw new SchemaVersionAheadError(...)` before any `writeStateFile` call.
- `src/services/state-manager.ts` (R-QGSK-3 skip-flag migration) — INVARIANT: `migrateLegacySkipQualityGatesFlags(state)` MUST be called in ALL THREE branches of `migrateSchema` (`schema_version === undefined`, `< schemaVersion`, and `>= 3`). The helper one-way-promotes the first non-empty legacy per-gate skip flag (`skip_readiness_reason` wins over `skip_ticket_audit_reason`) into `state.flags.skip_quality_gates_reason` and deletes both legacy fields. When the unified field is already set, legacy fields are still removed. The `>= 3` branch return value is folded into the `missingPipelineContinueOnPhaseFail || didMigrateRelaunch || didMigrateSignal || didMigrateSkipFlags` disjunction so the migration write fires only when something changed. Canonical flag for new PRDs/tickets: `state.flags.skip_quality_gates_reason` (non-empty trimmed string). Legacy flags `skip_readiness_reason` / `skip_ticket_audit_reason` still work (mux-runner R-QGSK-2 deprecation warning + auto-migrated on first read) but MUST NOT be used in new tickets. BREAKS: omitting the call from any branch leaves legacy-flag-only sessions un-migrated; the runtime call site (mux-runner `resolveQualityGateSkipReason`) correctly reads the unified flag first, so unmigrated sessions degrade silently rather than failing, but they accumulate deprecated fields indefinitely. ENFORCE: extension/tests/state-manager-skip-flags-migration.test.js (AC-4 a..e).
- `src/services/ac-phase-gate.ts` — INVARIANT: manifest command execution always passes finite positive `timeout`. BREAKS: phase/final gates wedge forever on hanging AC commands. ENFORCE: extension/tests/pipeline-runner.test.js. PATTERN_SHAPE: `rg --pcre2 "spawnSync\\([^\\n]+\\{[^}]*encoding: 'utf-8'(?![^}]*timeout)"`.
- `src/services/ac-phase-gate.ts` (R-PVTA host-tool preflight) — INVARIANT: `runCriterion` calls `detectMissingTools(criterion.command)` BEFORE any `spawnSync` and short-circuits with a `tool not installed: <X>` reason for absent non-guaranteed tools. BREAKS: phase/final gates silently skip toolchain checks and report `tool not installed` as a spurious criterion failure instead of a clear preflight block. ENFORCE: extension/tests/services/ac-phase-gate-tool-preflight.test.js. PATTERN_SHAPE: `detectMissingTools(` before any `spawnSync(` in `runCriterion`.
- `src/services/ac-phase-gate.ts` (R-VSGE glob safety) — INVARIANT: string-form criterion command never runs with bare `shell: true`; it runs argv-form or under `set -f` (glob-disabled shell). BREAKS: unquoted glob characters in AC commands expand against the filesystem and contaminate or bypass the test/lint gate. ENFORCE: extension/tests/services/ac-phase-gate-glob-safety.test.js. PATTERN_SHAPE: `shell: true` absent from the criterion-command branch.
- `src/services/recoverable-json.ts` — INVARIANT: recover `.tmp.<pid>` and `.tmp.<pid>.*` JSON snapshots. BREAKS: setup session maps vanish after interrupted rename. ENFORCE: extension/tests/get-session.test.js, extension/tests/resolve-state.test.js. PATTERN_SHAPE: `state|artifact|settings\.json\.tmp\.<pid>(\..+)?`.
- `src/services/recoverable-json.ts` (unreadable orphan tmp) — INVARIANT: unreadable orphan tmp snapshots are not invalid JSON and must never be unlinked during recovery scans. BREAKS: verifier preflight deletes the only recoverable state/artifact before operators can repair permissions. ENFORCE: extension/tests/verify-recapture-fired.test.js. PATTERN_SHAPE: `catch \{ return null; \}`-style tmp parse path feeding `unlinkSync\(tmpPath\)`.
- `src/services/circuit-breaker.ts` — INVARIANT: state reads use `StateManager.read()` and breaker/settings JSON use recoverable readers. BREAKS: stale tmp snapshots hide open breakers or ignore disabled config. ENFORCE: extension/tests/circuit-breaker.test.js.
- `src/services/pr-factory.ts` — INVARIANT: PR/session readers deriving repo path or prompt text from `state.json` use `StateManager.read()`. BREAKS: PR creation can target the wrong repo or prompt. ENFORCE: extension/tests/pr-factory.test.js.
- `src/bin/standup.ts` — INVARIANT: `range.until` is exclusive and never printed directly. BREAKS: headers and empty-range messages drift one day into the future. ENFORCE: extension/tests/standup.test.js.
- `src/bin/standup.ts` (date parsing) — INVARIANT: CLI `--since` round-trips exact `YYYY-MM-DD` components before scanning. BREAKS: impossible dates silently shift report windows. ENFORCE: extension/tests/standup.test.js.
- `src/bin/standup.ts` (activity filtering) — INVARIANT: parsed events are rechecked against `[since, until)` after filename prefiltering. BREAKS: mispartitioned activity lines bleed into the wrong day. ENFORCE: extension/tests/standup.test.js.
- `src/bin/standup.ts` (project tags) — INVARIANT: session project tags read recovered session state. BREAKS: stale working_dir basenames mislabel sessions. ENFORCE: extension/tests/standup.test.js.
- `src/bin/setup.ts` — INVARIANT: resume/state/settings/session-map reads recover tmp; session dir, local-day, integer flags, and settings stay canonical. BREAKS: stale budgets, split sessions, lost map entries, wrong dates, fractional limits. ENFORCE: extension/tests/setup.test.js.
- `src/bin/setup.ts` (R-ICP-3 / R-ICP-4 cap persistence) — INVARIANT: `max_iterations`, `max_time_minutes`, `worker_timeout_seconds`, and `backend` persist into `state.json` at initial setup time via `createInitialState`; on `--resume`, `applyResumeLimitConfig` and `applyResumeModeConfig` read persisted values and honor them as the active cap unless the matching CLI flag is explicit (`explicitFlags.has('max-iterations'|'max-time'|'worker-timeout'|'backend')`). If runtime code zeroes `worker_timeout_seconds`, resume MUST recover the explicit operator override from `state.flags.tier_cap_override.medium.worker_timeout_seconds` instead of re-deriving defaults. BREAKS: a session originally launched with `--max-iterations=100` resumes as cap=15 (default), prematurely truncating long-running bundles; same regression class for worker-timeout, max-time, and backend. ENFORCE: extension/tests/setup-resume-honors-persisted-cap.test.js. PATTERN_SHAPE: `applyResumeLimitConfig|applyResumeModeConfig|createInitialState|tier_cap_override\.medium\.worker_timeout_seconds`.
- `src/bin/mux-runner.ts` (state reads) — INVARIANT: control-flow reads of `state.json` in mux-runner and `runIteration()` use `StateManager.read()`. BREAKS: orphan tmp payloads are ignored or stale work keeps running. ENFORCE: extension/tests/iteration-outcome.test.js, extension/tests/mux-runner.test.js.
- `src/bin/mux-runner.ts` (state-iteration write) — INVARIANT: every iteration_start persists `state.iteration` for the current manager-loop iteration through `StateManager.update()`. BREAKS: resumed loops and progress monitors can report iteration 0 after later manager turns. ENFORCE: extension/tests/mux-runner-state-iteration.test.js, extension/tests/test-registration-hygiene.test.js. PATTERN_SHAPE: `state.iteration = iteration`.
- `src/bin/mux-runner.ts` (deactivation) — INVARIANT: clean-success exits (EPIC_COMPLETED, review_clean, max_iterations limit, operator-set max_time limit) use `finalizeTerminalState({ step: 'completed', runnerIteration: iteration, exitReason })`; the default path must not hit a wall-clock exit because `max_time_minutes` is opt-in (`> 0`) and absent/`0` disables it. Forensic paths (signal shutdown, circuit_open, stall, readiness halt, subprocess error, executeTimeoutHalt) use `safeDeactivate(statePath)` plus `recordExitReason` so step/current_ticket survive for postmortem. BREAKS: success exits leak stale step='research' + current_ticket forever, the default path dies on an implicit time cap, OR forensic paths overwrite the failure step. ENFORCE: extension/tests/iteration-outcome.test.js, extension/tests/mux-runner.test.js.
- `src/bin/mux-runner.ts` (forensic map cleanup) — INVARIANT: forensic exits that use `safeDeactivate + recordExitReason` also remove the runner's own `current_sessions.json` entry. BREAKS: launcher stop-hook and session lookup reattach to dead tmux owners after signal/error exits. ENFORCE: extension/tests/mux-runner.test.js.
- `src/bin/mux-runner.ts` (crash fallback) — INVARIANT: only the explicit crash-path `forceWrite` fallback may raw-parse state. BREAKS: dead command templates or cancelled sessions can be reanimated. ENFORCE: extension/tests/iteration-outcome.test.js.
- `src/bin/mux-runner.ts` (ownership) — INVARIANT: `tmux_mode` ownership claims stamp `pid = process.pid` when taking or refreshing ownership. BREAKS: crashed tmux sessions stay permanently active. ENFORCE: extension/tests/mux-runner-startup-validation.test.js.
- `extension/tests/mux-runner-relaunch.test.js` (fake-tmux-write-vs-read-timing) — INVARIANT: the forced delayed fake-`tmux display-message -p '#{pane_current_command}'` write must stay behind a bounded existence/content barrier before parsing `observed-state.log`. BREAKS: direct reads reintroduce `ENOENT observed-state.log` flakes or remove the regression pressure that proves the barrier. ENFORCE: extension/tests/mux-runner-relaunch.test.js. PATTERN_SHAPE: `waitForFileText|observed-state\\.log|pane_current_command`.
- `src/hooks/handlers/stop-hook.ts` (tmux passthrough) — INVARIANT: the default fallthrough APPROVEs when `state.tmux_mode === true`; launcher conversations of tmux-owned loops must be allowed to stop. BREAKS: launcher chats burn context forever after tmux takes control. ENFORCE: extension/tests/stop-hook-tmux-passthrough.test.js.
- `src/bin/mux-runner.ts` (codex relaunch) — INVARIANT: every per-iteration error branch consults `evaluateCodexManagerRelaunch()` before deactivation; eligible codex runs relaunch below cap with breaker closed, while claude exits. BREAKS: long-lived codex manager timeout strands remaining tickets. ENFORCE: extension/tests/iteration-outcome.test.js, extension/tests/mux-runner.test.js.
- `src/services/manager-relaunch.ts` — INVARIANT: relaunch wall-clock budget uses `elapsedSec >= maxTimeMins * 60`; equality is already exhausted. BREAKS: exact-cap subprocess errors relaunch one extra manager turn after the operator time limit. ENFORCE: extension/tests/iteration-outcome.test.js. PATTERN_SHAPE: `elapsedSec >= maxTimeMins \\* 60`.
- `src/bin/mux-runner.ts` — INVARIANT: relaunch decisions with `reason === 'time_limit'` must terminate via `exit_reason='limit'`, not forensic `'error'`. BREAKS: budget-exhausted sessions look failed and retain non-terminal state. ENFORCE: extension/tests/iteration-outcome.test.js, extension/tests/process-iteration-outcome.test.js. PATTERN_SHAPE: `relaunchDecision\\.reason === 'time_limit'|decision\\.reason === 'time_limit'`.
- `src/bin/mux-runner.ts` (settings) — INVARIANT: runner settings reads promote dead tmp and max-turn budgets stay positive integers. BREAKS: stale controls or malformed manager budgets. ENFORCE: extension/tests/mux-runner.test.js.
- `src/bin/mux-runner.ts` (R-CNAR-1 part 2 cap split) — INVARIANT: `applyTicketTierBudget()` MUST NOT overwrite `state.max_iterations`. The per-ticket tier ceiling lives in `state.current_ticket_max_iterations` (cache); `state.max_iterations` is the operator-set GLOBAL manager-loop cap and survives ticket transitions. The runMuxLoop cap-check fires two independent exits — per-ticket (`budgetIter >= state.current_ticket_max_iterations`) AND global (`state.iteration >= state.max_iterations`) — both with `exit_reason='iteration_cap_exhausted'`. `state.worker_timeout_seconds` IS still overwritten because it's documented as the per-spawn worker budget consumed by spawn-morty/jar-runner/spawn-refinement-team. PATTERN_SHAPE: `applyTicketTierBudget` MUST NOT contain `state.max_iterations = budget.max_iterations`. BREAKS: pre-fix `state.max_iterations = budget.max_iterations` silently truncated the operator's global cap to the smallest tier ceiling encountered; pipeline exited at the per-ticket budget regardless of how much global runway remained. ENFORCE: extension/tests/mux-runner-cap-split.test.js, extension/tests/ticket-tier.test.js.
- `src/bin/mux-runner.ts` (R-CNAR-1 stale-cache short-circuit) — INVARIANT: stale per-ticket cache MUST short-circuit the per-ticket cap-check whenever `state.current_ticket` is nullish, `state.current_ticket_max_iterations` is not a positive integer, `state.current_ticket_budget_start_iteration` is not a non-negative integer, or `state.current_ticket_tier` is outside the allowed tier set. PATTERN_SHAPE: stale-cache guard must read `state.current_ticket` truthy plus positive-integer `state.current_ticket_max_iterations`, non-negative-integer `state.current_ticket_budget_start_iteration`, and an allowed `state.current_ticket_tier`. BREAKS: stale cache on resume could falsely trip the per-ticket guard with no live ticket. ENFORCE: extension/tests/mux-runner-cap-split.test.js, extension/tests/ticket-tier.test.js.
- `src/bin/mux-runner.ts` (R-CNAR-1 iteration_start self-heal) — INVARIANT: on `iteration_start`, `current_ticket=null` plus any populated per-ticket cache field MUST self-heal via atomic cache clearing before the loop proceeds. PATTERN_SHAPE: iteration_start path with nullish `state.current_ticket` clears `current_ticket_max_iterations`, `current_ticket_budget_start_iteration`, and `current_ticket_tier` atomically. BREAKS: `iteration_start` without self-heal leaves the same cache fields stale every loop, re-tripping the stale-cache short-circuit indefinitely. ENFORCE: extension/tests/mux-runner-cap-split.test.js:138-190, extension/tests/ticket-tier.test.js.
- `src/bin/status.ts` — INVARIANT: status rendering uses the same recovered state snapshot as session discovery. BREAKS: status prints stale fields after lookup promoted a newer tmp. ENFORCE: extension/tests/status.test.js.
- `src/bin/retry-ticket.ts` (session resolution) — INVARIANT: resolved session path wins and worker timeout emits a strict positive integer. BREAKS: split writes or retry command spawn failure. ENFORCE: extension/tests/retry-ticket.test.js.
- `src/bin/retry-ticket.ts` (artifact archival) — INVARIANT: retry archives every lifecycle artifact matching `ARTIFACT_PREFIXES` before resetting `Todo`. PATTERN_SHAPE: `file === \`${prefix}.md\` || file.startsWith(\`${prefix}_\`)`. BREAKS: stale implementation/review evidence can validate a later failed rerun. ENFORCE: extension/tests/retry-ticket.test.js.
- `src/bin/worker-setup.ts` — INVARIANT: implicit same-cwd bootstrap requires an active session, and explicit `--resume` targets are authoritative and fail closed when missing. BREAKS: workers attach to inactive fallback sessions or wrong live sessions. ENFORCE: extension/tests/worker-setup.test.js.
- `src/services/pickle-utils.ts` — INVARIANT: updateState numeric controls stay integer and runner-valid. BREAKS: manual state edits make mux-runner reject startup. ENFORCE: extension/tests/update-state.test.js.
- `src/services/pickle-utils.ts` (R-CNAR-1 tier-cap override precedence) — INVARIANT: `getTicketTierBudgetWithOverrides(state, tier, settings?)` is the canonical ticket-tier budget accessor; precedence is `state.flags.tier_cap_override.<tier>.<field>` > `pickle_settings.tier_caps.<tier>.<field>` > `TICKET_TIER_BUDGETS[<tier>].<field>`. Per-field resolution: `max_iterations` and `worker_timeout_seconds` resolve independently so partial overrides never cascade-zero the other field. Invalid (non-positive-integer) values fall through. `pickle_settings.schema_version` v1 (absent) and v2 (= 2) are both honored. Legacy `ticketTierBudget`/`ticketInfoBudget` are thin shims that pass `state=null` and `settings=null`, yielding compiled defaults. BREAKS: per-session operator overrides for stuck tickets are silently ignored, OR settings overrides are dropped when state.flags is also set. ENFORCE: extension/tests/ticket-tier.test.js, extension/tests/state-field-invariants.test.js.
- `src/services/pickle-utils.ts`; `src/bin/microverse-runner.ts` (notifications) — INVARIANT: macOS `osascript` shell-outs route through `displayMacNotification` with `NOTIFICATION_TIMEOUT_MS`; terminal cap-exit paths must call that helper, never `spawnSync('osascript', …)` directly. BREAKS: Notification Center hangs block exit, leak cancel markers, or hang tmux panes. ENFORCE: extension/tests/notification-hang-guard.test.js, extension/src/bin/__tests__/microverse-runner.notify-operator.spec.ts. PATTERN_SHAPE: `spawnSync\\(['\"]osascript['\"]` outside `displayMacNotification` helper.
- `src/services/pickle-utils.ts` (getExtensionRoot validation) — INVARIANT: `getExtensionRoot()` accepts `EXTENSION_DIR` only when the extension sentinel exists; otherwise it falls back to the canonical root and emits stderr plus an activity event. BREAKS: inherited stale temp roots spawn missing watcher scripts forever. ENFORCE: extension/tests/get-extension-root-fallback.test.js, extension/tests/test-registration-hygiene.test.js. PATTERN_SHAPE: `extensionRootSentinelExists`.
- `src/services/pickle-utils.ts` (data root) — INVARIANT: `PICKLE_DATA_ROOT` is canonical, `PICKLE_DATA_DIR` is legacy fallback, and both feed every `getDataRoot()` caller consistently. BREAKS: setup, status, standup, hooks, and jar/session readers split storage roots. ENFORCE: extension/tests/pickle-utils.test.js, extension/tests/standup.test.js.
- `src/services/pickle-utils.ts` (session discovery) — INVARIANT: `current_sessions.json` is a cache; same-cwd `sessions/*/state.json` fallback can find live sessions after stale or failed map writes. BREAKS: cancel/status/retry/worker resume falsely report no active session. ENFORCE: extension/tests/cancel.test.js, extension/tests/get-session.test.js, extension/tests/retry-ticket.test.js, extension/tests/status.test.js, extension/tests/worker-setup.test.js.
- `src/services/pickle-utils.ts` (lookup recovery) — INVARIANT: session lookup reads use `StateManager.read()` so dead-pid active sessions demote before ranking. BREAKS: stale dead sessions shadow intended sessions. ENFORCE: extension/tests/get-session.test.js, extension/tests/retry-ticket.test.js.
- `src/services/pickle-utils.ts` (prune recovery) — INVARIANT: `pruneOldSessions()` reads recovered state and ignores implausibly future `started_at` in favor of on-disk mtime. BREAKS: dead or clock-skewed sessions pin old directories forever. ENFORCE: extension/tests/get-session.test.js, extension/tests/status.test.js, extension/tests/cancel.test.js, extension/tests/worker-setup.test.js, extension/tests/setup.test.js, extension/tests/log-commit.test.js.
- `src/services/pickle-utils.ts` (mapped-session evidence) — INVARIANT: mapped sessions with inactive, missing/non-boolean active, unreadable/missing state, dead pid, or wrong cwd rank below live same-cwd fallback. BREAKS: stale map entries target the wrong session. ENFORCE: extension/tests/get-session.test.js, extension/tests/retry-ticket.test.js.
- `src/services/pickle-utils.ts` (cwd-less mapping) — INVARIANT: mapped sessions with missing/empty `working_dir` are compatibility fallbacks only after same-cwd scan. BREAKS: cwd-less sessions outrank scanned live sessions. ENFORCE: extension/tests/get-session.test.js, extension/tests/retry-ticket.test.js.
- `src/services/pickle-utils.ts` (fallback recency) — INVARIANT: same-rank fallback scans choose the newest recovered session by state mtime or valid `started_at`, ignoring future-dated skew. BREAKS: stale clock-skewed sessions outrank fresher state. ENFORCE: extension/tests/get-session.test.js, extension/tests/retry-ticket.test.js.
- `src/services/pickle-utils.ts` (jar cwd) — INVARIANT: `addToJar()` uses recovered state for `working_dir`. BREAKS: jar metadata captures stale cwd. ENFORCE: extension/tests/jar-utils.test.js.
- `src/services/pickle-utils.ts` (monitor mode) — INVARIANT: `inferMonitorMode()` reads recovered state before choosing tmux layout. BREAKS: stale `command_template` spawns the wrong monitor layout. ENFORCE: extension/tests/ensure-monitor-window.test.js.
- `src/services/pickle-utils.ts` (restartDeadWatcherPanes) — INVARIANT: existing active monitor windows recover dashboard pane 0 and watcher panes 1, 2, and 3 by respawning non-`node` panes while leaving active `node` panes and inactive sessions alone; pane 0 recovery is enforced when the existing monitor window is still active but its dashboard pane is dead. BREAKS: monitor window has stale monitor or watcher panes for the rest of the pipeline lifetime; user has to manually relaunch each pane. ENFORCE: extension/tests/ensure-monitor-window.test.js (`restartDeadWatcherPanes: respawns dead pickle monitor and watcher panes 0, 1, 2, and 3`, `restartDeadWatcherPanes: all monitor panes already running node is a no-op`, `restartDeadWatcherPanes: inactive session skips pane probing and respawn`).
- `src/hooks/resolve-state.ts` (map fallback) — INVARIANT: hook resolution treats `current_sessions.json` as cache and falls back to same-cwd `sessions/*/state.json`. BREAKS: protections fail open after skipped or stale map writes. ENFORCE: extension/tests/resolve-state.test.js, extension/tests/config-protection.test.js.
- `src/hooks/resolve-state.ts` (recoverable reads) — INVARIANT: lookup and active-state reads use `StateManager.read()` so dead-pid active sessions demote. BREAKS: protections stay armed after a worker dies. ENFORCE: extension/tests/resolve-state.test.js, extension/tests/config-protection.test.js, extension/tests/stop-hook.test.js.
- `src/hooks/resolve-state.ts` (setup map distrust) — INVARIANT: hook handlers do not trust the map as sole truth because setup writes `state.json` before map update. BREAKS: live sessions are invisible to hooks when map update fails. ENFORCE: extension/tests/resolve-state.test.js, extension/tests/config-protection.test.js.
- `src/hooks/resolve-state.ts` (mapped-session filter) — INVARIANT: mapped state that is inactive, dead, unreadable, or for another cwd is ignored ahead of live same-cwd fallback. BREAKS: stale map entries suppress real active state. ENFORCE: extension/tests/resolve-state.test.js, extension/tests/config-protection.test.js, extension/tests/stop-hook.test.js.
- `src/hooks/resolve-state.ts` (fallback recency) — INVARIANT: same-cwd scans pick newest active session, then newest inactive fallback, by `started_at` or state mtime while ignoring future-dated skew. BREAKS: older or clock-skewed sessions shadow live repo state. ENFORCE: extension/tests/resolve-state.test.js, extension/tests/config-protection.test.js.
- `src/hooks/resolve-state.ts` (env candidate) — INVARIANT: `PICKLE_STATE_FILE` is only a candidate and falls back when it points at another cwd, inactive session, or unreadable state. BREAKS: stale env state disables active-repo protections. ENFORCE: extension/tests/resolve-state.test.js.
- `src/hooks/handlers/config-protection.ts` — INVARIANT: overrides use resolved state-file dir and feature flags read recovered `pickle_settings.json.tmp.<pid>`. BREAKS: stale dirs block overrides or stale settings ignore disabled protection. ENFORCE: extension/tests/config-protection.test.js.
- `src/hooks/handlers/stop-hook.ts` — INVARIANT: session state uses `StateManager.read()` and settings updates use recoverable JSON. BREAKS: stale active workers block exits or disabled auto-update still spawns. ENFORCE: extension/tests/stop-hook.test.js, extension/tests/stop-hook-state-matrix.test.js.
- `src/hooks/handlers/stop-hook.ts (idle backoff)` — INVARIANT: after 3 consecutive wait-pattern manager turns, stop-hook suppresses nudges until `state.json` mtime, worker artifact mtime, worker liveness, or the bounded fallback timer releases it; fallback-timer releases preserve a 2-turn warm state so the next degenerate wait re-engages immediately instead of burning three more manager turns. BREAKS: large-ticket worker waits churn manager turns and silently force mid-worker manager respawns. ENFORCE: extension/tests/stop-hook-idle-backoff.test.js, extension/tests/integration/manager-turn-budget-large-worker.test.js, `bash scripts/audit-trap-door-enforcement.sh`.
- `src/hooks/handlers/stop-hook.ts (update cadence)` — INVARIANT: `update_check_interval_hours` converts with `hours * 3600` before rate-limit comparison. BREAKS: completion hooks respawn update checks far too often. ENFORCE: extension/tests/stop-hook.test.js.
- `src/services/pickle-utils.ts`; `src/bin/retry-ticket.ts` — INVARIANT: retrying a ticket to `Todo` clears resolution timestamps, and later `Done`/`Skipped` transitions replace those keys. BREAKS: stale or duplicate frontmatter makes `parseTicketFrontmatter()` read the wrong state. ENFORCE: extension/tests/retry-ticket.test.js, extension/tests/pickle-utils.test.js.
- `src/services/git-utils.ts` — INVARIANT: ticket `updated` dates use `formatLocalDateKey()`, not UTC ISO slicing. BREAKS: evening updates land on tomorrow's ticket day. ENFORCE: extension/tests/git-utils.test.js.
- `src/services/jar-utils.ts` — INVARIANT: jar task directory dates use `formatLocalDateKey()`, not UTC ISO slicing. BREAKS: evening jarrings land under tomorrow's queue day. ENFORCE: extension/tests/jar-utils.test.js.
- `src/services/activity-logger.ts` — INVARIANT: activity JSONL filenames derive from the event timestamp as an explicit local-day `YYYY-MM-DD` key. BREAKS: historical or buffered writes land in the wrong file, and locale names become unreadable to pruning/reporting. ENFORCE: extension/tests/activity-logger.test.js.
- `src/bin/check-update.ts` — INVARIANT: cache/settings reads promote tmp, cache versions are semver or stale, update intervals are finite, writes use tmp rename, release downloads MUST use `gh release download -p '*.tar.gz'` not `-A tar.gz`, and updater inspection/install must extract exactly one payload root shared by `extension/package.json` and `install.sh`. BREAKS: source archives, split roots, or duplicate payload roots pass selection and can splice metadata from one subtree with installer code from another. ENFORCE: extension/tests/check-update.test.js. PATTERN_SHAPE: `gh release download .* (-A|--archive) '?tar\.gz'?|(^|[^[:alnum:]_])install\.sh$|(^|[^[:alnum:]_])extension/package\.json$` validated without counting shared roots.
- `src/services/worker-shutdown.ts` — INVARIANT: `flushAndExit(sessionLog, code)` calls `sessionLog.end()`, awaits the `'close'` event via `once(sessionLog, 'close')`, then calls `process.exit(code)`. Addresses graceful exit only; SIGKILL/segfault/OOM still produce 0-byte logs — R-WSE-2 catches all classes via `worker_partial_lifecycle_exit` event. BREAKS: replacing `sessionLog.end()` with `sessionLog.destroy()` skips the `'finish'`→`'close'` drain, leaving buffered data on the floor; removing `await once(sessionLog, 'close')` races `process.exit` against the OS write queue. ENFORCE: extension/tests/services/worker-shutdown.test.js.
- `src/bin/mux-runner.ts` (R-WSE-2 partial lifecycle exit) — INVARIANT: after every `runIteration()` call, `checkPartialLifecycleExit(sessionDir, statePath, ticketId)` runs in a best-effort try/catch and emits `worker_partial_lifecycle_exit` with `{ ticket, gate_payload: { artifacts_missing: string[], session_log_size: int } }` when `research_review.md` ends with `APPROVED` but at least one of `plan`, `conformance`, or `code_review` artifact prefixes is absent from the ticket dir. BREAKS: removing the check silently drops the only audit signal for partial lifecycle exits caused by SIGKILL/segfault/OOM (which R-WSE-1's graceful-exit fix cannot catch). ENFORCE: `grep -c "worker_partial_lifecycle_exit" extension/src/bin/mux-runner.ts` ≥ 2; extension/tests/activity-event-payload.test.js.
- `src/bin/mux-runner.ts` (R-WSE-3 failed-after-research-approved breadcrumb) — INVARIANT: after every `runIteration()` call, `checkFailedAfterResearchApproved(sessionDir, ticketId)` runs in the same best-effort try/catch and emits a stderr line matching `/\[warn\] \[<ISO-8601>\] ⚠ ticket <id> failed AFTER research APPROVED — see <session_dir>\/<id>\//` when `getTicketStatus()` resolves to `failed` AND `research_review.md` ends with `APPROVED`. BREAKS: removing the call drops the operator-visible breadcrumb for tickets that regressed from a research-approved state. ENFORCE: extension/tests/ticket-fail-after-research-approved.test.js.
- `extension/tests/wuwc-reproducer.test.js` (R-WUWC-NEW four-signal-class reproducer) — INVARIANT: The reproducer MUST assert ALL FOUR signal classes in a single test pass: (1) `worker_partial_lifecycle_exit` event with conformant `gate_payload`, (2) `⚠ ticket <id> failed AFTER research APPROVED` stderr breadcrumb, (3) `phase_no_progress` exit_reason on `pipeline-status.json`, (4) working-tree diff PRESERVED on disk (synthetic files still exist with original byte sizes). BREAKS: Dropping any single assertion lets one of the four R-WUWC prevention layers silently regress without trip-wire — operators discover the data loss again only after a fresh incident. ENFORCE: extension/tests/wuwc-reproducer.test.js. PATTERN_SHAPE: `worker_partial_lifecycle_exit[\s\S]*failed[\s\S]*AFTER[\s\S]*research[\s\S]*APPROVED[\s\S]*phase_no_progress` (must match the test source).
- `src/services/bundle-state-integrity.ts` — INVARIANT: AC-BUNDLE-03 audits promote newer dead state tmp snapshots before reading relaunch counts. BREAKS: bundle-end gates false-green over-cap Codex relaunch loops. ENFORCE: extension/tests/pipeline-runner.test.js. PATTERN_SHAPE: `readRecoverableJsonObject(statePath)`.
- `src/services/microverse-state.ts` — INVARIANT: microverse readers promote dead writer tmp snapshots before reading counters, and non-none metrics require positive finite `timeout_seconds`. BREAKS: stale regression counters or disabled metric hang guards. ENFORCE: extension/tests/services/microverse-state-iteration-regressions.test.js, extension/tests/init-microverse.test.js.
- `src/services/microverse-state.ts` (numeric validation) — INVARIANT: for metric mode, `timeout_seconds` and `convergence_target` must be positive finite numbers, validated at creation time, never at read time. BREAKS: disabled metric hang guards or NaN-driven recursion in convergence loop. ENFORCE: extension/tests/services/microverse-state-iteration-regressions.test.js, extension/tests/init-microverse.test.js.
- `src/services/microverse-state.ts`; `src/bin/microverse-runner.ts` (anatomy worker guard) — INVARIANT: `readMicroverseState` returns state shape-validated by `assertMicroverseStateShape`; anatomy-park worker sessions may omit `key_metric`, and `microverse-runner.ts` must route `mvState.key_metric.*` / history-description reads through helpers that tolerate the absent anatomy-park shape. BREAKS: anatomy-park sessions crash on `mvState.key_metric.description` or null history entries. ENFORCE: extension/tests/integration/anatomy-park-microverse-runner-no-key-metric.test.js. PATTERN_SHAPE: `mvState\.key_metric\.\w+|entry\.description`.
- `src/services/convergence-gate.ts` — INVARIANT: baseline reads recover tmp, baseline writes verify on disk, scope matching keeps package failures in scope, and test-script safety resolves delegated leaves after optional `cross-env`, `env`, or `KEY=value` prefixes. BREAKS: false-green baselines, gate crashes, or wrapper strings blocking/skipping safe delegated test runners. ENFORCE: extension/tests/services/convergence-gate-baseline.test.js, extension/tests/services/convergence-gate-workspaces.test.js, extension/tests/services/convergence-gate-test-safety.test.js. PATTERN_SHAPE: delegated `npm|pnpm|yarn run <script>` leaf resolution missing after optional `cross-env|env|KEY=value` prefixes.
- `src/services/convergence-gate.ts` (Citadel baseline tmp recovery) — INVARIANT: Citadel/convergence-gate baseline JSON reads must use `readRecoverableJsonObject()` to recover `.tmp.<pid>` snapshots before deciding pass/fail. BREAKS: stale citadel findings reused after interrupted writes can mark a regression green. ENFORCE: extension/tests/services/convergence-gate-baseline.test.js.
- `src/types/index.ts` (R-MBLE-2 MICROVERSE_FAILURE_REASONS split) — INVARIANT: `MICROVERSE_FAILURE_REASONS` MUST include `'baseline_unmeasurable_unrecoverable'` and `'judge_cli_missing'` alongside the existing fatal members, and MUST NOT include `'judge_timeout'`, `'baseline_unmeasurable_transient'`, or bare `'baseline_unmeasurable'`. BREAKS: widening the allowlist to transient or legacy baseline reasons routes recoverable microverse exits into fatal pipeline aborts, undoing the split between fatal and non-fatal baseline outcomes. ENFORCE: extension/tests/microverse-exit-reason-allowlist.test.js. PATTERN_SHAPE: the `new Set<MicroverseExitReason>` initializer includes `'baseline_unmeasurable_unrecoverable'` and excludes literal bare `'baseline_unmeasurable'`.
- `tests/test-registration-hygiene.test.js` — INVARIANT: recursive test discovery requires an explicit allowlist for skipped nested tests. BREAKS: new nested tests silently miss npm test. ENFORCE: extension/tests/test-registration-hygiene.test.js.
- `.claude/commands/send-to-morty.md` — INVARIANT: resume table includes artifact-exists/no-review rows before approved-review rows. BREAKS: resumed workers redo completed artifacts instead of reviewing them. ENFORCE: extension/tests/send-to-morty-resume.test.js. PATTERN_SHAPE: Markdown table rows matching `exists; no .*_review.md`.
- `src/lib/engine-keys-registry.ts` — INVARIANT: registry JSON fields are runtime-validated string arrays. BREAKS: frame analyzer crashes or misclassifies context keys. ENFORCE: extension/tests/engine-keys-registry.test.js.
- `src/services/linear-integration.ts` — INVARIANT: PICKLE_LINEAR_COMMAND is parsed into executable plus argv without shell. BREAKS: configured local bridge commands with arguments never receive ticket lifecycle payloads. ENFORCE: extension/tests/linear-integration.test.js. PATTERN_SHAPE: `PICKLE_LINEAR_COMMAND|execFileSync(invocation.bin, invocation.args`.
- `.github/workflows/release.yml` (release gate parity) — INVARIANT: the outer project's `CLAUDE.md` `## Versioning` section is the release gate source of truth, and line 22 of `.github/workflows/release.yml` mirrors it. BREAKS: releases can ship through a workflow gate that drifted from documented policy. ENFORCE: extension/tests/release-gate-parity.test.js.
- `.claude/commands/send-to-morty.md`; `.claude/commands/codex-rescue.md` (R-XBL-5 sub-tool override) — INVARIANT: both sub-tool command files document `subtool_backend_override` activity event emission; `subtool_backend_override` is registered in `VALID_ACTIVITY_EVENTS` (`extension/src/types/index.ts`). Per AC-BUNDLE-04 carve-out: these events are EXCLUDED from cross-backend leak count in `audit-worker-backends.ts` and reported separately as informational. BREAKS: `log-activity.js` rejects the event type (not in VALID_ACTIVITY_EVENTS), OR leak-count audits incorrectly flag sub-tool overrides as backend mismatches. ENFORCE: `grep -c "subtool_backend_override" .claude/commands/send-to-morty.md` ≥ 1; extension/tests/activity-event-payload.test.js.
- `extension/scripts/auto-resume.sh` (R-CNAR-2 foreground-only) — INVARIANT: auto-resume.sh MUST remain a foreground-only wrapper; `setsid`, `nohup`, `disown`, and any form of process-session detach are FORBIDDEN. The wrapper tracks `CHILD_PID` and kills it via `trap` on SIGTERM/SIGINT/SIGHUP so child mux-runner cannot outlive the parent shell. BREAKS: detached child mux-runner continues running after operator Ctrl-C, burning codex budget overnight (R21/R6 incident class). ENFORCE: `grep -c "setsid\|nohup\|disown" extension/scripts/auto-resume.sh` == 0 (string must not appear); `grep -c "trap.*_kill_child" extension/scripts/auto-resume.sh` ≥ 1; extension/tests/auto-resume-stop-conditions.test.js.
- `extension/scripts/auto-resume.sh` (R-CNAR-4 stop conditions) — INVARIANT: auto-resume loop halts unconditionally on any of: (a) same `current_ticket` + same `session_done_count` across consecutive retries (`no_progress_cycles ≥ 1`) AND `retry_index ≥ PROGRESS_THRESHOLD=3`; (b) `retry_index ≥ PICKLE_AUTO_RESUME_MAX_RETRIES` (default 10); (c) `exit_reason ≠ 'pipeline_phase_incomplete'` (codex 429, ticket-audit-failed, etc.); (d) wall-clock elapsed ≥ `PICKLE_AUTO_RESUME_MAX_WALL_SECONDS` (default 7200s). Each retry past index 3 prints `[warn] auto-resume retry N/M (no progress for K cycles)` to stderr. BREAKS: uncapped resume loop burns codex budget overnight (R6/R13 incident class). ENFORCE: extension/tests/auto-resume-stop-conditions.test.js.
- `extension/scripts/auto-resume.sh` (R-CNAR-5 regression) — INVARIANT: integration tests cover (a) a 5-ticket cap-hit session where auto-resume completes all tickets within PICKLE_AUTO_RESUME_MAX_RETRIES, and (b) SIGTERM to the parent shell kills the child mux-runner within 5s (R21 foreground-only mitigation). BREAKS: detached mux-runner survivors or uncapped resume loops regress silently. ENFORCE: extension/tests/integration/auto-resume-on-cap-hit.test.js.
- `src/bin/mux-runner.ts` (R-CNAR-6 spark smoke-run gate) — INVARIANT: when `state.backend === 'codex'` AND `state.codex_model` matches `/^gpt-5\.3-codex-spark/`, every iteration calls `evaluateSparkSmokeGate(state, sessionDir)` BEFORE `runIteration`. Gate halts mux-runner with `recordExitReason(statePath, 'codex_unhealthy_consecutive_failures') + safeDeactivate` when (i) tickets[0] or tickets[1] is `Failed` AND any `worker_session_<pid>.log` under that ticket dir matches `CODEX_CLI_ERROR_PATTERNS`, or (ii) any 3 consecutive tickets in `collectTickets` order are Failed-with-codex-CLI-error. `state.flags.skip_smoke_gate_reason='<reason>'` (non-empty trimmed string) short-circuits to bypass and emits `smoke_gate_bypassed` activity event exactly once per session via `smokeGateBypassEmitted` closure flag. Both `smoke_gate_bypassed` and `codex_unhealthy_consecutive_failures` are registered in `VALID_ACTIVITY_EVENTS` (`extension/src/types/index.ts` + `extension/types/index.js` mirror). The non-`pipeline_phase_incomplete` exit_reason makes auto-resume.sh stop per R-CNAR-4(c). BREAKS: spark codex sessions burn budget on a broken backend overnight; bypass flag is silently ignored or emits the bypass event every iteration spamming activity log. ENFORCE: extension/tests/integration/spark-smoke-gate.test.js.
- `src/bin/mux-runner.ts` (R-TAQ-3 ticket-audit gate) — INVARIANT: iteration-0 slot order is **readiness → ticket-audit → spawn**. After `runMuxReadinessGate` exits 0, guarded by `!ticketAuditGateChecked && curIter === 0`, mux-runner invokes `runTicketAuditGate` → `audit-ticket-bundle.js <session-dir>`. Non-zero exit halts with `recordExitReason('ticket_audit_failed') + safeDeactivate`, emits `ticket_audit_failed` event + stderr summary. `state.flags.skip_ticket_audit_reason='<reason>'` bypasses, emits `ticket_audit_bypassed`. Both events registered in `VALID_ACTIVITY_EVENTS`; `ticket_audit_failed` is in `ExitReason` + `isFailureExit` so auto-resume.sh stops per R-CNAR-4(c). Missing bin → graceful skip (`{status:'ok'}`). BREAKS: defect-ridden bundle spawns workers; wrong slot order defeats the pre-flight; silent bypass without event hides deliberate skips. ENFORCE: `grep -c "ticket_audit_bypassed\|ticket_audit_failed" extension/src/types/index.ts` ≥ 2; `grep -c "runTicketAuditGate" extension/src/bin/mux-runner.ts` ≥ 2; extension/tests/activity-event-payload.test.js.
- `src/services/classifier-utils.ts` (R-CCPM-2 e955ce4d; codex output-format classifier) — INVARIANT: Codex plain-text classifier MUST detect via block delimiters (`CODEX_DELIMITER_RE`); stream-json mode MUST require ≥1 `type:"assistant"` JSON line (`isAssistantJsonLine`); `detectOutputFormat` is the canonical exported format probe used by callers to identify which mode was selected. Callers with codex backend context MUST emit a stderr drift warning when `detectOutputFormat` returns `'plain-text'`. BREAKS: removing the block-delimiter check (`CODEX_DELIMITER_RE`) makes codex logs that stop emitting block delimiters silently pass their full content through plain-text fallback, enabling false positives on promise token detection in user/exec blocks. ENFORCE: extension/tests/mux-runner-classifier.test.js (`detectOutputFormat:` tests). PATTERN_SHAPE: `detectOutputFormat(output) === 'plain-text'` check at codex call sites in mux-runner.ts.
- `install.sh` (parity gate) — INVARIANT: after rsync, install.sh MD5-compares source-vs-deployed for the 5 most-trafficked compiled files (`types/index.js`, `services/state-manager.js`, `bin/spawn-morty.js`, `bin/mux-runner.js`, `services/pickle-utils.js`); mismatch exits 1 and refuses to leave a partial deploy. BREAKS: silent activity-event drop, schema-mismatch class bugs (run #2 dropped 8 events; run #5 ran 1h+ on May-3-era code). ENFORCE: extension/tests/install-script.test.js (parity gate test).
- `install.sh` (R-ICM-1 chmod glob) — INVARIANT: the chmod +x block uses a directory-glob `chmod +x "$EXTENSION_ROOT/extension/bin/"*.js` NOT a hand-maintained list. Adding a new file under `extension/bin/` requires NO install.sh edit — the glob covers it automatically. A post-install verification loop (`for _js in extension/bin/*.js; test -x`) then fails loud if any file is non-executable. BREAKS: reverting to a hand-list causes filemode regressions for new scripts (regression class: `e47ae8c3` dot-builder.js incident). ENFORCE: extension/tests/integration/install-chmod-coverage.test.js.
- `src/bin/pipeline-runner.ts` (R-APBS-1..3 anatomy-park subsystem enumeration) — INVARIANT: `discoverSubsystems(target)` MUST enumerate repo-root `/bin/` as a subsystem when it contains ≥3 source files (`.ts`, `.js`, `.py`, `.go`, `.rs`, `.java`, `.tsx`, `.jsx`); absence in default scope when `target=repoRoot` is a regression. Root cause of Open Finding #3: pipelines launched with `target=extension/` silently skip the 6 release-critical scripts in the repo-root `/bin/` because `discoverSubsystems(extension/)` only walks immediate children of `extension/`, never reaching the parent. `'bin'` MUST NOT be added to `EXCLUDED_DIRS`. BREAKS: adding `'bin'` to `EXCLUDED_DIRS` OR launching anatomy-park pipelines with `target=extension/` when the intent is full-repo review — both cause the 6 release-critical repo-root scripts to be permanently excluded from anatomy-park scope. ENFORCE: extension/tests/anatomy-park-resolveSubsystems-bin.test.js.
- `src/bin/check-scope-diff.ts` (R-APWS-6) — INVARIANT: when `scope.json` exists in session root, every worker `git commit` MUST be preceded by `check-scope-diff.js`; cross-scope coupling discovered in pre-stage is a *finding*, not a commit. BREAKS: staged paths outside `allowed_paths` are committed, defeating scope isolation. ENFORCE: extension/tests/check-scope-diff-preflight.test.js.
- `src/bin/check-scope-diff.ts` (AC-APWS-1 emission) — INVARIANT: the `outside_scope` exit path MUST emit a `worker_edit_outside_scope` activity event via `logActivity` with the schema-required `gate_payload` quartet (`scope_json_path`, `staged_paths_outside_scope`, `head_ref`, `suggested_remediation`); `--ticket-id` round-trips into `ticket_id`. BREAKS: producer→consumer disconnect — `/pickle-status` renderScopeDrift never surfaces leaks. ENFORCE: extension/tests/check-scope-diff-emits-event.test.js. PATTERN_SHAPE: `logActivity\(\{[^)]*event:\s*['"]worker_edit_outside_scope['"]` in CLI block before `process.exit(1)`.
- `.claude/commands/anatomy-park.md` (R-APWS-10 Phase 2 step 4.5 ordering) — INVARIANT: in Phase 2 of anatomy-park.md, the scope-preflight invocation of `check-scope-diff.js` MUST appear between step 4 ("Run the full test suite") and `git commit`; no `git commit` may appear between the step-4 anchor and the `check-scope-diff.js` invocation. BREAKS: a refactor that moves the preflight outside the test→preflight→commit ordering silently defeats scope isolation — a worker that skips the check before committing defeats the `worker_edit_outside_scope` event contract that feeds `/pickle-status`. ENFORCE: extension/tests/scope-preflight-ordering.test.js. PATTERN_SHAPE: `step 4 test-suite anchor must precede check-scope-diff.js invocation must precede git commit`.
- `.claude/commands/szechuan-sauce.md` (R-APWS-10 Override 7 scope-preflight ordering) — INVARIANT: the scope-preflight block in szechuan-sauce.md Override 7 MUST match the ordering `'Scope preflight' heading → check-scope-diff.js invocation → 'Exit 0**: proceed with commit' branch`; no path to `git commit` may bypass the preflight within this block. BREAKS: reordering or removing the preflight block lets szechuan-sauce workers commit outside-scope paths without emitting `worker_edit_outside_scope`, severing the event trail that `/pickle-status` relies on to surface scope drift. ENFORCE: extension/tests/scope-preflight-ordering.test.js. PATTERN_SHAPE: `'Scope preflight' heading must precede check-scope-diff.js must precede 'proceed with commit' branch`.
- `src/bin/status.ts` (R-APWS-10 renderScopeDrift output contract) — INVARIANT: `renderScopeDrift` MUST have exactly one `console.log` emission and it MUST match the interpolation `console.log(\`Scope drift: ${driftEvents.length} edit(s) outside scope.json — tickets: ${...}\`)`; when `driftEvents.length === 0` the function returns early and no `Scope drift:` line is printed. BREAKS: renaming the interpolated variables, splitting into multiple log calls, or adding a second emission site makes the output format diverge from the contract that downstream consumers (operators, scripts) parse, and the early-return guarantee is lost if the zero-event guard is removed. ENFORCE: extension/tests/pickle-status-scope-deviations.test.js. PATTERN_SHAPE: emission site uses the literal interpolation `console.log(\`Scope drift: ${driftEvents.length} edit(s) outside scope.json — tickets: ${...}\`)` in `renderScopeDrift`.
- `src/services/microverse-state.ts` (R-SLLJ-4 ledger set-ops) — INVARIANT: when both `current` and `previous` iterations of a session with `metric.type === 'llm'` carry populated `violation_ledger` arrays in `microverse.json`, `compareMetric` MUST use the set-ops branch (resolved/new/remaining sets) rather than falling back to pure-numeric comparison; falling back when ledger is present is a regression of the false-stall fix from session 2026-05-09-92dbdff2. BREAKS: false-stall reproduction where 4 consecutive iterations classify `held` despite real Observability fixes landing each iteration. ENFORCE: extension/tests/microverse-llm-judge-non-determinism-recovery.test.js. PATTERN_SHAPE: `compareMetric.*currentLedger.*previousLedger` plus set-difference / set-intersection in `microverse-state.ts`.
- `src/bin/microverse-runner.ts` (R-SLLJ-1 prompt context) — INVARIANT: `buildJudgePrompt` MUST accept `priorViolations: ViolationLedger[]` parameter and emit prompt section "Prior violations (DO NOT re-report unless still present)" when non-empty, capped at 50 most-recent by `last_seen_iter` to avoid context-window blowup. BREAKS: judge re-discovers different violations every iteration, recreating the false-stall pathology. ENFORCE: extension/tests/microverse-llm-judge-non-determinism-recovery.test.js. PATTERN_SHAPE: `buildJudgePrompt\(.*priorViolations`.
- `src/bin/microverse-runner.ts` (R-SLLJ-9 3-consecutive bail-out guard) — INVARIANT: the 3-consecutive `no_progress` bail-out block in `currentExitForFailureHistory` MUST be guarded by `state.key_metric?.type !== 'llm'`; LLM-judge sessions skip this heuristic because `compareMetric` set-ops (R-SLLJ-4) already distinguishes real-progress holds from genuine-stall holds. Defensive `?.` ensures undefined `key_metric` (anatomy-park sessions) falls to non-LLM path, preserving pre-fix behavior. BREAKS: round 2 session 2026-05-10-965b96f9 false-stalled at iter 7 despite stall_counter=5/50 because the hard-coded 3-consecutive check fired downstream of the ledger-aware path. ENFORCE: extension/tests/microverse-llm-judge-non-determinism-recovery.test.js. PATTERN_SHAPE: `if (state.key_metric\?\.type !== 'llm')` wrapping `if (last.failure_class === 'no_progress')` in `currentExitForFailureHistory`.
- `src/services/citadel/audit-runner.ts` (R-CCNW-2 analyzer wiring) — INVARIANT: every analyzer module under `extension/src/services/citadel/` (excluding `audit-runner.ts`, `reporter.ts`, `diff-walker.ts`, `prd-parser.ts`, `*-helpers.ts`, `*-types.ts`) MUST be imported and invoked by `audit-runner.ts`. Each invocation produces a `citadel_report.sections.<key>` entry — populated `findings: Finding[]` on a clean run, OR `{skipped: 'project_shape_mismatch', reason}` for inert analyzers. ENFORCE: extension/tests/citadel/citadel-analyzer-wiring.test.js. BREAKS: silent-skip class observed in mega bundle session 2026-05-09-7ff82595 where T3/T4/T6/T8 analyzers existed on disk but never ran. PATTERN_SHAPE: `import.*from\\s+'\\./[\\w-]+\\.js'` per analyzer in `audit-runner.ts`.
- `src/bin/pipeline-runner.ts` (R-MDS-1 phase-boundary monitor respawn) — INVARIANT: every non-citadel phase boundary that changes `state.step` from a pickle-class step (`research|plan|implement|verify|review|simplify`) to a microverse-class step (`anatomy-park|szechuan-sauce`) or vice versa MUST trigger `respawnMonitorWindowForMode(sessionDir, nextPhase)` BEFORE returning from `runPhaseIteration`. ENFORCE: extension/tests/integration/pipeline-state-coherence.test.js. BREAKS: dashboard freeze observed in mega bundle session 2026-05-09-7ff82595 where pane 1.0 stayed bound to pickle-phase template after pipeline transitioned to anatomy-park. PATTERN_SHAPE: `respawnMonitorWindowForMode` invocation count >= 3 in pipeline-runner.ts (one per non-citadel boundary).
- `src/bin/monitor.ts` (R-MDS-3 defense-in-depth tick re-check) — INVARIANT: monitor's render tick MUST call `inferModeFromStep(state.step)` and swap template inline when inferred mode differs from current; same-mode reads no-op; unreadable state preserves current mode (no swap). BREAKS: respawn race or operator-closed pane → indefinite stale dashboard; defense-in-depth absent. ENFORCE: extension/tests/monitor.test.js (mode-swap block). PATTERN_SHAPE: `inferModeFromStep.*state\.step` in monitor.ts tick loop.
- `src/bin/pipeline-runner.ts` (R-MDS-4 phase-boundary monitor mode transition) — INVARIANT: every `PHASE N/4` boundary MUST invoke `respawnMonitorWindowForMode(sessionDir, currentPhase)` so the monitor dashboard (pane 1.0) shows the phase-correct template; citadel phase boundaries may skip (pickle→citadel transition is excluded). BREAKS: monitor pane 1.0 freezes on pickle-phase template, displaying stale ticket data through anatomy-park + szechuan-sauce; operator confidence degrades; remote operators who trust the dashboard make wrong cancel/continue decisions. ENFORCE: extension/tests/integration/monitor-mode-transition.test.js.
- `src/bin/monitor.ts` (R-MWR-1 watchdog registration contract) — INVARIANT: `startRespawnWatchdog` fires immediately on registration, then every `RESPAWN_WATCHDOG_INTERVAL_MS` ms thereafter; first-tick failures are logged but MUST NOT prevent the recurring interval from being armed. BREAKS: collapsed watcher layouts linger for a full 30-second interval before recovery, or a synchronous first-tick error suppresses later respawn sweeps. ENFORCE: extension/tests/monitor-watchdog.test.js. PATTERN_SHAPE: `const handle = setInterval\(tick, intervalMs\)[\s\S]*tick\(\)` in monitor.ts.
- `src/services/pickle-utils.ts`; `src/lib/monitor-respawn.ts`; `src/bin/monitor.ts` (R-MMRT-1/R-MMRT-2 sessionDir validation) — INVARIANT: `restartDeadWatcherPanes(sessionDir, ...)` and `respawnMonitorWindowForMode(sessionDir, ...)` MUST call `validateSessionDirOrSkip(sessionDir, caller)` BEFORE any tmux send-keys/respawn-pane; `startRespawnWatchdog({ sessionDir })` MUST call `validateSessionDirOrSkip(opts.sessionDir, 'startRespawnWatchdog')` BEFORE scheduling. Invalid sessionDir (empty/non-string, path missing, `state.json` absent, or `path.resolve(state.session_dir) !== path.resolve(sessionDir)`) produces zero spawns and one `monitor_respawn_session_dir_invalid` event per (caller, sessionDir, reason) tuple per session (deduped via `_sessionDirInvalidEmitted`). `startRespawnWatchdog` returns `null` on fail. BREAKS: stale/injected `sessionDir` reaches `tmux respawn-pane`/`send-keys`, hits wrong window, corrupts pipelines. ENFORCE: `extension/tests/restart-dead-watcher-panes-sessiondir-validation.test.js`, `extension/tests/monitor-watchdog.test.js`, `extension/tests/integration/monitor-respawn-sessiondir-cascade.test.js`. PATTERN_SHAPE: `validateSessionDirOrSkip(sessionDir` or `validateSessionDirOrSkip(opts.sessionDir` at function top before any tmux spawn; `monitor_respawn_session_dir_invalid` emitted with `gate_payload: { caller, sessionDir, reason }`.
- `src/bin/microverse-runner.ts` (ticket 98dc9bed F1.1/F1.2/F1.4 judge→ledger consumer wiring) — INVARIANT: any future wiring of `parseLlmJudgeOutput` into the runtime path MUST also invoke `updateViolationLedger(state, judgeResult, iter)` from `microverse-state.ts` AND pass `currentLedger` / `previousLedger` (= `state.violation_ledger` snapshots before/after the iteration) to `compareMetric` so the R-SLLJ-4 set-ops branch fires. Bundle 2026-05-10 landed the helpers as exported functions but no production caller invokes them: `measureLlmMetricAttempt` consumes only `extractScore(output)`, `state.violation_ledger` stays empty, and `compareMetric` always falls through to its pure-numeric branch. BREAKS: re-introducing the dead-code class makes the entire R-SLLJ-1/3/4 fix orbit unreachable from the runtime — judges re-discover identical violations every iteration and false-stalls re-emerge. ENFORCE: extension/tests/microverse-llm-judge-non-determinism-recovery.test.js, extension/tests/services/microverse-state-iteration-regressions.test.js. PATTERN_SHAPE: `updateViolationLedger\(state, .*, iter\)` invocation inside `measureAndClassifyIteration` AND `compareMetric\(.*, .*, .*, .*, currentLedger, previousLedger` invocation in the same function.
- `src/bin/microverse-runner.ts`; `src/bin/subsystem-watcher.ts` (ticket 98dc9bed F2.1 current_subsystem producer) — INVARIANT: worker-mode subsystem loops MUST persist `microverse.json.current_subsystem = <convergence_file>.subsystems[current_index]` before each iteration, immediately after successful worker rotation, and immediately after subprocess-error rotation; same-subsystem worker success MUST still flush the returned `microverse.json` fields to disk. BREAKS: anatomy-park pane shows stale or fallback strings during live subsystem review, or resume/status readers miss same-subsystem state updates. ENFORCE: extension/tests/microverse.test.js, extension/tests/subsystem-watcher.test.js. PATTERN_SHAPE: worker convergence file with `subsystems` + `current_index` but no pre-iteration or post-rotation `current_subsystem` write in `microverse-runner.ts`.
- `src/bin/microverse-runner.ts` (R-APMW-10 cap-exit ledger sync) — INVARIANT: worker subprocess-error cap exits MUST still update `anatomy-park.json` stall_counts/current_index before returning. BREAKS: resumed anatomy runs re-enter a stalled subsystem past policy. ENFORCE: extension/tests/microverse.test.js. PATTERN_SHAPE: `nextCount >= Defaults\.WORKER_CONSECUTIVE_ERROR_CAP` without preceding `markWorkerSubsystemStalled\(state, ctx\.sessionDir\)` and `syncCurrentWorkerSubsystem\(state, ctx\.sessionDir\)`.
- `src/bin/pipeline-runner.ts` (ticket 98dc9bed F2.2 producer_done window) — INVARIANT: `setProducerDone(runtime, true) → respawnMonitorWindowForMode(...) → setProducerDone(runtime, false)` is a transient signal whose true-window closes faster than the morty-watcher poll. The reader at `subsystem-watcher.ts:48` and `morty-watcher.ts:132` MUST treat `producer_done === false` as the steady state and MUST NOT rely on a `true` observation as the only end-of-producer signal — observation requires landing the poll inside a sub-millisecond window. Defense-in-depth consumers must also poll session liveness (`state.active`) and ticket completion artifacts. BREAKS: any future consumer that branches solely on `producer_done === true` (without liveness fallback) will display the wrong message in the steady state. ENFORCE: extension/tests/subsystem-watcher.test.js (producer_done branch), extension/tests/morty-watcher.test.js (`handleNoWorkerLogs` no-logs branch).
- `src/hooks/handlers/config-protection.ts` (R-WSRC-GR git verb block) — INVARIANT: `detectProhibitedGitVerb(command)` returns `{verb}` iff `parseFirstShellWord(command)` is `git` AND the first non-flag positional arg after `git` is one of the 9 prohibited verbs (reset, switch, stash, rebase, pull, push, checkout-with-ref, commit --amend, fetch --prune); returns null for `git checkout -- <path>`, `git checkout .`, plain `git commit` (no `--amend`), and plain `git fetch` (no `--prune`). BREAKS: removing the detection re-opens the R-WSRC-GR class (5 worker violations in 24h prior to B-WSRC-GR); matching `git checkout -- <path>` as prohibited would break the runner's own path-scoped working-tree recovery path (commits `36ae2f76`, `63b9c346`, `45223a06`, `e80eaed5`, `2aa079c2`). ENFORCE: extension/tests/hooks/config-protection-git-boundary.test.js. PATTERN_SHAPE: `parseFirstShellWord` returning `git` AND second positional token matching one of the 9 verbs (or `commit ... --amend` / `fetch ... --prune` flag combinations).
- `extension/src/services/ticket-completion-evidence.ts:9` (R-AFCC-STAGE non-repo workingDir containment) — INVARIANT: a non-repo `workingDir` is a legitimate state for completion-evidence probing, NOT an error; `persistEvidence` with `opts.stage === 'best-effort'` MUST silently swallow git-add failure and return `{ action:'written', staged:false }`; `gateForPhantomDoneRevert` MUST map `kind:'inferred-stale'` to `action:'keep'` (not `'revert'`) because a stored-but-unverifiable SHA is still evidence. BREAKS: tightening git-add to always-throw makes every pipeline running against a non-repo workingDir permanently fail the Done-flip gate for tickets with a stored `completion_commit_inferred` SHA; changing `inferred-stale` to `revert` discards committed work from non-repo workers. ENFORCE: extension/tests/characterization/completion-commit-cluster/path-6-phantom-done-watcher-revert.test.js, extension/tests/characterization/completion-commit-cluster/path-7-phantom-done-watcher-backfill.test.js. PATTERN_SHAPE: `// R-AFCC-STAGE:` annotation in `ticket-completion-evidence.ts` AND `if (opts.stage === 'required') throw` in `persistEvidence` body.
- `extension/src/services/ticket-completion-evidence.ts:79` (R-AFCC-WRITE-OBSERVABILITY write-vs-stage telemetry split) — INVARIANT: `PersistResult.staged` boolean MUST be set to `true` when git-stage succeeds and `false` when git-stage fails (best-effort only); callers can distinguish a disk-write from a disk-write-plus-index-staged without inspecting git state separately. BREAKS: dropping `staged` from `PersistResult` collapses write and stage into a single observable, making it impossible for callers to detect silent git-add failures in best-effort mode — the non-repo containment case (R-AFCC-STAGE) produces no observable difference from a normal write. ENFORCE: extension/tests/characterization/completion-commit-cluster/path-1-worker-explicit-stamp.test.js, extension/tests/characterization/completion-commit-cluster/path-2-worker-autofill-belt-and-suspenders.test.js. PATTERN_SHAPE: `staged\?: boolean` in `PersistResult` in `extension/src/services/ticket-completion-evidence.ts`.
- `extension/src/services/ticket-completion-evidence.ts:227` (R-AFCC-CALLER-ENUMERATION oracle callsite audit) — INVARIANT: `ticket-completion-evidence.ts` MUST be imported by exactly 3 caller files in `extension/src/`: `mux-runner.ts`, `auto-fill-completion-commit.ts`, and `pickle-utils.ts`; any new caller requires a deliberate callsite-count update to this pin. BREAKS: a new caller adding a parallel non-oracle completion-check (custom git-log scan or direct `hasCompletionCommit`) diverges from the single-oracle contract and re-opens Done-flip inconsistencies that R-AFCC-DEEP-4A resolved. ENFORCE: extension/tests/characterization/completion-commit-cluster/path-1-worker-explicit-stamp.test.js (single-oracle contract); bash extension/scripts/audit-trap-door-enforcement.sh (R-AFCC-CALLER-ENUMERATION block greps caller count). PATTERN_SHAPE: `grep -rn "ticket-completion-evidence" extension/src/ --include="*.ts"` excluding `ticket-completion-evidence.ts` itself yields exactly 3 unique caller files.
- `extension/src/services/ticket-completion-evidence.ts` (R-AFCC-DEEP-CONSOLIDATED — single oracle for 'is this ticket attributably done?') — INVARIANT: `ticket-completion-evidence.ts` is the sole module answering "is this ticket attributably done?"; its public surface post R-AFCC-DEEP-4B is exactly `readEvidence`, `persistEvidence`, `gateForPhantomDoneRevert` plus associated types; pruned exports `gateForDoneFlip` and `recordPostGateOutcome` MUST NOT be re-introduced; the 8-file `@tier:integration` characterization suite at `extension/tests/characterization/completion-commit-cluster/` is the primary regression guard for the 8 Done-stamping paths. BREAKS: re-introducing pruned exports re-opens divergent Done-flip paths the consolidation closed; adding new exports without updating R-AFCC-CALLER-ENUMERATION silently expands the oracle surface beyond the 3-caller contract. ENFORCE: extension/tests/characterization/completion-commit-cluster/path-1-worker-explicit-stamp.test.js, extension/tests/characterization/completion-commit-cluster/path-8-operator-salvage-edit.test.js (representatives of the full 8-file suite). PATTERN_SHAPE: `export function readEvidence` AND `export function persistEvidence` AND `export function gateForPhantomDoneRevert` present in `ticket-completion-evidence.ts`; `gateForDoneFlip` AND `recordPostGateOutcome` absent.

## state.json Field Invariants

- INVARIANT: `active` is the session liveness flag; owners set it deliberately and recovery demotes dead owners. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `working_dir` is the canonical project directory and must come from recovered state before dispatch. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `step` advances only through lifecycle transitions understood by runners and status. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `iteration` is the current manager-loop counter and remains a finite integer. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `max_iterations` is a positive integer budget for manager loops. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `max_time_minutes` is an optional non-negative integer wall-clock budget; absent or `0` disables the wall-clock cap, positive values opt in. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `worker_timeout_seconds` is a positive integer worker budget. The compiled medium-tier default is `3600` seconds (60 minutes), raised from 2400 in R-WTB-A2 to give a safer R-PTG lifecycle floor for research + plan + implement + fast-test + type/lint + artifact/commit overhead. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `start_time_epoch` is the numeric epoch used for elapsed-time gates. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `completion_promise` is nullable completion evidence and is never used as the sole state authority. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `original_prompt` preserves the launch request for worker prompts and reports. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `current_ticket` is the nullable ticket pointer and changes only through ticket lifecycle operations. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `current_ticket_tier` is the optional mux-runner cache for the active ticket complexity tier and must be refreshed when the ticket changes. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/mux-runner-circuit-breaker.test.js.
- INVARIANT: `current_ticket_budget` is the optional mux-runner cache for the active ticket no-progress circuit-breaker budget and must match `current_ticket_tier`. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/mux-runner-circuit-breaker.test.js.
- INVARIANT: `current_ticket_max_iterations` is the optional mux-runner per-ticket iteration ceiling derived from the active ticket tier and cleared when the ticket changes. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/mux-runner-circuit-breaker.test.js.
- INVARIANT: `current_ticket_worker_timeout_seconds` is the optional mux-runner per-ticket worker timeout derived from the active ticket tier and cleared when the ticket changes. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/mux-runner-circuit-breaker.test.js.
- INVARIANT: `current_ticket_budget_start_iteration` is the optional mux-runner iteration baseline for the active ticket tier budget and resets when the ticket changes. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/mux-runner-circuit-breaker.test.js.
- INVARIANT: `history` is the lifecycle trace seed written only by `setup.ts` (empty array on init); runners do NOT append. Read by `pickle-utils.ts:buildHandoffSummary` as the new-vs-resume heuristic. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/pickle-utils.test.js.
- INVARIANT: `started_at` is the session start timestamp and remains parseable for recency ranking. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `session_dir` is the canonical session path written at setup. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `tmux_mode` records whether the session is controlled by tmux orchestration. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `min_iterations` is an optional non-negative lower bound for convergence. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `command_template` is the optional manager command replay template. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `chain_meeseeks` is an optional boolean follow-up review flag. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `schema_version` matches the current state schema after migration. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `prd_path` is the optional PRD path consumed by pipeline and citadel phases. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `start_commit` is the optional base commit consumed by diff-based phases. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `pid` is the owning process id when a runner claims liveness. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `launch_shell_pid` is the optional launch-shell PID breadcrumb written by per-session launch.sh at startup. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `pipeline_continue_on_phase_fail` is an optional boolean flag; when false, pipeline-runner halts on any non-zero non-citadel phase exit instead of continuing on recoverable failures. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `manager_relaunch_count` is the bounded count of in-session manager subprocess relaunches (codex tmux_mode reuse path). ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `last_error` is the optional `ErrorRecord` snapshot of the most recent iteration error; runners write it before forensic deactivation. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `last_subprocess_error` is the optional `ErrorRecord` snapshot for the most recent worker subprocess error feeding worker-mode cap logic. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `last_between_ticket_gate` is the optional between-ticket gate evidence record (`{ts, ok, failures}`) used to short-circuit duplicate gate runs across consecutive tickets. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `consecutive_short_responses` counts degenerate manager replies and resets on substantive output. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `phases_entered` is a monotonically extending phase-refresh ledger. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `activity` stores append-only per-session activity records. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `backend` is the manager backend and worker fallback backend. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `worker_backend` is the optional worker-spawn backend override; worker spawns prefer it over `backend` when present, while refinement spawns ignore it because `PICKLE_REFINEMENT_LOCK=1` still forces claude. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/backend-spawn.test.js.
- INVARIANT: `judge_backend_resolved` is the optional sticky-fallback judge backend resolved by R-SJET-4 after a typed measurement failure; clears on session reset. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `teams_mode` gates harness-team spawning and defaults off. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `max_parallel` is the optional positive worker concurrency cap. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `false_epic_completed_count` tracks repeated false completion claims for one ticket. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `false_epic_completed_ticket` ties false completion counts to the ticket that produced them. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `effort` is the optional codex reasoning-effort value. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `codex_manager_relaunch_count` is capped per state file by defaults. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `codex_manager_consecutive_no_progress` is the R-CMWL-4 schema-neutral counter of consecutive zero-progress codex manager relaunch passes (defaulted to 0 via normalizeV5StateDefaults); resets on any pass with ticket progress; halts at 2. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `codex_manager_relaunch_pending_baseline` is the R-CMWL-4 schema-neutral pending-ticket count stored at the last codex relaunch — the baseline for zero-progress detection; absent on first relaunch. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `recovery_attempts` is the R-ORSR-1 schema-neutral v5 recovery-controller attempt ledger (array of `RecoveryAttempt` `{ strategy, outcome, reason, iteration }`); defaulted to `[]` via normalizeV5StateDefaults with NO `LATEST_SCHEMA_VERSION` bump; an absent key reads back as `[]` and populated entries survive migration untouched. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/recovery-controller-foundation.test.js.
- INVARIANT: `hermes_toolsets` is the optional Hermes CLI toolset list passed to worker and manager spawns. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/hermes-spawn.test.js.
- INVARIANT: `hermes_provider` is the optional Hermes provider override passed to worker and manager spawns. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/hermes-spawn.test.js.
- INVARIANT: `hermes_model` is the optional Hermes model override passed to worker and manager spawns. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/hermes-spawn.test.js.
- INVARIANT: `hermes_max_turns` is the optional positive Hermes max-turns override passed to worker and manager spawns. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/hermes-spawn.test.js.
- INVARIANT: `codex_model` is the optional Codex model override passed to worker and manager spawns; falls back to `pickle_settings.default_codex_model`. ENFORCE: extension/tests/spawn-morty.test.js.
- INVARIANT: `grok_model` is the optional grok model override resolved by `resolveGrokModel` and passed to worker and manager spawns; absent yields the grok CLI default. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/grok-backend.test.js.
- INVARIANT: `kimi_model` is the optional kimi model override resolved by `resolveKimiModel` and passed to worker and manager spawns; absent yields the kimi CLI default. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/kimi-backend.test.js.
- INVARIANT: `gemini_model` is the optional gemini model override resolved by `resolveGeminiModel` and passed to worker and manager spawns; absent yields the gemini CLI default. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/gemini-backend.test.js.
- INVARIANT: `archaeology` is nullable project-context metadata. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `tickets_version` increments when course correction changes ticket state. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `last_course_correction` stores nullable metadata for the latest applied correction. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `phase_personas_active` records whether phase-persona prompts are active. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `flags` stores optional persisted runtime feature flags. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `readiness` stores optional readiness-gate state and cycle history. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `codex_version_seen` stores nullable codex version smoke-check evidence. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `exit_reason` is a nullable terminal-exit forensic marker; clean-success exits set it via `finalizeTerminalState`, forensic paths via `recordExitReason`, and `clearExitReason` is the only inverse. Reactivation clears stale `exit_reason` without resetting `step`/`current_ticket` by default; phase entry clears stale prior-phase `exit_reason` and `step` before spawning the next runner. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/state-manager.test.js, extension/tests/setup.test.js, extension/tests/pipeline-runner.test.js, extension/tests/mux-runner.test.js.
- INVARIANT: `monitor_panes` is an optional length-4 array of per-pane producer liveness flags. Written exclusively by `pipeline-runner.ts` at non-citadel phase boundaries (pane 2 is the producer pane for morty-watcher in pickle and subsystem-watcher in microverse). Missing or absent → migration initializes to 4 false entries (crash-recovery default). Writer flips `monitor_panes[2].producer_done = true` BEFORE `respawnMonitorWindowForMode`, resets to false AFTER it returns. Readers (morty-watcher, subsystem-watcher) switch the no-data message from the normal warning to "Producer complete" when the flag is true. ENFORCE: extension/tests/state-field-invariants.test.js, extension/tests/state-manager.test.js.
- INVARIANT: `pinned_branch` is the branch name captured at session bootstrap by `setup.ts` (via `git symbolic-ref --short HEAD`); null on detached HEAD. Read by `mux-runner.ts:checkHeadPinMismatch` before each ticket selection to detect external branch switches. ENFORCE: extension/tests/integration/head-pin-mismatch-detection.test.js.
- INVARIANT: `pinned_sha` is the commit SHA captured at session bootstrap by `setup.ts` (from `start_commit`). Used alongside `pinned_branch` by `checkHeadPinMismatch` to detect external HEAD moves on detached-HEAD sessions. ENFORCE: extension/tests/integration/head-pin-mismatch-detection.test.js.
- INVARIANT: `head_pin_mismatch_detail` is a nullable object written by `mux-runner.ts:checkHeadPinMismatch` when a HEAD mismatch is detected; carries `{ pinned_branch, observed_branch, pinned_sha, observed_sha }`. Read by `pipeline-runner.ts:logPhaseHaltReason` to surface human-readable mismatch info to stderr. ENFORCE: extension/tests/integration/head-pin-mismatch-detection.test.js.
- INVARIANT: `orphans_detected` is an optional append-only list of orphan session paths detected at session-map read time; forward-created by R-CCPM-3. Writers emit `orphan_session_detected` before appending. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `parent_session_hash` is the optional hash of the owning parent session used to validate manager-subprocess provenance; forward-created by R-CCPM-3. Null when the session was operator-launched. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `invocation_source` records how the current session was invoked (`'operator'` or `'manager_subprocess'`); forward-created by R-CCPM-4. Absent implies `'operator'`. ENFORCE: extension/tests/state-field-invariants.test.js.
- INVARIANT: `worker_artifact_progress` is an optional per-ticket map (`{ [ticketId]: { spawn_count, last_artifact_count, zero_progress_count } }`) initialized to `{}` on v4→v5 migration by `normalizeV5StateDefaults` (R-WSWA-1 schema bump); consumed by the R-WMW worker-artifact-progress fix. ENFORCE: extension/tests/state-field-invariants.test.js. PATTERN_SHAPE: `LATEST_SCHEMA_VERSION = 5` and `if (schemaVersion >= 5) normalizeV5StateDefaults(state)` in the migration dispatcher. ENFORCE: extension/tests/state-schema-version-deploy-parity.test.js.
- `extension/scripts/audit-subsystem-claude-md.sh` (R-CMD-4) — INVARIANT: subsystem CLAUDE audits default to a temp/override output path and only touch tracked reports when `OUTPUT_FILE_OVERRIDE` points there. BREAKS: routine audit reads dirty the repo via timestamp churn in `extension/audit/subsystem-claude-md-2026-05-08.json`, blocking later automation and masking real diffs. ENFORCE: extension/tests/audit-subsystem-claude-md.test.js. PATTERN_SHAPE: `OUTPUT_FILE="$AUDIT_DIR/subsystem-claude-md-2026-05-08.json"` or any default write into tracked `extension/audit/*.json` without explicit override.
- `install.sh` (R-PJV-6) — INVARIANT: Until the R-PJV-4 follow-up fix ships, `install.sh` MUST log a `pkgjson_revert_forensic_captured` activity event whenever `extension/package.json:version` is bumped (i.e. `SRC_V != DEPLOYED_V` at deploy time) without a corresponding uncommitted git change — meaning `git diff --name-only extension/package.json` is empty but the versions differ. This distinguishes the "auto-update wrote back the deployed version" scenario (H-C) from a normal version bump committed to git. BREAKS: silent version-revert events accumulate with no forensic audit trail; the R-PJV-4 root-cause analysis is delayed indefinitely. ENFORCE: extension/tests/install-pkgjson-version-trace.test.js. PATTERN_SHAPE: `pkgjson_revert_forensic_captured` in install.sh or a post-install hook.
- `install.sh` (install_sh_parity_check gate_payload) — INVARIANT: both `install_sh_parity_check` emission sites in install.sh MUST pass `--gate-payload "$_parity_payload"` to `log-activity.js`; the JSON object built via `jq -nc` carries `files_checked` (array), `mismatches` (array), `status` ("pass" or "fail") matching `activity-events.schema.json:install_sh_parity_check.gate_payload.required`. BREAKS: shell-emitted parity events are schema-non-conformant (payload data buried in `title` instead of `gate_payload`), exactly the failure mode iter-2 trap door (`src/bin/log-activity.ts gate_payload invariant`) anticipated. ENFORCE: extension/tests/install-sh-parity-event-gate-payload.test.js. PATTERN_SHAPE: `--gate-payload\s+"\$_parity_payload"` on each `log-activity.js install_sh_parity_check` line in install.sh.
- `src/bin/setup.ts` (time_cap_disabled_default schema conformance) — INVARIANT: the `time_cap_disabled_default` schema in `activity-events.schema.json` MUST match the actual emitter shape at `setup.ts:logActivity({event:'time_cap_disabled_default',...})`. The emitter uses `session` (not the legacy `session_id` from commit `0b16a707`) and never carries `prior_value` because the event fires only when `max_time_minutes` was never set (no prior value exists). Schema `required` MUST be `["event","ts"]`; `session` and `backend` are optional. BREAKS: re-introducing `session_id` or `prior_value` to schema `required` puts every fresh session's `time_cap_disabled_default` JSONL line into permanent schema-violation; this is the same regression class iter-7 caught for `ticket_audit_failed`. ENFORCE: extension/tests/time-cap-disabled-default-schema-conformance.test.js, extension/tests/activity-event-payload.test.js. PATTERN_SHAPE: `event:\s*['"]time_cap_disabled_default['"]` in `setup.ts` MUST be matched by a `time_cap_disabled_default` definition in `activity-events.schema.json` whose `required` array contains only `event` and `ts`.

## Build & Test

- Standard CI remains the single-pass gate in [ci.yml](/Users/gregorydickson/loanlight/pickle-rick/pickle-rick-claude/.github/workflows/ci.yml).
- The 30-run stability gate runs once per bundle as a pre-merge artifact via [stability-gate.yml](/Users/gregorydickson/loanlight/pickle-rick/pickle-rick-claude/.github/workflows/stability-gate.yml).
- Operator runbook: `gh workflow run stability-gate.yml -f run_count=30`
- Worker AC-7 remains unchanged and continues to rely on a single `npm run test:fast` pass.
- `src/bin/pipeline-runner.ts` (R-PRJT-2 judge_timeout recovery) — INVARIANT: when microverse exits with `exit_reason === 'judge_timeout'` from anatomy-park or szechuan-sauce, pipeline-runner MUST spawn finalize-gate.js (skill = 'anatomy-park' or 'szechuan') and continue or halt based on the gate's exit code, NOT abort. `judge_timeout` is a transient measurement timeout (4-attempt backoff); the converged work is valid. `judge_timeout` MUST NOT appear in `MICROVERSE_FAILURE_REASONS`. BREAKS: re-adding 'judge_timeout' to MICROVERSE_FAILURE_REASONS routes it to the "pipeline aborting (no finalize-gate)" path, discarding convergence work from anatomy-park/szechuan-sauce phases that had already converged. ENFORCE: extension/tests/integration/pipeline-runner-judge-timeout-recovery.test.js.
- `src/bin/pipeline-runner.ts` (R-SOA-5 handleShutdown diagnostic invariant) — INVARIANT: `installShutdownHandlers().handleShutdown` MUST emit a structured `signal_received` activity event for every signal entry, MUST persist the specific signal name as `state.json.exit_reason` (`signal:SIGINT`, `signal:SIGTERM`, `signal:SIGHUP`), and MUST dual-write the shutdown payload to `pipeline-runner.log` via both the human-readable `Received <signal> — shutting down pipeline` line and the structured `signal_received { ... }` line. BREAKS: collapsing signal-specific shutdown paths or dropping either write re-introduces unattributable shutdown sessions and burns 10-30 minutes of operator triage per event, the same class observed in session `2026-05-10-84ad0873`. ENFORCE: extension/tests/pipeline-runner-signal-attribution.test.js.

## R-CLOSER-ADJACENCY-AUDIT

Every closer MUST run the 6-step adjacency-audit checklist from `.claude/commands/citadel.md` under `## R-CLOSER-ADJACENCY-AUDIT` before committing. This template catches adjacent-mode bugs (the R-AFCC-STAGE class) that narrow per-symptom fixes miss.

Steps: (1) adjacent-path enumeration, (2) adjacent-mode enumeration, (3) trap-door delta confirmation, (4) cross-module importer check, (5) stamp-pair parity, (6) pre-flight context grep.

Maps to AC-AFCC-DEEP-02. See `.claude/commands/citadel.md` for the full template.

## Characterization Safety Nets

The completion-commit cluster characterization suite lives at `extension/tests/characterization/completion-commit-cluster/` (8 test files, 35 tests, committed in 3de26d83). Every file carries `// @tier: integration`, so `npm run test:integration` automatically discovers and enforces the suite via the recursive `discoverTierFiles` scanner in `extension/bin/test-runner.js`.

**Release-gate invariant (R-AFCC-DEEP-CONSOLIDATED):** These tests MUST pass on every release. They are the primary regression guard for the 8 Done-stamping paths in the completion-commit cluster. A passing `npm run test:integration` is required evidence before tagging any release (see `## Versioning` in this file's parent CLAUDE.md). Do NOT quarantine or suppress these files without a corresponding R-AFCC-DEEP-CONSOLIDATED exception record.
