Skip to content

gate(eligibility): hold PRs against a priority issue until its window opens (#9738) - #9847

Merged
JSONbored merged 4 commits into
mainfrom
fix/linked-issue-rules-per-repo
Jul 29, 2026
Merged

gate(eligibility): hold PRs against a priority issue until its window opens (#9738)#9847
JSONbored merged 4 commits into
mainfrom
fix/linked-issue-rules-per-repo

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #9738.

Why

Priority issues carry the highest payout, so assignment fairness matters most there — and first-come pickup is only fair if everyone can see the issue before anyone can act on it. A PR opened moments after the label lands means the window between "issue becomes valuable" and "issue is claimed" was effectively zero for everyone else watching the repo.

What

A PR closing a gittensor:priority issue is gate-eligible only once the label has been publicly present for gate.priorityEligibilityWindow minutes — default 30, per repo, 0 disables it.

A PR arriving inside the window is not rejected. It is held, with a neutral comment naming the moment it becomes eligible, and proceeds normally once the window elapses. No penalty beyond waiting, and the contributor keeps their work. The hold rides the existing heldForManualReview rail, so it structurally cannot close a PR.

Two decisions worth your eyes

The clock is anchored to the EARLIEST labeling event, not the most recent. The issue requires that re-applying the label not reset the clock for already-open PRs; an earliest anchor gives that to everyone and makes "when does this issue open for work" a single knowable instant that nothing later can move. The trade: a label removed and re-added months later reopens work immediately — which I think is right, since the issue was public that whole time.

Every unknown fails open — an unreadable timestamp, an absent label, a GraphQL error, a missing token. Holding a contributor's PR on a fact we could not read is a penalty for our own gap.

Merge holds are data now

Adding a hold used to be three independent edits — the input type, the heldForManualReview fold, and an exhaustive test fixture — each forgettable on its own. Declaring a hold and then not folding it into the decision compiled fine.

MERGE_HOLD_INPUTS is now the one table. The input type (Record<MergeHoldInput, boolean>), the fold, and both test fixtures derive from it. Adding a hold is one entry, and omitting the wiring is a compile error at the single call site. I hit this the moment I added the sixth hold, which is what prompted it.

normalizeOptionalNonNegativeInteger is new because 0 is meaningful here: the existing positive-integer normalizer would have rejected an operator's explicit "off" as invalid and silently applied the default — turning it back on.

Validation

  • 100% statements and branches on both changed source files (priority-eligibility-window.ts, pr-disposition.ts), measured directly.
  • 26 new tests: the pure rule (boundary-exact, every fail-open path, fractional/non-finite windows) and the resolver (which linked issue is consulted, the two-issue case, the no-read shortcut, and nine ways it declines to hold).
  • npx vitest run --changed=origin/main: 15143 passed. The four miner CLI files that fail without a built dist/ pass once built — unrelated to this change.
  • tsc clean; docs:drift-check, ui:openapi:check, command-reference:check, manifest:drift-check all green. The gate field is documented in both example configs, with the yml-key↔field alias declared where the drift checker expects it.

Not in this PR

#9737 (author-based eligibility — strip gittensor:priority from non-maintainer-authored issues) is the epic's other half and needs a new issues.labeled webhook path; it is its own change.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 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 e487193 Commit Preview URL

Branch Preview URL
Jul 29 2026, 06:58 PM

@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-29 19:14:07 UTC

23 files · 1 AI reviewer · no blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR closes #9738 by adding a priority-issue eligibility window: a PR closing a `gittensor:priority`-labeled issue is held (never rejected) until the label has been publicly visible for a configurable window, anchored to the earliest LABELED_EVENT so re-labeling can't reset the clock. The pure evaluator fails open on every unknown (missing timestamp, unparseable date, non-positive window), the GraphQL reader is properly scoped and capped at one page, and the hold is wired through the existing `heldForManualReview` rail via a new `MERGE_HOLD_INPUTS` table that also refactors three previously-independent hold-declaration sites into one, closing a real class of bug (a hold declared but not folded into the decision). Test coverage is thorough, including the millisecond boundary case and case-insensitive label matching. The codecov/patch failure (76% vs 99% target) and validate/validate-code failures with no detail are visible in CI status but not diagnosable from the diff alone, and the branch is 4 commits behind default, which is a plausible partial explanation.

Nits — 6 non-blocking
  • `resolvePriorityEligibilityHold` in src/review/priority-eligibility-window.ts holds on the FIRST linked issue still inside its window per the loop, but combined with the `issueLabels` map's early-continue optimization, this could subtly under-hold if the caller's cached `issueLabels` map is stale relative to a very recently re-applied label — worth a comment noting the cache must be freshly resolved per run.
  • `src/queue/processors.ts` computes `priorityEligibilityHold` unconditionally on every PR pass with a `.catch(() => undefined)` swallowing all errors silently; consider at least a debug log so a persistently-failing GraphQL read doesn't go unnoticed.
  • The magic numbers flagged by the external brief (e.g. `60_000` ms-per-minute in priority-eligibility-window.ts) are self-evident from context and not worth extracting into named constants.
  • `fetchIssueLabelFirstAppliedAt` in src/github/backfill.ts reads only first:100 timeline items and returns null beyond that (fails open) — reasonable per the comment, but the 100-event cap deserves a one-line justification tied to a realistic max re-label count.
  • Consider logging (even at debug level) when `resolvePriorityEligibilityHold`'s catch swallows a GraphQL error in src/queue/processors.ts, so a systemic API failure doesn't silently fail-open indefinitely without any operator visibility.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

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 #9738
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 (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 344 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 344 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Partially addressed
The PR fully implements the gate rule, hold-not-reject behavior, earliest-label-event anchoring, configurable window, and thorough tests, closely matching the issue's scope and acceptance criteria. However, the issue explicitly asks to add the window to 'the same label-policy page as the author-eligibility rule' so contributors can see it, and the diff only updates config-example comments/manifest

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: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 344 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 &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; 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: merge · clause: success
  • config: e110d5e6ba0adecb68c0c93d619ba60227d320add2789c0fcfddb4d8f56e0f61 · pack: oss-anti-slop · ci: passed
  • record: b9635a9d02ebac806e5d85534fe8d049455129358426cff6cd7c7c981b0eea69 (schema v5, head e487193)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 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

@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.11504% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.75%. Comparing base (8e2d51d) to head (e487193).
⚠️ Report is 10 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/loopover-engine/src/focus-manifest.ts 96.15% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #9847    +/-   ##
========================================
  Coverage   91.75%   91.75%            
========================================
  Files         919      920     +1     
  Lines      112987   113098   +111     
  Branches    27193    27224    +31     
========================================
+ Hits       103667   103777   +110     
- Misses       8034     8035     +1     
  Partials     1286     1286            
Flag Coverage Δ
backend 95.69% <100.00%> (+<0.01%) ⬆️
engine 71.84% <94.11%> (+0.03%) ⬆️

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

Files with missing lines Coverage Δ
packages/loopover-engine/src/index.ts 100.00% <100.00%> (ø)
...ages/loopover-engine/src/settings/pr-type-label.ts 78.08% <100.00%> (+2.20%) ⬆️
src/github/backfill.ts 96.04% <100.00%> (+0.03%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/queue/processors.ts 94.74% <100.00%> (+<0.01%) ⬆️
src/review/priority-eligibility-window.ts 100.00% <100.00%> (ø)
src/settings/agent-actions.ts 98.25% <100.00%> (+0.01%) ⬆️
src/settings/pr-disposition.ts 100.00% <100.00%> (ø)
src/signals/focus-manifest.ts 99.61% <100.00%> (+<0.01%) ⬆️
src/types.ts 100.00% <ø> (ø)
... and 1 more

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 93.87kB (1.2%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.92MB 93.87kB (1.2%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-B84vOXT7.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-XpzG3Eip.js (New) 932.17kB 932.17kB 100.0% 🚀
openapi.json 176 bytes 750.12kB 0.02%
assets/docs.fumadocs-spike-api-reference-ZhV6BAd4.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/charts-vendor-Ctuzdvzc.js (New) 379.33kB 379.33kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-gOnA6JTz.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/react-vendor-BZse5iG4.js (New) 192.96kB 192.96kB 100.0% 🚀
assets/modal-DDzoTs3N.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-BrFvmuph.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/self-hosting-operations-DWX8-_N1.js (New) 134.31kB 134.31kB 100.0% 🚀
assets/motion-vendor-tX31MP0E.js (New) 125.38kB 125.38kB 100.0% 🚀
assets/self-hosting-configuration-1mklU-_N.js (New) 116.4kB 116.4kB 100.0% 🚀
assets/maintainer-panel-jEb1S-WD.js (New) 78.96kB 78.96kB 100.0% 🚀
assets/tuning-_XqW8VWg.js (New) 67.35kB 67.35kB 100.0% 🚀
assets/what-you-can-verify-Cj9CcDTI.js (New) 61.2kB 61.2kB 100.0% 🚀
assets/self-hosting-release-checklist-B-RCS-69.js (New) 51.82kB 51.82kB 100.0% 🚀
assets/verify-this-review-Ck0f0_EJ.js (New) 50.91kB 50.91kB 100.0% 🚀
assets/backtest-calibration-BqECoNVn.js (New) 46.46kB 46.46kB 100.0% 🚀
assets/self-hosting-security-B1i8UerX.js (New) 44.97kB 44.97kB 100.0% 🚀
assets/routes-_e1RqNv1.js (New) 36.4kB 36.4kB 100.0% 🚀
assets/github-app-CCJ128o0.js (New) 36.16kB 36.16kB 100.0% 🚀
assets/self-hosting-troubleshooting-Qjo9MFul.js (New) 33.56kB 33.56kB 100.0% 🚀
assets/how-reviews-work-BedvUzLb.js (New) 33.09kB 33.09kB 100.0% 🚀
assets/self-hosting-github-app-OGthmeNB.js (New) 32.96kB 32.96kB 100.0% 🚀
assets/ams-operations-runbook-D7PQNKMi.js (New) 30.02kB 30.02kB 100.0% 🚀
assets/ams-deployment-DLVbIHJB.js (New) 28.59kB 28.59kB 100.0% 🚀
assets/owner-panel-BDrxYe0p.js (New) 28.22kB 28.22kB 100.0% 🚀
assets/maintainer-self-hosting-9gxct3TQ.js (New) 27.91kB 27.91kB 100.0% 🚀
assets/app-CS4rCoHw.js (New) 25.82kB 25.82kB 100.0% 🚀
assets/ams-discovery-plane-BsQZIy8T.js (New) 25.18kB 25.18kB 100.0% 🚀
assets/privacy-security-BQuEZgwY.js (New) 24.82kB 24.82kB 100.0% 🚀
assets/ui-vendor-Cy00BlWs.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/ams-config-precedence-Bg8Ez47M.js (New) 24.19kB 24.19kB 100.0% 🚀
assets/self-hosting-quickstart-BraikMwr.js (New) 24.1kB 24.1kB 100.0% 🚀
assets/ams-kill-switch-incident-CLI5VnJ2.js (New) 23.44kB 23.44kB 100.0% 🚀
assets/miner-panel-BdD_LLRd.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-BsB6oT_c.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/ams-observability-CLw0NVB8.js (New) 20.08kB 20.08kB 100.0% 🚀
assets/ams-goal-spec-BP1BblmU.js (New) 19.77kB 19.77kB 100.0% 🚀
assets/self-hosting-backup-scaling-qWfWgkbb.js (New) 18.95kB 18.95kB 100.0% 🚀
assets/federated-fleet-intelligence-Op_e3MF8.js (New) 18.7kB 18.7kB 100.0% 🚀
assets/maintainer-install-trust-BI33gMBc.js (New) 18.03kB 18.03kB 100.0% 🚀
assets/beta-onboarding-Cwfu_tHO.js (New) 17.65kB 17.65kB 100.0% 🚀
assets/api._op-BOZxTX2t.js (New) 17.54kB 17.54kB 100.0% 🚀
assets/self-hosting-docs-audit-DL8r1gzv.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/self-hosting-rees-LLfkLBVN.js (New) 15.65kB 15.65kB 100.0% 🚀
assets/docs._slug-VFbSdzTA.js (New) 15.63kB 15.63kB 100.0% 🚀
assets/miner-quickstart-cU-rBOPh.js (New) 15.62kB 15.62kB 100.0% 🚀
assets/self-hosting-ai-providers-DKEGAaZx.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/mcp-clients-piMY6cFy.js (New) 14.54kB 14.54kB 100.0% 🚀
assets/playground-panel-DFWcqUbs.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/ams-sizing-DVJhFWss.js (New) 14.17kB 14.17kB 100.0% 🚀
assets/owner-checklist-Cfadybn5.js (New) 14.12kB 14.12kB 100.0% 🚀
assets/fairness-86MRWT_n.js (New) 13.98kB 13.98kB 100.0% 🚀
assets/self-hosting-unified-ams-orb-DN8sJITm.js (New) 12.04kB 12.04kB 100.0% 🚀
assets/self-hosting-releases-CZ95nbuT.js (New) 12.03kB 12.03kB 100.0% 🚀
assets/ams-unattended-scheduling-Dw184y5b.js (New) 11.58kB 11.58kB 100.0% 🚀
assets/label-policy-Lq5v7KBm.js (New) 11.51kB 11.51kB 100.0% 🚀
assets/miner-coding-agent-DF2zqlDo.js (New) 11.38kB 11.38kB 100.0% 🚀
assets/capacity-BhB5kXsT.js (New) 10.51kB 10.51kB 100.0% 🚀
assets/troubleshooting-5pS6pAwW.js (New) 10.34kB 10.34kB 100.0% 🚀
assets/app.audit-C5cCI0rM.js (New) 10.22kB 10.22kB 100.0% 🚀
assets/app.config-generator-Bp_g-eHb.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/scoreability-C0bI3NHl.js (New) 8.28kB 8.28kB 100.0% 🚀
assets/self-hosting-rees-analyzers-tWtMpp1v.js (New) 8.25kB 8.25kB 100.0% 🚀
assets/maintainers-CzgE08XL.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-D4DMyDY7.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/self-hosting-rag-Dn_4-Wy9.js (New) 7.77kB 7.77kB 100.0% 🚀
assets/agents-VuYqrh2r.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/ams-fleet-manifest-CfrlfJUA.js (New) 7.39kB 7.39kB 100.0% 🚀
assets/ams-env-reference-Dh37dJ6E.js (New) 7.12kB 7.12kB 100.0% 🚀
assets/commands-panel-Dueb7pok.js (New) 6.74kB 6.74kB 100.0% 🚀
assets/maintainer-workflow-8KE-amFA.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/ai-summaries-CRXyf8WY.js (New) 6.46kB 6.46kB 100.0% 🚀
assets/digest-panel-BKw5-B39.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Bg9voHRU.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-BFdUaWJG.js (New) 6.06kB 6.06kB 100.0% 🚀
assets/docs.index-CH2WvMgd.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/loopover-commands-DpkVcZ8_.js (New) 5.31kB 5.31kB 100.0% 🚀
assets/branch-analysis-CahWlpgk.js (New) 5.29kB 5.29kB 100.0% 🚀
assets/miner-workflow-Dd0FZ_Hi.js (New) 4.91kB 4.91kB 100.0% 🚀
assets/api.index-DWSfIB6e.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/quickstart-C-Fx6n4p.js (New) 4.32kB 4.32kB 100.0% 🚀
assets/upstream-drift-CegaAWIi.js (New) 3.97kB 3.97kB 100.0% 🚀
assets/docs-Bto4QDhr.js (New) 2.93kB 2.93kB 100.0% 🚀
assets/api-LR8b2VJl.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-DjpXfBI1.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-CqpJpby1.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-QFyIazm8.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-C1zB7AQE.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-CVdkbo2A.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-Bj2gy51G.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-BT-n29gi.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-BWxZOYhH.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-ChXSGvrH.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-DL39IjQr.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-BhZ120JE.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-8OO0woD9.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-Bx2scA94.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-Ch7hB7Yb.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-sjRCt_UR.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-CBVLMG84.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-BRqp4Yh1.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-DB03sbec.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-zJJ2Ewgu.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-D0IQtAsE.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BvED4g5g.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-BFfZJnR3.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-ejr0AzX1.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-nFOuXHwi.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-DMi3XUAg.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-CRHHNyUs.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-BBCfKLYQ.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-lbJITgQH.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-BgXJAGwW.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-BVOO8KZ6.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-DmuOBTK_.js (Deleted) -930.54kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-BiYizERa.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/charts-vendor-CcAPoEeQ.js (Deleted) -290.77kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-CR-HVJw_.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/react-vendor-BbFKED--.js (Deleted) -189.49kB 0 bytes -100.0% 🗑️
assets/modal-Bh2Pstju.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-C5pmRngj.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/self-hosting-operations-jdhwnSpO.js (Deleted) -134.31kB 0 bytes -100.0% 🗑️
assets/motion-vendor-THGzLkD2.js (Deleted) -125.39kB 0 bytes -100.0% 🗑️
assets/self-hosting-configuration-DwCFaekz.js (Deleted) -116.4kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-vRgZejj6.js (Deleted) -78.96kB 0 bytes -100.0% 🗑️
assets/tuning-CZsAIqlr.js (Deleted) -67.35kB 0 bytes -100.0% 🗑️
assets/what-you-can-verify-we6EqhJX.js (Deleted) -61.2kB 0 bytes -100.0% 🗑️
assets/self-hosting-release-checklist-CL3IrDTy.js (Deleted) -51.82kB 0 bytes -100.0% 🗑️
assets/verify-this-review-33S95RSl.js (Deleted) -50.91kB 0 bytes -100.0% 🗑️
assets/backtest-calibration-BkI7CUDI.js (Deleted) -46.46kB 0 bytes -100.0% 🗑️
assets/self-hosting-security-Bfk-kWq9.js (Deleted) -44.97kB 0 bytes -100.0% 🗑️
assets/routes-BwZanfho.js (Deleted) -36.41kB 0 bytes -100.0% 🗑️
assets/github-app-DI9JRtvp.js (Deleted) -36.16kB 0 bytes -100.0% 🗑️
assets/self-hosting-troubleshooting-D77WSh4l.js (Deleted) -33.56kB 0 bytes -100.0% 🗑️
assets/how-reviews-work-B4I9Ivku.js (Deleted) -33.09kB 0 bytes -100.0% 🗑️
assets/self-hosting-github-app-DfVHqnrm.js (Deleted) -32.96kB 0 bytes -100.0% 🗑️
assets/ams-operations-runbook-DzO-4592.js (Deleted) -30.02kB 0 bytes -100.0% 🗑️
assets/ams-deployment-CF1MBS0g.js (Deleted) -28.59kB 0 bytes -100.0% 🗑️
assets/owner-panel-DyLfvX3b.js (Deleted) -28.18kB 0 bytes -100.0% 🗑️
assets/maintainer-self-hosting-DeI66YCt.js (Deleted) -27.91kB 0 bytes -100.0% 🗑️
assets/app-DGZRE2ek.js (Deleted) -25.82kB 0 bytes -100.0% 🗑️
assets/ams-discovery-plane-CcSBdqwX.js (Deleted) -25.18kB 0 bytes -100.0% 🗑️
assets/privacy-security-DhEa8Pk4.js (Deleted) -24.82kB 0 bytes -100.0% 🗑️
assets/ui-vendor-BkoA_Smn.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/ams-config-precedence-dtrTQTy_.js (Deleted) -24.19kB 0 bytes -100.0% 🗑️
assets/self-hosting-quickstart-BAqMRwqJ.js (Deleted) -24.1kB 0 bytes -100.0% 🗑️
assets/ams-kill-switch-incident-MSHDckf7.js (Deleted) -23.44kB 0 bytes -100.0% 🗑️
assets/miner-panel-biMAstHf.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-84M0G1fA.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/ams-observability-Xj0N10hl.js (Deleted) -20.08kB 0 bytes -100.0% 🗑️
assets/ams-goal-spec-Dpo-MoO8.js (Deleted) -19.77kB 0 bytes -100.0% 🗑️
assets/self-hosting-backup-scaling-DHHj1VkB.js (Deleted) -18.95kB 0 bytes -100.0% 🗑️
assets/federated-fleet-intelligence-BwSKaYxu.js (Deleted) -18.7kB 0 bytes -100.0% 🗑️
assets/maintainer-install-trust-sa_WkEVZ.js (Deleted) -18.03kB 0 bytes -100.0% 🗑️
assets/beta-onboarding-Btra-9kf.js (Deleted) -17.65kB 0 bytes -100.0% 🗑️
assets/api._op-C-KbGH_o.js (Deleted) -17.54kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-BbUViMf-.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/self-hosting-rees-CJsRU-74.js (Deleted) -15.65kB 0 bytes -100.0% 🗑️
assets/docs._slug-Dt66RhOu.js (Deleted) -15.63kB 0 bytes -100.0% 🗑️
assets/miner-quickstart-_UCI1Q6_.js (Deleted) -15.62kB 0 bytes -100.0% 🗑️
assets/self-hosting-ai-providers-DW4Nish9.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/mcp-clients-BFfZPUpp.js (Deleted) -14.54kB 0 bytes -100.0% 🗑️
assets/playground-panel-BCXho-F6.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/ams-sizing-CSKUjOej.js (Deleted) -14.17kB 0 bytes -100.0% 🗑️
assets/owner-checklist-CvlJYXzU.js (Deleted) -14.12kB 0 bytes -100.0% 🗑️
assets/fairness-kjOE3asl.js (Deleted) -13.98kB 0 bytes -100.0% 🗑️
assets/self-hosting-unified-ams-orb-C2EM11SP.js (Deleted) -12.04kB 0 bytes -100.0% 🗑️
assets/self-hosting-releases-OIMVvnvg.js (Deleted) -12.03kB 0 bytes -100.0% 🗑️
assets/ams-unattended-scheduling-CmFBc05c.js (Deleted) -11.58kB 0 bytes -100.0% 🗑️
assets/label-policy-BBNS6y7Y.js (Deleted) -11.51kB 0 bytes -100.0% 🗑️
assets/miner-coding-agent-Kz2bi4CD.js (Deleted) -11.38kB 0 bytes -100.0% 🗑️
assets/capacity-dWQ2po0o.js (Deleted) -10.51kB 0 bytes -100.0% 🗑️
assets/troubleshooting-BvtGF1Tk.js (Deleted) -10.34kB 0 bytes -100.0% 🗑️
assets/app.audit-BjdQsFXs.js (Deleted) -10.22kB 0 bytes -100.0% 🗑️
assets/app.config-generator-BgZxTfTv.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/scoreability-OCOaRii1.js (Deleted) -8.28kB 0 bytes -100.0% 🗑️
assets/self-hosting-rees-analyzers-BFDa3uBQ.js (Deleted) -8.25kB 0 bytes -100.0% 🗑️
assets/maintainers-CXA9epZe.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-BxCTDCPB.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/self-hosting-rag-CxRS_Rof.js (Deleted) -7.77kB 0 bytes -100.0% 🗑️
assets/agents-DUIRFYWm.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/ams-fleet-manifest-cEJPlg_G.js (Deleted) -7.39kB 0 bytes -100.0% 🗑️
assets/ams-env-reference-39xo9xWm.js (Deleted) -7.12kB 0 bytes -100.0% 🗑️
assets/commands-panel-Xx6qPoXO.js (Deleted) -6.74kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-D3gjtL4a.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/ai-summaries-C9qii1GE.js (Deleted) -6.46kB 0 bytes -100.0% 🗑️
assets/digest-panel-KrzEtSjo.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DXNWL9vt.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-Dlne7qtC.js (Deleted) -6.06kB 0 bytes -100.0% 🗑️
assets/docs.index-DVYKFvua.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/loopover-commands-1GsPEE2-.js (Deleted) -5.31kB 0 bytes -100.0% 🗑️
assets/branch-analysis-BkHtPQ2O.js (Deleted) -5.29kB 0 bytes -100.0% 🗑️
assets/miner-workflow-CO28ozt0.js (Deleted) -4.91kB 0 bytes -100.0% 🗑️
assets/api.index-BqPKFY23.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/quickstart-B62b00Qx.js (Deleted) -4.32kB 0 bytes -100.0% 🗑️
assets/upstream-drift-t1jJXvGv.js (Deleted) -3.97kB 0 bytes -100.0% 🗑️
assets/docs-BCFa6nrX.js (Deleted) -2.93kB 0 bytes -100.0% 🗑️
assets/api-DsBuOqvW.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-CRosj20q.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-*.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-Buj0vVLe.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-BLdwhlny.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-ByM9x7fk.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-CtHp3gJ-.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-WM4pdav3.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-YKFs9i-4.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-BPDtHB9-.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-DTq6Y8-J.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-DH2wJ6HC.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-o3HIfNF1.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-CHEACfD6.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-CfGEIM62.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-CtNuUgiU.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-CAbiFXWv.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-D78XDSDo.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-DfEUBnNF.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-BeJZxXby.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-Cd36DAk3.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-B0AR3Zm3.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DCa3B7LM.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-Dh8Vz1Jo.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-CeMX8wVc.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-DB3Dj-m-.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-DGPfvWFv.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-D1dYBIT_.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-BwXILDcv.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-DHYzhO1f.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

… opens (#9738)

Priority issues carry the highest payout, so assignment fairness matters most there -- and first-come
pickup is only fair if everyone can SEE the issue before anyone can act on it. A PR opened moments
after the label lands means the window between "issue becomes valuable" and "issue is claimed" was
effectively zero for everyone else watching the repo.

A PR closing a `gittensor:priority` issue is now gate-eligible only once the label has been publicly
present for `gate.priorityEligibilityWindow` minutes (default 30, per repo, `0` disables it). A PR
arriving inside the window is NOT rejected: it is HELD with a neutral comment naming the moment it
becomes eligible, and proceeds normally once the window elapses. No penalty beyond waiting.

Two decisions worth stating:

- The clock is anchored to the EARLIEST labeling event, not the most recent. The spec requires that
  re-applying the label not reset the window for already-open PRs; an earliest anchor gives that to
  everyone and makes "when does this issue open for work" a single knowable instant nothing can move.
- Every unknown FAILS OPEN -- an unreadable timestamp, an absent label, a GraphQL error, a missing
  token. Holding a contributor's PR on a fact we could not read is a penalty for our own gap.

The hold reuses the existing merge-hold rail (`heldForManualReview`), so it can never close a PR.

Merge holds become DATA rather than a widening list of booleans. `MERGE_HOLD_INPUTS` is the one
table; the input type (`Record<MergeHoldInput, boolean>`), the `heldForManualReview` fold, and both
test fixtures all derive from it. Adding a hold was three independent edits that could each be
forgotten -- declaring one and not folding it into the decision compiled fine. It is now one entry,
and omitting the wiring is a compile error at the single call site.

`normalizeOptionalNonNegativeInteger` exists because `0` is meaningful here (it turns the rule off)
and the positive-integer normalizer would have discarded it as invalid and silently applied the
default -- an operator's explicit "off" becoming "on".

100% statements and branches on both changed source files.
…ig round-trip it exposed

The GraphQL label-timestamp read had no tests at all: every fail-open path, the
earliest-labeling anchor, and the query shape (first:, not last: -- using last:
would return the most RECENT labeling, which is exactly the value re-applying the
label could use to push a contributor's window back) are now pinned.

Writing the manifest test surfaced a real gap: gateConfigToJson never emitted
priorityEligibilityWindow, so the setting did not round-trip.

resolvePriorityEligibilityHold now DEFAULTS fetchLabeledAt to the real GraphQL read
rather than returning undefined when none is injected -- its doc comment already
claimed that default existed. The production caller passes facts only, and a new
test proves the default really reaches GitHub instead of every case passing on an
injected fake.

resolvePriorityTypeLabel replaces the ad-hoc `typeLabels?.priority ?? DEFAULT`
each caller spelled for itself. The label-author rule (#9737) and this window act on
the same label; two spellings could disagree about a repo that renamed it.
Codecov counts a /* v8 ignore */ line as unhit, so both suppressions are gone rather
than carried:

DEFAULT_PRIORITY_LABEL names the built-in value, so resolvePriorityTypeLabel returns
it directly. PrTypeLabelSet is an open Record, which made DEFAULT_TYPE_LABELS.priority
read as possibly-undefined and forced a fallback branch nothing could ever take.

The .catch on resolvePriorityEligibilityHold is removed: that function catches its own
GitHub read and cannot reject, so the guard was unreachable code that read as
tested-and-fine while never running.

The remaining branch is closed by an end-to-end test instead: a clean, approved, green
PR that would MERGE is held because the priority issue it closes only became public a
minute ago -- held, never closed.
…INE's own suite

packages/loopover-engine/src/** is credited by TWO Codecov uploads: the unflagged
backend one from the root vitest run, and an "engine" flag fed by the package's own
node:test suite (codecov.yml's own note: a PR touching engine source should get
credit from the suite that actually behaviour-tests it). These two surfaces were
only covered by the root suite, so the engine flag reported one uncovered statement
per file -- which is where codecov/patch's missing 2 lines came from. A full
unsharded local run (25,512 tests) confirms the backend side is already at 100%.

resolvePriorityTypeLabel is exported from the engine index so its own suite can
reach it, and MERGE_HOLD_INPUTS drops an export nothing outside its file used.
@JSONbored
JSONbored force-pushed the fix/linked-issue-rules-per-repo branch from 3eaac48 to e487193 Compare July 29, 2026 18:54
@JSONbored
JSONbored merged commit 35e574d into main Jul 29, 2026
14 checks passed
@JSONbored
JSONbored deleted the fix/linked-issue-rules-per-repo branch July 29, 2026 19:14
JSONbored added a commit that referenced this pull request Jul 29, 2026
… ENGINE's own suite

Same cause as the sibling fix on #9847. packages/loopover-engine/src/** is credited
by two Codecov uploads, and the "engine" flag is fed by the package's own node:test
suite -- so a module that only the root vitest suite exercises reads as uncovered
there. author-association.ts moved INTO the engine in this PR, which makes every one
of its lines new to that flag.

The predicate is now behaviour-tested in the engine suite, including through
buildPullRequestAdvisory, since the gate-advisory twin is one of its callers and is
where the fifth duplicate copy lived.
JSONbored added a commit that referenced this pull request Jul 29, 2026
… ENGINE's own suite

Same cause as the sibling fix on #9847. packages/loopover-engine/src/** is credited
by two Codecov uploads, and the "engine" flag is fed by the package's own node:test
suite -- so a module that only the root vitest suite exercises reads as uncovered
there. author-association.ts moved INTO the engine in this PR, which makes every one
of its lines new to that flag.

The predicate is now behaviour-tested in the engine suite, including through
buildPullRequestAdvisory, since the gate-advisory twin is one of its callers and is
where the fifth duplicate copy lived.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gate(eligibility): minimum open-window before PRs against priority issues are gate-eligible

1 participant