Skip to content

Durable per-issue gate state on the issue (substrate) #166

Description

@aaddrick

Problem

Nothing about an issue's gate state survives a run boundary. processIssue builds a fresh ctx every time:

  • settled: [] at :4821
  • metrics: { ..., pr_review_iters: 0, ... } at :4846

On the process_pr resume path at :4871, the run does worktree setup, pushes one "Resumed Run" decision, and calls reviewAndMerge(ctx), which starts its loop at iter = 1 against MAX_PR_REVIEW_ITERATIONS. Nothing reads a prior count, and settledBlock(ctx) renders empty into both review prompts at :4619 and :4639.

So a resumed run judges the PR with no record of what the contrarian gates adjudicated, and draws a fresh review budget. The reviewers are told to read issue and PR comments, which is a prose narrative, not a record.

runs.jsonl cannot fix this. buildLedgerLine at :5613 carries sixteen fields and gate_findings is not among them; the full per-issue detail lives in logs/<logs_dir>/runs/<run_tag>.json, which the engine never reads back. More decisively, logs/ is gitignored and the mill skill documents runs.jsonl as a per-host local artifact, so it evaporates in the cross-host resume case this exists to serve.

Change

This issue ships the substrate only. No consumer.

  1. Write. Post a structured block, behind the existing marker convention <!-- ticketmill <repo>#<issue> -->, carrying { schema, issue, gate_budgets, settled }. Two boundaries are enough: once when the plan gate resolves, and once per pr-review iteration. Append-only, read last-wins, mirroring the outcomes.jsonl contract that diffOutcomeGrades already implements.
  2. Read. Add one step to the preflight probe (:6905-6949) returning the raw blocks as strings, and a field on PREFLIGHT_SCHEMA (:478) to carry them.
  3. Parse. All parsing is pure JS above the TICKETMILL-TEST-HARNESS-SPLIT marker at :6522. The agent returns raw text and never decides, matching the OUTCOMES_SCHEMA.prior_ledger_lines contract and the PIN documented in docs/architecture/metrics.md under "A quality-fix iteration closed a PIN violation".

Two constraints

The fail-open must be visible. The preflight probe runs at M.probe (haiku, low effort, :352), already carries six numbered steps, and on death returns a hardcoded default at :6947. Three states must be distinguishable downstream: block found and parsed, block genuinely absent (a first run), and read failed. A read failure that silently presents as "absent" would hand the eventual consumer a fresh budget, which is the exact silent cap exhaustion this substrate exists to close. Log the failure case.

Self-validate the write. Because the consumer lands a tier later, this issue would otherwise ship an unexercised write path into GitHub comments. Include a same-run read-back that logs a mismatch, so the substrate is proven before anything depends on it.

If a new agent stage key is added, add it to the M map at :351. stageOpts at :412 defaults unknown keys to { model: 'sonnet' }, so an unregistered key silently runs at sonnet.

Acceptance criteria

  • The block is written at both boundaries and re-read across a simulated resume.
  • The parser is unit-tested above the split marker with no gh or git in the loop.
  • Found, absent, and read-failed are distinguishable, and read-failed logs.
  • Malformed JSON, a truncated block, and a block carrying a different issue number all fail open to today's behaviour rather than throwing.
  • A run with no prior block produces byte-identical behaviour to today.
  • The write is idempotent enough that a re-run does not corrupt last-wins reads.
  • node --test stays green. Add a dedicated test file; extend tests/decision-records.test.js if the settled ledger shape changes.

Out of scope

Nothing consumes the parsed state in this issue. Seeding ctx.metrics and ctx.settled, and enforcing an exhausted budget, is the follow-up that depends on this.

Documentation target

Document in CHANGELOG.md and in docs/architecture/gate-hygiene.md (create it if absent), adding its row to docs/architecture/index.md's file map.

Do NOT edit docs/architecture/pipeline.md, metrics.md, or failure-semantics.md. Their moved prose is hash-frozen by tests/architecture-provenance.test.js, and since the tech-docs stage runs at :4707, after the last test-loop execution, a frozen-prose edit surfaces only as a red CI check on the batch PR. New pages are authored text and are explicitly allowed; see docs/architecture/CLAUDE.md.

Metadata

Metadata

Assignees

Labels

gate-hygieneGate hygiene: findings, caps, and per-problem budgets (sequenced)

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions