Skip to content

Commit e6e6521

Browse files
authored
feat(calibration): reason-code enrichment pass — the flat-confidence era's only discriminator (#8243) (#8258)
Investigation result first: the constant-1.0 decision confidence has NO live writer to fix — those decisions came from the retired legacy content gate (review_targets decisions stopped 2026-06-22); the live path records finding-level confidences that genuinely vary. What the backfill era needs instead is segmentability: pass C copies the ledger's own decision reasonCode onto each phase-1 fired row (DB-only, idempotent, distinct provenance), separating AI-judgment closes (dual_review_declined, 48%) from deterministic ones. Applied to both stores (460/460 each) and immediately decisive: thin_description closes reversed 100% (20/20 — pure friction), checks_failed 47%, dual_review_declined 30%, strict_duplicate 15%. Per-reason reversal rates are exactly the evidence the disposition and drift work (#8211 tracks A/D) consume. Closes #8243
1 parent 8e34212 commit e6e6521

3 files changed

Lines changed: 78 additions & 5 deletions

File tree

scripts/backfill-calibration-corpus-phase2-core.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ export const RETRO_SUCCESSOR_PROVENANCE = "github_successor_scan";
2020
export const RETRO_SAME_PR_MERGED_PROVENANCE = "github_same_pr_merged";
2121
/** Distinct provenance for pass B's re-fetched raw context. */
2222
export const RAW_CONTEXT_REFETCH_PROVENANCE = "github_raw_context_refetch";
23+
/** Provenance for pass C's reason-code enrichment (#8243): the ledger's own decision reasonCode copied
24+
* onto the fired row so AI-judgment closes (dual_review_declined) are segmentable from deterministic
25+
* ones — the backfill era's confidence axis is flat (a constant 1.0 from the retired legacy writer),
26+
* so reason class is the only within-era discriminator the corpus has. */
27+
export const REASON_CODE_ENRICHMENT_PROVENANCE = "review_targets_reason_code";
2328

2429
/** A phase-1 backfilled close decision, hydrated with the GitHub truth the wrapper fetched. */
2530
export type HistoricalCloseSide = {
@@ -131,7 +136,7 @@ export function patchFiredMetadataWithDiff(metadataJson: string, diff: string):
131136
}
132137

