Skip to content

fix(review): harden backlog-convergence-sweep against duplicate work#4563

Merged
JSONbored merged 1 commit into
mainfrom
claude/backlog-convergence-sweep-hardening-4502
Jul 10, 2026
Merged

fix(review): harden backlog-convergence-sweep against duplicate work#4563
JSONbored merged 1 commit into
mainfrom
claude/backlog-convergence-sweep-hardening-4502

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Closes fix(review): backlog-convergence-sweep lacks the anti-duplication guards its sibling agent-regate-sweep just got #4502. backlog-convergence-sweep had none of the three anti-duplication layers its sibling agent-regate-sweep already has, and its settings-resolution loop was still sequential (the perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899 bug already fixed for the sibling). Ports all three protections from fanOutAgentRegateSweepJobs + the concurrency fix:
    1. A BACKLOG_CONVERGENCE_SWEEP_TRIGGER_TYPES backlog check in index.ts — defers a second trigger while one is pending/processing.
    2. claimBacklogConvergenceFanoutSlot — atomic fan-out-slot claim on a dedicated global_agent_controls.last_backlog_convergence_fanout_at column, collapsing a burst of fan-out triggers to one.
    3. A per-repo draining guard: sweepRepoBacklogConvergence now stamps a dedicated pull_requests.last_backlog_convergence_regated_at marker at dispatch (mirroring #audit-sweep-dispatch-stamp), and fanOutBacklogConvergenceSweepJobs's resolution loop skips any repo whose marker is still fresh (isRegateSweepDraining, a new BACKLOG_CONVERGENCE_SWEEP_FRESHNESS_MS = 30 min window sized to this sweep's own cadence).
    4. The settings-resolution loop is now mapWithConcurrencyLimit-bounded (was a sequential for...await), porting perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899's fix.
  • Kept the new stamp/claim columns separate from the sibling's (last_regated_at / last_regate_fanout_at) so the two differently-cadenced sweeps' in-flight signals never conflate.

Scope

Validation

  • git diff --check
  • npm run actionlint (via npm run test:ci)
  • npm run typecheck
  • npm run test:coverage locally (unsharded) — every new invariant/regression test confirmed to FAIL without its corresponding fix and pass with it, verified by temporarily reverting each mechanism and re-running
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries — invariant test (duplicate trigger deferred, not duplicated), regression test (worker-crash/reclaim-timeout scenario reproduced: a stuck-draining repo is skipped, an idle one still fans out), and a concurrency-bounded-resolution test (every candidate repo still resolves, none silently dropped) per fix(review): backlog-convergence-sweep lacks the anti-duplication guards its sibling agent-regate-sweep just got #4502's requirements

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests — N/A, no such change.
  • API/OpenAPI/MCP behavior is updated and tested where needed — N/A, no external behavior change (internal scheduling/dedup only).
  • UI changes use live API data — N/A, no UI change.
  • UI Evidence — N/A, no visible/UI change.
  • Public docs/changelogs updated — N/A.

…4502)

backlog-convergence-sweep had none of the three anti-duplication layers its
sibling agent-regate-sweep already has: no trigger-backlog check, no atomic
fan-out-slot claim, and no per-repo draining guard, plus its settings
resolution loop was still sequential (the #3899 bug already fixed for the
sibling). A crashed/restarted worker's stuck "processing" trigger row
(reclaimed only after queueProcessingTimeoutMs's 30-minute default, which
coincides with this sweep's own cadence) could go unnoticed by the next tick
and re-enqueue duplicate per-repo and per-PR jobs underneath an unfinished
fan-out.

Ports all three protections from fanOutAgentRegateSweepJobs: a
BACKLOG_CONVERGENCE_SWEEP_TRIGGER_TYPES backlog check in index.ts,
claimBacklogConvergenceFanoutSlot for atomic fan-out dedup, and a dedicated
last_backlog_convergence_regated_at marker (stamped at dispatch by
sweepRepoBacklogConvergence) feeding a draining check in the now
concurrency-bounded fan-out resolution loop.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored merged commit f5d5135 into main Jul 10, 2026
8 checks passed
@JSONbored JSONbored deleted the claude/backlog-convergence-sweep-hardening-4502 branch July 10, 2026 03:32
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.08%. Comparing base (73c9d76) to head (aed5102).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4563   +/-   ##
=======================================
  Coverage   94.08%   94.08%           
=======================================
  Files         427      427           
  Lines       37961    37999   +38     
  Branches    13864    13871    +7     
=======================================
+ Hits        35715    35753   +38     
  Misses       1586     1586           
  Partials      660      660           
Files with missing lines Coverage Δ
src/db/repositories.ts 96.59% <100.00%> (+0.02%) ⬆️
src/db/schema.ts 72.72% <ø> (ø)
src/index.ts 95.45% <100.00%> (+0.27%) ⬆️
src/queue/processors.ts 95.30% <100.00%> (+0.02%) ⬆️
src/settings/agent-sweep.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JSONbored added a commit that referenced this pull request Jul 10, 2026
…ay collision on main)

