From f4dd64517410ce2a6a17de38be07243d1c531539 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:05:26 -0700 Subject: [PATCH] fix(miner): narrow generateReplayScoringKey's rejection type generateReplayScoringKey never lints or scrubs frozen context, so it can only ever reject on selection -- reusing ReplayTaskRejected advertised an unscrubbable_forward_reference branch this function can never produce. Adds a regression test pinning the documented eligibility asymmetry against generateReplayTask for the same candidate. --- .../lib/replay-task-generation.d.ts | 11 +++++++++- .../unit/miner-replay-task-generation.test.ts | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/gittensory-miner/lib/replay-task-generation.d.ts b/packages/gittensory-miner/lib/replay-task-generation.d.ts index 02cb3941b4..f42d381fef 100644 --- a/packages/gittensory-miner/lib/replay-task-generation.d.ts +++ b/packages/gittensory-miner/lib/replay-task-generation.d.ts @@ -80,6 +80,15 @@ export type ReplayScoringKey = { groundTruth: unknown; }; +// generateReplayScoringKey never lints/scrubs frozen context (it doesn't touch context text at all), so +// unlike ReplayTaskRejected it can only ever reject on selection -- narrower than reusing ReplayTaskRejected, +// which would advertise an "unscrubbable_forward_reference" branch this function can never actually produce. +export type ReplayScoringKeyRejected = { + eligible: false; + rejected: "selection"; + reasons: string[]; +}; + export function detectForwardReferences( text: unknown, context: ForwardRefContext | null | undefined, @@ -114,4 +123,4 @@ export function generateReplayTask( export function generateReplayScoringKey( candidate: FreezePointCandidate | null | undefined, options: ReplayTaskOptions | null | undefined, -): ReplayScoringKey | ReplayTaskRejected; +): ReplayScoringKey | ReplayScoringKeyRejected; diff --git a/test/unit/miner-replay-task-generation.test.ts b/test/unit/miner-replay-task-generation.test.ts index 4d2a8c72d6..115b4ace76 100644 --- a/test/unit/miner-replay-task-generation.test.ts +++ b/test/unit/miner-replay-task-generation.test.ts @@ -222,5 +222,27 @@ describe("gittensory-miner leakage-safe replay task generation (#3011)", () => { reasons: ["insufficient_prior_history", "insufficient_revealed_history"], }); }); + + // REGRESSION: pins the intentional eligibility asymmetry documented on generateReplayScoringKey itself -- + // it never lints/scrubs frozen context, so a candidate generateReplayTask rejects for an unscrubbable + // forward reference still yields a scoring key here. A caller must not assume the two are a matched pair + // without checking generateReplayTask's own result (see the same candidate's rejection above). + it("still returns a scoring key for a candidate whose frozen context generateReplayTask rejects as unscrubbable", () => { + const candidate = { ...eligible, frozenContextTexts: ["the tally hit 250 last month"] }; + + const replayTask = generateReplayTask(candidate, CONTEXT, options); + expect(replayTask).toEqual({ + eligible: false, + rejected: "unscrubbable_forward_reference", + residual: [{ kind: "bare-issue-number", value: 250 }], + }); + + const scoringKey = generateReplayScoringKey(candidate, options); + expect(scoringKey).toEqual({ + eligible: true, + commitCount: 10, + groundTruth: { merged: true, approach: "refactor" }, + }); + }); }); });