refactor(review): extract shared close-enforcement gate helper - #4643
Conversation
Part of #4602 (second acceptance criterion): the 5 deterministic close- enforcement paths in src/queue/processors.ts each duplicated ~150-230 lines of scaffolding -- agent-action-mode resolution, the close-autonomy check, dry-run/paused stand-down, write-permission readiness, and the live freshness re-check -- the exact class of duplication that let 2 of the 5 silently miss the close-autonomy check (fixed in #4637). Extracts withPrActuationLock (the byte-identical lock claim/try/finally- release wrapper) and evaluateCloseEnforcementGate (the mode/autonomy/ standdown/permission-readiness/freshness scaffolding, parameterized by each caller's exact detail text and metadata) so all 5 call sites now share one function for that scaffolding. The actual GitHub mutation and its post- success side effects stay caller-owned, since they differ too much to unify (self-close's reopen-then-close with asymmetric error handling vs. the other 4's single close call). Two pre-existing, intentionally-preserved asymmetries: draft-dodge records no audit event on a paused/frozen repo (every other guard does), and reopen-reclose has never had a write-permission-readiness check (every other guard does). Neither is introduced or fixed here -- a refactor must not change behavior.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4643 +/- ##
==========================================
- Coverage 94.11% 94.11% -0.01%
==========================================
Files 433 433
Lines 38494 38425 -69
Branches 14037 14010 -27
==========================================
- Hits 36229 36162 -67
Misses 1604 1604
+ Partials 661 659 -2
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-10 10:06:38 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
Concerns raised — review before merging
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Depends on #4637 (already merged into
main, and this branch is rebased on top of it) — this PR isthe fast-follow it deferred. Do not merge before #4637 (it already is merged; this note is for
review-time clarity on ordering/provenance).
Part of #4602 (second acceptance criterion).
Summary
src/queue/processors.tshas 5 deterministic close-enforcement paths that bypass the unified agentmaintenance planner by design (draft-dodge, reopen-reclose, review-evasion self-close, review-evasion
draft-conversion, repeated draft-cycling). Each duplicated ~150-230 lines of near-identical scaffolding —
agent-action-mode resolution, the close-autonomy check, dry-run/paused stand-down, write-permission
readiness, and the live PR-freshness re-check immediately before mutating. That duplication is exactly
what let 2 of the 5 silently miss the close-autonomy check (the bug #4637 just fixed) — with 5 independent
copies of this scaffolding, the same class of gap can recur any time a 6th caller is added or one copy
drifts from its siblings.
What changed
Extracts two shared functions, used by all 5 call sites:
withPrActuationLock— the byte-identical per-PR actuation-lock claim/try/finally-release wrapperevery "maybe*" entry point duplicated (only the
policylabel and wrapped callee differed).evaluateCloseEnforcementGate— the mode/autonomy/stand-down/permission-readiness/freshnessscaffolding, parameterized per caller by
eventType,targetKey,actionLabel/actor(for theformulaic close-autonomy-denied text), and fully caller-supplied detail strings + metadata for the
dry-run / paused / permission-not-ready / freshness-stale audit events. Returns a discriminated
{ proceed: true } | { proceed: false; reason }result;reasonexists only because the reopen-reclosecaller's own return value depends on why the gate denied (an autonomy denial maps to its "allowed"
outcome, every other denial maps to "reclosed"/handled — this distinction predates this refactor and is
preserved exactly).
The actual GitHub mutation and its post-success side effects (courtesy comment, label, moderation
escalation) stay in each caller — they differ too much to unify safely: self-close does a
reopen-then-close with asymmetric error handling (a reopen failure denies+returns; a close failure
re-throws to trigger a queue retry, since the reopen already succeeded), while the other 4 do a single
closePullRequestcall.Two pre-existing asymmetries, deliberately preserved (not touched by this refactor)
Reading all 5 in full surfaced two gaps that already existed before this PR and are not fixed here —
a refactor must not change behavior, only its shape:
paused/frozen stand-down (
outcome: "denied"); draft-dodge's originalif (live) {...} else if (dry_run) {...}had noelsebranch, so a paused repo silently no-ops. The gate'spaused: nullparameter for the draft-dodge call site reproduces this exactly (see the comment at that call site).
getInstallation+resolveAgentPermissionReadiness({ actionClass: "close" })before mutating;recloseDisallowedReopenIfNeedednever did. The gate'spermissionReadiness: nullparameter for thatcall site skips the step entirely, matching the original.
Both are flagged inline at their respective call sites. Neither is in scope for this PR.
Zero behavior change — line-by-line equivalence
For all 5 call sites: same
eventType, sametargetKey, same auditoutcome, samedetailstring(byte-for-byte, including each caller's own em-dash vs. double-hyphen convention), same
metadatashape,same control flow, same error handling on the mutation step. Verified by diffing each of the 5 functions
against pre-refactor
git show 1ab582fba:src/queue/processors.tsline by line while writing this, andempirically: the existing
test/unit/queue.test.tssuite (795 tests, all 5 close-enforcement pathsextensively covered) passes completely unmodified — no test needed a single assertion changed.
Testing
npm run typecheck— clean.npx vitest run test/unit/queue.test.ts— 795/795 passing, unmodified.vitest --coveragelimited to this file/test) shows the new shared code is fullyexercised by the existing suite; the only two uncovered lines in the touched range are pre-existing,
unmodified
.catch(() => undefined)callbacks outside this diff.behavior end-to-end, and the refactor introduces no new distinctly-testable branches beyond what those
tests already drive through the shared gate.
Full
npm run test:ciwas not run locally for this pass (single-file typecheck + targeted-suite pass wasthe agreed bar for this internal refactor); CI will run the full gate on push.
Scope note
No API/schema/wrangler/DB changes, so no generated artifacts to regenerate.