Skip to content

Batch 2026-07-28-gate-hygiene-t2: 1 issue (Batch_2026-07-28_134922) - #189

Merged
aaddrick merged 2 commits into
mainfrom
Batch_2026-07-28_134922
Jul 29, 2026
Merged

Batch 2026-07-28-gate-hygiene-t2: 1 issue (Batch_2026-07-28_134922)#189
aaddrick merged 2 commits into
mainfrom
Batch_2026-07-28_134922

Conversation

@aaddrick

Copy link
Copy Markdown
Owner

Batch integration branch for the gate-hygiene tier 2 run. A human reviews and merges this; the engine never merges to the base branch itself.

Closes #167

What landed

#167 — A finding is a hypothesis at the evaluator-fed fix stages (PR #185)

The verify-then-adopt-or-rebut framing that the two contrarian revision stages already carried now reaches the three fix stages fed by a judge's opinion: quality-fix, test-quality-fix, and pr-fix. The two fed by a real failure, test-fix and browser-fix, are deliberately untouched, since telling a fixer that a failing test is a hypothesis inverts a correct guard.

FIX_SCHEMA gains an optional rebutted: [{finding_id, evidence}], so a fixer who thinks a finding is wrong has somewhere to put that other than an error return.

The enforcement is deterministic JS, not another prompt rule: a round that rebuts findings while changing no files and applying no fixes is a rebuttal-only round. Quality and test loops exit on one; the merge gate allows one and halts to needs_human on a second consecutive round, recording carried-unresolved with a Verification Gaps line rather than approving.

Run note

This batch was launched for #167 and #168 and stopped by request partway through #168, which had reached its second plan iteration with no implementation commit. #168 is not in this PR. Its claim and worktree are intact and it resumes against this same batch branch.

Verification

