Skip to content

engine(miner): the maxIterations/costCeilingReached field docs contradict decideNextAction's real precedence, and neither ordering is tested #9997

Description

@JSONbored

⚠️ 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions