Skip to content

feat(github-app): reviewer-routing auto_request action - #833

Closed
YB0y wants to merge 1 commit into
JSONbored:mainfrom
YB0y:feat/reviewer-routing-auto-request-830
Closed

feat(github-app): reviewer-routing auto_request action#833
YB0y wants to merge 1 commit into
JSONbored:mainfrom
YB0y:feat/reviewer-routing-auto-request-830

Conversation

@YB0y

@YB0y YB0y commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the opt-in reviewer-routing feature end-to-end, closing #830 (follow-up to the deferred auto_request action from #540).
Adds a new reviewerRoutingMode setting with three modes:

Mode Behavior
off (default) Feature disabled — no change to existing behaviour
advisory Surface the top CODEOWNERS reviewer suggestion in the PR panel only (no GitHub API side-effects)
auto_request Surface suggestion and call GitHub's request-reviewers API for the top-ranked user — outward-facing, strict safety guards apply

The auto_request mode includes three hard safety invariants from the issue spec:

  • Newcomer guard — never fires for a first-time external contributor (0 merged PRs in this repo), reusing the authorHistory detection already threaded for feat(github-app): first-time-contributor-aware gating #552
  • Idempotency — checks currently-requested reviewers before calling the API; never re-requests on repeated webhooks
  • Team skip — team entries (@org/team) are excluded at CODEOWNERS parse time; only individual user logins reach the API

Related Issue

Change Type

  • New feature
  • Database migration (additive, non-breaking)
  • New signal / logic module
  • GitHub API integration
  • Config-as-code (.gittensory.yml settings: block)
  • OpenAPI schema update
  • Unit tests added

Real Behaviour Proof

CODEOWNERS parsing (unit-testable, no network)

// Given
const codeowners = `
* @globalowner
*.ts @tsowner        // later rule overrides for .ts files
/src/ @srcowner      // anchored dir rule
`;

buildReviewerRouting(["src/index.ts", "README.md"], codeowners)
// → suggestions: [{ login: "tsowner", fileCount: 1 }, { login: "srcowner", fileCount: 1 }, { login: "globalowner", fileCount: 1 }]
// "tsowner" wins src/index.ts (last matching rule), "globalowner" wins README.md

Newcomer guard (verified by test)

// PR author has 0 merged PRs → authorMergedPrCount = 0
// auto_request block: isNewcomer = true → skipped entirely
// No GitHub API call is made

Idempotency guard (verified by test)

const alreadyRequested = new Set(["alice"]); // already has a pending review request
const candidate = suggestions.find(s => s.login !== authorLogin && !alreadyRequested.has(s.login));
// → skips "alice", picks "bob" (or undefined if no other reviewer)

Migration — additive only

ALTER TABLE repository_settings ADD COLUMN reviewer_routing_mode TEXT NOT NULL DEFAULT 'off';
-- Existing rows get 'off' automatically. No data loss. No backfill needed.

Checklist

  • Migration is additive (ALTER TABLE … ADD COLUMN … DEFAULT 'off') — zero downtime, safe rollback
  • reviewerRoutingMode defaults to "off" everywhere (DB default, code default, getRepositorySettings missing-row path) — existing repos are unaffected
  • auto_request block is best-effort: wrapped in try/catch, failures are audited via recordAuditEvent and never abort the main surface publish
  • Newcomer guard reuses authorHistory.mergedPrCount already computed for feat(github-app): first-time-contributor-aware gating #552 — no extra DB query
  • Idempotency: getRequestedReviewers is called before every requestPullRequestReviewers
  • Team entries (@org/team) excluded at CODEOWNERS parse time, never reach the GitHub API reviewers array
  • reviewerRoutingMode is surfaced in .gittensory.yml settings: block via parseSettingsOverride
  • OpenAPI schema updated for both RepositorySettings and RepoSettingsPreviewSchema
  • Optional field on RepositorySettings (matches badgeEnabled precedent) — zero existing test fixture changes needed
  • 15 unit tests cover: parsing, glob semantics, last-rule-wins, team skip, multi-owner ranking, newcomer/idempotency invariants

@YB0y
YB0y requested a review from JSONbored as a code owner June 17, 2026 16:13
@dosubot dosubot Bot added the size:L label Jun 17, 2026
@ghost

ghost commented Jun 17, 2026

Copy link
Copy Markdown

Note

Gittensory Gate skipped

PR closed before full evaluation. No late first comment was created.

Signal Result Evidence Action
Gate result ⚠️ Skipped #833 is no longer open. No action.

💰 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.

@ghost ghost added the gittensory:reviewed label Jun 17, 2026
@ghost

This comment has been minimized.

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.22222% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.71%. Comparing base (5d9d73a) to head (2122855).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/signals/reviewer-routing.ts 95.74% 0 Missing and 2 partials ⚠️
src/queue/processors.ts 94.73% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main     #833    +/-   ##
========================================
  Coverage   96.71%   96.71%            
