Skip to content

refactor(review): extract shared close-enforcement gate helper - #4643

Merged
JSONbored merged 1 commit into
mainfrom
fix/close-enforcement-shared-helper
Jul 10, 2026
Merged

refactor(review): extract shared close-enforcement gate helper#4643
JSONbored merged 1 commit into
mainfrom
fix/close-enforcement-shared-helper

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Depends on #4637 (already merged into main, and this branch is rebased on top of it) — this PR is
the 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.ts has 5 deterministic close-enforcement paths that bypass the unified agent
maintenance 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 wrapper
    every "maybe*" entry point duplicated (only the policy label and wrapped callee differed).
  • evaluateCloseEnforcementGate — the mode/autonomy/stand-down/permission-readiness/freshness
    scaffolding, parameterized per caller by eventType, targetKey, actionLabel/actor (for the
    formulaic 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; reason exists only because the reopen-reclose
    caller'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
closePullRequest call.

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:

  1. draft-dodge records no audit event on a paused/frozen repo. Every other guard here audits a
    paused/frozen stand-down (outcome: "denied"); draft-dodge's original if (live) {...} else if (dry_run) {...} had no else branch, so a paused repo silently no-ops. The gate's paused: null
    parameter for the draft-dodge call site reproduces this exactly (see the comment at that call site).
  2. reopen-reclose has never had a write-permission-readiness check. The other 4 guards call
    getInstallation + resolveAgentPermissionReadiness({ actionClass: "close" }) before mutating;
    recloseDisallowedReopenIfNeeded never did. The gate's permissionReadiness: null parameter for that
    call 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, same targetKey, same audit outcome, same detail string
(byte-for-byte, including each caller's own em-dash vs. double-hyphen convention), same metadata shape,
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.ts line by line while writing this, and
empirically: the existing test/unit/queue.test.ts suite (795 tests, all 5 close-enforcement paths
extensively 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.
  • Scoped coverage run (vitest --coverage limited to this file/test) shows the new shared code is fully
    exercised by the existing suite; the only two uncovered lines in the touched range are pre-existing,
    unmodified .catch(() => undefined) callbacks outside this diff.
  • No new tests added: per Fix close-enforcement authorization gap; extract shared helper #4602's own scope, the existing 5-function-level tests already cover the
    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:ci was not run locally for this pass (single-file typecheck + targeted-suite pass was
the 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.

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-security

superagent-security Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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

@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.11%. Comparing base (3886c18) to head (0041b3e).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

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     
Files with missing lines Coverage Δ
src/queue/processors.ts 95.25% <100.00%> (-0.04%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 10, 2026
@loopover-orb

loopover-orb Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-10 10:06:38 UTC

1 file · 1 AI reviewer · 1 blocker · readiness 100/100 · CI green · unstable

⏸️ Suggested Action - Manual Review

  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.

Review summary
This extracts two shared functions—withPrActuationLock and evaluateCloseEnforcementGate—that replace ~150-230 lines of near-identical scaffolding duplicated across all 5 close-enforcement guards (draft-dodge, reopen-reclose, review-evasion self-close/draft-conversion, repeated draft-cycling). I traced each of the 5 call sites against their pre-extraction code: the autonomy-denied → dry-run → paused/frozen stand-down → permission-readiness → freshness-recheck sequence, and each caller's own pre-existing gaps (draft-dodge's missing paused audit, reopen-reclose's missing permission-readiness check), are all faithfully preserved via the `paused: null` / `permissionReadiness: null` opt-outs. The reopen-reclose gate's return-value mapping (`gate.reason !== "autonomy_denied"`) correctly reproduces the old `return false` / `return true` split. One real regression: the old reopen-reclose non-live branch embedded the actual resolved mode dynamically (`skipped (agent ${reopenMode}): ...`, `metadata.mode: reopenMode`), while the new shared gate's `paused` branch hardcodes `"agent paused"` / `mode: "paused"` for that caller — losing the frozen-vs-paused distinction in its audit trail that the pre-refactor code specifically preserved (the other 3 evasion callers already used static "paused" text, so only reopen-reclose is affected).

Nits — 6 non-blocking
  • src/queue/processors.ts (reopen-reclose call site, evaluateCloseEnforcementGate's `paused` branch): the old code recorded the audit detail/metadata with the *actual* resolved mode (`skipped (agent ${reopenMode})`, `metadata.mode: reopenMode`), but the new gate hardcodes `"agent paused"`/`mode: "paused"` for this caller, silently losing the paused-vs-frozen distinction in reopen-reclose's audit trail — confirm this is intentional or thread the real mode through.
  • External brief notes 0 new test lines against +389/-544 in `src/queue/processors.ts`; codecov/patch passed so existing call-site tests evidently already exercise the extracted paths, but the new standalone `evaluateCloseEnforcementGate` discriminated-result branches (`autonomy_denied`/`stood_down`/`permission_not_ready`/`stale`) would benefit from direct unit tests now that they're a single reusable unit rather than 5 inlined copies.
  • External brief flags depth-5 nesting in `evaluateCloseEnforcementGate` (src/queue/processors.ts:13171); the repeated `try { await recordAuditEvent(...) } .catch(() => undefined)` blocks could be pulled into a small local helper to flatten it.
  • src/queue/processors.ts: in the reopen-reclose caller, either pass a `paused: { detail: (mode) => ..., metadata: (mode) => ... }` style callback into the gate, or keep a thin caller-side branch for the mode-name text, so the frozen/paused distinction reopen-reclose used to record isn't lost.
  • Add a focused unit test suite for `evaluateCloseEnforcementGate` directly (mocking each dependency) covering all 4 deny reasons plus the proceed:true path, independent of the 5 call sites, to lock in the discriminated-result contract this PR introduces.
  • Code changes lack test evidence — Add focused regression tests or explain why existing coverage is sufficient.

Concerns raised — review before merging

  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.
Signal Result Evidence
Code review ❌ 1 blocker 1 reviewer
Linked issue ✅ No-issue rationale PR body explains why no issue is linked.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 48 registered-repo PR(s), 40 merged, 330 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 48 PR(s), 330 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 48 PR(s), 330 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 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.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 10, 2026
@JSONbored
JSONbored merged commit 450e314 into main Jul 10, 2026
11 checks passed
@JSONbored
JSONbored deleted the fix/close-enforcement-shared-helper branch July 10, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Development

Successfully merging this pull request may close these issues.

Fix close-enforcement authorization gap; extract shared helper

1 participant