Skip to content

miner(discover): drop discovery-index candidates whose repo bans AI contributions #9680

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

supplementWithDiscoveryIndex in packages/loopover-miner/lib/discover-cli.ts folds candidates served by the optional hosted discovery index into the local fan-out result:

const supplemented = response.candidates
  .filter((candidate) => !seen.has(dedupeKey(candidate.repoFullName, candidate.issueNumber)))
  .map((candidate) => ({ ...candidate, assignees: [...(candidate.assignees ?? [])], labels: [...candidate.labels] }) as RawCandidateIssue);

Dedupe is the only filter. Meanwhile:

  • RawCandidateIssue.aiPolicyAllowed is declared as the literal type true (packages/loopover-miner/lib/opportunity-fanout.ts:52), so the as RawCandidateIssue cast is laundering a possible false through a type that promises it can never be false.
  • The index client deliberately preserves a false: normalizeDiscoveryIndexCandidate writes aiPolicyAllowed: candidate.aiPolicyAllowed !== false.
  • The local path enforces the ban hard: fetchTargetIssues does if (!verdict.allowed) return []; (packages/loopover-miner/lib/opportunity-fanout.ts:522-523).
  • Nothing downstream re-checks it. opportunity-ranker.ts:77 only copies the field through and never filters on it; portfolio-discovery.ts's normalizeRankedIssue ignores it entirely.

So an index-served candidate flagged "this repo bans automated contributions" is ranked and enqueued into the local portfolio backlog, where the loop will later claim and attempt it. discover-cli.ts's own module contract describes the index as only partially trusted and already hardens against a misbehaving host elsewhere (page-size clamping), which is exactly the posture this filter is missing.

Requirements

  • supplementWithDiscoveryIndex must drop any index-served candidate whose aiPolicyAllowed is false, before the dedupe filter's output is cast to RawCandidateIssue.
  • The check must be candidate.aiPolicyAllowed !== false (so a candidate that omits the field entirely is still kept, matching normalizeDiscoveryIndexCandidate's own default).
  • The dropped count must be surfaced: extend the existing recordDiscoveryTelemetry("discover_query", ...) call in the same function to also carry the number of candidates dropped for this reason, using the same options-object shape that call already passes.
  • No change to the local fan-out path, to the ranker, or to RawCandidateIssue's declared type.

⚠️ Required pattern: mirror fetchTargetIssues's own ban enforcement at packages/loopover-miner/lib/opportunity-fanout.ts:522-523 — the candidate is dropped, not down-ranked and not warned about. It does NOT satisfy this issue to relax RawCandidateIssue.aiPolicyAllowed from true to boolean, to add the filter in opportunity-ranker.ts or portfolio-discovery.ts (the local path already guarantees the invariant before ranking; a second downstream filter would be a competing mechanism), or to add a config flag that makes the drop optional.

Deliverables

  • supplementWithDiscoveryIndex filters out candidates with aiPolicyAllowed === false before building supplemented.
  • The recordDiscoveryTelemetry("discover_query", ...) call in that function reports the number of candidates dropped by the new filter.
  • A new named regression test in test/unit/miner-discover-cli.test.ts injects a queryDiscoveryIndex returning one candidate with aiPolicyAllowed: false and one with true, and asserts only the true one reaches the ranked/enqueued output.
  • A second case asserts a candidate that omits aiPolicyAllowed entirely is still kept.

All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example adding the filter but omitting the "field absent is still kept" case, which proves the filter did not become a fail-closed drop of every index candidate — does not resolve this issue.

Test Coverage Requirements

packages/loopover-miner/lib/**/*.ts IS inside Codecov's coverage.include in vitest.config.ts, so the 99%+ branch-counted codecov/patch gate applies exactly as for src/**. Both arms of the new aiPolicyAllowed !== false predicate need a test (dropped and kept), plus the field-absent arm, plus the telemetry count assertion. The fix needs a named regression test that fails against the current code.

Expected Outcome

A compromised, stale, or simply buggy discovery-index response can no longer put an AI-banned repo's issue into the miner's own portfolio backlog; the local fan-out's if (!verdict.allowed) return [] invariant holds for index-supplemented candidates too.

Links & Resources

packages/loopover-miner/lib/discover-cli.ts:180-201, packages/loopover-miner/lib/opportunity-fanout.ts:52, :515-523, packages/loopover-miner/lib/discovery-index-client.ts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions