Skip to content

fix(miner): discover-cli.js's discovery-index supplement stubs assignees to [], silently bypassing the #7040 repo-owner exclusion filter for index-sourced candidates #7442

Description

@JSONbored

Context

Issue #7040 (closed, milestone Miner Wave 4.5 — AMS Hardening Round 2) added a structural, always-on
safeguard: contribution-profile-filter.ts's isAssignedToRepoOwner excludes any discover candidate
assigned to its own repo's owner login, and the file's own header comment states this "applies to EVERY
candidate unconditionally" (lines 11-14), unlike the three label-based eligibility rules which only engage
when a repo's ContributionProfile has an explicit signal.

That "EVERY candidate" claim is not actually true for one entire candidate source. discover-cli.ts's
supplementWithDiscoveryIndex (lines 169-188) merges in extra candidates from the hosted discovery-index
service (wired by #7168) and explicitly stubs every one of them with assignees: []:

.map((candidate) => ({ ...candidate, assignees: [], labels: [...candidate.labels] }) as RawCandidateIssue);

The comment right above it is honest about why ("assignees is absent from the hosted contract (#7168) so
we annotate []"), but neither #7168 nor #7040 cross-referenced the other, so nobody flagged the actual
consequence: isAssignedToRepoOwner(candidate) reads candidate.assignees ?? [] and loops over it -- an
always-empty array means the function can NEVER detect an owner-assignment for a discovery-index-sourced
candidate, no matter how the real issue is actually assigned on GitHub. The exclusion silently no-ops for
this entire source. Contrast with the label-based rules: those still work correctly for index-sourced
candidates because labels genuinely IS present in the hosted contract (DiscoveryIndexCandidate.labels)
and is copied through unmodified.

This is a real gap between a documented safety invariant ("applies to every candidate unconditionally") and
the actual behavior for one code path, not a hypothetical: discover-cli.ts calls
filterCandidatesByProfiles(fanOut.issues, ...) directly on fanOut.issues (which includes the
discovery-index-supplemented rows) in both the dry-run and real-run code paths (lines 421 and 543).

Requirements

  • filterCandidatesByProfiles/isAssignedToRepoOwner must not silently treat "assignees data was never
    available for this candidate" the same as "confirmed, this candidate has no assignees" -- these are
    different states today and only the filter code conflates them.
  • The actual fix requires the discovery-index contract to be able to carry real assignee data end to end:
    extend DiscoveryIndexCandidate (packages/loopover-engine/src/discovery-index-contract.ts) with an
    optional assignees?: readonly string[] field (mirroring how labels is already typed there), update its
    normalizeDiscoveryIndexRequest/normalizeDiscoveryIndexResponse functions accordingly, and update
    discover-cli.ts's supplementWithDiscoveryIndex to copy the real value through (falling back to []
    only when the served response genuinely omits the field, e.g. talking to an older discovery-index server
    build) instead of unconditionally stubbing it.
  • Whatever the final data-plumbing shape, isAssignedToRepoOwner should not change its own logic — the fix
    is getting real assignee data to it, not weakening or special-casing the exclusion check itself.
  • No behavior change for the non-discovery-index (direct GitHub fan-out) candidate path, which already
    carries real assignees data correctly.

Deliverables

  • DiscoveryIndexCandidate (and its request/response normalize functions in
    discovery-index-contract.ts) gains a real assignees field, following the exact existing precedent
    of the labels field in the same type.
  • discover-cli.ts's supplementWithDiscoveryIndex copies the real assignees through instead of
    stubbing [], with the stub retained only as a genuine fallback for a server response that omits the
    field.
  • A regression test proving a discovery-index-sourced candidate assigned to its own repo's owner is
    correctly excluded by filterCandidatesByProfiles (today it silently is NOT).
  • A regression test confirming the existing behavior is unchanged for a discovery-index server response
    that genuinely omits the field (fail-safe: falls back to [], not to skipping the filter check
    entirely).

Test Coverage Requirements

99%+ Codecov patch coverage (branch-counted) on every changed line/branch in discovery-index-contract.ts
and discover-cli.ts. Must include the "owner-assigned index-sourced candidate is now excluded" regression
test above — this is the actual defect this issue exists to close, so it must be directly, explicitly
covered, not incidentally exercised by an unrelated existing test.

Expected Outcome

A candidate assigned to its own repo's owner is excluded from AMS discovery consistently across BOTH the
direct GitHub fan-out path and the discovery-index-supplemented path, instead of the safeguard only working
for the former.

Links & Resources

A note on scope/uncertainty

The cleanest full fix touches discovery-index-contract.ts, which is also consumed by
packages/discovery-index/src/** — a separately-deployed, maintainer-owned hosted service (per
vitest.config.ts's own comment, its server.ts is only exercised by a Docker build+boot path). A
contributor PR to the miner-side contract type alone can land safely (additive, optional field, backward
compatible), but the discovery-index SERVER itself would also need to start actually populating assignees
in its query response for the fix to have any live effect — that may be a separate maintainer-side deploy
step outside a single contributor PR's reach. This could ship as one contributor-open issue if the
server-side change is equally mechanical and safe to include, or split (contract + client as
contributor-open, server-side population as a fast-follow) — the labels field already proves this exact
plumbing shape end-to-end, so the pattern itself is not in question.

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