node --check + lint-engine + bash -n + manifest JSON   pass
node --test                                            781/781   (+39 from #167)
bash tests/setup-worktree.test.sh                       32/32
engine size                            478,435 / 524,288 bytes

Release commit chore(release): v0.2.2 was added by hand, since the run was stopped before the Report phase that normally writes it.

aaddrick added 2 commits July 28, 2026 16:18
…x stages (#185)

* feat(gate-findings): add rebuttal primitives for evaluator-fed fix stages (#167)

Task 1 of #167: behaviour-neutral primitives that let a fixer treat a
reviewer's finding as a hypothesis to verify rather than a command to obey.

- FIX_SCHEMA.rebutted ({finding_id, evidence}), not in required — mirrors
  REVIEW_SCHEMA.issues staying out of its own required list.
- FINDING_HYPOTHESIS_ASK beside HANDOFF_ASK/COMMIT_SHA_ASK: verify before
  acting, record disproof in `rebutted` if wrong, rebut only what you didn't
  fix, only bracketed-id findings are rebuttable, and a gate-agnostic
  consequence clause (no immediate-exit claim, since pr-review continues
  into another review round).
- normalizeRebuttals(raw, findings): pure, fails toward today's behavior on
  every drop (non-array, blank finding_id/evidence, unrendered id); carries
  the matched finding's summary through.
- contestedBlock(ctx): pure, renders rebutted-but-not-adjudicated findings
  back to the next reviewer with an inverted (vs settledBlock) closing
  contract, and states the iteration-2+ do-not-re-flag instruction doesn't
  apply to a contested entry. Never calls settleDecision(). Rendered
  immediately after settledBlock() at the quality/spec/code review prompts;
  emits '' until a later task populates ctx.contested.
- retypeGateDisposition(ctx, gate, from, to): pure, moves one disposition
  count between buckets without touching count/severity; from === to is a
  documented count-preserving no-op, reachable at MAX_QUALITY_ITERATIONS.
- ctx.metrics.rebuttal_only_rounds, plus tests/harness.js freshMetrics()
  parity.

None of these are wired into the fix stages' control flow yet (task 2/3);
FINDING_HYPOTHESIS_ASK and retypeGateDisposition are unused in production
until then.

45 new unit tests in tests/gate-findings.test.js. node scripts/lint-engine.js
--fix kept .claude/workflows/ticketmill.js in lockstep. node --test: 763/763
passing.

* feat(gate-findings): wire rebuttal-only exits into the quality and test loops (#167)

Renders FINDING_HYPOTHESIS_ASK in the quality-fix and test-quality-fix
prompts only when the reviewer/validator named structured findings, and
wires normalizeRebuttals()/retypeGateDisposition()/contestedBlock() (from
task 1) into both evaluator-fed fix loops so a fix that rebuts every
finding and applies none can never approve its own gate:

- runQualityLoop: a third loop-exit flag `rebutted`, kept separate from
  `degraded` so the rolling degrade window is untouched, retypes the
  disposition already booked for that iteration to 'carried-unresolved',
  records the rebuttal as contested (not settled), and rolls up to exactly
  one VERIFY_SKIPS line per issue (ctx.quality_rebuttals /
  quality_rebuttal_skip_index), mirroring the existing cap roll-up.
- runTestLoop: same predicate at test-quality-fix, exits through the
  loop's own { ok: true } path (never ok:false) so a rebuttal-only round
  can never route through the 'test-loop' merge-block stage key. No
  gate key to retype here (test-quality books none today).

The two ORACLE-fed fix stages (test-fix, browser-fix) are untouched and
keep their anti-rebuttal guards — a failing test is ground truth, not a
hypothesis to verify.

Extends tests/quality-loop.test.js and tests/test-loop.test.js with the
rebuttal-only exit, partial-rebuttal/omitted-field/blank-evidence
non-triggers, the rolled-up VERIFY_SKIPS line, and scope-pinning
assertions proving FINDING_HYPOTHESIS_ASK renders only at the three
evaluator-fed sites and never at test-fix.

* feat(gate-findings): wire the pr-review merge gate's rebuttal-only continue

Renders FINDING_HYPOTHESIS_ASK in the pr-fix prompt when either reviewer
returned structured findings, and evaluates the rebuttalOnly predicate over
the union of both reviewers' rendered finding sets. Unlike the quality and
test loops (which exit their gate on a rebuttal-only round), pr-review
CONTINUES: it retypes the iteration's disposition to carried-unresolved,
carries the dispute into ctx.contested so the next iteration's reviewers see
it, records one un-rolled Verification Gaps line, and moves on to the next
review iteration instead of ending the gate. A per-call counter permits
exactly one such round per issue; a second sets haltReason and falls through
to the existing needs_human path, leaving the PR open. The `continue`
precedes runQualityLoop so a rebuttal-only round (empty files_changed) never
burns a quality gate against an untouched tree.

Issue #167

* docs(gate-hygiene): document the rebuttal framing for evaluator-fed fix stages (#167)

Add a "Rebuttal: a finding is a hypothesis, not a command" section to
gate-hygiene.md covering what a rebuttal is and its evidence bar, why
the framing is scoped to quality-fix/test-quality-fix/pr-fix and not
test-fix/browser-fix, FIX_SCHEMA.rebutted being schema-wide but read
at only three sites, normalizeRebuttals' fail-toward-today drops, the
three per-gate exits and why only pr-review can block a merge,
contestedBlock vs settledBlock's inverted contract, and
rebuttal_only_rounds (a continuation, not an exit, on its first
pr-review increment). Extend the gate-hygiene.md row in index.md's
file map, and correct three now-stale passages in engine-internals.md
that #167 falsifies: the carried-unresolved disposition enumeration
(missing the rebuttal-only route), the "only findings_empty_exits
distinguishes them" claim at pr-review, and findingsBlock's
description of a rendered finding's job list.

* fix(gate-findings): trim standing engine prose, close a disposition-tally gap

- Move the #167 rebuttal-framing comment blocks (contestedBlock,
  FINDING_HYPOTHESIS_ASK, retypeGateDisposition, normalizeRebuttals, and the
  three in-loop rebuttalOnly explainers) out of workflows/ticketmill.js into
  one-sentence pointers at docs/architecture/gate-hygiene.md, where the same
  rationale already lives — clears lint-engine's 92% size warning.
- reviewAndMerge()'s second (halting) rebuttal-only pr-fix round now calls
  retypeGateDisposition before breaking, so gate_findings['pr-review'] never
  leaves a leftover 're-litigated' count on the run a human is sent to
  inspect.
- Reword gate-hygiene.md's normalizeRebuttals claim: it enforces only
  non-blankness, not the prompt's concrete-evidence bar.
- Make contestedBlock's do-not-re-flag override site-agnostic so it also
  covers spec review's "stay consistent with prior reviews" instruction.
- Correct the contestedBlock comment and gate-hygiene.md to state plainly
  that nothing closes a ctx.contested entry today — a known gap, not the
  "only a reviewer closes it" claim that didn't match the code.

Fixes code review findings code-i1-1 through code-i1-5 on PR #185.

* docs(gate-hygiene): correct rebuttal_only_rounds halt-round description

[code-i2-1] The halting second pr-fix rebuttal-only round retypes the
disposition and breaks without incrementing the counter (by design, pinned
at tests/pr-review-gate.test.js:568-570), but the prose said the counter
"bounds an issue to at most two rebuttal-only pr-fix rounds" without noting
the halt itself is never counted. Clarify that the counter tracks
continuations, not rounds, and that the halt is already carried by
haltReason and the needs_human status.
@aaddrick
aaddrick merged commit 196a109 into main Jul 29, 2026
1 check passed
@aaddrick
aaddrick deleted the Batch_2026-07-28_134922 branch July 29, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A finding is a hypothesis at the evaluator-fed fix stages

1 participant