========================================
  Files         108      110     +2     
  Lines       14641    14749   +108     
  Branches     5298     5333    +35     
========================================
+ Hits        14160    14265   +105     
- Misses        102      103     +1     
- Partials      379      381     +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

.map((seg) =>
seg
.split("*")
.map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ReDoS vulnerability in CODEOWNERS glob-to-regex conversion

CODEOWNERS glob-to-regex conversion does not escape ?, enabling ReDoS via malicious patterns.

Add ? to the escaped regex metacharacters in matchesCodeownersPattern or use a dedicated glob-to-regex library.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name="src/signals/reviewer-routing.ts">
<violation number="1" location="src/signals/reviewer-routing.ts:73">
<priority>P2</priority>
<title>ReDoS vulnerability in CODEOWNERS glob-to-regex conversion</title>
<evidence>The matchesCodeownersPattern function converts CODEOWNERS glob patterns into regular expressions by escaping some regex metacharacters, but the ? character is not escaped. A repository owner or attacker who can modify a target repository&apos;s CODEOWNERS file could inject patterns containing many ? characters. These become regex quantifiers (?) that can trigger catastrophic backtracking when matched against file paths, causing CPU exhaustion and Denial of Service in the gittensory worker processing the pull request.</evidence>
<recommendation>Escape the ? character alongside other regex metacharacters in the replace call within matchesCodeownersPattern. Update the regex from /[.+^${}()|[\]\]/g to /[.+^${}()|[\]\?]/g so that ? is treated as a literal character in CODEOWNERS patterns, eliminating the ReDoS vector.</recommendation>
</violation>
</file>

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 17, 2026
JSONbored added a commit that referenced this pull request Jun 17, 2026
The first Wave-2 / Phase-0 piece: a graduated autonomy dial the whole
agent layer reads before acting. Deny-by-default.

- AutonomyLevel (observe -> suggest -> propose -> auto_with_approval ->
  auto) + AgentActionClass (review/request_changes/approve/merge/close/
  label) + AutonomyPolicy (per-action-class map) types.
- src/settings/autonomy.ts: resolveAutonomy(autonomy, actionClass) — THE
  single gate the action layer (#778) consults; returns 'observe' for any
  unset/malformed class (deny-by-default). Plus isActingAutonomyLevel,
  autonomyRequiresApproval, normalizeAutonomyPolicy. Pure + 100% covered.
- Persisted on repository_settings as a JSON map (migration 0042,
  default '{}' = deny-by-default), mirroring commandAuthorization; parsed
  + resolved through the existing yml > DB > defaults resolver. Resolvable
  from .gittensory.yml via the settings: block (a malformed block never
  blanks the DB-configured policy). Surfaced in the GET /settings OpenAPI
  shape.

The richer autoMaintain config block (merge/close policy, requireApprovals)
+ dashboard write are #774; the action layer that consults resolveAutonomy
is #778.

NOTE: migration 0042 is also used by the open #833 (reviewer-routing) —
whichever merges second rebases + renumbers.
JSONbored added a commit that referenced this pull request Jun 17, 2026
Builds on #773: the per-repo auto-maintain policy + a dashboard surface.

- AutoMaintainPolicy { requireApprovals, mergeMethod } + normalizeAuto-
  MaintainPolicy (conservative defaults squash/1; requireApprovals clamped
  to [0,10]; invalid merge method -> squash). Pure + 100% covered.
- Persisted on repository_settings as JSON (migration 0043, default '{}'
  -> defaults), mirroring autonomy/commandAuthorization.
- Config-as-code: .gittensory.yml settings.autoMaintain (+ settings.
  autonomy from #773) resolved through the existing yml > DB > defaults
  resolver; a non-mapping block is ignored, never blanking the DB policy.
- Dashboard: a new 'Auto-maintain (agent layer)' section in the
  maintainer settings editor (#130) — per-action autonomy selectors for
  all six action classes + approvals-before-auto-merge + merge method.
- Maintainer PUT /settings now accepts autonomy + autoMaintain (bounded:
  requireApprovals 0..10; unknown autonomy action classes dropped by the
  DB normalizer; out-of-range approvals rejected at the boundary).

NOTE: migration 0043 follows #773's 0042 — both ahead of the open #833;
whichever merges into a 0042/0043 collision rebases + renumbers.

The action layer that consults this (resolveAutonomy + the policy) is #778.
JSONbored added a commit that referenced this pull request Jun 17, 2026
Phase-0 safety controls the action layer (#778) must consult before any
action — the second gate alongside resolveAutonomy.

- src/settings/agent-execution.ts (pure, 100% covered):
  - resolveAgentActionMode -> paused | dry_run | live. Safest wins: a
    global OR per-repo pause halts everything; else dry-run logs without
    mutating; else live. agentActionModeExecutes is true only for live.
  - isGlobalAgentPause: the operator emergency brake via env
    AGENT_ACTIONS_PAUSED (truthy-string idiom).
  - buildAgentActionAudit: a structured who/what/why/outcome/mode audit
    record (eventType agent.action.<class>) so live actions AND dry-run
    shadows record on one shape — extends the existing audit-event infra.
- Per-repo settings agentPaused + agentDryRun (migration 0044, default
  false), wired like badgeEnabled across types/schema/repositories/openapi
  /settings-preview yml block, plus the maintainer PUT /settings.
- Dashboard: kill-switch + dry-run toggles in the auto-maintain section.

Deferred to #778 (needs real actions): the action layer honoring the mode,
the dry-run feed into the recommendation-outcome loop, and revert-where-
possible.

NOTE: migration 0044 follows #773/#774's 0042/0043, ahead of the open #833.

@JSONbored JSONbored left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good:

  • Feature is directionally useful for maintainer automation.
  • CODEOWNERS parsing and reviewer request helpers have meaningful tests.
  • Settings/API surface is mostly threaded through.

Bad:

  • Merge state is DIRTY with real conflicts against current main.
  • Adds migrations/0042_reviewer_routing_mode.sql while main already has 0042_agent_autonomy.sql.
  • getRequestedReviewers returns empty set on API failure even though caller comment says this should skip conservatively.
  • CODEOWNERS fetch uses public raw HEAD, not installation-auth/base/ref-pinned content.
  • Codecov fails and Superagent flagged one security concern.

Change requests:

  • Rebase/renumber migration and regenerate conflicted OpenAPI artifacts.
  • Make requested-reviewer lookup failure skip auto-request, not proceed as “none requested.”
  • Fetch CODEOWNERS from the correct repo/ref using installation auth where possible.
  • Resolve Codecov/security flag before merge.

@YB0y

YB0y commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@JSONbored Could you plz review my PR? Thanks!

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 18, 2026
@JSONbored
JSONbored self-requested a review June 18, 2026 23:48

@JSONbored JSONbored left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #833 — feat(github-app): reviewer-routing auto_request action [YB0y]
Action: CHANGES REQUESTED
Issue #830: PARTIALLY CI: pass

Good:

  • Clean CODEOWNERS parser/ranker + installation-auth reviewer-request helpers; strict opt-in +
    newcomer guard; migration renumbered 0042→0046 (the earlier collision is resolved). Meaningful tests.

Flagged:

  1. Unresolved ReDoS review thread (reviewer-routing.ts ~73): glob→regex does not escape ? — still
    absent from the escaped metacharacter set; attacker-controlled CODEOWNERS can trigger it.
  2. getRequestedReviewers returns an empty Set on API failure, so the caller proceeds as "none
    requested" (can re-request) — contradicts its own comment; should fail-closed/skip.
  3. CODEOWNERS fetched from public raw HEAD, not installation-auth/ref-pinned content.
  4. Behind main; rebase.

@YB0y
YB0y force-pushed the feat/reviewer-routing-auto-request-830 branch from bdd649c to 35b4a82 Compare June 19, 2026 02:57
@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 19, 2026
…SONbored#830)

Add opt-in reviewer-routing feature with three modes: off (default),
advisory (surface CODEOWNERS suggestions in panel only), and auto_request
(also call GitHub request-reviewers API for the top-ranked suggestion).

- migrations/0046: add reviewer_routing_mode column (default 'off')
- src/types.ts: ReviewerRoutingMode union + optional field on RepositorySettings
- src/db/schema.ts + repositories.ts: parseReviewerRoutingMode, full read/write wiring
- src/signals/reviewer-routing.ts: CODEOWNERS parser + buildReviewerRouting ranker
- src/github/reviewer-request.ts: fetchCodeownersFile, getRequestedReviewers, requestPullRequestReviewers
- src/signals/focus-manifest.ts: expose reviewerRoutingMode in settings: block
- src/signals/settings-preview.ts + src/openapi/schemas.ts: include in preview/API surface
- src/queue/processors.ts: auto_request fires after label step; newcomer guard
  (0 merged PRs → skip), already-requested reviewers skipped (idempotent),
  teams skipped at CODEOWNERS parse time, best-effort (failures audited, never abort)
- test/unit/reviewer-routing.test.ts: parseCodeowners + buildReviewerRouting unit tests
  covering newcomer-guard invariants, idempotency, glob semantics, ranking

Closes JSONbored#830
@YB0y
YB0y force-pushed the feat/reviewer-routing-auto-request-830 branch from 35b4a82 to 2122855 Compare June 19, 2026 04:02
@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 19, 2026
@ghost

ghost commented Jun 20, 2026

Copy link
Copy Markdown

reviewbot · advisory review

Merge conflict — rebase on the latest base and push.

Route Viewport Before After
/ desktop before desktop after desktop
mobile before mobile after mobile

@JSONbored JSONbored closed this Jun 20, 2026
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. pr:flagged PR flagged for review by security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(github-app): reviewer-routing auto_request action (#540 follow-up)

2 participants