Skip to content

Commit 7a8b9b6

Browse files
kai392RealDiligent
andauthored
feat(calibration): pure counterfactual fixture assembler per the replay contract (#8220) (#8247)
Co-authored-by: RealDiligent <brave.challenge007@gmail.com>
1 parent ae119df commit 7a8b9b6

4 files changed

Lines changed: 230 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Counterfactual fixture assembler (#8220, sub-epic phase 2): from a labeled corpus, select and shape
2+
// exactly the cases that are replayable under the #8219 contract — pure selection, no AI calls, no IO.
3+
// Implements the contract's fixture/sampling shapes VERBATIM (counterfactual-contract.ts); every exclusion
4+
// is accounted for in the return shape (the #8139 skipped-case discipline), never silently dropped.
5+
//
6+
// PROVENANCE: the #8207/#8170 backfill patches a re-fetched row's metadata with a `rawContextProvenance`
7+
// tag (RAW_CONTEXT_REFETCH_PROVENANCE in scripts/backfill-calibration-corpus-phase2-core.ts); the live
8+
// #8129/#8130 capture writers never set that key. Presence of the key is therefore the era discriminator —
9+
// any tagged row is backfilled context, an untagged replayable row is live-captured.
10+
//
11+
// SAMPLING: when the eligible set exceeds the contract's budget, membership is decided by the same
12+
// content-hash discipline as splitBacktestCorpus — sha256(`${seed}:${targetKey}`), first 8 hex chars as the
13+
// rank, lowest ranks win — never "the first N", which would bias toward old cases. Selection keeps the
14+
// corpus's own case order (no shuffle); a rank tie (two firings of the SAME target share a hash) breaks
15+
// toward the earlier case, deterministically.
16+
17+
import { createHash } from "node:crypto";
18+
import type { BacktestCase } from "./backtest-corpus.js";
19+
import {
20+
isReplayableCase,
21+
type CounterfactualFixture,
22+
type CounterfactualSamplingContract,
23+
type CounterfactualSkipReason,
24+
} from "./counterfactual-contract.js";
25+
26+
export type CounterfactualFixtureAssembly = {
27+
/** Replayable fixtures in corpus order, at most `contract.maxFixtures` of them. */
28+
fixtures: CounterfactualFixture[];
29+
/** Why every non-fixture case was excluded — `fixtures.length` plus these counts always sums to the
30+
* input corpus size (pinned by an invariant test). */
31+
skipped: Record<CounterfactualSkipReason, number>;
32+
};
33+
34+
function sampleRank(seed: string, targetKey: string): number {
35+
return parseInt(createHash("sha256").update(`${seed}:${targetKey}`).digest("hex").slice(0, 8), 16);
36+
}
37+
38+
function toFixture(backtestCase: BacktestCase): CounterfactualFixture {
39+
// isReplayableCase already guaranteed a non-empty string diff for every case reaching here.
40+
const metadata = backtestCase.metadata!;
41+
return {
42+
fixtureId: backtestCase.targetKey,
43+
label: backtestCase.label,
44+
boundedInputs: { diff: metadata.diff as string },
45+
provenance: "rawContextProvenance" in metadata ? "raw_context_refetch" : "live_capture",
46+
};
47+
}
48+
49+
/**
50+
* Assemble the replayable fixture set for one campaign per the #8219 contract: filter to cases carrying
51+
* bounded raw context (via the contract's own {@link isReplayableCase}), apply the seeded deterministic
52+
* sample when the eligible set exceeds `contract.maxFixtures`, and emit fixtures with era provenance —
53+
* with full skip accounting for everything excluded. Deterministic: same corpus + contract ⇒ same fixture
54+
* set, byte for byte.
55+
*/
56+
export function assembleCounterfactualFixtures(
57+
cases: readonly BacktestCase[],
58+
contract: CounterfactualSamplingContract,
59+
): CounterfactualFixtureAssembly {
60+
const skipped: Record<CounterfactualSkipReason, number> = { no_raw_context: 0, sampled_out: 0 };
61+
const eligible: BacktestCase[] = [];
62+
for (const backtestCase of cases) {
63+
if (!isReplayableCase(backtestCase)) {
64+
skipped.no_raw_context += 1;
65+
continue;
66+
}
67+
eligible.push(backtestCase);
68+
}
69+
70+
let selected = eligible;
71+
if (eligible.length > contract.maxFixtures) {
72+
const kept = new Set(
73+
eligible
74+
.map((backtestCase, index) => ({ index, rank: sampleRank(contract.seed, backtestCase.targetKey) }))
75+
.sort((a, b) => a.rank - b.rank || a.index - b.index)
76+
.slice(0, contract.maxFixtures)
77+
.map((entry) => entry.index),
78+
);
79+
selected = eligible.filter((_, index) => kept.has(index));
80+
skipped.sampled_out = eligible.length - selected.length;
81+
}
82+
83+
return { fixtures: selected.map(toFixture), skipped };
84+
}

packages/loopover-engine/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export * from "./calibration/backtest-corpus.js";
167167
export * from "./calibration/repo-corpus-slice.js";
168168
export * from "./calibration/ams-prediction-corpus.js";
169169
export * from "./calibration/counterfactual-contract.js";
170+
export * from "./calibration/counterfactual-fixtures.js";
170171
export * from "./calibration/backtest-score.js";
171172
export * from "./calibration/backtest-compare.js";
172173
export * from "./calibration/backtest-report.js";
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import assert from "node:assert/strict";
2+
import { test } from "node:test";
3+
4+
import {
5+
assembleCounterfactualFixtures,
6+
COUNTERFACTUAL_SAMPLE_SEED_PREFIX,
7+
type BacktestCase,
8+
} from "../dist/index.js";
9+
10+
const SEED = `${COUNTERFACTUAL_SAMPLE_SEED_PREFIX}:workspace-suite`;
11+
12+
function replayable(targetKey: string, label: BacktestCase["label"] = "confirmed", extraMetadata: Record<string, unknown> = {}): BacktestCase {
13+
return {
14+
ruleId: "ai_consensus_defect",
15+
targetKey,
16+
outcome: "close",
17+
label,
18+
firedAt: "2026-07-01T00:00:00.000Z",
19+
decidedAt: "2026-07-02T00:00:00.000Z",
20+
metadata: { diff: `@@ diff for ${targetKey}`, ...extraMetadata },
21+
};
22+
}
23+
24+
test("barrel: the public entrypoint re-exports the fixture assembler (#8220)", () => {
25+
assert.equal(typeof assembleCounterfactualFixtures, "function");
26+
});
27+
28+
test("assembler round-trip: eligibility, era provenance, and skip accounting per the #8219 contract", () => {
29+
const { fixtures, skipped } = assembleCounterfactualFixtures(
30+
[
31+
replayable("acme/widgets#1", "reversed"),
32+
{ ...replayable("acme/widgets#2"), metadata: { confidence: 0.5 } },
33+
replayable("acme/widgets#3", "confirmed", { rawContextProvenance: "github_raw_context_refetch" }),
34+
],
35+
{ seed: SEED, maxFixtures: 10 },
36+
);
37+
assert.equal(fixtures.length, 2);
38+
assert.equal(fixtures[0]!.provenance, "live_capture");
39+
assert.equal(fixtures[1]!.provenance, "raw_context_refetch");
40+
assert.deepEqual(skipped, { no_raw_context: 1, sampled_out: 0 });
41+
});
42+
43+
test("seeded sampling is deterministic and accounts every sampled-out case", () => {
44+
const cases = Array.from({ length: 25 }, (_, i) => replayable(`acme/widgets#${i + 1}`));
45+
const first = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 9 });
46+
assert.equal(first.fixtures.length, 9);
47+
assert.equal(first.skipped.sampled_out, 16);
48+
assert.deepEqual(assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 9 }), first);
49+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
// Import the engine SOURCE directly (not the built dist) -- coverage.include lists
4+
// packages/loopover-engine/src/**, so only a source-path import exercises the .ts these branches live in
5+
// (the dist-importing twin in packages/loopover-engine/test/ covers the built barrel for the workspace
6+
// suite). Same pattern as backtest-corpus-engine.test.ts / repo-corpus-engine.test.ts.
7+
import { assembleCounterfactualFixtures } from "../../packages/loopover-engine/src/calibration/counterfactual-fixtures";
8+
import { COUNTERFACTUAL_SAMPLE_SEED_PREFIX } from "../../packages/loopover-engine/src/calibration/counterfactual-contract";
9+
import type { BacktestCase } from "../../packages/loopover-engine/src/calibration/backtest-corpus";
10+
11+
const SEED = `${COUNTERFACTUAL_SAMPLE_SEED_PREFIX}:test-campaign`;
12+
13+
function replayable(targetKey: string, label: BacktestCase["label"] = "confirmed", extraMetadata: Record<string, unknown> = {}): BacktestCase {
14+
return {
15+
ruleId: "ai_consensus_defect",
16+
targetKey,
17+
outcome: "close",
18+
label,
19+
firedAt: "2026-07-01T00:00:00.000Z",
20+
decidedAt: "2026-07-02T00:00:00.000Z",
21+
metadata: { diff: `@@ diff for ${targetKey}`, ...extraMetadata },
22+
};
23+
}
24+
25+
describe("assembleCounterfactualFixtures (#8220)", () => {
26+
it("shapes replayable cases into fixtures with era provenance, skipping context-less cases with accounting", () => {
27+
const cases: BacktestCase[] = [
28+
replayable("acme/widgets#1", "reversed"),
29+
{ ...replayable("acme/widgets#2"), metadata: { confidence: 0.9 } }, // no diff — not replayable
30+
{ ...replayable("acme/widgets#3"), metadata: { diff: "" } }, // empty diff — not replayable
31+
replayable("acme/widgets#4", "confirmed", { rawContextProvenance: "github_raw_context_refetch" }),
32+
];
33+
const { fixtures, skipped } = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 10 });
34+
expect(fixtures).toEqual([
35+
{
36+
fixtureId: "acme/widgets#1",
37+
label: "reversed",
38+
boundedInputs: { diff: "@@ diff for acme/widgets#1" },
39+
provenance: "live_capture",
40+
},
41+
{
42+
fixtureId: "acme/widgets#4",
43+
label: "confirmed",
44+
boundedInputs: { diff: "@@ diff for acme/widgets#4" },
45+
provenance: "raw_context_refetch",
46+
},
47+
]);
48+
expect(skipped).toEqual({ no_raw_context: 2, sampled_out: 0 });
49+
// Sum invariant: every input case is a fixture or an accounted skip.
50+
expect(fixtures.length + skipped.no_raw_context + skipped.sampled_out).toBe(cases.length);
51+
});
52+
53+
it("applies the seeded sample only when the eligible set exceeds the budget, preserving corpus order", () => {
54+
const cases = Array.from({ length: 20 }, (_, i) => replayable(`acme/widgets#${i + 1}`));
55+
const under = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 20 });
56+
expect(under.fixtures).toHaveLength(20);
57+
expect(under.skipped.sampled_out).toBe(0);
58+
59+
const sampled = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 7 });
60+
expect(sampled.fixtures).toHaveLength(7);
61+
expect(sampled.skipped.sampled_out).toBe(13);
62+
// Corpus order is preserved within the sample — fixture ids ascend by original position.
63+
const positions = sampled.fixtures.map((fixture) => cases.findIndex((c) => c.targetKey === fixture.fixtureId));
64+
expect(positions).toEqual([...positions].sort((a, b) => a - b));
65+
});
66+
67+
it("is deterministic per seed, differs across seeds, and never selects 'the first N'", () => {
68+
const cases = Array.from({ length: 30 }, (_, i) => replayable(`acme/widgets#${i + 1}`));
69+
const first = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 10 });
70+
expect(assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 10 })).toEqual(first);
71+
72+
const otherSeed = assembleCounterfactualFixtures(cases, { seed: `${SEED}-b`, maxFixtures: 10 });
73+
expect(otherSeed.fixtures.map((f) => f.fixtureId)).not.toEqual(first.fixtures.map((f) => f.fixtureId));
74+
// Hash-ranked membership, not positional truncation.
75+
expect(first.fixtures.map((f) => f.fixtureId)).not.toEqual(cases.slice(0, 10).map((c) => c.targetKey));
76+
});
77+
78+
it("breaks a same-target rank tie toward the earlier case, deterministically", () => {
79+
// Two firings of the SAME target share a sample hash — a two-element sort MUST compare exactly that
80+
// tied pair, forcing the position tie-break: the earlier firing wins the single slot.
81+
const cases = [replayable("acme/widgets#7", "confirmed"), replayable("acme/widgets#7", "reversed")];
82+
const { fixtures, skipped } = assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 1 });
83+
expect(fixtures).toHaveLength(1);
84+
expect(fixtures[0]).toMatchObject({ fixtureId: "acme/widgets#7", label: "confirmed" });
85+
expect(skipped.sampled_out).toBe(1);
86+
// Reproducible byte-for-byte.
87+
expect(assembleCounterfactualFixtures(cases, { seed: SEED, maxFixtures: 1 })).toEqual({ fixtures, skipped });
88+
});
89+
90+
it("returns an empty assembly for an empty corpus", () => {
91+
expect(assembleCounterfactualFixtures([], { seed: SEED, maxFixtures: 5 })).toEqual({
92+
fixtures: [],
93+
skipped: { no_raw_context: 0, sampled_out: 0 },
94+
});
95+
});
96+
});

0 commit comments

Comments
 (0)