133138
export type Phase2Report = {
134-
pass: "successors" | "raw-context";
139+
pass: "successors" | "raw-context" | "reason-codes";
135140
scanned: number;
136141
patched: number;
137142
alreadyPatched: number;
@@ -153,7 +158,7 @@ export type Phase2Report = {
153158
export function renderPhase2Report(report: Phase2Report, mode: "dry-run" | "apply"): string {
154159
const lines = [
155160
`Calibration corpus backfill phase 2 (${mode}) — pass ${report.pass}, provenance ${
156-
report.pass === "successors" ? RETRO_SUCCESSOR_PROVENANCE : RAW_CONTEXT_REFETCH_PROVENANCE
161+
report.pass === "successors" ? RETRO_SUCCESSOR_PROVENANCE : report.pass === "raw-context" ? RAW_CONTEXT_REFETCH_PROVENANCE : REASON_CODE_ENRICHMENT_PROVENANCE
157162
}`,
158163
` scanned: ${report.scanned} patched: ${report.patched} already-patched: ${report.alreadyPatched} no-match/skipped: ${report.noMatch}`,
159164
...(report.pass === "successors"
@@ -167,6 +172,18 @@ export function renderPhase2Report(report: Phase2Report, mode: "dry-run" | "appl
167172
return lines.join("\n");
168173
}
169174

175+
/**
176+
* Patch a phase-1 fired row's metadata with the ledger's own decision reasonCode (#8243). Idempotent
177+
* (already-tagged rows return null) and never guesses (unparseable metadata or a blank code return null).
178+
*/
179+
export function patchFiredMetadataWithReasonCode(metadataJson: string, reasonCode: string): string | null {
180+
const metadata = parseObject(metadataJson);
181+
if (!metadata) return null;
182+
if (typeof metadata.reasonCode === "string") return null;
183+
if (reasonCode.trim() === "") return null;
184+
return JSON.stringify({ ...metadata, reasonCode: reasonCode.trim(), reasonCodeProvenance: REASON_CODE_ENRICHMENT_PROVENANCE });
185+
}
186+
170187
function parseObject(json: string): Record<string, unknown> | null {
171188
try {
172189
const parsed: unknown = JSON.parse(json);

scripts/backfill-calibration-corpus-phase2.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ import {
2424
patchFiredMetadataWithDiff,
2525
patchOverrideMetadataToReversed,
2626
patchOverrideMetadataToSamePrMerged,
27+
patchFiredMetadataWithReasonCode,
2728
renderPhase2Report,
2829
type HistoricalCloseSide,
2930
type Phase2Report,
3031
type SuccessorSide,
3132
} from "./backfill-calibration-corpus-phase2-core.js";
3233
import { BACKFILL_RULE_ID } from "./backfill-calibration-corpus-core.js";
3334

34-
type Pass = "successors" | "raw-context";
35+
type Pass = "successors" | "raw-context" | "reason-codes";
3536
type Args = {
3637
db: string;
3738
remote: boolean;
@@ -61,7 +62,7 @@ function parseArgs(argv: string[]): Args {
6162
}
6263
else if (flag === "--pass") {
6364
const value = argv[++i];
64-
if (value !== "successors" && value !== "raw-context") throw new Error(`--pass must be successors or raw-context, got ${value}`);
65+
if (value !== "successors" && value !== "raw-context" && value !== "reason-codes") throw new Error(`--pass must be successors, raw-context, or reason-codes, got ${value}`);
6566
args.pass = value;
6667
} else if (flag === "--max-requests") args.maxRequests = Number(argv[++i]);
6768
else if (flag === "--state-file") args.stateFile = argv[++i]!;
@@ -461,6 +462,39 @@ async function runRawContextPass(args: Args, budget: RequestBudget, state: Curso
461462
return report;
462463
}
463464

465+
/** Pass C (#8243): copy the ledger's own decision reasonCode onto each phase-1 fired row — DB-only, no
466+
* GitHub. The backfill era's confidence axis is flat (constant 1.0 from the retired legacy writer), so
467+
* reason class (AI-judgment dual_review_declined vs deterministic codes) is the era's only
468+
* within-corpus discriminator. Idempotent via the patcher's already-tagged null. */
469+
async function runReasonCodesPass(args: Args): Promise<Phase2Report> {
470+
const report: Phase2Report = { pass: "reason-codes", scanned: 0, patched: 0, alreadyPatched: 0, noMatch: 0, matchedSameAuthor: 0, matchedSharedIssueOnly: 0, matchedSamePrMerged: 0, requestsUsed: 0, exhaustedBudget: false, resumeFrom: null };
471+
const decisionRows = await executeSql(
472+
args,
473+
`SELECT repo, number, json_extract(decision_json, '$.reasonCode') AS reason FROM review_targets WHERE kind = 'pull_request' AND decision_json IS NOT NULL`,
474+
);
475+
const reasonByTarget = new Map<string, string>();
476+
for (const row of decisionRows) {
477+
if (typeof row.repo === "string" && typeof row.reason === "string" && row.reason !== "") {
478+
reasonByTarget.set(`${row.repo}#${row.number}`, row.reason);
479+
}
480+
}
481+
for (const row of await loadBackfillRows(args, "fired")) {
482+
report.scanned += 1;
483+
const reason = reasonByTarget.get(row.target_key);
484+
if (!reason) {
485+
report.noMatch += 1;
486+
continue;
487+
}
488+
const patched = patchFiredMetadataWithReasonCode(row.metadata_json, reason);
489+
if (patched === null) report.alreadyPatched += 1;
490+
else if (args.apply) {
491+
await applyMetadataUpdate(args, backfillFiredId(row.target_key), patched);
492+
report.patched += 1;
493+
} else report.patched += 1;
494+
}
495+
return report;
496+
}
497+
464498
async function main(): Promise<void> {
465499
const args = parseArgs(process.argv.slice(2));
466500
const pgConnection = resolvePgConnection(args.pgPresent, args.pgValue, process.env.DATABASE_URL);
@@ -473,7 +507,9 @@ async function main(): Promise<void> {
473507
? args.planIn
474508
? await runSuccessorsFromPlan(args)
475509
: await runSuccessorsPass(args, budget, state)
476-
: await runRawContextPass(args, budget, state);
510+
: args.pass === "raw-context"
511+
? await runRawContextPass(args, budget, state)
512+
: await runReasonCodesPass(args);
477513
await pgSession?.close();
478514
writeFileSync(args.stateFile, `${JSON.stringify(state, null, 2)}\n`);
479515
console.log(renderPhase2Report(report, args.apply ? "apply" : "dry-run"));

test/unit/backfill-calibration-corpus-phase2.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import {
88
patchFiredMetadataWithDiff,
99
patchOverrideMetadataToReversed,
1010
patchOverrideMetadataToSamePrMerged,
11+
patchFiredMetadataWithReasonCode,
1112
renderPhase2Report,
1213
RETRO_SUCCESSOR_PROVENANCE,
1314
RETRO_SAME_PR_MERGED_PROVENANCE,
15+
REASON_CODE_ENRICHMENT_PROVENANCE,
1416
RAW_CONTEXT_REFETCH_PROVENANCE,
1517
type HistoricalCloseSide,
1618
type Phase2Report,
@@ -131,6 +133,22 @@ describe("metadata patchers (#8170)", () => {
131133
});
132134
});
133135

136+
describe("patchFiredMetadataWithReasonCode (#8243)", () => {
137+
it("tags the fired row with the ledger's reasonCode + provenance, exactly once, never guessing", () => {
138+
const original = JSON.stringify({ confidence: 1, backfilled: true });
139+
const patched = JSON.parse(patchFiredMetadataWithReasonCode(original, "dual_review_declined")!) as Record<string, unknown>;
140+
expect(patched.reasonCode).toBe("dual_review_declined");
141+
expect(patched.reasonCodeProvenance).toBe(REASON_CODE_ENRICHMENT_PROVENANCE);
142+
expect(patched.backfilled).toBe(true); // phase-1 fields survive
143+
// Idempotent + never-guess arms.
144+
expect(patchFiredMetadataWithReasonCode(JSON.stringify(patched), "checks_failed")).toBeNull();
145+
expect(patchFiredMetadataWithReasonCode(original, " ")).toBeNull();
146+
expect(patchFiredMetadataWithReasonCode("not-json", "checks_failed")).toBeNull();
147+
// Whitespace-trimmed code.
148+
expect((JSON.parse(patchFiredMetadataWithReasonCode(original, " scope_failure ")!) as Record<string, unknown>).reasonCode).toBe("scope_failure");
149+
});
150+
});
151+
134152
describe("ids + report rendering (#8170)", () => {
135153
it("derives the deterministic phase-1 row ids (the only rows the passes may touch)", () => {
136154
expect(backfillOverrideId("acme/widgets#7")).toBe("backfill:ai_consensus_defect:acme/widgets#7:override");
@@ -149,6 +167,8 @@ describe("ids + report rendering (#8170)", () => {
149167
"apply",
150168
);
151169
expect(exhausted).toContain(RAW_CONTEXT_REFETCH_PROVENANCE);
170+
const reasonPass = renderPhase2Report({ ...base, pass: "reason-codes" }, "dry-run");
171+
expect(reasonPass).toContain(REASON_CODE_ENRICHMENT_PROVENANCE);
152172
expect(exhausted).toContain("budget exhausted");
153173
expect(exhausted).toContain("resume from: acme/widgets#7");
154174
});

0 commit comments

Comments
 (0)