Skip to content

feat(regate-sweep): add opt-in oldest-first ordering mode to selectRegateCandidates #3815

Description

@JSONbored

Context

Found during the 2026-07-06 incident audit (parent: #1667). During backlog recovery today, the order in which stuck PRs got reviewed looked effectively random to the operator, who expected (and wants going forward) deterministic oldest-first draining per repo.

Verified against src/settings/agent-sweep.ts (selectRegateCandidates) and src/queue/processors.ts (sweepRepoRegate) as they exist today. The candidate sort is repairPriority || regateProgress || number, where regateProgress falls back through lastRegatedAtcreatedAt → epoch(0). This is deliberately staleness-based (per the file's own header comment): a PR regated 2 minutes ago sorts after one regated an hour ago, regardless of PR number or creation time. PR number is only the final tiebreaker when regateProgress values are exactly equal, which almost never happens once any PR has been swept at least once. Net effect: after the first sweep pass touches a repo, PR selection order for all subsequent sweeps is driven by "which PR I regated longest ago," not creation order.

Separately, the per-PR fan-out stagger (delaySeconds = Math.min(index * 10, 600)) does propagate the chosen sort's array order into dispatch order, but real-time webhook-driven review (untouched by any of this) can process/merge any PR at any moment regardless of sweep state — so a sort-key change alone cannot make cross-path ordering fully deterministic; it can only control the scheduled-sweep path's own selection order.

Requirements

  • Add an explicit, opt-in, default-off orderMode parameter to selectRegateCandidates, choosing between the existing staleness sort (kept as-is, unchanged default, for its documented convergence guarantee) and a new oldest-first sort keyed on createdAt ascending (falling back to PR number when createdAt is missing). repairPriority (outage-repair PRs) still sorts ahead of both, unchanged.
  • Do not silently replace the staleness order globally — its docstring explains it exists specifically so the sweep provably converges over all open PRs even when updatedAt-driven writes are suppressed (dry-run/paused). The new mode must preserve an equivalent convergence guarantee (verify via a test mirroring the existing staleness convergence tests: full backlog coverage in ceil(open/max) ticks).
  • Gate the new ordering behind a resolvable per-repo setting (e.g. regateSweepOrderMode: "staleness" | "oldest-first", default "staleness") so no existing repo's behavior changes silently, and the owner can opt specific repos in.
  • Explicitly document that this is a selection-time preference, not a delivery guarantee — real-time webhook-driven review is not gated by this sort and can still process any PR out of order at any time.

Deliverables

  • src/settings/agent-sweep.ts: orderMode field on selectRegateCandidates's input, a creationOrder helper, branched sort comparator.
  • New regateSweepOrderMode repo setting end-to-end (DB column + migration if settings are DB-backed, resolver default, .gittensory.yml schema entry, OpenAPI regen per this repo's own contributing skill).
  • src/queue/processors.ts: thread the resolved orderMode through sweepRepoRegate.
  • test/unit/agent-sweep.test.ts: new test block mirroring existing staleness convergence tests (basic order, tie-break, multi-tick convergence, repairPriority still wins over both orderings).
  • Regression test asserting the fanned-out delaySeconds sequence is monotonic in the chosen order.

Expected outcome

Repos that opt into oldest-first get scheduled re-gate sweeps that process open PRs in creation-time order per repo per tick, with the same convergence guarantee the existing staleness mode already provides. Repos that don't opt in see zero behavior change. The change is scoped to scheduled-sweep selection order only, not a claim about cross-path (webhook vs. sweep) determinism.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions