Skip to content

feat(agent-actions): add an install-wide contributor open-item cap across repos#2649

Closed
JSONbored wants to merge 8 commits into
mainfrom
feat/global-contributor-open-item-cap
Closed

feat(agent-actions): add an install-wide contributor open-item cap across repos#2649
JSONbored wants to merge 8 commits into
mainfrom
feat/global-contributor-open-item-cap

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • contributorOpenPrCap/contributorOpenIssueCap only see one repo — a self-host operator gating multiple repos on one install has no fleet-wide view: an actor spreading low-volume spam/farming PRs or issues across several gated repos never trips any single repo's own cap, even though their fleet-wide open-item count is high.
  • Adds an optional GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP env var — install-scoped, not a per-repo .gittensory.yml/repository_settings field, mirroring ADMIN_GITHUB_LOGINS's own bare-env-var shape (no new DB table/migration needed). Off by default.
  • When set, sums an author's open PRs + open issues across every repo this install/instance tracks (same D1 database, no cross-instance networking, no new caching infra) via a new countOpenItemsByAuthorAcrossInstall query, checked only when the existing per-repo cap didn't already match (no redundant query once a close is already decided).
  • Reuses the exact same contributor_cap closeKind/close-message shape the per-repo cap already uses, and the same autoCloseExemptLogins exemption list the review-nag cooldown already uses.

