Context
buildAttemptGovernorContext (packages/gittensory-miner/lib/attempt-input-builder.js) hardcodes convergenceInput: { attempts: 0, consecutiveFailures: 0, reenqueues: 0, reachedDone: false } on every single call — a documented, intentional placeholder (see the file's own "KNOWN, DOCUMENTED GAPS" header) because attempt-log.js's schema has no repo+issue index and the portfolio-queue table tracks no attempt-history columns. packages/gittensory-engine/src/portfolio/non-convergence.ts — the pure detector this data is FOR — already exists, is fully typed and tested, and explicitly says the queue table needs to grow attempt-history columns; it just has no real data flowing into it today. This literal always under-estimates (reads "fresh, no prior failures" even on a real Nth attempt), which fails toward LETTING an attempt through rather than blocking one — not unsafe today, but it means the non-convergence detector has never actually detected anything real.
Dependencies
None — independently shippable. Does not touch non-convergence.ts's own classifier/thresholds or the Governor chokepoint's decision logic (governor-chokepoint.ts/governor-chokepoint-persisted.js, #5134) — this is purely about producing a REAL PortfolioConvergenceInput to feed the ALREADY-EXISTING detector, not changing what the detector does with it.
Requirements
- Add attempt-history columns to
miner_portfolio_queue (packages/gittensory-miner/lib/portfolio-queue.js): an attempts counter, a consecutive-failures counter, and a reenqueue counter — nullable/defaulted so this is a normal additive schema migration (mirror the file's own established applySchemaMigrations per-column-presence-check idiom already used for leased_at).
- Increment the attempts counter on claim (
claimNext/claimBatch).
- Increment the reenqueue counter on every transition FROM
in_progress back TO queued without reaching done (requeueItem, and the stuck-item-reclaim sweep) — exactly the "cycling queued -> in_progress -> queued without ever reaching done" behavior non-convergence.ts's own header names as the reenqueue trigger.
- Track consecutive failures: increment on a requeue/reclaim (an attempt that did not reach done), reset to 0 on
markDone.
reachedDone is simply status === 'done' — no new column needed for that one.
- Add a read function (e.g.
getAttemptHistory(repoFullName, identifier, apiBaseUrl)) returning a real PortfolioConvergenceInput-shaped object.
- Wire
buildAttemptGovernorContext to call it instead of the hardcoded literal — attempt-input-builder.js stays pure itself (per its own header discipline), so the real portfolio-queue read happens in the caller (attempt-cli.js) and gets passed in, matching how every other already-computed dependency reaches this composer today.
- Out of scope: changing
non-convergence.ts's thresholds, or anything about reputationHistory/selfPlagiarismCandidate (the file's OTHER documented, still-open gap) — that's a separate concern this issue does not touch.
Deliverables / Acceptance Criteria
Test Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1) schema-migration tests for a pre-existing on-disk file missing the new columns (mirrors portfolio-queue.js's own leased_at migration test pattern); (2) counter-correctness tests across claim -> requeue -> reclaim -> done sequences, including a REGRESSION test proving a genuinely non-convergent item (repeated requeue without ever reaching done) now produces a PortfolioConvergenceInput that evaluateNonConvergence classifies as non_convergent — the first real exercise of that detector; (3) an invariant test that reachedDone only ever reflects status === 'done', never fabricated.
Expected Outcome
The Governor's non-convergence detector — fully built since Wave 3, never fed real data — starts actually detecting stuck items instead of always reading "fresh." Also closes a real gap Wave 5's multi-tenant scale will need (a customer's stuck/looping task should be caught, not silently retried forever on their compute budget).
Links & Resources
packages/gittensory-miner/lib/attempt-input-builder.js (the hardcoded literal + its own gap comment)
packages/gittensory-miner/lib/portfolio-queue.js (schema + claim/requeue/reclaim/done functions)
packages/gittensory-engine/src/portfolio/non-convergence.ts (the existing pure detector this feeds)
- Theme: AMS hardening / Wave 5 prerequisite
Context
buildAttemptGovernorContext(packages/gittensory-miner/lib/attempt-input-builder.js) hardcodesconvergenceInput: { attempts: 0, consecutiveFailures: 0, reenqueues: 0, reachedDone: false }on every single call — a documented, intentional placeholder (see the file's own "KNOWN, DOCUMENTED GAPS" header) becauseattempt-log.js's schema has no repo+issue index and the portfolio-queue table tracks no attempt-history columns.packages/gittensory-engine/src/portfolio/non-convergence.ts— the pure detector this data is FOR — already exists, is fully typed and tested, and explicitly says the queue table needs to grow attempt-history columns; it just has no real data flowing into it today. This literal always under-estimates (reads "fresh, no prior failures" even on a real Nth attempt), which fails toward LETTING an attempt through rather than blocking one — not unsafe today, but it means the non-convergence detector has never actually detected anything real.Dependencies
None — independently shippable. Does not touch
non-convergence.ts's own classifier/thresholds or the Governor chokepoint's decision logic (governor-chokepoint.ts/governor-chokepoint-persisted.js, #5134) — this is purely about producing a REALPortfolioConvergenceInputto feed the ALREADY-EXISTING detector, not changing what the detector does with it.Requirements
miner_portfolio_queue(packages/gittensory-miner/lib/portfolio-queue.js): an attempts counter, a consecutive-failures counter, and a reenqueue counter — nullable/defaulted so this is a normal additive schema migration (mirror the file's own establishedapplySchemaMigrationsper-column-presence-check idiom already used forleased_at).claimNext/claimBatch).in_progressback TOqueuedwithout reachingdone(requeueItem, and the stuck-item-reclaim sweep) — exactly the "cycling queued -> in_progress -> queued without ever reaching done" behaviornon-convergence.ts's own header names as the reenqueue trigger.markDone.reachedDoneis simplystatus === 'done'— no new column needed for that one.getAttemptHistory(repoFullName, identifier, apiBaseUrl)) returning a realPortfolioConvergenceInput-shaped object.buildAttemptGovernorContextto call it instead of the hardcoded literal —attempt-input-builder.jsstays pure itself (per its own header discipline), so the real portfolio-queue read happens in the caller (attempt-cli.js) and gets passed in, matching how every other already-computed dependency reaches this composer today.non-convergence.ts's thresholds, or anything aboutreputationHistory/selfPlagiarismCandidate(the file's OTHER documented, still-open gap) — that's a separate concern this issue does not touch.Deliverables / Acceptance Criteria
miner_portfolio_queuegains real attempt-history columns via a normal additive migrationPortfolioConvergenceInputbuildAttemptGovernorContextreceives and forwards the real data instead of the hardcoded literalnon-convergence.ts's classifier itself is untouchedTest Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1) schema-migration tests for a pre-existing on-disk file missing the new columns (mirrors
portfolio-queue.js's ownleased_atmigration test pattern); (2) counter-correctness tests across claim -> requeue -> reclaim -> done sequences, including a REGRESSION test proving a genuinely non-convergent item (repeated requeue without ever reaching done) now produces aPortfolioConvergenceInputthatevaluateNonConvergenceclassifies asnon_convergent— the first real exercise of that detector; (3) an invariant test thatreachedDoneonly ever reflectsstatus === 'done', never fabricated.Expected Outcome
The Governor's non-convergence detector — fully built since Wave 3, never fed real data — starts actually detecting stuck items instead of always reading "fresh." Also closes a real gap Wave 5's multi-tenant scale will need (a customer's stuck/looping task should be caught, not silently retried forever on their compute budget).
Links & Resources
packages/gittensory-miner/lib/attempt-input-builder.js(the hardcoded literal + its own gap comment)packages/gittensory-miner/lib/portfolio-queue.js(schema + claim/requeue/reclaim/done functions)packages/gittensory-engine/src/portfolio/non-convergence.ts(the existing pure detector this feeds)