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
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.
Context
Issue #7040 (closed, milestone
Miner Wave 4.5 — AMS Hardening Round 2) added a structural, always-onsafeguard:
contribution-profile-filter.ts'sisAssignedToRepoOwnerexcludes any discover candidateassigned 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
ContributionProfilehas an explicit signal.That "EVERY candidate" claim is not actually true for one entire candidate source.
discover-cli.ts'ssupplementWithDiscoveryIndex(lines 169-188) merges in extra candidates from the hosted discovery-indexservice (wired by #7168) and explicitly stubs every one of them with
assignees: []: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)readscandidate.assignees ?? []and loops over it -- analways-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
labelsgenuinely 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.tscallsfilterCandidatesByProfiles(fanOut.issues, ...)directly onfanOut.issues(which includes thediscovery-index-supplemented rows) in both the dry-run and real-run code paths (lines 421 and 543).
Requirements
filterCandidatesByProfiles/isAssignedToRepoOwnermust not silently treat "assignees data was neveravailable for this candidate" the same as "confirmed, this candidate has no assignees" -- these are
different states today and only the filter code conflates them.
extend
DiscoveryIndexCandidate(packages/loopover-engine/src/discovery-index-contract.ts) with anoptional
assignees?: readonly string[]field (mirroring howlabelsis already typed there), update itsnormalizeDiscoveryIndexRequest/normalizeDiscoveryIndexResponsefunctions accordingly, and updatediscover-cli.ts'ssupplementWithDiscoveryIndexto 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.
isAssignedToRepoOwnershould not change its own logic — the fixis getting real assignee data to it, not weakening or special-casing the exclusion check itself.
carries real
assigneesdata correctly.Deliverables
DiscoveryIndexCandidate(and its request/response normalize functions indiscovery-index-contract.ts) gains a realassigneesfield, following the exact existing precedentof the
labelsfield in the same type.discover-cli.ts'ssupplementWithDiscoveryIndexcopies the real assignees through instead ofstubbing
[], with the stub retained only as a genuine fallback for a server response that omits thefield.
correctly excluded by
filterCandidatesByProfiles(today it silently is NOT).that genuinely omits the field (fail-safe: falls back to
[], not to skipping the filter checkentirely).
Test Coverage Requirements
99%+ Codecov patch coverage (branch-counted) on every changed line/branch in
discovery-index-contract.tsand
discover-cli.ts. Must include the "owner-assigned index-sourced candidate is now excluded" regressiontest 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
packages/loopover-miner/lib/discover-cli.ts(supplementWithDiscoveryIndex, lines 169-188)packages/loopover-miner/lib/contribution-profile-filter.ts(isAssignedToRepoOwner, lines 46-55; headercomment, lines 11-14)
packages/loopover-engine/src/discovery-index-contract.ts(DiscoveryIndexCandidate, lines 44-57)assignees: []stub
A note on scope/uncertainty
The cleanest full fix touches
discovery-index-contract.ts, which is also consumed bypackages/discovery-index/src/**— a separately-deployed, maintainer-owned hosted service (pervitest.config.ts's own comment, itsserver.tsis only exercised by a Docker build+boot path). Acontributor 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
assigneesin 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
labelsfield already proves this exactplumbing shape end-to-end, so the pattern itself is not in question.