main currently has three DIFFERENT already-merged files at 0134
(#4549/#4558/#4563) -- tracked separately as its own fix (PR #4577, not yet
merged). Since that fix already claims through 0138, take 0139 here to
avoid colliding with it once it lands; this branch's own
db:migrations:check will stay red until #4577 merges (unrelated to this
branch's own changes), and will need one more rebase afterward.
JSONbored added a commit that referenced this pull request Jul 10, 2026
…ay collision on main)

main currently has three DIFFERENT already-merged files at 0134
(#4549/#4558/#4563) -- tracked separately as its own fix (PR #4577, not yet
merged). Since that fix already claims through 0138, take 0139 here to
avoid colliding with it once it lands; this branch's own
db:migrations:check will stay red until #4577 merges (unrelated to this
branch's own changes), and will need one more rebase afterward.
JSONbored added a commit that referenced this pull request Jul 10, 2026
…tion ledger (#4577)

* feat(review): persist a login-keyed predict-gate-vs-live-gate calibration ledger

Neither the MCP predict_gate tool nor contributor_gate_history persists
whether a contributor's self-reported predict-gate verdict matched the REAL
gate decision their PR eventually received -- the one calibration signal
the review stack itself uniquely owns, since it terminates both the
self-review call and the live gate for the same login/repo.

predicted_gate_calibration_ledger pairs the most recent predict_gate_calls
row (#4516) for a (login, project) against the real decision at the same
call sites as recordContributorGateDecision, writing one immutable row per
(login, project, pr, commit) -- ON CONFLICT DO NOTHING, never DO UPDATE, so
a webhook replay can never overwrite an already-recorded pairing. Write-only
and server-side only: nothing reads it yet (mirrors contributor_gate_history's
own precedent), and no MCP tool or other contributor-reachable surface can
write to or read from it, preserving its value as anti-farming-resistant
ground truth for a future #2349 consumer.

Depends on #4516 (predicted_gate_calls) -- built stacked on that branch per
the issue's own explicit note that the two may be built together; will be
rebased onto main once that PR merges.

Fixes #4517

* fix(db): renumber ledger migration 0135 -> 0138 (0134/0135 collide on main)

origin/main independently landed three files at 0134 and two at 0135 (from
already-merged PRs #4549, #4558, #4563) since this branch's last rebase.
Rather than touch already-merged migration files here, take the next
genuinely free number for this branch's own new migration; the 0134/0135
collision among already-merged files is a separate main-red issue handled
in its own PR.

* fix(db): resolve migration collision at 0134 on main (already-merged PRs)

Three already-merged PRs independently claimed 0134 (#4549's
review_targets_cadence_idx, #4558's predicted_gate_calls, and #4563's
pr_last_backlog_convergence_regated_at). CI on this branch inherits main's
full migrations/ directory regardless of which PR fixes it, so this can't
be deferred to a separate PR without also blocking this one. Renumber the
two newer files (keeping #4563's oldest 0134 file in place); this branch's
own new migration was already moved to 0138 to stay clear of both this
collision and #4563's separate 0135 collision.
JSONbored added a commit that referenced this pull request Jul 10, 2026
…PRs) (#4581)

Three already-merged PRs independently claimed 0134 (#4549's
review_targets_cadence_idx, #4558's predicted_gate_calls, and #4563's
pr_last_backlog_convergence_regated_at). CI on this branch inherits main's
full migrations/ directory regardless of which PR fixes it, so this can't
be deferred to a separate PR without also blocking this one. Renumber the
two newer files (keeping #4563's oldest 0134 file in place); this branch's
own new migration was already moved to 0138 to stay clear of both this
collision and #4563's separate 0135 collision.
JSONbored added a commit that referenced this pull request Jul 10, 2026
…-wide for confirmed miners (#4546)

* fix(review): make submitter-reputation burst/AI-spend defense install-wide for confirmed miners

getSubmitterReputation's burst/low-sample thresholds are scoped
WHERE project = ? AND submitter = ? -- a single repo. A fleet
identity spreading a handful of gate-passing-but-low-value PRs
across dozens of repos in one self-hosted install never accumulates
enough same-repo sample density to read as "burst" or "low"
anywhere, so the reputation defense never fires for it and every one
of its submissions burns full paid AI-review spend indefinitely.

- New getSubmitterReputationAcrossInstall in submitter-reputation.ts:
  the identical quality-weighted signal derivation, scoped by
  review_targets.installation_id instead of project (that column
  already existed, migrations/0050; this adds the supporting index).
- New getEffectiveSubmitterReputation in reputation-wire.ts: the
  per-repo signal, additionally widened to the install-wide view for
  a CONFIRMED official Gittensor miner -- but only when the per-repo
  signal alone doesn't already justify caution, so an ordinary
  contributor or an already-flagged submitter pays no extra lookup.
- Extracted the miner-identity check (shared with #4512) into
  src/gittensor/miner-detection-cache.ts so both this module and
  unlinked-issue-guardrail.ts can use it without a circular import
  through processors.ts.
- Wired into both call sites: shouldSkipAiForReputation (the main
  AI-spend gate) and the vision-review reputation check.

Fixes #4513

* fix: renumber migration 0130 -> 0131 (0130 was claimed by #4538, already merged to main)

* fix(test): account for getEffectiveSubmitterReputation's miner-identity check in visual-vision tests

runVisualVisionForAdvisory now resolves reputation via getEffectiveSubmitterReputation
(#4513), which checks confirmed-official-miner identity (a fetch to
api.gittensor.io/miners) whenever the submitter's per-repo signal is
neutral -- a real, intentional behavior change this test file's existing
"no network calls at all" assertions didn't account for. Stub that one
identity check to resolve cleanly and assert precisely that no OTHER
(BYOK/vision-spend) network call happens, rather than asserting zero fetch
calls outright.

* fix(db): renumber migration 0131 -> 0133 (0131/0132 claimed by merged PRs)

origin/main has since merged #4545 (0131_screenshot_table_gate_matrix.sql)
and #4554 (0132_impact_map_query_cache.sql, itself a collision fix); rebase
onto current main and take the next free number.

* fix(db): renumber migration 0133 -> 0134 (0133 claimed by merged PR #4556)

Rebase onto current main and take the next free number now that
migrations/0133_screenshot_table_gate_skill_link.sql (from #4556) occupies
the number this branch previously took.

* fix(db): renumber migration 0134 -> 0139 (0134 has a pre-existing 3-way collision on main)

main currently has three DIFFERENT already-merged files at 0134
(#4549/#4558/#4563) -- tracked separately as its own fix (PR #4577, not yet
merged). Since that fix already claims through 0138, take 0139 here to
avoid colliding with it once it lands; this branch's own
db:migrations:check will stay red until #4577 merges (unrelated to this
branch's own changes), and will need one more rebase afterward.

* test(review): close 2 coverage gaps surfaced by the #4514 rebase-merge

getEffectiveSubmitterReputation's getRepository().catch() needed a real
read-failure test (added); its isConfirmedOfficialMiner().catch() is
unreachable (that function already catches every internal failure point
itself) and getSubmitterReputationAcrossInstall's results ?? [] fallback is
the same "D1 always populates results" case already v8-ignored elsewhere in
this codebase -- both marked accordingly.

* fix(review): isolate #4507's reputation-single-read invariant from cross-test miner-cache pollution

Several earlier tests in this file cache "contributor" as a confirmed official
Gittensor miner (5-min TTL) in the shared per-file D1 instance. That collided
with #4513's new install-wide reputation widening, which correctly detects the
stale cache and adds a 4th reputation-scan D1 read for a submitter this test
never intended to be a miner. Use a submitter login unique to this test instead.
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.

fix(review): backlog-convergence-sweep lacks the anti-duplication guards its sibling agent-regate-sweep just got

1 participant