## Trap Doors

- `git-utils.ts` (`probeConcurrentGitAccess` advisory contract — R-PIWG-5) — INVARIANT: `probeConcurrentGitAccess` is ADVISORY — its result MUST only trigger a warn log plus a `concurrent_git_access_detected` activity event; it MUST NEVER be used as a hard launch block that causes `setup.ts` to abort or return a non-zero exit; every `spawnSync`/`execFileSync` subprocess spawned inside the function MUST carry a finite positive `timeout` option (current default 5_000 ms). BREAKS: converting the advisory warn into a hard guard causes legitimate launches (e.g. a background git fetch holding the lock momentarily) to fail; dropping a subprocess timeout re-opens the wedged-launch-probe class where `lsof` or `pgrep` hangs indefinitely, blocking session startup. ENFORCE: extension/tests/probe-concurrent-git-access.test.js, extension/tests/setup.test.js. PATTERN_SHAPE: `TRAP DOOR: probeConcurrentGitAccess advisory-probe`.
- `git-utils.ts` (AP-HS6-1 completion_commit add-path) — INVARIANT: `updateTicketFrontmatter` MUST apply `applyCompletionCommitField` to the frontmatter SLICE (which ends in the closing `---`), never the full document. `setFrontmatterField`'s add-path fallback `/\n---(\r?\n?)$/` is `$`-anchored and only matches when the closing delimiter is at end-of-string. BREAKS: an absent `completion_commit` is silently dropped on the `status:Done` success path (`spawn-morty.ts`), stamping Done with no completion evidence (`done_without_commit_evidence`). The sibling `upsertFrontmatterField` (pickle-utils) is the safe reference — it inserts via `lastIndexOf('\n---', fm.end - 1)`. ENFORCE: extension/tests/git-utils-ticket-frontmatter.test.js (`completion_commit when the field is ABSENT`). PATTERN_SHAPE: `applyCompletionCommitField(updatedBase, patch)` on the frontmatter-only slice, NOT `applyCompletionCommitField(updatedBase + original.slice(fm.end), …)`.
- `backend-spawn.ts` — INVARIANT: `resolveBackendFromStateFileWithSource(cliBackend)` must honor CLI override before reading persisted `state.backend`. BREAKS: relaunch/spawn callers silently route to stale session backend instead of the operator-selected backend. ENFORCE: `backend-spawn.test.js` CLI override precedence coverage. PATTERN_SHAPE: exported resolver with `cliBackend?: Backend` and `source: 'cli-flag-override'`.
- `backend-spawn.ts` (AC-8 MCP forwarding invariant) — INVARIANT: `buildClaudeWorkerInvocation` and `buildClaudeManagerInvocation` MUST push `--mcp-config <path>` into the spawned `args` array whenever `resolveMcpConfigPath(opts.settingsBag)` returns a non-null, non-undefined path; both functions follow the pattern `const mcpCfg = opts.mcpConfig ?? resolveMcpConfigPath(opts.settingsBag); if (mcpCfg) args.push('--mcp-config', mcpCfg)`. BREAKS: removing or short-circuiting the `if (mcpCfg) args.push('--mcp-config', mcpCfg)` conditional silently drops MCP forwarding for all worker and manager claude-backend spawns — workers can no longer access operator-configured MCP servers (Linear, GitHub, etc.) even when `worker_mcp_config_path` is set in `pickle_settings`. ENFORCE: `extension/tests/services/backend-spawn-mcp.test.js`, `extension/tests/integration/worker-mcp-access.test.js`. PATTERN_SHAPE: `'--mcp-config'` literal present in `buildClaudeWorkerInvocation` body.
- `backend-spawn.ts` (R-CXOR-3 codex git-boundary parity — chosen option (a)) — INVARIANT: the post-iteration `detectAndRecoverHeadRegression` audit in `mux-runner.ts` is the authoritative git-boundary guard for codex-backend workers. `buildCodexInvocation` spawns codex via `codex exec --dangerously-bypass-approvals-and-sandbox`; the codex subprocess runs bash commands directly and NEVER routes through Claude Code's PreToolUse hook dispatch. Therefore `isGitVerbBlockedByRWSRCGR` (which checks `PICKLE_ROLE` and blocks prohibited git verbs only for claude-backend workers) CANNOT intercept `git reset --hard` from a codex worker. The authoritative recovery path is: after every worker iteration, `mux-runner.ts` calls `detectAndRecoverHeadRegression({ ticketId, workingDir, startCommit, completionCommitSha, ... })` in the post-Done-detection branch; this function compares HEAD to `start_commit` and either ff-reattaches the orphaned commit or marks the ticket Failed — no silent orphan is possible. Option (b) (wrapper-git on PATH injected per codex spawn) was rejected: requires compiling a PATH-shim, adds a new ENOENT failure mode, duplicates `detectProhibitedGitVerb` logic outside its home module, and is redundant with the already-wired R-CXOR-1 audit. Option (c) (reference-transaction git hook installed per session) was rejected: codex uses `--skip-git-repo-check`, git hook inheritance under `--ephemeral` and `--ignore-user-config` is undefined, and the setup path is fragile. Claude-backend behavior is UNCHANGED: `detectProhibitedGitVerb` + `isGitVerbBlockedByRWSRCGR` in `hooks/handlers/config-protection.ts` remain authoritative for claude workers via the PreToolUse hook; these functions must not be modified. BREAKS: removing `detectAndRecoverHeadRegression` from the "ticket already marked Done" branch of `runMuxRunnerMain` (currently at `mux-runner.ts:5549-5563`) eliminates the only git-boundary enforcement for codex workers, allowing a codex worker to silently orphan commits via `git reset --hard <baseline>` and have the pipeline accept Done-at-baseline as valid completion evidence. ENFORCE: extension/tests/codex-git-boundary-parity.test.js. PATTERN_SHAPE: `detectAndRecoverHeadRegression(` called in the "ticket already marked Done" branch of `runMuxRunnerMain` guarded by `previousTicketStartCommit`; `detectProhibitedGitVerb(` present and unmodified in `hooks/handlers/config-protection.ts`.
- `agent-md-loader.ts` — INVARIANT: agent frontmatter list fields (`tools`, `principles`) must accept both CSV scalars and YAML `- item` lists. BREAKS: standard override markdown crashes `loadAgentMd`, blanking phase personas and debate agents. ENFORCE: `agent-md-loader.test.js` YAML-list load coverage. PATTERN_SHAPE: `tools:` or `principles:` followed by indented `- ` items.
- `activity-logger.ts` — INVARIANT: buffered activity writes flush only into the current `getActivityDir()` root; entries captured under a different data root must be dropped instead of retried forever. BREAKS: one unwritable temp root contaminates later sessions with permanent stderr spam and dead-path retries. ENFORCE: `activity-logger.test.js` stale-root buffer-drop coverage. PATTERN_SHAPE: `pendingBuffer.splice(0)` flushed without `path.dirname(entry.filepath) === activityDir`.
- `pickle-utils.ts` — INVARIANT: `inferMonitorMode` must keep the layout-selector `MonitorMode` union at `extension/src/services/pickle-utils.ts:1614` pinned to `'pickle' | 'meeseeks' | 'council' | 'refinement' | 'szechuan-sauce' | 'anatomy-park'`; this trap door covers the layout-selector union only, not the separate render-mode `MonitorMode` in `extension/src/bin/monitor.ts`. BREAKS: future workers collapse `inferMonitorMode` back to a partial union, dropping `'refinement'` or microverse layout modes by confusing it with the distinct render-mode union. ENFORCE: `extension/tests/monitor-mode-resilience.test.js` R-MWCL-1 mode-inference coverage. PATTERN_SHAPE: `export type MonitorMode = 'pickle' | 'meeseeks' | 'council' | 'refinement' | 'szechuan-sauce' | 'anatomy-park';` adjacent to `inferMonitorMode(...)`.
- `pickle-utils.ts` (R-MWCL-1 command_template mode inference) — INVARIANT: `inferMonitorMode(sessionDir, log?)` resolves monitor mode from `state.json.command_template` via startsWith globs (`pickle*→pickle`, `meeseeks*→meeseeks`, `council*→council`) and exact matches (`anatomy-park.md→anatomy-park`, `szechuan-sauce.md→szechuan-sauce`, `refinement.md→refinement`); missing/empty `command_template` or unrecognized template defaults to `pickle` and emits WARN via the optional `log` callback with prefix `[ensureMonitorWindow]`. `ensureMonitorWindow` passes its `log` function to `inferMonitorMode` so mode-inference warnings surface in the runner log alongside the `created 4-pane monitor (mode=...)` line. BREAKS: dropping the `log` pass-through silences mode-inference regressions; collapsing the startsWith globs to exact matches drops `council-of-ricks.md` (council* glob) and future meeseeks/pickle variants. ENFORCE: `extension/tests/pickle-utils-ensure-monitor-window-mode.test.js`. PATTERN_SHAPE: `inferMonitorMode(opts.sessionDir, log)` (not bare `inferMonitorMode(opts.sessionDir)`) in `ensureMonitorWindow` context initialization.
- `pickle-utils.ts` (R-WTB-4 worker-timeout / R-PTG floor pin) — INVARIANT: `TICKET_TIER_BUDGETS.medium.worker_timeout_seconds` must stay at or above the documented R-PTG worker lifecycle floor because medium-tier workers must cover research, plan, implement, the per-ticket fast-test gate, type/lint checks, artifact writes, and commit overhead before timeout; the compiled default is `60 * 60` (`3600`) seconds (raised from `2400` in R-WTB-A2 to give 60-minute headroom; b04f41d6 incident precedent). Two-layer design: R-WTB-A1's artifact-progress detector (`extension/CLAUDE.md` R-WTB-A1 trap-door) owns the "no halt while productive" guarantee; this pin is the static floor. BREAKS: lowering the medium-tier default below the R-PTG floor silently turns compliant medium tickets into timeout-prone runs during the required worker gate, even when the code change itself is small. ENFORCE: `extension/tests/integration/worker-timeout-tier-budget.test.js` R-WTB-3 timeout-budget regression coverage. PATTERN_SHAPE: `TICKET_TIER_BUDGETS.medium.worker_timeout_seconds` in `extension/src/services/pickle-utils.ts` remains `>= 60 * 60` with `worker-timeout-tier-budget.test.js` asserting the documented medium-tier default.
- `pickle-utils.ts` — INVARIANT: session-map prune and same-cwd lookup must recover tmp-only `state.json` before phantom/readable checks. BREAKS: cancel/status/retry/worker-setup prune or miss live recoverable sessions. ENFORCE: `extension/tests/cancel.test.js`. PATTERN_SHAPE: `fs\\.accessSync\\(.*state\\.json` or `new StateManager\\(\\)\\.read\\(statePath\\)` inside session discovery helpers.
- `state-manager.ts` — INVARIANT: orphan tmp promotion must reject snapshots missing core fields or newer than the runtime schema before `renameSync`. BREAKS: partial or future-schema tmp overwrites readable `state.json`, then older runtimes brick on recovery. ENFORCE: `state-manager.test.js` orphan tmp recovery coverage. PATTERN_SHAPE: `state.json.tmp.<pid>` JSON object promoted via `isStateSnapshotNewer(...)`; INVARIANT: reset-step snapshots with `step: null` are valid recoverable state during phase handoff. BREAKS: corrupt-base recovery drops newer `clearExitReason` tmp state and loses phase-reset progress. ENFORCE: `state-manager.test.js` reset-step orphan tmp coverage. PATTERN_SHAPE: `clearExitReason(..., { resetStep: true })` followed by orphan tmp recovery.
- `citadel/audit-runner.ts` — INVARIANT: Citadel header state reads must call `readRecoverableJsonObject(state.json)` without pre-gating on `existsSync(state.json)` before inspecting `recoverable_phase_failure` activity. BREAKS: orphan or newer dead-owner tmp snapshots hide pickle failure headers and false-green the audit preamble. ENFORCE: `extension/tests/citadel-cross-phase-fixture.test.js`. PATTERN_SHAPE: `buildCitadelReportHeader\\(` with `existsSync\\(statePath\\)` or raw `JSON.parse\\(readFileSync\\(statePath`; INVARIANT: cross-phase artifact reads must promote recoverable `anatomy-park.json` / `szechuan-sauce.json` tmp snapshots before missing/corrupt checks. BREAKS: Citadel drops fresh replay findings or emits stale `anatomy-park:missing`. ENFORCE: `extension/tests/citadel-cross-phase-fixture.test.js`. PATTERN_SHAPE: `readPhaseFindings\\(` with `existsSync\\(artifactPath\\)` or raw `JSON.parse\\(readFileSync\\(artifactPath`.
- `citadel/trap-door-coverage-audit.ts` — INVARIANT: scoped runs emit orphan findings only for changed tests/docs, but `referencedFiles` still derives from all `CLAUDE.md`. BREAKS: repo-wide orphan noise or changed tests bypass unchanged ENFORCE docs. ENFORCE: `extension/tests/citadel/trap-door-coverage-audit.test.js`. PATTERN_SHAPE: scoped `auditTrapDoorCoverage(diff)` + repo-wide `referencedFiles`; INVARIANT: bare subsystem `ENFORCE:` refs resolve from `extension/tests/`, not repo root. BREAKS: valid trap doors false-report `orphan-enforce` and `orphan-test-file` drift. ENFORCE: `extension/tests/citadel/trap-door-coverage-audit.test.js`. PATTERN_SHAPE: `ENFORCE: \`?<name>.test.js` in subsystem `CLAUDE.md` without `extension/tests/` prefix.
- `citadel/audit-runner.ts` — INVARIANT: malformed `composes:` graphs must fail Citadel; never downgrade `parseWithComposes(...)` errors to `parsePrdMarkdown(...)`. BREAKS: bad composed PRD paths audit as false-clean root-only graphs. ENFORCE: `extension/tests/citadel-audit-runner.test.js`. PATTERN_SHAPE: `catch (err) { if (err instanceof ComposesError) parsedPrd = parsePrdMarkdown(...) }`.
- `citadel/prd-parser.ts` (walkComposeChain diamond) — INVARIANT: cycle detection MUST use a DFS recursion-path set (`onPath`, add-on-enter/remove-on-exit), NOT a cumulative `visited` set; a node reached via two branches is a benign diamond (skip via `processed`), not a cycle. BREAKS: a valid diamond compose graph throws ComposesCycleError, crashing the whole UNwrapped Citadel audit. ENFORCE: `extension/tests/citadel/prd-parser.test.js`. PATTERN_SHAPE: `onPath.add(realPath)` before recurse + `onPath.delete(realPath)` after in walkComposeChain.
- `citadel/divergence-reconciliation.ts` — INVARIANT: `changedLineEvidence` MUST try/catch its `readFileSync` and return `[]` on failure, because `reconcileDivergences` runs UNwrapped by `safeRunAnalyzer` in `audit-runner.ts`. BREAKS: one changed file unreadable from the working tree (deleted/moved post-`head`, broken symlink, perms) throws out of `buildCitadelAuditReport` and crashes the entire Citadel audit. ENFORCE: `extension/tests/citadel-divergence-reconciliation.test.js`. PATTERN_SHAPE: `readFileSync(path.join(repoRoot, file.path)` inside `changedLineEvidence` without an enclosing `try`.
- `citadel/ac-shape-audit.ts` (`readManifestText`) — INVARIANT: every session-manifest read MUST be try/catch (return `''` on failure), NOT `existsSync`-filter + `readFileSync`-map; `auditAcShape` runs UNwrapped by `safeRunAnalyzer`. `existsSync` proves existence, not readability — EACCES/EISDIR or a TOCTOU atomic-rename of `refinement_manifest.json` throws out of `buildCitadelAuditReport` and crashes the whole audit. ENFORCE: `extension/tests/citadel-ac-shape-manifest-unreadable.test.js`. PATTERN_SHAPE: `existsSync(X)` then unguarded `readFileSync/statSync/readdirSync(X)` in an UNwrapped analyzer (audit-runner unwrapped set: ac-shape, diff-hygiene `fileSize`, sibling-auth, rule-set-invariant, divergence, project-shape, diff-walker, parseWithComposes). Working-tree changed-file reads MUST be guarded; the PRD-input read (`ac-shape:72`, `audit-runner:79`) and `parseWithComposes` throw BY DESIGN (mandatory precondition, fail-Citadel).
- `citadel/diff-hygiene.ts` (`fileSize`) — INVARIANT: `fileSize` MUST try/catch `statSync` (return 0), NOT `existsSync`-then-`statSync`, because `auditDiffHygiene` runs UNwrapped by `safeRunAnalyzer`. BREAKS: a TOCTOU-removed or unstattable added file throws out of `buildCitadelAuditReport`, crashing the whole audit. ENFORCE: `extension/tests/citadel-diff-hygiene-filesize-unreadable.test.js`. PATTERN_SHAPE: `statSync(fullPath)` in `fileSize` without an enclosing `try`.
- `citadel/diff-walker.ts` (`getChangedLineRanges`/`summarizeBlame`) — INVARIANT: per-file `runGit` calls MUST pass `check=false` (fail soft to `''`), NOT the default `check=true`, because `walkDiff` runs UNwrapped by `safeRunAnalyzer` (audit-runner.ts:84) and its output feeds every downstream analyzer. `getDiffFiles` (the aggregate diff) stays `check=true` BY DESIGN as the mandatory precondition, like `parseWithComposes`. BREAKS: a gitlink/submodule pointer or type-change in the diff makes per-file `git blame`/`git diff` exit non-zero ("fatal: no such path"); an unguarded throw crashes the entire Citadel audit over one un-blameable file. ENFORCE: `extension/tests/citadel-diff-walker-gitlink-unblameable.test.js`. PATTERN_SHAPE: per-file `runGit([...], repoRoot)` inside `walkDiff` helpers without the trailing `false` check arg.
- `convergence-gate.ts` — INVARIANT: test-script safety must inspect delegated leaf commands before blocking `integration`-named wrappers. BREAKS: `npm test` chains like `test -> test:integration -> node bin/test-runner.js` false-green by skipping tests entirely. ENFORCE: `extension/tests/services/convergence-gate-test-safety.test.js`. PATTERN_SHAPE: raw `scripts.test` regex block without recursive `npm|pnpm|yarn run <script>` leaf resolution.
- `convergence-gate.ts` (R-FGNC Finding #18 gate classifier) — INVARIANT: `buildFailures` uses the subprocess EXIT CODE as the sole pass/fail signal (`exitCode === 0` → zero failures, regardless of stderr); it scans BOTH stdout+stderr combined (never `stderr || stdout` — real tsc/eslint errors land on stdout while pnpm env-noise lands on stderr) and runs `stripEnvNoise` to drop pnpm `WARN  Issue while reading "<file>"` config-read lines BEFORE failure-line parsing. BREAKS: a pnpm `.npmrc` `${TOKEN}` WARN on stderr masks the real TS/lint failures, the classifier reports only the benign WARN, and the remediator exhausts its cap on noise while real toolchain debt ships silently. ENFORCE: `extension/tests/services/convergence-gate-env-noise.test.js`. PATTERN_SHAPE: `stripEnvNoise|ENV_NOISE_WARN_RE`.
- `microverse-state.ts` — INVARIANT: `violation_ledger` is the live judge-visible set, so each full judge result must replace it and drop resolved entries. BREAKS: stale prior violations suppress re-reported regressions and skew set-op classification. ENFORCE: `extension/tests/microverse-helpers.test.js` resolved-ledger regression. PATTERN_SHAPE: `updateViolationLedger(...)` mutates/appends without rebuilding from current `judgeResult.violations`.
- `bundle-state-integrity.ts` — INVARIANT: `statePathsForBundle` must include `microverse_*` children when `state.json` exists only as a newer recoverable `state.json.tmp.*`, not just when the base file exists. BREAKS: AC-BUNDLE-03 skips child relaunch-cap violations and false-greens exhausted bundles. ENFORCE: `extension/tests/pipeline-runner.test.js` tmp-only microverse relaunch-cap coverage. PATTERN_SHAPE: `entry.name.startsWith('microverse_')` with child enrollment gated only by `existsSync(childStatePath)`.
- `jar-utils.ts` — INVARIANT: `addToJar` must treat `state.json.tmp.*` as live session state before any "not found" preflight. BREAKS: tmp-only recoverable sessions cannot be jarred, so metadata/deactivation never happen. ENFORCE: `extension/tests/jar-utils.test.js`. PATTERN_SHAPE: `existsSync(statePath)` guard before `StateManager.read(statePath)` in `addToJar`.
- `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 }`.
- `recoverable-json.ts` — INVARIANT: `readRecoverableJsonObject(path)` is the sole JSON recovery primitive for files that may have `.tmp.<pid>` partial writes; direct `fs.readFileSync` + `JSON.parse` at sites that read mutable session artifacts (state.json, microverse.json, anatomy-park.json, pipeline-status.json, settings) is forbidden because it bypasses orphan-tmp promotion and dead-pid demotion. BREAKS: interrupted writes produce corrupted reads, the runtime ignores valid recoverable snapshots, and pipelines silently strand on stale base files while a newer `.tmp.<pid>` sibling holds the live state. ENFORCE: `extension/tests/get-session.test.js`, `extension/tests/resolve-state.test.js`, `extension/tests/state-manager.test.js`. PATTERN_SHAPE: `readRecoverableJsonObject(...)` at every mutable-session-artifact read site; no `JSON.parse(fs.readFileSync(...))` in services that read those artifacts.
- `promise-tokens.ts` — INVARIANT: `scrubForbiddenWorkerTokens(text)` must be applied to all worker output before promise-token detection; `FORBIDDEN_WORKER_TOKENS` is the authoritative list of orchestrator-only tokens (`EPIC_COMPLETED`, etc.) that workers must never emit, and `PROMISE_TOKENS` is the authoritative detected-token set. BREAKS: orchestrator-only tokens emitted by workers escape detection, falsely advance the lifecycle (e.g. a worker accidentally emitting `EPIC_COMPLETED` would short-circuit the manager loop), and corrupt session forensics. ENFORCE: `extension/tests/activity-event-payload.test.js`, `extension/tests/promise-tokens.test.js`. PATTERN_SHAPE: `scrubForbiddenWorkerTokens(` invoked on every worker stdout/stderr stream before classifier/promise-token detection paths.
- `pickle-utils.ts` (R-MWCL-3 collapsed-layout-repair) — INVARIANT: when `readPaneCurrentCommand` returns `null` for any watcher pane, `restartDeadWatcherPanes` MUST first probe `tmux list-panes -t <session>:monitor`; on probe failure (window entirely gone), it MUST call `ensureMonitorWindowFn` (injectable, defaults to `ensureMonitorWindow`) and return without any `split-window`; on probe success (window survives), `recreateMissingWatcherPane` MUST issue split-window then send-keys then `select-layout tiled`, and every repair log line MUST contain the literal tag `collapsed-layout-repair`. BREAKS: when `remain-on-exit off` removes a dead pane entirely (collapsed layout), pane repair silently skips with no `select-layout` and no escalation when the whole window is gone — monitor layout stays corrupted for the rest of the pipeline lifetime. ENFORCE: `extension/tests/pickle-utils-restart-dead-watcher-panes-collapsed.test.js`. PATTERN_SHAPE: `list-panes` probe before `recreateMissingWatcherPane`; `select-layout tiled` after successful `send-keys`; `collapsed-layout-repair` in every repair log line; `ensureMonitorWindowFn` parameter on `restartDeadWatcherPanes`.
- `pickle-utils.ts`; `src/bin/monitor.ts` (R-MWCL bundle — monitor spawn-mode + collapsed-layout repair) — INVARIANT 1: `ensureMonitorWindow` MUST infer monitor mode from `state.command_template` when no explicit mode argument is given; defaulting to `pickle` for non-pickle templates causes immediate `render()` crashes and full monitor death. INVARIANT 2: `restartDeadWatcherPanes` MUST handle the case where a target pane index has been collapsed away by tmux (`readPaneCurrentCommand === null`) by creating a new pane via `tmux split-window`, not by logging-and-continuing. INVARIANT 3: `render()` in `monitor.js` MUST treat mode-mismatch errors as recoverable (`{ active: false }` + retry) rather than `process.exit(2)`. BREAKS: inferred-mode regression silently routes non-pickle pipelines into the pickle layout, crashing the dashboard for anatomy-park and szechuan-sauce phases; collapsed pane layouts persist for the rest of the pipeline lifetime without `split-window` fallback; mode-mismatch kills the entire monitor dashboard irreversibly instead of recovering. ENFORCE: extension/tests/integration/monitor-collapsed-layout-respawn.test.js (introduced by ticket ce1642b1). PATTERN_SHAPE: `ensureMonitorWindow\(.*['\"]pickle['\"]` called with a non-pickle `state.command_template`; `if \(currentCommand === null\) \{[\s\S]*continue;` in `restartDeadWatcherPanes` without a `tmux split-window` fallback above it; `setInterval\(tick, 30_000\)` in `startRespawnWatchdog` without a synchronous `tick()` invocation immediately following.
- `state-manager.ts` (R-POD — paused-orphan demotion AND-vs-OR predicate) — INVARIANT: `getPausedOrphanDemotion` MUST require BOTH `ageMs >= STATE_STALE_THRESHOLD_MS` (300_000 ms) AND `deadMappedPid`. Either condition alone is insufficient signal to demote a session — fresh state with a stale session-map PID is a healthy session whose launch-shell PID has merely rolled over, and stale state with a live mapped PID is a slow but still-owned session. BREAKS: reverting `&&` to `||` demotes healthy sessions on PID rollover (fresh state, stale map) or keeps orphaned sessions active indefinitely (stale state, live map). ENFORCE: `extension/tests/services/resolve-state-paused-orphan.test.js` covers all 4 cells of the `(age, mapped-pid)` matrix. PATTERN_SHAPE: `||` between an age-staleness check and a process-liveness check in any demotion / orphan-detection predicate — should be `&&`. TICKET_TRACEABILITY: R-POD-1 (`0ff20931`), R-POD-2 (`df11df4d`), R-POD-3 (`2a92a614`) from `prds/p3-state-manager-paused-orphan-demotion-or-aggression.md`.
- `src/services/pickle-utils.ts` (R-RIC-EXPLICIT — hasCompletionCommit explicit-source) — INVARIANT: `hasCompletionCommit` MUST honor explicit `completion_commit:` frontmatter in `<sessionDir>/<ticketId>/linear_ticket_<ticketId>.md`. BREAKS: returns `source: 'inferred'` when explicit, causing `guardCompletionCommitBeforeDone` to fatal mid-bundle (R-RIC regression filed as MASTER_PLAN #83; 2026-05-26 ea04b6f8 incident). ENFORCE: `extension/tests/has-completion-commit-explicit-source.test.js`. PATTERN_SHAPE: `readFrontmatterField` call against `linear_ticket_<id>.md` (via `ticketFilePath(sessionDir, ticketId)` or equivalent) before falling back to grep-on-message-body.
- `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`.
- `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`, `extension/scripts/audit-ac-command-glob-safety.sh`. PATTERN_SHAPE: `shell: true` absent from the criterion-command branch.
- `verify-command-safety.ts` (R-PVTA/R-VSGE shared predicate) — INVARIANT: `NON_GUARANTEED_TOOLS`, `detectMissingTools`, `containsUnquotedGlobHazard` are defined ONLY in `verify-command-safety.ts`; `ac-phase-gate.ts`, `convergence-gate.ts`, and `audit-ac-command-glob-safety.sh` import from it. BREAKS: divergent inline copies of the predicate drift silently — host-tool detection or glob-hazard classification in ac-phase-gate and convergence-gate diverge from the shared definition without detection. ENFORCE: `extension/tests/services/verify-command-safety.test.js`.

## Module Export Catalog

Machine-maintained export index for the `services/` subsystem. The audit script treats these names as the minimum subsystem surface that this document covers.

- `ac-phase-gate.ts` -> `AC_PHASE_MANIFEST`, `AcEvaluationPhase`, `AcPhaseCriterion`, `AcPhaseGateFailure`, `AcPhaseGateResult`, `runAcPhaseGate`
- `activity-logger.ts` -> `_clearPendingBuffer`, `_getPendingBuffer`, `_setRetryDelayMs`, `getActivityDir`, `logActivity`, `pruneActivity`
- `agent-md-loader.ts` -> `AgentMdFrontmatter`, `AgentModel`, `LoadAgentMdOptions`, `LoadedAgentMd`, `defaultAgentsDir`, `loadAgentMd`, `parseAgentMdFrontmatter`, `resolveAgentMdPath`
- `artifact-validation.ts` -> `findMissingPrefixes`, `listLinearTicketFiles`, `requiredTierArtifactPrefixes`
- `backend-spawn.ts` -> `AddDirOutsideSandboxError`, `BackendPreSpawnAssertion`, `JudgeInvocationOptions`, `ManagerInvocationOptions`, `ReasoningEffort`, `SpawnInvocation`, `WorkerBackendResolution`, `WorkerInvocationOptions`, `__resetBackendWarnings`, `assertAddDirsUnderTmpdirIfTestMode`, `assertBackendPreSpawn`, `backendEnvOverrides`, `buildJudgeInvocation`, `buildManagerInvocation`, `buildWorkerInvocation`, `isBackend`, `loadBackendFromSession`, `resolveBackend`, `resolveBackendFromStateFile`, `resolveBackendFromStateFileWithSource`, `resolveWorkerBackendFromState`, `resolveWorkerBackendFromStateFile`
- `bundle-finalize.ts` -> `BundleFinalizeTicket`, `BundleTestFloorContribution`, `BundleTestFloorInput`, `BundleTestFloorResult`, `computeBundleTestFloor`, `parseCommitTestDelta`, `parseRefinementBaseline`, `renderMorningSummaryFrontmatter`
- `bundle-state-integrity.ts` -> `RelaunchCapAuditResult`, `RelaunchCapAuditViolation`, `auditCodexManagerRelaunchCaps`
- `calibration-corpus.ts` -> `CALIBRATION_SCHEMA_VERSION`, `CALIBRATION_SUITES`, `CalibrationBaseline`, `CalibrationDriftEntry`, `CalibrationDriftReport`, `CalibrationFixtureResult`, `CalibrationSuite`, `DEFAULT_CALIBRATION_DRIFT_THRESHOLD_PCT`, `assertKnownSuite`, `buildCalibrationBaseline`, `calibrationBaselinePath`, `checkCalibrationDrift`, `loadCalibrationThresholdPct`, `writeCalibrationBaseline`
- `circuit-breaker.ts` -> `CircuitBreakerConfig`, `CircuitBreakerState`, `CircuitState`, `CircuitTransition`, `IterationResult`, `ProgressResult`, `canExecute`, `countFilesChanged`, `detectProgress`, `extractErrorSignature`, `initCircuitBreaker`, `isConstraintDiscoverySignature`, `loadSettings`, `normalizeErrorSignature`, `readCircuitBreakerState`, `recordIterationResult`, `resetCircuitBreaker`
- `classifier-utils.ts` -> `detectOutputFormat`, `extractAssistantContent`, `observeCodexToolCallStream`
- `codex-manager-relaunch.ts` -> `currentManagerRelaunchCount`, `evaluateCodexManagerRelaunch`, `managerRelaunchCap`, `managerRelaunchCapForBackend`, `recordCodexManagerRelaunch`
- `convergence-defaults.ts` -> `DEFAULT_ADVERSARY_MODEL`, `DEFAULT_ADVERSARY_PROMPT`, `DEFAULT_ADVERSARY_SEALED_FROM_SOURCE`, `DEFAULT_BODY_MAX_VISITS`, `DEFAULT_BUILD_API_CMD`, `DEFAULT_BUILD_UI_CMD`, `DEFAULT_COMMIT_PUSH_TIMEOUT`, `DEFAULT_CONVERGENCE_EPSILON`, `DEFAULT_CONVERGE_MAX_VISITS`, `DEFAULT_CONVERGE_TIMEOUT`, `DEFAULT_FIX_BACKEND_HARNESS`, `DEFAULT_FIX_BACKEND_MODEL`, `DEFAULT_FIX_BACKEND_PROMPT`, `DEFAULT_FIX_FRONTEND_HARNESS`, `DEFAULT_FIX_FRONTEND_MODEL`, `DEFAULT_FIX_FRONTEND_PROMPT`, `DEFAULT_FIX_TIMEOUT`, `DEFAULT_FP_VERIFY_CMD`, `DEFAULT_GOAL_GATE_MAX_VISITS`, `DEFAULT_GOAL_GATE_TIMEOUT`, `DEFAULT_LINT_CMD`, `DEFAULT_MAX_ITERATIONS`, `DEFAULT_MECHANICAL_BUILD_TIMEOUT`, `DEFAULT_MECHANICAL_MAX_VISITS`, `DEFAULT_MECHANICAL_TESTS_TIMEOUT`, `DEFAULT_REPRO_VERIFY_CMD`, `DEFAULT_REVIEW_BE_MODEL`, `DEFAULT_REVIEW_BE_PROMPT`, `DEFAULT_REVIEW_FE_MODEL`, `DEFAULT_REVIEW_FE_PROMPT`, `DEFAULT_REVIEW_INT_MODEL`, `DEFAULT_REVIEW_INT_PROMPT`, `DEFAULT_REVIEW_TIMEOUT`, `DEFAULT_TESTS_API_CMD`
- `convergence-gate.ts` -> `BaselineMissingError`, `BaselineStaleError`, `BaselineWriteFailedError`, `GATE_REMEDIATION_EVENT_NAMES`, `GateError`, `GateTimeoutError`, `RunGateOpts`, `assertBaselineFresh`, `assignOccurrenceIndices`, `buildFailures`, `detectProjectType`, `filterByScope`, `getWorkspacePackages`, `runGate`, `stripEnvNoise`, `subtractBaseline`
- `council-fanout.ts` -> `StackTier`, `SubagentSpec`, `planFanOut`
- `council-schema.ts` -> `BranchEntry`, `CodexBranchResult`, `CodexVerdictValue`, `CouncilSchemaError`, `Directive`, `Finding`, `KNOWN_CATEGORIES`, `KnownCategory`, `StackOverview`, `SubagentPayload`, `TrapDoor`, `validateDirective`, `validateSubagentPayload`
- `dot-builder.ts` -> `BUILD_ERROR_CODES`, `BuildError`, `BuildErrorCode`, `BuildResult`, `BuildResultNs`, `BuilderSpec`, `BuilderSpecNs`, `DefenseMatrix`, `DefenseMatrixNs`, `Diagnostic`, `DiagnosticNs`, `DotBuilder`, `MicroverseOpts`, `MicroverseOptsNs`, `PhaseSpec`, `PhaseSpecNs`, `StylesheetConfig`, `StylesheetConfigNs`, `ValidationResult`, `ValidationResultNs`, `WorkspaceOpts`, `WorkspaceOptsNs`, `_parseConvergenceSpec`, `_parsePhases`
- `git-utils.ts` -> `ConcurrentGitHolder`, `DiffEntry`, `DiffStatus`, `TicketFrontmatterPatch`, `getBranchName`, `getDiffFiles`, `getGithubUser`, `getHeadBranch`, `getHeadSha`, `getMergeBase`, `isGitIgnoredPath`, `isWorkingTreeDirty`, `listWorkingTreeDirtyPaths`, `lookupCommandForPid`, `probeConcurrentGitAccess`, `resetToSha`, `runGit`, `updateTicketFrontmatter`, `updateTicketStatus`
- `jar-utils.ts` -> `addToJar`
- `judge-spawn-env.ts` -> `JudgeBackend`, `buildJudgeSpawnEnv`, `getJudgeEnvForAttempt`
- `linear-integration.ts` -> `emitBundleLinearComments`, `syncLinearTicketStatus`
- `manager-relaunch.ts` -> `ManagerRelaunchDecision`, `ManagerRelaunchExitKind`, `RelaunchEvaluation`, `currentManagerRelaunchCount`, `evaluateManagerRelaunch`, `managerRelaunchCap`, `managerRelaunchCapForBackend`, `recordManagerRelaunch`
- `metrics-utils.ts` -> `BackendTokenTotals`, `DailyLOC`, `DailyTokens`, `MetricsCache`, `MetricsReport`, `MetricsRow`, `MetricsTotals`, `ProjectSummary`, `buildReport`, `formatNumber`, `parseGitLogOutput`, `parseSessionLine`, `scanGitRepos`, `scanSessionFiles`, `shortenSlug`
- `microverse-state.ts` -> `LedgerSnapshot`, `assertMicroverseStateShape`, `classifyFailure`, `clearAmnesiacExits`, `compareMetric`, `createMicroverseState`, `findLastAcceptedEntry`, `generateViolationId`, `getLastAcceptedScore`, `isConverged`, `readMicroverseState`, `readRecoverableJsonObject`, `recordAmnesiacExit`, `recordFailedApproach`, `recordIteration`, `recordStall`, `resolveStallLimit`, `updateViolationLedger`, `writeMicroverseState`
- `pickle-utils.ts` -> `CompletionCommitEvidence`, `ComposeManagerPromptOpts`, `DEFAULT_WORKER_TEST_GATE_TIMEOUT_MS`, `DisplayMacNotificationOptions`, `EnsureMonitorWindowOptions`, `EnsureMonitorWindowResult`, `MANAGER_ROLE_FRAMING_BLOCK`, `MatrixStyle`, `MissingTicketError`, `MonitorMode`, `NOTIFICATION_TIMEOUT_MS`, `RAIN_CHARS`, `RetryLockOptions`, `Style`, `TICKET_TIER_BUDGETS`, `TIER_DIFF_ENVELOPE`, `TicketComplexityTier`, `TicketInfo`, `TicketStatus`, `TicketTierBudget`, `TierCapPartial`, `TierCapsConfig`, `VALID_TICKET_COMPLEXITY_TIERS`, `_resetExtensionDirFallbackForTests`, `buildHandoffSummary`, `clearTicketCacheFields`, `clearTicketResolutionTimestamps`, `collectTickets`, `composeManagerPromptFromSkill`, `detectLogTruncation`, `displayMacNotification`, `drainLog`, `drainStreamJsonLines`, `ensureMonitorWindow`, `extractFrontmatter`, `findSessionPathForCwd`, `formatLocalDateKey`, `formatTime`, `getDataRoot`, `getExtensionRoot`, `getHeight`, `getMicroverseSettings`, `getTicketStatus`, `getTicketTierBudgetWithOverrides`, `getWidth`, `hasCommitReferencingTicketSince`, `hasCompletionCommit`, `inferMonitorMode`, `isoCompactStamp`, `latestIterationLog`, `loadPickleSettingsBag`, `markTicketDone`, `markTicketSkipped`, `matrixSeparator`, `monitorModesCompatible`, `normalizeTicketComplexityTier`, `parseTicketFrontmatter`, `printMinimalPanel`, `pruneOldSessions`, `pruneOrphanedMapEntries`, `readFrontmatterField`, `readPickleSettingsTierCaps`, `readStateTierCapOverrides`, `resolveJudgeBackend`, `resolveSessionPath`, `resolveWorkerTestGateTimeoutMs`, `restartDeadWatcherPanes`, `runCmd`, `safeErrorMessage`, `sleep`, `statusSymbol`, `stripSetupSection`, `stripStepOneBlock`, `ticketFilePath`, `ticketInfoBudget`, `ticketTierBudget`, `topoSortTickets`, `updateState`, `upsertFrontmatterField`, `withRetryLock`, `wrapText`, `writeStateFile`
- `pr-factory.ts` -> `createPR`
- `project-type-classifier.ts` -> `ClassifyProjectTypeOptions`, `PROJECT_TYPE_CATEGORIES`, `ProjectTypeCategory`, `ProjectTypeClassification`, `ProjectTypeConfidence`, `ProjectTypeDefinition`, `ProjectTypeScore`, `classifyProjectType`, `getProjectTypesCsvPath`, `loadProjectTypeDefinitions`
- `promise-tokens.ts` -> `FORBIDDEN_WORKER_TOKENS`, `PROMISE_TOKENS`, `PromiseToken`, `ScrubResult`, `scrubForbiddenWorkerTokens`
- `recoverable-json.ts` -> `readRecoverableJsonObject`
- `scope-resolver.ts` -> `ParsedScope`, `RefreshEntry`, `RefreshScopeOpts`, `ScopeArgs`, `ScopeError`, `ScopeErrorCode`, `ScopeJson`, `ScopeMode`, `ScopeStrategy`, `buildScopeV1Schema`, `computeOneHop`, `filterByPaths`, `filterBySubsystem`, `parseScope`, `refreshScope`, `resolveScope`
- `state-manager.ts` -> `ClearExitReasonOpts`, `FinalizeOpts`, `InvalidActivityEventError`, `SchemaVersionAheadError`, `SchemaVersionDeployDriftError`, `SchemaWriteOpts`, `StateManager`, `TimeoutStubMeta`, `WithLockOptions`, `assertSchemaVersionDeployParity`, `clearExitReason`, `finalizeTerminalState`, `isProcessAlive`, `readMappedPid`, `recordExitReason`, `safeDeactivate`, `withLock`, `writeActivityEntry`, `writePipelineAutoResumedEvent`, `writeTimeoutStub`
- `ticket-completion-evidence.ts` -> `EvidenceCtx`, `EvidenceKind`, `EvidenceResult`, `PersistOpts`, `PersistResult`, `RevertDecision`, `RevertPolicy`, `gateForPhantomDoneRevert`, `persistEvidence`, `readEvidence`
- `transaction-ticket-ops.ts` -> `ApplyCourseCorrectionRestructureInput`, `ApplyCourseCorrectionRestructureResult`, `CourseCorrectionApplyLedgerEntry`, `CourseCorrectionBranch`, `CourseCorrectionRecoveryClass`, `CourseCorrectionRecoveryMode`, `MaterializeTicketFileSpec`, `MaterializeTicketSpec`, `MaterializedTicketPlan`, `PlannedFileWrite`, `PlannedTicketWrite`, `RecoverCourseCorrectionInput`, `RecoverCourseCorrectionResult`, `ReverseLedger`, `ReverseLedgerEntry`, `TicketStatus`, `TicketTransactionContext`, `applyCourseCorrectionRestructure`, `materializeNewTicket`, `recoverCourseCorrectionFromLedger`, `replayReverseLedger`, `updateTicketStatusInTransaction`
- `worker-shutdown.ts` -> `flushAndExit`
