Skip to content

fix(orb): close-authority integrity across four review-engine gaps - #9206

Merged
JSONbored merged 1 commit into
mainfrom
fix/close-authority-9127-9129-9130-9132
Jul 27, 2026
Merged

fix(orb): close-authority integrity across four review-engine gaps#9206
JSONbored merged 1 commit into
mainfrom
fix/close-authority-9127-9129-9130-9132

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Four fixes to the review-engine's close/gate-execution path, all touching the same close-authority surface:

  • orb(queue): foreground-liveness cancels every deliberate delay, not just rate-limit deferrals — the contributor's promised close grace window collapses from 300s to 60s #9127 — foreground-liveness sweep released deliberate delays, not just rate-limit deferrals. releaseStaleForegroundDeferrals (pg-queue.ts / sqlite-queue.ts) selected any pending foreground row scheduled in the future with no provenance filter, and isRateLimitAdmissionNowClear returned true for any job with no rate-limit bucket — so a job enqueued with a deliberate delaySeconds (e.g. the linked-issue flag-then-close 300s grace window) was released on the very next sweep tick (≤60s). Added a deferred_by provenance column, set only at the three admission-gate defer sites (rate-limit, maintenance-admission, installation-concurrency); the candidate SELECT now requires deferred_by IS NOT NULL, structurally excluding enqueue-time delays regardless of age. The rate-limit-clear recheck is now scoped to deferred_by='rate_limit' specifically (a maintenance-admission or installation-concurrency deferral has no rate-limit bucket either, and would have hit the same false-premise bug for a different admission kind). Corrected the module header's false premise in foreground-liveness.ts. Mirrored in both the Postgres and SQLite queue backends.

  • orb(gate): any contributor can force-close a rival's PR by citing the same issue number — breaker-exempt, default-on, and it has already closed 2 PRs #9129duplicate_pr_risk was an offensive close primitive. The finding was derived entirely from a rival PR's own body text, blocked by default, and exempt from the close-precision breaker — so any contributor could force-close a rival's clean PR by citing the same issue number in a throwaway PR (no code required). Now requires corroboration (a changed-file-path overlap, or the sibling being a non-trivial real change) before the concrete duplicate_pr_risk code fires at all; an uncorroborated citation gets the separate, always-non-blocking duplicate_pr_risk_unconfirmed code. Dropped the duplicate-link check from hasConcreteCloseEvidence (the breaker can now downgrade a duplicate-driven close). Changed the code-level default for duplicatePrGateMode from block to advisory. Most importantly: evaluateGateCheckCore now HOLDS (neutral) rather than closes when a gate fails solely on duplicate_pr_risk, under any duplicatePrGateMode — there is no configuration left that lets this finding close a PR. Mirrored into the engine package's gate-advisory.ts twin to keep the live-gate/predicted-gate parity contract accurate (predicted-gate would otherwise have kept predicting the old, wrong verdict).

  • orb(safety): SELFHOST_DEPLOYMENT_MODE=dry-run does not reach the executor — a 'posts nothing' instance still cancels CI, globally blacklists contributors, and writes fabricated merge outcomes #9130SELFHOST_DEPLOYMENT_MODE=dry-run didn't reach the executor. The instance-wide kill switch was consulted only inside makeInstallationOctokit's request hook — one layer below every decision that depends on it. The executor computed its own mode from per-repo settings alone, believed it was live, and drove real outcome writes, CI cancellation, Discord/Slack notifications, and moderation escalation off a mutation that was actually suppressed at the wire. Folded forcedSelfhostMode(env) into resolveAgentActionMode as the first ("most restrictive wins") precedence term, threaded through all 22 call sites across processors.ts, agent-action-executor.ts, review-evasion.ts, automation-state.ts, and client.ts's own resolveRepoActionMode. Routed the CI-cancel raw-fetch call (cancelInFlightWorkflowRunsForHeadSha, the last installation write that bypassed the octokit suppression hook) through the same chokepoint. Gave mergePullRequest a suppressed field so a synthetic shadow response can never look like a real merge to the executor, as defense in depth.

  • orb(breaker): #9086's review_nag breaker coverage is inert — both nag close sites skip the breaker entirely, and each close is scored as a merge misprediction #9132 — review-nag breaker coverage was inert. orb(gate): the six anti-abuse close paths are all exempt from the precision breaker AND from live recheck #9086 added review_nag to the breaker's content-inspection close kinds, but neither review-nag close site ever called applyPrecisionBreakers or recordNativeGateDecision — they built a plan and executed it directly. The breaker could never engage, and (because no gate_decision row was written) every review-nag close was later scored as a merge misprediction against the repo's calibration corpus (the gate_decision can contradict the action taken — stale 'merge' verdicts recorded after CI-failure closes distort calibration #8825 bug class). Extracted the shared sequence (applyPrecisionBreakersmaybeApplyCloseAuditHoldoutrecordNativeGateDecision(reasonCode: "policy_close:review_nag")buildDecisionRecord/persistDecisionRecord → contributor/predicted-gate calibration) into finalizePolicyCloseDisposition and wired both review-nag close sites through it.

