⚠️ 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
packages/loopover-miner/lib/attempt-cli.ts carries two type drifts that the code itself flags in comments and works around with casts.
1. A real outcome missing from the exported result union. runAttempt returns exit code 11 with a blockedResult object whose outcome is "blocked_max_concurrent_claims", then does options.onResult?.(blockedResult as AttemptCliResult). AttemptCliResult (lines 98-125) has no such variant. The knock-on is real: loop-cli.ts:76 types the loop's own per-cycle summary field as attemptOutcome?: AttemptCliResult["outcome"] | "attempt_error", so the JSON loop --json emits can carry a value its own exported type says is impossible, and loop-cli.ts:507's permanentBlock/requeue classification cannot be type-checked against it.
This is the exact defect class already fixed once for the sibling variant: closed issue #9331, "ams: attempt-cli's blocked_own_open_pr outcome is missing from the exported AttemptCliResult type", added blocked_own_open_pr (now line 101). blocked_max_concurrent_claims was never given the same treatment.
2. A real injection seam missing from the options type. runAttempt reads its reputation-history loader through a cast onto its own options object:
const readReputationHistory =
(options as RunAttemptOptions & { loadReputationHistory?: typeof loadReputationHistory }).loadReputationHistory ??
loadReputationHistory;
RunAttemptOptions (lines 139-186) declares every other seam explicitly (getAttemptHistory, recordOwnSubmission, resolveClaimConflict, ...). A caller reading the public type has no way to know this seam exists.
Requirements
- Add a
blocked_max_concurrent_claims variant to the AttemptCliResult union, carrying exactly the fields blockedResult builds: CommonAttemptResultFields & { outcome: "blocked_max_concurrent_claims"; reason: string; maxConcurrentClaims: number; activeClaimCount: number }.
- Remove the
as AttemptCliResult cast on the options.onResult?.(blockedResult) call and the ".d.ts drift" comment above it.
- Add
loadReputationHistory?: typeof loadReputationHistory; to RunAttemptOptions, placed beside the other resolver seams, with a one-line doc comment matching their style.
- Remove the inline
(options as RunAttemptOptions & { ... }) cast and the ".d.ts drift" comment above it.
- No runtime behaviour may change: the emitted object's fields, the exit code
11, and the resolution order (options.loadReputationHistory ?? loadReputationHistory) must all be identical.
⚠️ Required pattern: mirror the already-shipped fix for blocked_own_open_pr (packages/loopover-miner/lib/attempt-cli.ts:101) for the union variant, and mirror the neighbouring declared seams such as getAttemptHistory?: typeof GetAttemptHistoryFn; (line 174) for the options field. It does NOT satisfy this issue to widen outcome to string, to leave either cast in place, to introduce a separate BlockedAttemptResult type that is not part of the AttemptCliResult union, or to fix only one of the two drifts.
Deliverables
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example adding the union variant while leaving the RunAttemptOptions cast in place — does not resolve this issue.
Test Coverage Requirements
packages/loopover-miner/lib/**/*.ts IS inside Codecov's coverage.include in vitest.config.ts, so the 99%+ branch-counted codecov/patch gate applies exactly as for src/**. Both arms of the options.loadReputationHistory ?? loadReputationHistory nullish coalesce need a test (injected and default), and the max-concurrent-claims block must be exercised end to end.
Expected Outcome
AttemptCliResult describes every outcome runAttempt can actually report — so loop --json's attemptOutcome field is type-accurate — and every injection seam runAttempt honours is declared on RunAttemptOptions, with no type assertions left in either path.
Links & Resources
packages/loopover-miner/lib/attempt-cli.ts:98-125, :139-186, :770-778, :817-842, packages/loopover-miner/lib/loop-cli.ts:70-84. Precedent: closed issue #9331.
Context
packages/loopover-miner/lib/attempt-cli.tscarries two type drifts that the code itself flags in comments and works around with casts.1. A real outcome missing from the exported result union.
runAttemptreturns exit code11with ablockedResultobject whoseoutcomeis"blocked_max_concurrent_claims", then doesoptions.onResult?.(blockedResult as AttemptCliResult).AttemptCliResult(lines 98-125) has no such variant. The knock-on is real:loop-cli.ts:76types the loop's own per-cycle summary field asattemptOutcome?: AttemptCliResult["outcome"] | "attempt_error", so the JSONloop --jsonemits can carry a value its own exported type says is impossible, andloop-cli.ts:507'spermanentBlock/requeue classification cannot be type-checked against it.This is the exact defect class already fixed once for the sibling variant: closed issue #9331, "ams: attempt-cli's blocked_own_open_pr outcome is missing from the exported AttemptCliResult type", added
blocked_own_open_pr(now line 101).blocked_max_concurrent_claimswas never given the same treatment.2. A real injection seam missing from the options type.
runAttemptreads its reputation-history loader through a cast onto its own options object:RunAttemptOptions(lines 139-186) declares every other seam explicitly (getAttemptHistory,recordOwnSubmission,resolveClaimConflict, ...). A caller reading the public type has no way to know this seam exists.Requirements
blocked_max_concurrent_claimsvariant to theAttemptCliResultunion, carrying exactly the fieldsblockedResultbuilds:CommonAttemptResultFields & { outcome: "blocked_max_concurrent_claims"; reason: string; maxConcurrentClaims: number; activeClaimCount: number }.as AttemptCliResultcast on theoptions.onResult?.(blockedResult)call and the ".d.ts drift" comment above it.loadReputationHistory?: typeof loadReputationHistory;toRunAttemptOptions, placed beside the other resolver seams, with a one-line doc comment matching their style.(options as RunAttemptOptions & { ... })cast and the ".d.ts drift" comment above it.11, and the resolution order (options.loadReputationHistory ?? loadReputationHistory) must all be identical.Deliverables
AttemptCliResultincludes ablocked_max_concurrent_claimsvariant withreason,maxConcurrentClaims, andactiveClaimCount.options.onResult?.(blockedResult)call at the max-concurrent-claims block no longer casts, and the ".d.ts drift" comment is gone.RunAttemptOptionsdeclaresloadReputationHistory?: typeof loadReputationHistory, the inline cast is gone, and its ".d.ts drift" comment is gone.test/unit/miner-attempt-cli.test.tsdrives the max-concurrent-claims block (injected claim ledger returning{ claimed: false, ... }), capturesonResult's argument, and assertsoutcome === "blocked_max_concurrent_claims"together with themaxConcurrentClaimsandactiveClaimCountvalues and the exit code11.loadReputationHistorypassed through the plain (uncast)RunAttemptOptionsis the one actually used.All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example adding the union variant while leaving the
RunAttemptOptionscast in place — does not resolve this issue.Test Coverage Requirements
packages/loopover-miner/lib/**/*.tsIS inside Codecov'scoverage.includeinvitest.config.ts, so the 99%+ branch-countedcodecov/patchgate applies exactly as forsrc/**. Both arms of theoptions.loadReputationHistory ?? loadReputationHistorynullish coalesce need a test (injected and default), and the max-concurrent-claims block must be exercised end to end.Expected Outcome
AttemptCliResultdescribes every outcomerunAttemptcan actually report — soloop --json'sattemptOutcomefield is type-accurate — and every injection seamrunAttempthonours is declared onRunAttemptOptions, with no type assertions left in either path.Links & Resources
packages/loopover-miner/lib/attempt-cli.ts:98-125,:139-186,:770-778,:817-842,packages/loopover-miner/lib/loop-cli.ts:70-84. Precedent: closed issue #9331.