Closes #2562.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint (via npm run test:ci)
  • npm run db:migrations:check — no new migration (env-var config, not a DB-backed setting)
  • npm run typecheck
  • npm run test:coverage locally — tests covering: an actor over the install-wide cap but under every individual repo's cap is caught (PR and issue paths); off-by-default (env var unset); autoCloseExemptLogins exempts the author on both the PR and issue paths; a below-generous-cap actor is not closed; the global check is skipped once the per-repo check already matched (asserted via which cap number appears in the close comment); a blank/non-numeric/non-positive env var all behave as off; an author-less (ghost) PR is excluded, not crashed on; a direct unit test on countOpenItemsByAuthorAcrossInstall (cross-repo sum, closed/other-author exclusion, case-insensitivity).
  • npm run test:workers (via npm run test:ci)
  • npm run build:mcp (via npm run test:ci)
  • npm run test:mcp-pack (via npm run test:ci)
  • npm run ui:openapi:check — no schema change (install-wide, not a RepositorySettings field, so intentionally not surfaced in the OpenAPI schema, mirroring the existing global-blacklist singleton's own DB-only, no-route precedent)
  • npm run ui:lint (via npm run test:ci)
  • npm run ui:typecheck (via npm run test:ci)
  • npm run ui:build (via npm run test:ci)
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

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. — N/A, no auth/session/CORS surface touched.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — N/A, no per-repo/API-visible setting added (install-wide env var only).
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. — N/A, no UI change.
  • Visible UI changes include a UI Evidence section below with screenshots. — N/A, no UI change.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs. — N/A, an install-level env var for self-host operators, not a .gittensory.yml-documented setting.

Notes

  • Fifth of a batch of independent maintainer-only roadmap items being worked one PR at a time.

@dosubot dosubot Bot added the size:L label Jul 3, 2026
@loopover-orb

loopover-orb Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

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

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-03 05:23:52 UTC

6 files · 1 AI reviewer · 1 blocker · readiness 100/100 · CI green · dirty

⏸️ Suggested Action - Manual Review

  • AI reviewers agree on a likely critical defect: src/db/repositories.ts:3138 and src/db/repositories.ts:3144 cap PR and issue candidates at 2000 rows each while src/queue/processors.ts:3928 accepts any positive GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP, so GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP=2500 with 2501 open PRs by one author can return at most 2001 counted items and fail to close an over-cap item
  • page the query or reject unsupported cap values. — Resolve the flagged defect, or override if the AI reviewers are mistaken, then re-run the gate.

Review summary
The change adds an env-gated install-wide open PR+issue cap, wires it into both PR and issue maintenance paths, and correctly keeps the new setting out of per-repo config/schema. The live-verification approach is directionally right, but the repository query uses fixed row caps that make the new cap silently fail for reachable configurations. Until that counting path is threshold-safe, the feature does not enforce the contract it exposes.

Blockers

  • src/db/repositories.ts:3138 and src/db/repositories.ts:3144 cap PR and issue candidates at 2000 rows each while src/queue/processors.ts:3928 accepts any positive GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP, so GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP=2500 with 2501 open PRs by one author can return at most 2001 counted items and fail to close an over-cap item; page the query or reject unsupported cap values.
Nits — 6 non-blocking
  • nit: src/queue/processors.ts:3928 floors fractional env values like "2.9" to 2, which is surprising for an operator-facing cap; reject non-integers or document the coercion.
  • nit: src/queue/processors.ts:3995 live-checks every cached row even after the global cap is already exceeded, so pass the configured cap through and stop after cap + 1 confirmed open items.
  • Add a regression test in test/unit/queue.test.ts or test/unit/data-spine.test.ts for GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP above 2000 with all matching items in one table.
  • Change listOpenItemsByAuthorAcrossInstall in src/db/repositories.ts to fetch enough rows for the configured threshold, or make parseGlobalContributorOpenItemCapEnv reject caps the query cannot prove.
  • Consider returning a bounded threshold result from verifiedGlobalOpenItemCount instead of a full count when the only caller needs to know whether count > cap.
  • 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.

Concerns raised — review before merging

  • src/db/repositories.ts:3138 and src/db/repositories.ts:3144 cap PR and issue candidates at 2000 rows each while src/queue/processors.ts:3928 accepts any positive GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP, so GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP=2500 with 2501 open PRs by one author can return at most 2001 counted items and fail to close an over-cap item; page the query or reject unsupported cap values.
Signal Result Evidence
Code review ❌ 1 blocker 1 reviewer
Linked issue ✅ Linked #2562
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 (size label size:L; 1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 61 registered-repo PR(s), 52 merged, 500 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 61 PR(s), 500 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 registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 61 PR(s), 500 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • No action.
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 gittensor gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. labels Jul 3, 2026
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.11%. Comparing base (cfbdc9f) to head (0506954).
⚠️ Report is 10 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2649   +/-   ##
=======================================
  Coverage   96.10%   96.11%           
=======================================
  Files         237      237           
  Lines       26540    26606   +66     
  Branches     9625     9645   +20     
=======================================
+ Hits        25507    25573   +66     
  Misses        424      424           
  Partials      609      609           
Files with missing lines Coverage Δ
src/db/repositories.ts 96.63% <100.00%> (+0.04%) ⬆️
src/queue/processors.ts 92.73% <100.00%> (+0.15%) ⬆️
src/settings/agent-actions.ts 94.32% <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.

@JSONbored
JSONbored force-pushed the feat/global-contributor-open-item-cap branch from cb18e14 to bb8ff58 Compare July 3, 2026 00:35
Comment thread src/queue/processors.ts Outdated
@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jul 3, 2026
@JSONbored JSONbored self-assigned this Jul 3, 2026
JSONbored added 5 commits July 2, 2026 19:29
…ross repos

contributorOpenPrCap/contributorOpenIssueCap only see one repo, so an
actor spreading low-volume spam across several repos on the same
self-host install never trips any single repo's own cap. Adds an
optional GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP env var (install-scoped, not
per-repo, mirroring ADMIN_GITHUB_LOGINS) that sums an author's open
PRs + open issues across every repo this install gates, checked only
when the per-repo cap didn't already match. Reuses the existing
contributor_cap closeKind and autoCloseExemptLogins list. Off by
default.

Closes #2562.
…sing

The global aggregate trusted the stored DB cache directly, so a stale
open row from another repo (closed on GitHub after this instance's
cache went stale) could inflate the count and trigger a wrongful
irreversible close -- the existing per-repo issue cap already avoids
this exact failure mode via sibling live-verification. Adds
listOpenItemsByAuthorAcrossInstall (rows, not just a count) and
verifiedGlobalOpenItemCount, which live-confirms every OTHER
contributing item before trusting it toward the cap. Also fixes the
close/audit message mislabeling a combined PR+issue total as a single
kind by widening itemKind to "pull requests and issues".

Addresses gate review findings on #2562.
A global-cap close said "this repository's configured limit" even
though the count spans every repo the install gates -- misleading
since the limit isn't scoped to the one repo being evaluated.

Addresses a gate review finding on #2562.
listOpenItemsByAuthorAcrossInstall queried pullRequests/issues with no
installationId scoping at all, despite its own doc comment asserting
every row already belonged to a repo the calling install gates. Since
those tables key by repoFullName (a plain string, not an FK), a
contributor with open items on a DIFFERENT installation's repo could
leak into this installation's cap count.

Scope the query through repositories.installationId first (matching
the existing markRepositoriesRemovedFromInstallation precedent), then
inArray(...) against the resulting repoFullNames -- this codebase has
no Drizzle joins to lean on instead.
…allback

codecov/patch flagged one uncovered branch: verifiedGlobalOpenItemCount's
own installation-token mint failing and falling back to
GITHUB_PUBLIC_TOKEN for the cross-repo live-verification GET calls
(src/queue/processors.ts:3958). Mirrors the existing coverage for the
same fallback pattern on the per-repo issue-cap path, but exercises the
GLOBAL check's own token mint specifically.
@JSONbored
JSONbored force-pushed the feat/global-contributor-open-item-cap branch from 48a1723 to 82ef382 Compare July 3, 2026 02:31
JSONbored added 2 commits July 2, 2026 19:42
…test

GITHUB_PUBLIC_TOKEN: "public-fallback-token" (22 chars) matched the
gate's generic-secret-assignment heuristic. Switch to the file's
existing "public-token" convention (12 chars, already used by every
other GITHUB_PUBLIC_TOKEN fixture here), which is short enough to fall
under the pattern's 16-char threshold.
verifiedGlobalOpenItemCount ran an unbounded Promise.all over every
open row across the install (up to 4000, since the two source queries
each cap at 2000) -- a single contributor webhook could fire that many
concurrent GitHub API calls and exhaust the installation's rate limit
for every other repo it gates.

Cap the live-verification batch to 10 concurrent requests via a small
worker-pool helper, mirroring the same pattern already used for GitHub
API fan-out in src/github/backfill.ts (duplicated locally rather than
shared, matching that file's own precedent of not centralizing it).

Addresses a security-scan finding on #2562.
@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jul 3, 2026
…, not silent

listRepoFullNamesForInstallation and listOpenItemsByAuthorAcrossInstall used
fixed LIMITs (5000 repos, 2000 PRs, 2000 issues) that silently dropped rows
once hit -- violating the feature's own contract that every repo in the
install is counted, and letting an install or author above those limits
undercount toward (and bypass) GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP with no
signal anything was truncated.

Raises both limits to 20,000 (far beyond any realistic install/author scale)
and, on the rare install where a limit is still hit, records an audit event
(agent.global_open_item_cap.repo_list_truncated /
.author_items_truncated) instead of returning a truncated result with no
trace. The live-verification fan-out this feeds is already bounded to a
fixed worker pool (a prior commit on this branch).
@JSONbored

Copy link
Copy Markdown
Owner Author

Superseded — issue #2562 was independently implemented and merged via #2678 while this PR was in flight (both were built in parallel during the same campaign). Closing this one rather than duplicating already-shipped work.

The gate's own review on this PR caught a real cross-installation scoping bug in my implementation before I noticed #2678 had landed. That same bug exists in #2678's shipped version too (it counts an author's open items with no installation scoping at all) — filed and fixed directly as #2687.

@JSONbored JSONbored closed this Jul 3, 2026
@JSONbored
JSONbored deleted the feat/global-contributor-open-item-cap branch July 4, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Development

Successfully merging this pull request may close these issues.

feat(agent-actions): add an install-wide contributor open-item cap across repos

1 participant