Summary
Shortly, gittensor's transferred-issue anti-gaming gate is dead on the mirror path.
das-github-mirror declares issues.is_transferred BOOLEAN NOT NULL DEFAULT FALSE (packages/db/03_issues.sql:18) and exposes the column on the public miner API (packages/das/src/api/miners/miners.service.ts:83,149 — both getIssues and the inlined linked-issue rows on getPullRequests). No code path in das-github-mirror ever writes is_transferred = true:
webhook/handlers/issue.handler.ts does not branch on payload.action === "transferred". It upserts the same set of fields for every action; isTransferred is omitted from the upsert payload, so the DB default (false) sticks.
webhook/github-fetcher.service.ts:869-892 backfill issue query only requests timelineItems(itemTypes: [LABELED_EVENT, UNLABELED_EVENT], first: 30) — TRANSFERRED_EVENT is not in the list.
- Grep
isTransferred|is_transferred across packages/das/src shows zero write sites.
On the gittensor side, the mirror path treats this field as the first anti-gaming gate in mirror_scan.py:_classify_issue:
if issue.is_transferred:
bt.logging.debug(f' issue #{issue.issue_number} ({issue.repo_full_name}): ignore (transferred)')
return 'ignore'
Because the mirror always returns is_transferred=false, this branch is unreachable in production. The mirror-path docstring (mirror_scan.py:12-19) explicitly lists not issue.is_transferred among the gates, but the gate never fires.
Why this is a regression and not a fresh oversight
The legacy (pre-mirror) scan path used to detect transfers and had this exact class of bug — fixed by issues #404 and #405 with PRs #415 / #420 / #429. The mirror migration shipped in PR #796 routed OSS scoring + issue discovery through the mirror service for mirror_enabled repos. The mirror integration carried over the field shape (is_transferred returned in API responses, parsed into MirrorIssue.is_transferred, gated in _classify_issue) but not the producer — nothing on the mirror side actually populates it.
Net result: the anti-gaming protection added by #420/#429 silently regressed for every repo on the mirror path.
Reproduction
- Pick a mirror-enabled tracked repo. Open an issue, then transfer it to a different repo using GitHub's UI.
- GitHub fires
issues webhook with action="transferred". The mirror's IssueHandler.handle runs and upserts the row — is_transferred is never touched.
- Confirm directly:
SELECT issue_number, state, is_transferred
FROM issues
WHERE repo_full_name = '<owner>/<repo>'
AND issue_number = <n>;
is_transferred is false.
GET /api/v1/miners/<authorId>/issues returns the row with "is_transferred": false.
- In
mirror_scan._classify_issue the issue does not short-circuit at line 431; it falls through to normal 'solved' / 'not-solved-closed' classification.
Expected behavior
Mirror must populate is_transferred=true for any issue that has been transferred. Two producer paths are needed for parity with the rest of the schema:
-
Live webhook (issue.handler.ts): when payload.action === "transferred", set data.isTransferred = true in the upsert. (Optional: also persist the destination from payload.changes.new_repository / payload.changes.new_issue for observability — not required to fix the gate.)
-
Backfill (github-fetcher.service.ts:869): add TRANSFERRED_EVENT to timelineItems(itemTypes: [...], first: 30) and, when seen, set isTransferred=true on the upsert. Without this, transfers that happened before App install or before the most-recent backfill window stay invisible.
Impact
Notes
- Tests covering the gate exist (
tests/validator/issue_discovery/test_mirror_scan.py:184 test_transferred_ignored, tests/validator/oss_contributions/mirror/test_scoring.py:549 test_transferred_blocks) — they pass because they construct is_transferred=True directly. They don't exercise the producer side, which is where the real bug lives.
- This issue is purely about the producer (mirror). The gittensor consumer is already correct.
Summary
Shortly, gittensor's transferred-issue anti-gaming gate is dead on the mirror path.
das-github-mirrordeclaresissues.is_transferred BOOLEAN NOT NULL DEFAULT FALSE(packages/db/03_issues.sql:18) and exposes the column on the public miner API (packages/das/src/api/miners/miners.service.ts:83,149— bothgetIssuesand the inlined linked-issue rows ongetPullRequests). No code path in das-github-mirror ever writesis_transferred = true:webhook/handlers/issue.handler.tsdoes not branch onpayload.action === "transferred". It upserts the same set of fields for every action;isTransferredis omitted from the upsert payload, so the DB default (false) sticks.webhook/github-fetcher.service.ts:869-892backfill issue query only requeststimelineItems(itemTypes: [LABELED_EVENT, UNLABELED_EVENT], first: 30)—TRANSFERRED_EVENTis not in the list.isTransferred|is_transferredacrosspackages/das/srcshows zero write sites.On the gittensor side, the mirror path treats this field as the first anti-gaming gate in
mirror_scan.py:_classify_issue:Because the mirror always returns
is_transferred=false, this branch is unreachable in production. The mirror-path docstring (mirror_scan.py:12-19) explicitly listsnot issue.is_transferredamong the gates, but the gate never fires.Why this is a regression and not a fresh oversight
The legacy (pre-mirror) scan path used to detect transfers and had this exact class of bug — fixed by issues #404 and #405 with PRs #415 / #420 / #429. The mirror migration shipped in PR #796 routed OSS scoring + issue discovery through the mirror service for
mirror_enabledrepos. The mirror integration carried over the field shape (is_transferredreturned in API responses, parsed intoMirrorIssue.is_transferred, gated in_classify_issue) but not the producer — nothing on the mirror side actually populates it.Net result: the anti-gaming protection added by #420/#429 silently regressed for every repo on the mirror path.
Reproduction
issueswebhook withaction="transferred". The mirror'sIssueHandler.handleruns and upserts the row —is_transferredis never touched.is_transferredisfalse.GET /api/v1/miners/<authorId>/issuesreturns the row with"is_transferred": false.mirror_scan._classify_issuethe issue does not short-circuit at line 431; it falls through to normal'solved'/'not-solved-closed'classification.Expected behavior
Mirror must populate
is_transferred=truefor any issue that has been transferred. Two producer paths are needed for parity with the rest of the schema:Live webhook (
issue.handler.ts): whenpayload.action === "transferred", setdata.isTransferred = truein the upsert. (Optional: also persist the destination frompayload.changes.new_repository/payload.changes.new_issuefor observability — not required to fix the gate.)Backfill (
github-fetcher.service.ts:869): addTRANSFERRED_EVENTtotimelineItems(itemTypes: [...], first: 30)and, when seen, setisTransferred=trueon the upsert. Without this, transfers that happened before App install or before the most-recent backfill window stay invisible.Impact
mirror_scan.py:18(not issue.is_transferred) is dead. Transferred issues classify as'solved'or'not-solved-closed'and feedsolved_count/valid_solved_count/closed_count, distorting issue-discovery credibility on every mirror-enabled repo where transfers occur.Notes
tests/validator/issue_discovery/test_mirror_scan.py:184test_transferred_ignored,tests/validator/oss_contributions/mirror/test_scoring.py:549test_transferred_blocks) — they pass because they constructis_transferred=Truedirectly. They don't exercise the producer side, which is where the real bug lives.