Skip to content

fix(#77): deterministic near-green crossing — rotating-error detector + completion-only steer#170

Closed
agjs wants to merge 2 commits into
mainfrom
fix/near-green-rotation-v2
Closed

fix(#77): deterministic near-green crossing — rotating-error detector + completion-only steer#170
agjs wants to merge 2 commits into
mainfrom
fix/near-green-rotation-v2

Conversation

@agjs

@agjs agjs commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What

Closes the #77 near-green oscillation — the confirmed remaining barrier to deterministic green. A live 4-slice CRM build (build22, on current main with the acceptance gate + jsx-no-bind guide + nav-scope) parked all 4 slices: each REACHED green transiently, then sprayed back up (Company trace: 1↔red(40)↔GREEN↔red(56)…) and exhausted the escalation ladder without ever locking green. The per-idiom walls are down; this rotating-error tail is what's left.

How

At near green the model can clear its single remaining error, but the fix spawns a new one (extract a component → its required sibling set + colocated tests are now missing → fixing those creates more…). The error count stays low while the error set rotates, so the build never reaches 0. Count-only WS-B can't see it.

  • Rotating-error detector (near-green-checkpoint.ts): errorSetSignature + isNearGreenRotation over a trailing window — fires only on a count and phase plateau with a per-cycle-changing signature.
  • Completion-only steer + WS-B standdown (turn.ts): while rotation is active the model is steered to complete atomically, and the count-spray rollback stands down so the steered completion spike isn't reverted.
  • Behind TSFORGE_NO_NEAR_GREEN_ROTATION (default on).

Genuine per-cycle rotation (panel round-2, the historical blocker)

The soundness concern that shelved this work before: the detector judged rotation purely from gate output, so a flaky/stateful gate — or the check-tool + settleGate double-run (#59) re-evaluating unchanged files — could emit A→B→C signatures with no real fix-one→spawn-another cycle and falsely stand the rollback net down.

Fixed at the root: each near-green sample now carries an independent worktree revision — a content hash over the glob-resolved scope files, via the same snapshot substrate WS-B reverts with (so scope can't drift). isNearGreenRotation requires both the signature and the rev to change every cycle. A rotating signature over unchanged files reads as not-rotating and leaves the rollback net engaged. The rev is computed only on near-green cycles, so the hot path is untouched on red/green.

Review

  • harness-review panel: PASS (2 rounds). Round-1 surfaced the one genuine-rotation major (above); round-2 returned PASS with zero findings. Also addressed: rotationEmit() extracted so injectFeedback stays under the cognitive-complexity ceiling; stale test comment removed.
  • typecheck 0, lint 0, format clean.
  • All near-green + escalation/steer/checkpoint/session-gate/boringstack-build/acceptance suites pass (155 in the focused run; 3014/3036 in the full suite — the 9 failures are pre-existing network-flaky AgentRunner/review-round tests, "model endpoint unreachable", unrelated to this change).
  • New tests: constant-rev flaky gate is NOT rotation; the integration test now performs a genuine per-cycle edit (it previously demonstrated the bug by firing rotation with no edits).

Every core-loop change is flag-gated and the other loop paths are pinned unchanged.

agjs added 2 commits July 22, 2026 20:15
#77)

At near-green a build can sit at a stable low count while the error SET rotates
— each single-error fix spawns a different one (extract a component → its
siblings/tests are now missing → revert → rotate) — so it never reaches 0.
Count-only WS-B can't see it. build17 parked on this; build16 crossed only by
luck (4/4 harness-diagnose: the count stays at best-ever while the fingerprint
rotates).

Detector (near-green-checkpoint.ts): errorSetSignature — sorted unique per-error
tokens (rule|file family when present, else the key; stack-agnostic, line-
independent). isNearGreenRotation requires BOTH a count PLATEAU (the window sits
at one count — a moving count like 2→1→1 is progress, not rotation) AND a
changing signature (not all-same, which is a stuck single).

trackNearGreenRotation (turn.ts) records {count, sig} on near-green cycles,
tolerates a bounded run of spikes between them (MAX_NEAR_GREEN_SPIKE_GAP — past
it the stale window clears so far-apart episodes can't combine), does NOT record
completion-phase churn, and clears on green.

On rotation, injectFeedback prepends a GENERIC completion-only steer that
cleanly separates the two poles (do NOT open new surface — no new components/
splits/features; but DO create the siblings + colocated test a current error
requires, atomically, even though they aren't in the error list yet). Gated on
FOUR conditions: flag on (authoritative at emit) AND detector fired AND count ≤N
(not a spike) AND not completion phase. The near-green banner drops ONLY its
'do NOT create new files' lockdown clause when the steer fires (it would
contradict the steer) but KEEPS the regression callout.

Per-drive state cleared at both drive boundaries; a mid-run kill-switch flip
clears the sticky flag. Flag nearGreenRotation() default ON, kill
TSFORGE_NO_NEAR_GREEN_ROTATION. Generic — no stack strings. Tests cover the
detector (plateau/stuck/descent/key-fallback), the tracker (rotation, spike-
ignore, spike-gap-clear, completion-skip, green-clear, mid-run-kill), the flag,
the drive-boundary clear, and the user-visible injection (prepend, banner
lockdown-drop + regression-keep, flag-off/spike/completion/no-detector
suppression).
…anel round-2)

Panel major finding: trackNearGreenRotation recorded every gate result and
judged rotation purely from gate OUTPUT (error-set signature), with no
independent signal that the scope files actually changed between cycles. A
flaky/stateful gate — or a re-evaluation of the same unedited files (the
check-tool + settleGate double-run, #59) — could emit A->B->C signatures with
no fix-one->spawn-another cycle and falsely stand the WS-B rollback net down.

- Stamp each near-green sample with an INDEPENDENT worktree rev (a content hash
  over the glob-resolved scope files via the same snapshot substrate WS-B rolls
  back with, so scope can't drift). isNearGreenRotation now requires BOTH the
  signature AND the rev to change on every cycle -> a rotating signature over
  UNCHANGED files reads as not-rotating and leaves the rollback net engaged.
  rev is computed only on near-green cycles (the only branch that consumes it).
- Extract the rotation-emit decision into rotationEmit() so injectFeedback stays
  under the cognitive-complexity ceiling (minor finding).
- Fix stale near-green-banner.test.ts comment referencing a removed param
  (minor finding).
- Tests: track() helper stamps a fresh rev per cycle (models a real edit); new
  constant-rev cases assert a flaky/re-run gate is NOT rotation; integration
  test now writes a genuine per-cycle edit.
@agjs

agjs commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #172 (consolidated fix stack). #172 includes this branch's final #77 commits; merge #172 instead.

@agjs agjs closed this Jul 23, 2026
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.

1 participant