feat(orb): detect APR repo-transfer acceptance/expiry via scheduled poll - #8005
Conversation
GitHub repo transfers are asynchronous and acceptance-gated, so initiateAprRepoTransfer's
{initiated:true} only means a transfer is PENDING. This adds the ratified poll-not-webhook
detection (JSONbored#7741):
- classifyAprRepoTransferOutcome + probeAprRepoTransfer: probe the repo via the App
installation token and mark accepted / accepted-and-departed / expired-at-7-days /
still-pending. Pure and injectable, unit-tested without the live cron.
- pollPendingAprRepoTransfers: reconcile every pending transfer and the per-repo AMS
pause, reusing the EXISTING agentPaused kill-switch (setAprRepoDispatchPaused) - keep
dispatch frozen while pending, resume on accepted-and-still-installed or expired/declined,
leave departed accepts alone. requestAprRepoTransfer now freezes dispatch on initiation.
- Scheduled wiring reuses the existing cron/queue mechanism: an hourly poll-apr-repo-transfers
job gated behind default-off LOOPOVER_APR_TRANSFER_POLL; the live-loop dispatch arm is
v8-ignored, all real logic lives in the injectable functions. The pending-transfer store
fail-empties until JSONbored#7664 lands, mirroring loadAprIdeaCompletion.
100% patch coverage on changed source lines; typecheck/build/diff-check clean.
Closes JSONbored#7741
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-22 17:23:39 UTC
Review summary Nits — 4 non-blocking
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. 🟩 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.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8005 +/- ##
==========================================
- Coverage 91.92% 89.30% -2.63%
==========================================
Files 741 98 -643
Lines 75957 22151 -53806
Branches 23048 3863 -19185
==========================================
- Hits 69827 19781 -50046
+ Misses 5037 2188 -2849
+ Partials 1093 182 -911
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…8605) Live diagnosis on the self-hosted ORB box: metagraphed's secret_leak gate false-positive rate climbed from 38% to 56% over 6 hours, holding mergeable PRs. Root cause wasn't a pattern-match false positive -- it was the fail-closed "content exceeded the scan cap" block on regenerated OpenAPI/JSON-schema artifacts. metagraphed's openapi.json is ~1.9MB and api-components.schema.json ~514KB, both well past the old 512,000-char cap; three recent PRs (#8106, #8095, #8005) were all held for this reason and all merged anyway once manually verified clean. The underlying fetcher (grounding-wire.ts's makeGithubFileFetcher) already requests the raw+json media type specifically to bypass GitHub's Contents API ~1MB base64-JSON envelope ceiling, so the real limit was always this local constant, not GitHub's. Raising it only expands scan coverage -- more content becomes fetchable-and-scannable instead of being marked incomplete-and-blocked -- so this can't weaken detection on anything the old cap already caught. 4MB gives headroom above the largest observed real case (loopover's own openapi.json is already at 525KB, past the old cap too). Fetch count/concurrency stay capped separately, unaffected.
Summary
Closes #7741 —
initiateAprRepoTransferreturns{ initiated: true }on a 2xx, but GitHub repo transfers are asynchronous and acceptance-gated; nothing detected whether a pending transfer was accepted, declined, or expired. This implements the ratified poll-based detection (poll, not webhook — a transfer to a customer's own account moves the repo outside our App's installation, so no webhook is reliably deliverable).What changed (
src/orb/apr-repo-transfer.ts)Detection/expiry are pure, injectable functions (the live loop is wired but
v8 ignore'd, matching the existing relay-drain pattern), so the logic is unit-tested directly:probeAprRepoTransfer—GET /repos/{owner}/{repo}with the App installation token.404→ accepted-and-departed (ownership moved, App lost access);2xxwhoseowner.loginis now the target → resolved-under-target (accepted); anything else → still pending.classifyAprRepoTransferOutcome— pure: resolved →accepted, departed →accepted_departed, elseexpiredoncenow - initiatedAt >= 7 days(APR_REPO_TRANSFER_EXPIRY_MS), elsepending.pollPendingAprRepoTransfers— per pending transfer: probe → classify → reconcile.Pause reuse (deliverable 2):
setAprRepoDispatchPausedtoggles the existing per-repoagentPausedkill-switch viaupsertRepositorySettings— no new mechanism.requestAprRepoTransferfreezes dispatch on initiation; the poll resumes on accepted-and-still-installed or expired/declined, and leavesaccepted_departedalone (nothing left to dispatch).Scheduling (reused, not reinvented): a new
poll-apr-repo-transfersjob type, hourly enqueue insrc/index.tsgated behind a default-offLOOPOVER_APR_TRANSFER_POLLflag (keeps existing cron tests byte-identical), and a dispatch arm insrc/queue/job-dispatch.ts. The pending-transfer store fail-empties (no-op) until #7664 persists rows — mirroring the merged #8001loadAprIdeaCompletionprecedent.Tests (deliverable 3)
Extended
test/unit/orb-apr-repo-transfer.test.tsin its existing style: accepted, accepted-departed, expired-at-7-days, still-pending-within-window, plus probe/pause/flag cases; added a flag-on enqueue test totest/unit/index.test.ts.Verified locally on top of current
main:npm run typecheckclean,@loopover/enginebuild clean,git diff --checkclean, 82 tests pass, and 100% patch coverage —src/orb/apr-repo-transfer.tsandsrc/index.tsboth report zero uncovered statements and zero uncovered branches. Thejob-dispatch.tslive-loop arm isv8 ignore'd (the injectable logic it binds is covered directly in the unit test).