You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
IterationState.maxIterations documents an unconditional abandon. packages/loopover-engine/src/miner/iterate-policy.ts:73:
/** Hard ceiling enforced INSIDE this policy (#2333's own deliverable: not left to an external caller to * remember to enforce). `iterationNumber >= maxIterations` abandons regardless of self-review outcome. */
maxIterations: number;
The function's own precedence list, twelve lines further down, says the opposite. packages/loopover-engine/src/miner/iterate-policy.ts:146:
* 3. `selfReview.kind === "pass"` -- the ONLY path to `"handoff"`, narrowed by `autonomyLevel` (#6560): ...
* 4. `iterationNumber >= maxIterations` -- abandons at the hard ceiling regardless of whether the blocker set
* was still changing (genuine incremental progress does not buy unlimited iterations).
* 5. `costCeilingReached` -- abandons at the hard cost ceiling, same rationale as the iteration ceiling above.
The code matches the precedence list, not the field doc: the pass branch at packages/loopover-engine/src/miner/iterate-policy.ts:170 returns "handoff" before control ever reaches
the iterationNumber >= maxIterations check at packages/loopover-engine/src/miner/iterate-policy.ts:190 or the costCeilingReached check at packages/loopover-engine/src/miner/iterate-policy.ts:197. So decideNextAction({ iterationNumber: 5, maxIterations: 5, costCeilingReached: true, selfReview: { kind: "pass" }, ... })
returns "handoff", which the field doc says is impossible.
costCeilingReached's own doc has the same problem
(packages/loopover-engine/src/miner/iterate-policy.ts:76): it calls itself "the loop mechanics' (#2333)
OWN 'max-cost ceiling enforced inside the loop' deliverable, alongside the iteration ceiling above",
which likewise reads as unconditional.
The code's ordering is the correct one — the module header's own strategic constraints
(packages/loopover-engine/src/miner/iterate-policy.ts:14) say the ceilings exist "so a stuck loop stops
wasting turns (or spend) chasing a submission that was never going to land", which does not describe an
attempt that has just reached a clean predicted-gate pass. The defect is that a reader of the field doc
(and any caller writing enforcement against it) is told a guarantee that does not hold, and that packages/loopover-engine/test/iterate-policy.test.ts contains no case at all combining a pass
self-review with a reached ceiling: the ceiling cases at packages/loopover-engine/test/iterate-policy.test.ts:87 and :109 all use non-pass states, so the
precedence between step 3 and steps 4-5 is unpinned and a future reordering would break nothing.
Requirements
decideNextActionWithReason and decideNextAction behaviour must NOT change. The pass branch stays
ahead of the two ceiling branches; every existing assertion in packages/loopover-engine/test/iterate-policy.test.ts must keep passing unmodified.
The maxIterations field doc at packages/loopover-engine/src/miner/iterate-policy.ts:73 must state the
real contract: the ceiling abandons when the self-review has NOT reached a clean pass, and a clean pass at
or past the ceiling still hands off (subject to autonomyLevel). The phrase "regardless of self-review
outcome" must be removed.
The costCeilingReached field doc at packages/loopover-engine/src/miner/iterate-policy.ts:76 must carry
the same correction, so the two ceilings are described identically.
Both corrected docs must point at the numbered precedence list on decideNextActionWithReason as the
authority, so there is one canonical statement of the ladder rather than three.
What must NOT change: the precedence list itself
(packages/loopover-engine/src/miner/iterate-policy.ts:143-158) is already accurate and must stay
byte-identical; AbandonReason, IterateLoopDecision, every reason string, and the autonomyLevel
handling at packages/loopover-engine/src/miner/iterate-policy.ts:170-189 must all be untouched.
⚠️ Required pattern: keep the fix inside packages/loopover-engine/src/miner/iterate-policy.ts and its
test file. What does NOT satisfy this issue: (a) reordering the checks so the ceilings run before the pass branch to make the field doc true — that would abandon an attempt that has already reached a clean
predicted-gate pass, discarding completed work, and it would break the module's own documented ladder;
(b) adding a new IterationState flag (e.g. enforceCeilingsOverPass) to make both readings available —
a second mechanism instead of one correct contract; (c) a docs-only PR with no test pinning the
precedence; (d) a test-only PR that leaves the contradictory field docs in place.
Deliverables
packages/loopover-engine/src/miner/iterate-policy.ts — the maxIterations and costCeilingReached
field docs corrected as specified above; the string regardless of self-review outcome no longer
appears on maxIterations.
A regression test at packages/loopover-engine/test/iterate-policy.test.ts named for this bug,
asserting decideNextActionWithReason({ ...base, iterationNumber: 5, maxIterations: 5, selfReview: { kind: "pass" } })
returns { action: "handoff" } with no abandonReason.
The same test file asserts decideNextActionWithReason({ ...base, costCeilingReached: true, selfReview: { kind: "pass" } })
returns { action: "handoff" } with no abandonReason.
The same test file asserts the both-ceilings case: { iterationNumber: 5, maxIterations: 5, costCeilingReached: true, selfReview: { kind: "pass" } }
still returns "handoff".
The same test file asserts the two branches that DO still win over a pass, so the ladder is pinned
end to end: rejectionSignaled: true with selfReview: { kind: "pass" } returns abandonReason: "rejection_signaled", and autonomyLevel: "observe" with selfReview: { kind: "pass" }
at a reached ceiling returns abandonReason: "autonomy_observe_only" (NOT "max_iterations_reached").
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example one
that corrects the two doc comments and adds only the maxIterations + pass test, skipping the cost-ceiling
and autonomy_observe_only cases — does not resolve this issue.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted. vitest.config.ts's coverage.include covers src/**/*.ts and packages/loopover-engine/src/**/*.ts — the touched path packages/loopover-engine/src/miner/iterate-policy.ts IS measured. The change itself is comment-only, so
the patch's measured lines come from whatever the diff touches; the tests above exist to pin the precedence,
and each names both arms of the branch it covers: selfReview.kind === "pass" (taken → handoff, not taken →
ceiling checks run), iterationNumber >= maxIterations (both), costCeilingReached === true (both), and autonomyLevel === "observe" (both).
Engine lines are credited by two uploads whose hits are unioned — add the test to packages/loopover-engine/test/** as well as any root test/** coverage, or the patch gate can still fail. packages/loopover-engine/test/iterate-policy.test.ts is the engine-side file; if a root test/** suite
also exercises decideNextAction, mirror the new precedence assertions there too.
Expected Outcome
IterationState's two ceiling fields describe the enforcement the policy actually performs, and the
precedence between a clean predicted-gate pass and the iteration/cost ceilings is pinned by tests, so a
future reordering of decideNextActionWithReason fails the suite instead of silently discarding a passing
attempt (or silently granting unlimited iterations).
Links & Resources
packages/loopover-engine/src/miner/iterate-policy.ts:73 — the maxIterations doc that claims an unconditional abandon
packages/loopover-engine/src/miner/iterate-policy.ts:76 — the costCeilingReached doc with the same problem
packages/loopover-engine/src/miner/iterate-policy.ts:143 — the accurate precedence list
packages/loopover-engine/src/miner/iterate-policy.ts:170 — the pass branch that runs first
packages/loopover-engine/src/miner/iterate-policy.ts:190 — the maxIterations branch that runs second
packages/loopover-engine/test/iterate-policy.test.ts:87 — the existing ceiling cases, none of which use a pass self-review
Context
IterationState.maxIterationsdocuments an unconditional abandon.packages/loopover-engine/src/miner/iterate-policy.ts:73:The function's own precedence list, twelve lines further down, says the opposite.
packages/loopover-engine/src/miner/iterate-policy.ts:146:The code matches the precedence list, not the field doc: the
passbranch atpackages/loopover-engine/src/miner/iterate-policy.ts:170returns"handoff"before control ever reachesthe
iterationNumber >= maxIterationscheck atpackages/loopover-engine/src/miner/iterate-policy.ts:190or thecostCeilingReachedcheck atpackages/loopover-engine/src/miner/iterate-policy.ts:197. SodecideNextAction({ iterationNumber: 5, maxIterations: 5, costCeilingReached: true, selfReview: { kind: "pass" }, ... })returns
"handoff", which the field doc says is impossible.costCeilingReached's own doc has the same problem(
packages/loopover-engine/src/miner/iterate-policy.ts:76): it calls itself "the loop mechanics' (#2333)OWN 'max-cost ceiling enforced inside the loop' deliverable, alongside the iteration ceiling above",
which likewise reads as unconditional.
The code's ordering is the correct one — the module header's own strategic constraints
(
packages/loopover-engine/src/miner/iterate-policy.ts:14) say the ceilings exist "so a stuck loop stopswasting turns (or spend) chasing a submission that was never going to land", which does not describe an
attempt that has just reached a clean predicted-gate pass. The defect is that a reader of the field doc
(and any caller writing enforcement against it) is told a guarantee that does not hold, and that
packages/loopover-engine/test/iterate-policy.test.tscontains no case at all combining apassself-review with a reached ceiling: the ceiling cases at
packages/loopover-engine/test/iterate-policy.test.ts:87and:109all use non-passstates, so theprecedence between step 3 and steps 4-5 is unpinned and a future reordering would break nothing.
Requirements
decideNextActionWithReasonanddecideNextActionbehaviour must NOT change. Thepassbranch staysahead of the two ceiling branches; every existing assertion in
packages/loopover-engine/test/iterate-policy.test.tsmust keep passing unmodified.maxIterationsfield doc atpackages/loopover-engine/src/miner/iterate-policy.ts:73must state thereal contract: the ceiling abandons when the self-review has NOT reached a clean pass, and a clean pass at
or past the ceiling still hands off (subject to
autonomyLevel). The phrase "regardless of self-reviewoutcome" must be removed.
costCeilingReachedfield doc atpackages/loopover-engine/src/miner/iterate-policy.ts:76must carrythe same correction, so the two ceilings are described identically.
decideNextActionWithReasonas theauthority, so there is one canonical statement of the ladder rather than three.
(
packages/loopover-engine/src/miner/iterate-policy.ts:143-158) is already accurate and must staybyte-identical;
AbandonReason,IterateLoopDecision, every reason string, and theautonomyLevelhandling at
packages/loopover-engine/src/miner/iterate-policy.ts:170-189must all be untouched.Deliverables
packages/loopover-engine/src/miner/iterate-policy.ts— themaxIterationsandcostCeilingReachedfield docs corrected as specified above; the string
regardless of self-review outcomeno longerappears on
maxIterations.packages/loopover-engine/test/iterate-policy.test.tsnamed for this bug,asserting
decideNextActionWithReason({ ...base, iterationNumber: 5, maxIterations: 5, selfReview: { kind: "pass" } })returns
{ action: "handoff" }with noabandonReason.decideNextActionWithReason({ ...base, costCeilingReached: true, selfReview: { kind: "pass" } })returns
{ action: "handoff" }with noabandonReason.{ iterationNumber: 5, maxIterations: 5, costCeilingReached: true, selfReview: { kind: "pass" } }still returns
"handoff".pass, so the ladder is pinnedend to end:
rejectionSignaled: truewithselfReview: { kind: "pass" }returnsabandonReason: "rejection_signaled", andautonomyLevel: "observe"withselfReview: { kind: "pass" }at a reached ceiling returns
abandonReason: "autonomy_observe_only"(NOT"max_iterations_reached").All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example one
that corrects the two doc comments and adds only the
maxIterations+passtest, skipping the cost-ceilingand
autonomy_observe_onlycases — does not resolve this issue.Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted.
vitest.config.ts'scoverage.includecoverssrc/**/*.tsandpackages/loopover-engine/src/**/*.ts— the touched pathpackages/loopover-engine/src/miner/iterate-policy.tsIS measured. The change itself is comment-only, sothe patch's measured lines come from whatever the diff touches; the tests above exist to pin the precedence,
and each names both arms of the branch it covers:
selfReview.kind === "pass"(taken → handoff, not taken →ceiling checks run),
iterationNumber >= maxIterations(both),costCeilingReached === true(both), andautonomyLevel === "observe"(both).Engine lines are credited by two uploads whose hits are unioned — add the test to
packages/loopover-engine/test/**as well as any roottest/**coverage, or the patch gate can still fail.packages/loopover-engine/test/iterate-policy.test.tsis the engine-side file; if a roottest/**suitealso exercises
decideNextAction, mirror the new precedence assertions there too.Expected Outcome
IterationState's two ceiling fields describe the enforcement the policy actually performs, and theprecedence between a clean predicted-gate pass and the iteration/cost ceilings is pinned by tests, so a
future reordering of
decideNextActionWithReasonfails the suite instead of silently discarding a passingattempt (or silently granting unlimited iterations).
Links & Resources
packages/loopover-engine/src/miner/iterate-policy.ts:73— themaxIterationsdoc that claims an unconditional abandonpackages/loopover-engine/src/miner/iterate-policy.ts:76— thecostCeilingReacheddoc with the same problempackages/loopover-engine/src/miner/iterate-policy.ts:143— the accurate precedence listpackages/loopover-engine/src/miner/iterate-policy.ts:170— thepassbranch that runs firstpackages/loopover-engine/src/miner/iterate-policy.ts:190— themaxIterationsbranch that runs secondpackages/loopover-engine/test/iterate-policy.test.ts:87— the existing ceiling cases, none of which use apassself-review