fix(orb): actuation atomicity, close-critical concurrency, and reward-label integrity#9235
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
❌ 4 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
1919f22 to
d51f5ea
Compare
Logic backtestReplayed 0 historical case(s) for Backtest comparison:
|
d51f5ea to
5194abb
Compare
|
Caution 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-27 08:56:08 UTC
Review summary Nits — 5 non-blocking
CI checks failing
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
🟩 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.
|
…er PR The blended pull_requests.linked_issue_claimed_at column preserves an old timestamp whenever the new linked-issue set overlaps the old one, so a PR that claimed a throwaway issue on day one and later adds a valuable issue inherits the day-one timestamp for the new issue too -- letting an old placeholder PR backdate a claim and steal the duplicate-cluster winner slot. Add a durable per-(repo, PR, issue) claim ledger (linked_issue_claims, migration 0191) written once, immutably, the first time each issue is observed linked to a PR. The duplicate-winner election now reads this ledger, scoped to only the issue(s) actually contested with an open sibling, instead of the PR-level blended value. Closes #9160
Wire the per-(PR, issue) claim ledger (previous commit) into every duplicate-winner call site: resolveScopedLinkedIssueClaimedAt reads the ledger's earliest claim among only the issue(s) actually contested with an open sibling, and buildPullRequestAdvisory's election now compares against that scoped value instead of the PR's blended linkedIssueClaimedAt column. Falls back to the blended column when the caller doesn't resolve a scoped value, so every unwired caller keeps prior behavior exactly. Closes #9160
…ue authors A contributor who authored (or was assigned) the linked issue previously inherited every label on it unconditionally, including reward-semantic labels from an additive mapping -- bypassing the trustMaintainerAuthoredIssueForReward opt-in and the maintainer-authorship check every other path already requires. Split the unlock: author/assignee identity still unlocks a plain type label unconditionally, but a reward-semantic label (named by any mapping with removeOtherTypeLabels !== true) now always falls through to the same opt-in + maintainer-check gate regardless of the author match. Closes #9161
…try points The #label-close-split-brain guard only inspected the same-batch planned array, so a rejected or breaker-downgraded close could leave its paired enforcement label applied to a PR that stayed open. Two paths never carried the pair through to the executor's own correlation check: - downgradeCloseToHold now also drops any label whose closeKind matches a close it just downgraded (the breaker-downgrade half). - The approval-queue accept path now re-derives pairing from the durable audit trail (the same agent.action.close record every execution entry point writes through) before applying a close-coupled label, since the queue stages one action per row and an independently rejected close has nothing correlating it to the label in the same batch. Closes #9158
…t just the re-check The per-author contributor-cap mutex was released before the merge it guards, reopening the #7284 TOCTOU: a concurrent cap-close could claim the just-released lock, live-verify the merging PR as still open, and wrong-close it for a cap the merge was about to relieve milliseconds later. The lock now spans the actual merge mutation via a finally that wraps performMutation, matching the sibling cap-close path. The approval-queue accept path also builds its own contributorCapMergeRecheck now, using the same resolvePerRepoContributorCapMatch the live webhook path uses -- previously an accepted staged merge skipped the pre-merge re-check entirely. Closes #9159
5194abb to
e8639e6
Compare
Summary
Four related integrity gaps in the ORB review engine, bundled into one PR since they all touch the same actuation/label-correlation surface:
Closes orb(actuation): the label-vs-close correlation guard is batch-scoped — a rejected or breaker-downgraded close still brands the PR with its enforcement label #9158 — the
#label-close-split-brainlabel-vs-close correlation guard only inspected the same-batchplannedarray, so a rejected or breaker-downgraded close could leave its paired enforcement label applied to a PR that stayed open. Two paths never carried the pair through:downgradeCloseToHold(src/settings/agent-actions.ts) now also drops anylabelaction whosecloseKindmatches a close it just downgraded (the breaker-downgrade half).src/services/agent-approval-queue.ts) now re-derives pairing from the durable audit trail (the sameagent.action.closerecord every execution entry point writes through vialatestCompletedAuditEventDetail) before applying a close-coupled label, since the queue stages one action per row and an independently rejected close has nothing correlating it to the label in the same batch.Closes orb(race): the contributor-cap mutex is released before the merge it guards, so the #7284 TOCTOU is still open — a legitimate PR is closed for a cap already relieved #9159 — the per-author contributor-cap mutex was released before the merge it guards, reopening the feat(miner): add tenant create/list/destroy control-plane admin CLI #7284 TOCTOU.
src/services/agent-action-executor.tsnow holds the lock across the actual merge mutation (afinallywrapsperformMutation, not just the re-check), matching the sibling cap-close path. The approval-queue accept path (src/services/agent-approval-queue.ts) now also builds its owncontributorCapMergeRecheckusing the sameresolvePerRepoContributorCapMatchthe live webhook path uses — previously an accepted staged merge skipped the pre-merge re-check entirely.Closes orb(gate): overlap-preserving claim timestamps let an old PR backdate a claim on any issue and steal the duplicate-winner slot #9160 —
linkedIssueClaimedAtwas a single PR-level timestamp that blends every currently-linked issue into one value, so an old placeholder PR could add a valuable issue later and inherit the placeholder's backdated claim time, stealing the duplicate-cluster winner slot. Added a durable per-(repo, PR, issue)claim ledger (linked_issue_claims, migration0191) written once, immutably, the first time each issue is observed linked to a PR.resolveScopedLinkedIssueClaimedAt(src/queue/duplicate-detection.ts) reads this ledger scoped to only the issue(s) actually contested with an open sibling, and every duplicate-winner call site (src/queue/processors.ts,src/rules/advisory.ts) now threads that scoped value through instead of the blended PR-level column.Closes orb(rewards): a contributor who authored the linked issue inherits every label on it, bypassing the reward-label trust gate and its opt-in flag #9161 — a contributor who authored (or was assigned) the linked issue inherited every label on it unconditionally, including reward-semantic labels from an additive mapping, bypassing the
trustMaintainerAuthoredIssueForRewardopt-in and the maintainer-authorship check every other path already requires.src/review/linked-issue-label-propagation-fetch.tsnow splits the unlock: author/assignee identity still unlocks a plain type label unconditionally, but a reward-semantic label (named by any mapping withremoveOtherTypeLabels !== true) always falls through to the same opt-in + maintainer-check gate regardless of the author match.Judgment calls
linked_issue_claimed_atcolumn (same COALESCE chain as0084_pull_request_linked_issue_claimed_at.sql), so pre-existing links keep their historical relative ordering rather than resetting to "whenever this PR next syncs." This is exactly as backdated as today's pre-fix behavior for those rows (no worse) — the fix's actual benefit (a newly-added issue getting its own fresh clock) applies going forward.contributorOpenPrCapandautoCloseExemptLoginsare config-as-code only (resolved from the repo's.loopover.ymlfocus manifest), notrepository_settingsDB columns — the new approval-queue tests seed them viaupsertRepoFocusManifest, matching the existing pattern forexpectedCiContexts.Test plan
npx tsc --noEmit -p tsconfig.json --incremental false— clean (after rebuilding@loopover/engine)npm run db:migrations:check— 195 migrations OK, contiguous 0001..0191, next free 0192npm run db:schema-drift:check—src/db/schema.tsmatchesmigrations/agent-action-executor.test.ts,agent-approval-queue.test.ts,agent-actions.test.ts,linked-issue-label-propagation-fetch.test.ts,db-parsers.test.ts,duplicate-winner.test.ts,resolve-scoped-linked-issue-claimed-at.test.ts(new),rules.test.ts,reconcile-live-duplicate-siblings.test.ts,transient-locks.test.ts— 950 passed, 0 failed