Follow-up noted, not fixed here (per #9132's own scoping)

Audited all 7 executeAgentMaintenanceActions call sites. Besides the main disposition path (already correct) and the two review-nag sites (fixed here):

  • processors.ts's contributor_cap early-close short-circuit (~line 3013) has the same gap-class as review-nag (a policy close bypassing the breaker/recording sequence) and is a good follow-up candidate for finalizePolicyCloseDisposition.
  • Two update_branch-only forced-rebase call sites are not a merge/close disposition at all (no gate decision concept applies).
  • agent-approval-queue.ts's accept-time replay already recorded a gate decision once, at original staging time — needs a closer look before concluding it's a gap.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused (all four issues touch the same close/gate-execution subsystem, as directed by the linked issues) and does not mix in unrelated backend, UI, MCP, docs, dependency, or deploy changes.
  • This follows CONTRIBUTING.md and does not touch site/, CNAME, or **/lovable/**.
  • Maintainer PR — every other bar (coverage, generated artifacts, house rules) still applies in full; linked via closing keywords below.

Closes #9127
Closes #9129
Closes #9130
Closes #9132

Validation

Ran targeted checks, not the full npm run test:ci — see the note below for why and what was substituted.

  • git diff --check — clean.
  • npm run actionlint — not run (no workflow files touched).
  • npm run typecheck (tsc --noEmit) — ran repeatedly against the full changed set; zero new errors (pre-existing failures are all Cannot find module '@loopover/engine' in files that need npm run build --workspace @loopover/engine first, unrelated to this diff).
  • npm run test:coverage — not run in unsharded form; substituted with npx vitest run --changed=origin/main, which selected 555 test files / 12,702 tests across the real import graph of this diff (12,686 passed, 16 pre-existing integration skips requiring a real Postgres) after fixing every test this diff's behavior change legitimately invalidated (duplicate-detection golden fixtures, engine-parity goldens, and the review-nag/foreground-liveness/dry-run test suites this PR extends). Every new/changed line in the four issues' functions has a dedicated invariant or regression test (see below); I did not generate an unsharded coverage % number.
  • npm run test:workers — not run.
  • npm run build:mcp — ran, clean (needed to rebuild @loopover/miner/@loopover/mcp against the rebuilt @loopover/engine so their own compiled-dist/ tests weren't stale).
  • npm run test:mcp-pack — not run directly; MCP-affected tests were included in the --changed sweep above.
  • npm run ui:openapi:check — clean (no API/schema changes; verified anyway).
  • npm run ui:lint / ui:typecheck / ui:build — not run (no UI changes in this PR).
  • npm audit --audit-level=moderate — 5 pre-existing high-severity findings, all in eslint's transitive brace-expansion/minimatch chain on a clean origin/main checkout before any of my changes (confirmed by running the same audit before starting). Unrelated to this diff; fixing them requires an unrelated breaking eslint major-version bump, out of scope here.
  • New/changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries — see per-issue test list below.

Additional generated-artifact/drift checks run and confirmed clean: npm run db:migrations:check, npm run db:schema-drift:check, npm run selfhost:env-reference:check, npm run cf-typegen:check, npm run manifest:drift-check, npm run engine-parity:drift-check (the last one specifically validates the packages/loopover-engine twin this PR also edits for #9129).

If any required check was skipped, explain why: instructed not to run the full npm run test:ci gate for this PR (it will run in CI regardless); relied on targeted tsc --noEmit plus a full vitest run --changed=origin/main sweep instead, since that sweep exercises every test whose real import graph touches this diff — a stronger signal than a partial manual selection, even without an unsharded coverage percentage.

Per-issue test coverage

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 — the dry-run/mode-precedence change is exactly this class of change; see the orb(safety): SELFHOST_DEPLOYMENT_MODE=dry-run does not reach the executor — a 'posts nothing' instance still cancels CI, globally blacklists contributors, and writes fabricated merge outcomes #9130 invariant test above.
  • API/OpenAPI/MCP behavior is updated and tested where needed — no public API surface changed; verified via ui:openapi:check.
  • UI changes use live API data — N/A, no UI changes.
  • Visible UI changes include a UI Evidence section — N/A, no UI changes.
  • Public docs/changelogs are updated where needed — no CHANGELOG.md edit (not a release-prep PR); no other public docs reference the internals this PR changes.

Notes

  • Skipped the @loopover/mcp gate-prediction preflight (loopover_predict_gate / loopover_check_slop_risk / loopover_lint_pr_text) — this is a non-interactive maintainer session and the tool's device-flow login isn't available here. Relied on the vitest --changed sweep, targeted typecheck, and manual review of this description instead.
  • orb(gate): any contributor can force-close a rival's PR by citing the same issue number — breaker-exempt, default-on, and it has already closed 2 PRs #9129's fix required mirroring the corroboration/hold logic into packages/loopover-engine/src/advisory/gate-advisory.ts (the intentionally-divergent predicted-gate twin) — not something I'd assumed going in, but test/contract/live-gate-parity.test.ts correctly caught that the live gate and predicted gate would otherwise silently disagree on this exact scenario. Updated the one golden fixture this touches (duplicate-pr-block) and its golden JSON via the project's own scripts/record-engine-parity-goldens.ts.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui df89791 Commit Preview URL

Branch Preview URL
Jul 27 2026, 06:58 AM

@superagent-security

Copy link
Copy Markdown
Contributor

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

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

loopover-orb Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-27 06:55:18 UTC

32 files · 1 AI reviewer · no blockers · CI green · dirty

⏸️ Suggested Action - Manual Review

Review summary
This PR bundles four related fixes to the review-engine's close-authority surface: a provenance-tagged `deferred_by` column to stop the foreground-liveness sweep from releasing deliberate enqueue-time delays (#9127), corroboration-gating for `duplicate_pr_risk` plus a duplicate-only HOLD in `evaluateGateCheckCore` so it can never auto-close a PR (#9129, mirrored in both host and engine copies), an instance-wide self-host kill switch threaded through `resolveAgentActionMode` and a new `suppressed` outcome for `cancelInFlightWorkflowRunsForHeadSha` (#9130), and routing the two review-nag close sites through a new `finalizePolicyCloseDisposition` helper so the close-precision breaker and gate-decision recording actually engage for policy closes (#9132). The wiring is consistent: `deferred_by` is set only at admission-gate defer sites and cleared on release/coalesce, the rate-limit recheck is correctly scoped to `deferred_by='rate_limit'`, and the duplicate-only hold correctly falls through to failure when mixed with a genuinely critical blocker. Both `src/rules/advisory.ts` and its `packages/loopover-engine` twin were updated in lockstep, which the repo's own parity convention requires.

Nits — 5 non-blocking
  • src/queue/processors.ts:14101/14307 — the two `finalizePolicyCloseDisposition` call sites are near-identical inline blocks (repo/pullNumber/headSha/authorLogin/deliveryId/planned/settings); consider a small shared wrapper to avoid drift between `maybeThrottleReviewNagPing` and `maybeThrottleMonitoredMentions`.
  • src/github/app.ts:519 and src/settings/agent-execution.ts:38 — the `orb(safety): SELFHOST_DEPLOYMENT_MODE=dry-run does not reach the executor — a 'posts nothing' instance still cancels CI, globally blacklists contributors, and writes fabricated merge outcomes #9130` issue number appears as a bare comment marker rather than a named constant; fine for now but worth extracting if more call sites accrue.
  • packages/loopover-engine/src/advisory/gate-advisory.ts and src/rules/advisory.ts both duplicate `hasDuplicateOverlapCorroboration` byte-for-byte — relies entirely on the parity test to catch drift; consider whether this and the duplicate-only-hold block could be hoisted to a shared util given the stated intent to eventually converge (Converge the scoring/rules twins #4881).
  • Confirm the `live-gate-parity contract test` mentioned in the engine-copy comment actually covers `hasDuplicateOverlapCorroboration` and the duplicate-only hold branch, not just the older shared markers.
  • Verify `deferred_by` is included in any existing dead-letter/backfill queries elsewhere in these queue files that select job rows by status/run_after, since a query missed here could silently exclude the newly-provenance-tagged rows.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9127, #9129, #9130, #9132
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 (4 linked issues).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 13 registered-repo PR(s), 13 merged, 299 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 13 PR(s), 299 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The PR adds a deferred_by provenance column set only at the three admission-gate defer sites (rate_limit, maintenance_admission, installation_concurrency), scopes the release SELECT to deferred_by IS NOT NULL, restricts the rate-limit-clear recheck to deferred_by='rate_limit', mirrors the fix in both pg-queue.ts and sqlite-queue.ts, and corrects the module header's false premise — directly matchin

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: 13 PR(s), 299 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: 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.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: success
  • config: 03a7f8b529a9 · pack: oss-anti-slop
  • record: 2250f4e449ec (schema v3, head cc7d78a)

🟩 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 LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

Four related fixes to the review-engine's close/gate-execution path:

- queue liveness sweep released ANY future-scheduled foreground row, not just
  rate-limit deferrals, collapsing the contributor grace window from 300s to
  ~60s. Added a `deferred_by` provenance column (pg + sqlite queues) set only
  at admission-gate defer sites, so an enqueue-time delay is now structurally
  excluded from the release candidate set regardless of age.

- duplicate_pr_risk was derived entirely from a rival PR's own body text yet
  could one-shot-close a PR by default, exempt from the close-precision
  breaker. Corroboration (changed-file overlap, or the sibling being a
  non-trivial real change) now gates the concrete code from the always-
  advisory duplicate_pr_risk_unconfirmed code; the code-level default
  changed to advisory; and a gate that fails solely on a duplicate finding
  now HOLDS instead of closing, under any duplicatePrGateMode.

- SELFHOST_DEPLOYMENT_MODE=dry-run was consulted only inside the octokit
  request hook, one layer below every decision that depends on it — the
  executor believed it was live and drove real outcome writes, CI
  cancellation, notifications, and moderation escalation off a suppressed
  mutation. Folded the instance-wide switch into resolveAgentActionMode as
  the first precedence term (threaded through every call site), routed the
  CI-cancel raw fetch through the same chokepoint, and made a suppressed
  merge structurally distinguishable from a real one.

- review-nag closes never went through applyPrecisionBreakers or
  recordNativeGateDecision, so #9086's breaker coverage was inert and the
  close was invisible to merge-precision calibration. Extracted the shared
  precision-breaker + gate-decision-recording sequence into
  finalizePolicyCloseDisposition and wired both review-nag close sites
  through it.

Mirrored the duplicate-detection fix into the engine package's gate-advisory
twin (packages/loopover-engine) to keep the live-gate/predicted-gate parity
contract green, and updated the golden fixtures it feeds.

Closes #9127, Closes #9129, Closes #9130, Closes #9132
@JSONbored
JSONbored force-pushed the fix/close-authority-9127-9129-9130-9132 branch from cc7d78a to df89791 Compare July 27, 2026 06:56
@github-actions

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (e1ae2f0) and head (df89791) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@JSONbored
JSONbored merged commit 376ad8a into main Jul 27, 2026
7 checks passed
@JSONbored
JSONbored deleted the fix/close-authority-9127-9129-9130-9132 branch July 27, 2026 07:02
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.46154% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.93%. Comparing base (4d21d7c) to head (df89791).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ages/loopover-engine/src/advisory/gate-advisory.ts 84.21% 2 Missing and 1 partial ⚠️
src/queue/processors.ts 88.00% 0 Missing and 3 partials ⚠️
src/services/agent-action-executor.ts 60.00% 0 Missing and 2 partials ⚠️
src/rules/advisory.ts 94.73% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9206      +/-   ##
==========================================
- Coverage   93.94%   93.93%   -0.02%     
==========================================
  Files         821      821              
  Lines       81601    81656      +55     
  Branches    24769    24787      +18     
==========================================
+ Hits        76662    76705      +43     
- Misses       3556     3558       +2     
- Partials     1383     1393      +10     
Flag Coverage Δ
backend 95.20% <88.46%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/github/app.ts 97.95% <100.00%> (+<0.01%) ⬆️
src/github/client.ts 99.62% <ø> (ø)
src/github/pr-actions.ts 100.00% <100.00%> (ø)
src/queue/review-evasion.ts 95.66% <ø> (ø)
src/selfhost/foreground-liveness.ts 100.00% <ø> (ø)
src/selfhost/sqlite-queue.ts 99.61% <100.00%> (+<0.01%) ⬆️
src/services/automation-state.ts 100.00% <ø> (ø)
src/settings/agent-actions.ts 98.56% <ø> (-0.01%) ⬇️
src/settings/agent-execution.ts 94.44% <100.00%> (ø)
src/rules/advisory.ts 98.03% <94.73%> (-0.21%) ⬇️
... and 3 more

... and 1 file with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment