modelrig 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +86 -0
  3. package/dist/bakeoff/disagreement.d.ts +93 -0
  4. package/dist/bakeoff/disagreement.d.ts.map +1 -0
  5. package/dist/bakeoff/disagreement.js +140 -0
  6. package/dist/bakeoff/engine.d.ts.map +1 -1
  7. package/dist/bakeoff/engine.js +58 -2
  8. package/dist/bakeoff/pairwise.d.ts +102 -0
  9. package/dist/bakeoff/pairwise.d.ts.map +1 -0
  10. package/dist/bakeoff/pairwise.js +214 -0
  11. package/dist/bakeoff/replay.d.ts +6 -0
  12. package/dist/bakeoff/replay.d.ts.map +1 -1
  13. package/dist/bakeoff/replay.js +6 -2
  14. package/dist/bakeoff/report.d.ts +7 -0
  15. package/dist/bakeoff/report.d.ts.map +1 -1
  16. package/dist/bakeoff/report.js +46 -0
  17. package/dist/bakeoff/types.d.ts +24 -0
  18. package/dist/bakeoff/types.d.ts.map +1 -1
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +9 -9
  21. package/dist/mask/apply.d.ts +80 -0
  22. package/dist/mask/apply.d.ts.map +1 -0
  23. package/dist/mask/apply.js +228 -0
  24. package/dist/mask/detect.d.ts +38 -0
  25. package/dist/mask/detect.d.ts.map +1 -0
  26. package/dist/mask/detect.js +167 -0
  27. package/dist/mask/index.d.ts +21 -0
  28. package/dist/mask/index.d.ts.map +1 -0
  29. package/dist/mask/index.js +54 -0
  30. package/dist/mask/mask-policy-cli.d.ts +11 -0
  31. package/dist/mask/mask-policy-cli.d.ts.map +1 -0
  32. package/dist/mask/mask-policy-cli.js +33 -0
  33. package/dist/mask/policy.d.ts +38 -0
  34. package/dist/mask/policy.d.ts.map +1 -0
  35. package/dist/mask/policy.js +187 -0
  36. package/dist/mask/proof.d.ts +54 -0
  37. package/dist/mask/proof.d.ts.map +1 -0
  38. package/dist/mask/proof.js +120 -0
  39. package/dist/mask/receipt.d.ts +30 -0
  40. package/dist/mask/receipt.d.ts.map +1 -0
  41. package/dist/mask/receipt.js +49 -0
  42. package/dist/mask/seam.d.ts +53 -0
  43. package/dist/mask/seam.d.ts.map +1 -0
  44. package/dist/mask/seam.js +120 -0
  45. package/dist/mask/types.d.ts +81 -0
  46. package/dist/mask/types.d.ts.map +1 -0
  47. package/dist/mask/types.js +10 -0
  48. package/dist/mask/vault.d.ts +50 -0
  49. package/dist/mask/vault.d.ts.map +1 -0
  50. package/dist/mask/vault.js +81 -0
  51. package/dist/receipts/types.d.ts +65 -0
  52. package/dist/receipts/types.d.ts.map +1 -0
  53. package/dist/receipts/types.js +134 -0
  54. package/dist/routes/loader-warnings.d.ts +8 -0
  55. package/dist/routes/loader-warnings.d.ts.map +1 -1
  56. package/dist/routes/loader-warnings.js +23 -0
  57. package/dist/routes/loader.d.ts +1 -1
  58. package/dist/routes/loader.d.ts.map +1 -1
  59. package/dist/routes/loader.js +24 -25
  60. package/dist/routes/mask-loader.d.ts +18 -0
  61. package/dist/routes/mask-loader.d.ts.map +1 -0
  62. package/dist/routes/mask-loader.js +39 -0
  63. package/dist/routes/objective-loader.d.ts.map +1 -1
  64. package/dist/routes/objective-loader.js +51 -17
  65. package/dist/routes/screen-loader.d.ts +23 -0
  66. package/dist/routes/screen-loader.d.ts.map +1 -0
  67. package/dist/routes/screen-loader.js +79 -0
  68. package/dist/routes/template.d.ts +10 -0
  69. package/dist/routes/template.d.ts.map +1 -1
  70. package/dist/routes/template.js +10 -3
  71. package/dist/run/accept.d.ts +4 -0
  72. package/dist/run/accept.d.ts.map +1 -1
  73. package/dist/run/accept.js +2 -0
  74. package/dist/run/dispatch.d.ts +35 -0
  75. package/dist/run/dispatch.d.ts.map +1 -1
  76. package/dist/run/dispatch.js +33 -0
  77. package/dist/run/grounding.d.ts +47 -2
  78. package/dist/run/grounding.d.ts.map +1 -1
  79. package/dist/run/grounding.js +113 -6
  80. package/dist/run/raw.d.ts.map +1 -1
  81. package/dist/run/raw.js +6 -1
  82. package/dist/run/record-attempt.d.ts.map +1 -1
  83. package/dist/run/record-attempt.js +12 -1
  84. package/dist/run/run.d.ts.map +1 -1
  85. package/dist/run/run.js +19 -16
  86. package/dist/screen/provenance.d.ts +59 -0
  87. package/dist/screen/provenance.d.ts.map +1 -0
  88. package/dist/screen/provenance.js +104 -0
  89. package/dist/screen/types.d.ts +71 -0
  90. package/dist/screen/types.d.ts.map +1 -0
  91. package/dist/screen/types.js +9 -0
  92. package/dist/status-cli.d.ts +11 -0
  93. package/dist/status-cli.d.ts.map +1 -0
  94. package/dist/status-cli.js +21 -0
  95. package/dist/status-runs.d.ts +38 -0
  96. package/dist/status-runs.d.ts.map +1 -0
  97. package/dist/status-runs.js +80 -0
  98. package/dist/status.d.ts +7 -2
  99. package/dist/status.d.ts.map +1 -1
  100. package/dist/status.js +11 -2
  101. package/dist/telemetry/ingest-protocol.d.ts +8 -0
  102. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  103. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  104. package/dist/telemetry/store-migrations.js +11 -0
  105. package/dist/telemetry/store.d.ts.map +1 -1
  106. package/dist/telemetry/store.js +12 -3
  107. package/dist/types/provider.d.ts +11 -0
  108. package/dist/types/provider.d.ts.map +1 -1
  109. package/dist/types/provider.js +9 -0
  110. package/dist/types/route.d.ts +55 -0
  111. package/dist/types/route.d.ts.map +1 -1
  112. package/dist/types/telemetry.d.ts +7 -0
  113. package/dist/types/telemetry.d.ts.map +1 -1
  114. package/dist/validate-cli.d.ts.map +1 -1
  115. package/dist/validate-cli.js +13 -0
  116. package/dist/verify/types.d.ts +6 -0
  117. package/dist/verify/types.d.ts.map +1 -1
  118. package/docs/agent-skill/SKILL.md +131 -0
  119. package/docs/how-it-fits.md +34 -0
  120. package/docs/instrumentation.md +221 -0
  121. package/docs/llms.txt +276 -0
  122. package/docs/migration-playbook.md +512 -0
  123. package/docs/quickstart.md +339 -0
  124. package/package.json +18 -2
  125. package/receipts.d.ts +12 -0
@@ -0,0 +1,102 @@
1
+ /**
2
+ * N5 — pairwise preference vs the reference arm (near-optimal brief §3.2 N5).
3
+ *
4
+ * A second, complementary read on regret: for each replayed sample, is the SERVED
5
+ * arm's output BETTER than, tied with, or WORSE than the REFERENCE arm's on the
6
+ * same captured input? Aggregated to a `pairwise_win_rate` (win=1, tie=0.5,
7
+ * loss=0) with a seeded bootstrap CI — the RigIndex pairwise idea, scoped to one
8
+ * bake-off's arm-vs-reference comparison.
9
+ *
10
+ * DETERMINISTIC-FIRST (F5), exactly like the regret quality tiers:
11
+ * 1. When BOTH sides carry a per-sample value-accuracy score (eval case /
12
+ * value_checks / Q1 gate), the verdict is the sign of their difference —
13
+ * no model call. This is the free, always-preferred tier.
14
+ * 2. Only when a sample cannot be decided deterministically does an OPT-IN,
15
+ * CAPPED, METERED pairwise judge (`bakeoff.pairwise.judge`) compare the two
16
+ * outputs. Judge model ∈ declared candidates (#223 F3, enforced at load).
17
+ *
18
+ * The aggregation math is PURE and unit-tested directly; the judge reuses the
19
+ * real `run()` loop (no new dispatch path, no run.ts edit) exactly as judge.ts.
20
+ */
21
+ import type { RunDeps } from "../run/run";
22
+ import type { CandidateSpec, RouteBundle } from "../types/route";
23
+ import type { ReplaySampleResult } from "./replay";
24
+ /** The served arm's standing vs the reference on one sample. `win` = the arm's
25
+ * output is better than the reference's; `loss` = worse; `tie` = indistinguishable. */
26
+ export type PairwiseOutcome = "win" | "tie" | "loss";
27
+ /** How a single verdict was decided (F5): a free deterministic comparison of two
28
+ * value-accuracy scores, or a billed pairwise judge call. */
29
+ export type PairwiseSource = "deterministic" | "judge";
30
+ /** Aggregate pairwise standing of an arm vs the reference (N5). `winRate` counts
31
+ * a win as 1 and a tie as 0.5; the CI is a seeded bootstrap of the per-sample
32
+ * win values, so it is reproducible given the same sample set. */
33
+ export interface PairwiseStats {
34
+ readonly winRate: number;
35
+ readonly winRateCi95: readonly [number, number];
36
+ /** Samples with a verdict (deterministic + judge); the CI's n. */
37
+ readonly nDecided: number;
38
+ readonly wins: number;
39
+ readonly ties: number;
40
+ readonly losses: number;
41
+ /** Paired samples that could NOT be decided (no deterministic score and no
42
+ * judge, or the judge was capped/failed) — excluded from the rate, counted. */
43
+ readonly nUndecided: number;
44
+ /** Where the decided verdicts came from — "deterministic", "judge", or "mixed". */
45
+ readonly source: "deterministic" | "judge" | "mixed";
46
+ }
47
+ /** Seeded so a bake-off's pairwise CI is reproducible (like the regret CI). A
48
+ * distinct seed from the regret bootstrap keeps the two resamplings independent. */
49
+ export declare const PAIRWISE_BOOTSTRAP_REPS = 2000;
50
+ export declare const PAIRWISE_BOOTSTRAP_SEED = 20260824;
51
+ /** The win-value of an outcome (win=1, tie=0.5, loss=0) — the quantity the win
52
+ * rate averages. */
53
+ export declare function winValue(o: PairwiseOutcome): number;
54
+ /**
55
+ * The DETERMINISTIC pairwise verdict (tier 1): the sign of `armQuality −
56
+ * refQuality`. Returns null when EITHER side lacks a value-accuracy score — the
57
+ * caller then falls to the judge. A tie within EPS is a genuine tie.
58
+ */
59
+ export declare function deterministicPairwise(armQuality: number | null, refQuality: number | null): PairwiseOutcome | null;
60
+ /**
61
+ * Aggregate per-sample verdicts into `PairwiseStats` (N5). `decided` are the
62
+ * verdicts that resolved; `nUndecided` the paired samples that did not. The CI is
63
+ * a seeded bootstrap of the per-sample win values (win=1/tie=0.5/loss=0), so it
64
+ * is deterministic given the verdict list + seed. Pure.
65
+ */
66
+ export declare function aggregatePairwise(decided: readonly {
67
+ readonly outcome: PairwiseOutcome;
68
+ readonly source: PairwiseSource;
69
+ }[], nUndecided: number, seed?: number, reps?: number): PairwiseStats;
70
+ /**
71
+ * Score ONE arm's pairwise standing vs the reference over the paired replay set
72
+ * (N5). Deterministic-first: each paired sample's verdict is the sign of the two
73
+ * value-accuracy scores; a sample neither side scored falls to the capped judge
74
+ * (when supplied), else it is undecided (counted, excluded from the rate). Pairs
75
+ * by captureId; unpaired samples are ignored. Returns null stats when there are
76
+ * NO paired samples at all. `outcomes` maps captureId → verdict (for N6 mining).
77
+ */
78
+ export declare function scoreArmPairwise(armSamples: readonly ReplaySampleResult[], refSamples: readonly ReplaySampleResult[], judge: PairwiseJudge | null): Promise<{
79
+ stats: PairwiseStats | null;
80
+ outcomes: Map<string, PairwiseOutcome>;
81
+ }>;
82
+ /** Build the synthetic pairwise-judge route for a base route + judge model. The
83
+ * judge model is declared WITHOUT its trial flag so it serves live here. */
84
+ export declare function buildPairwiseJudgeRoute(baseRoute: RouteBundle, model: CandidateSpec): RouteBundle;
85
+ /** A per-arm pairwise scorer: `compare(armOutput, refOutput, taskInput)` returns
86
+ * the arm's outcome vs the reference, or null when the cap is hit or the judge
87
+ * could not produce a valid verdict. */
88
+ export interface PairwiseJudge {
89
+ compare(armOutput: unknown, refOutput: unknown, taskInput: unknown): Promise<PairwiseOutcome | null>;
90
+ used(): number;
91
+ }
92
+ /**
93
+ * Build a pairwise judge over the run loop (mirrors makeJudgeScorer). Each arm
94
+ * gets a FRESH judge so `maxSamples` is per arm. The comparison is presented in a
95
+ * FIXED order (A = served arm, B = reference); position bias is a known limit,
96
+ * noted in verification — a fixed order keeps the judge DETERMINISTIC for tests.
97
+ */
98
+ export declare function makePairwiseJudge(deps: RunDeps, baseRoute: RouteBundle, judge: {
99
+ readonly model: CandidateSpec;
100
+ readonly maxSamples: number;
101
+ }): PairwiseJudge;
102
+ //# sourceMappingURL=pairwise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairwise.d.ts","sourceRoot":"","sources":["../../src/bakeoff/pairwise.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;uFACuF;AACvF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAErD;6DAC6D;AAC7D,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,OAAO,CAAC;AAEvD;;kEAEkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;mFAC+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;CACtD;AAED;oFACoF;AACpF,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAKhD;oBACoB;AACpB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,eAAe,GAAG,IAAI,CAKxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;CAAE,EAAE,EAC1F,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAgC,EACtC,IAAI,GAAE,MAAgC,GACrC,aAAa,CA2Bf;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,KAAK,EAAE,aAAa,GAAG,IAAI,GAC1B,OAAO,CAAC;IAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,CAAC,CA8BlF;AAmBD;4EAC4E;AAC5E,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,WAAW,CAejG;AAED;;wCAEwC;AACxC,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACrG,IAAI,IAAI,MAAM,CAAC;CAChB;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpE,aAAa,CAmCf"}
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ /**
3
+ * N5 — pairwise preference vs the reference arm (near-optimal brief §3.2 N5).
4
+ *
5
+ * A second, complementary read on regret: for each replayed sample, is the SERVED
6
+ * arm's output BETTER than, tied with, or WORSE than the REFERENCE arm's on the
7
+ * same captured input? Aggregated to a `pairwise_win_rate` (win=1, tie=0.5,
8
+ * loss=0) with a seeded bootstrap CI — the RigIndex pairwise idea, scoped to one
9
+ * bake-off's arm-vs-reference comparison.
10
+ *
11
+ * DETERMINISTIC-FIRST (F5), exactly like the regret quality tiers:
12
+ * 1. When BOTH sides carry a per-sample value-accuracy score (eval case /
13
+ * value_checks / Q1 gate), the verdict is the sign of their difference —
14
+ * no model call. This is the free, always-preferred tier.
15
+ * 2. Only when a sample cannot be decided deterministically does an OPT-IN,
16
+ * CAPPED, METERED pairwise judge (`bakeoff.pairwise.judge`) compare the two
17
+ * outputs. Judge model ∈ declared candidates (#223 F3, enforced at load).
18
+ *
19
+ * The aggregation math is PURE and unit-tested directly; the judge reuses the
20
+ * real `run()` loop (no new dispatch path, no run.ts edit) exactly as judge.ts.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.PAIRWISE_BOOTSTRAP_SEED = exports.PAIRWISE_BOOTSTRAP_REPS = void 0;
24
+ exports.winValue = winValue;
25
+ exports.deterministicPairwise = deterministicPairwise;
26
+ exports.aggregatePairwise = aggregatePairwise;
27
+ exports.scoreArmPairwise = scoreArmPairwise;
28
+ exports.buildPairwiseJudgeRoute = buildPairwiseJudgeRoute;
29
+ exports.makePairwiseJudge = makePairwiseJudge;
30
+ const node_crypto_1 = require("node:crypto");
31
+ const bootstrap_1 = require("../rigindex/bootstrap");
32
+ const run_1 = require("../run/run");
33
+ const failures_1 = require("../types/failures");
34
+ /** Seeded so a bake-off's pairwise CI is reproducible (like the regret CI). A
35
+ * distinct seed from the regret bootstrap keeps the two resamplings independent. */
36
+ exports.PAIRWISE_BOOTSTRAP_REPS = 2000;
37
+ exports.PAIRWISE_BOOTSTRAP_SEED = 20260824;
38
+ /** Tie band: value-accuracy scores within EPS are a tie, not a hair-split win. */
39
+ const PAIRWISE_EPS = 1e-9;
40
+ /** The win-value of an outcome (win=1, tie=0.5, loss=0) — the quantity the win
41
+ * rate averages. */
42
+ function winValue(o) {
43
+ return o === "win" ? 1 : o === "tie" ? 0.5 : 0;
44
+ }
45
+ /**
46
+ * The DETERMINISTIC pairwise verdict (tier 1): the sign of `armQuality −
47
+ * refQuality`. Returns null when EITHER side lacks a value-accuracy score — the
48
+ * caller then falls to the judge. A tie within EPS is a genuine tie.
49
+ */
50
+ function deterministicPairwise(armQuality, refQuality) {
51
+ if (armQuality === null || refQuality === null)
52
+ return null;
53
+ const diff = armQuality - refQuality;
54
+ if (Math.abs(diff) <= PAIRWISE_EPS)
55
+ return "tie";
56
+ return diff > 0 ? "win" : "loss";
57
+ }
58
+ /**
59
+ * Aggregate per-sample verdicts into `PairwiseStats` (N5). `decided` are the
60
+ * verdicts that resolved; `nUndecided` the paired samples that did not. The CI is
61
+ * a seeded bootstrap of the per-sample win values (win=1/tie=0.5/loss=0), so it
62
+ * is deterministic given the verdict list + seed. Pure.
63
+ */
64
+ function aggregatePairwise(decided, nUndecided, seed = exports.PAIRWISE_BOOTSTRAP_SEED, reps = exports.PAIRWISE_BOOTSTRAP_REPS) {
65
+ const wins = decided.filter((d) => d.outcome === "win").length;
66
+ const ties = decided.filter((d) => d.outcome === "tie").length;
67
+ const losses = decided.filter((d) => d.outcome === "loss").length;
68
+ const nDecided = decided.length;
69
+ const hasDet = decided.some((d) => d.source === "deterministic");
70
+ const hasJudge = decided.some((d) => d.source === "judge");
71
+ const source = hasDet && hasJudge ? "mixed" : hasJudge ? "judge" : "deterministic";
72
+ if (nDecided === 0) {
73
+ return { winRate: 0, winRateCi95: [0, 0], nDecided: 0, wins: 0, ties: 0, losses: 0, nUndecided, source };
74
+ }
75
+ const values = decided.map((d) => winValue(d.outcome));
76
+ const winRate = values.reduce((a, b) => a + b, 0) / nDecided;
77
+ // Bootstrap the per-sample win values (the pairing is already fixed), take each
78
+ // resample's mean, then the 2.5/97.5 percentiles.
79
+ const rng = (0, bootstrap_1.mulberry32)(seed);
80
+ const means = [];
81
+ for (let b = 0; b < reps; b += 1) {
82
+ let sum = 0;
83
+ for (let i = 0; i < nDecided; i += 1)
84
+ sum += values[Math.floor(rng() * nDecided)];
85
+ means.push(sum / nDecided);
86
+ }
87
+ means.sort((a, b) => a - b);
88
+ const lo = means[Math.floor(0.025 * reps)];
89
+ const hi = means[Math.min(reps - 1, Math.floor(0.975 * reps))];
90
+ return { winRate, winRateCi95: [lo, hi], nDecided, wins, ties, losses, nUndecided, source };
91
+ }
92
+ /**
93
+ * Score ONE arm's pairwise standing vs the reference over the paired replay set
94
+ * (N5). Deterministic-first: each paired sample's verdict is the sign of the two
95
+ * value-accuracy scores; a sample neither side scored falls to the capped judge
96
+ * (when supplied), else it is undecided (counted, excluded from the rate). Pairs
97
+ * by captureId; unpaired samples are ignored. Returns null stats when there are
98
+ * NO paired samples at all. `outcomes` maps captureId → verdict (for N6 mining).
99
+ */
100
+ async function scoreArmPairwise(armSamples, refSamples, judge) {
101
+ const refByCapture = new Map(refSamples.map((s) => [s.captureId, s]));
102
+ const decided = [];
103
+ const outcomes = new Map();
104
+ let paired = 0;
105
+ let undecided = 0;
106
+ for (const arm of armSamples) {
107
+ const ref = refByCapture.get(arm.captureId);
108
+ if (ref === undefined)
109
+ continue;
110
+ paired += 1;
111
+ const det = deterministicPairwise(arm.valueAccuracy, ref.valueAccuracy);
112
+ if (det !== null) {
113
+ decided.push({ outcome: det, source: "deterministic" });
114
+ outcomes.set(arm.captureId, det);
115
+ continue;
116
+ }
117
+ // Deterministic can't decide (a side lacks value accuracy). Only a PRODUCED
118
+ // pair can be judged (both need an output); the judge is capped internally.
119
+ if (judge !== null && arm.failureClass === null && ref.failureClass === null) {
120
+ const verdict = await judge.compare(arm.outputText ?? null, ref.outputText ?? null, arm.inputText ?? null);
121
+ if (verdict !== null) {
122
+ decided.push({ outcome: verdict, source: "judge" });
123
+ outcomes.set(arm.captureId, verdict);
124
+ continue;
125
+ }
126
+ }
127
+ undecided += 1;
128
+ }
129
+ if (paired === 0)
130
+ return { stats: null, outcomes };
131
+ return { stats: aggregatePairwise(decided, undecided), outcomes };
132
+ }
133
+ // ── The opt-in, capped, metered pairwise judge (tier 2) ──────────────────────
134
+ const PAIRWISE_SCHEMA = {
135
+ type: "object",
136
+ properties: { verdict: { type: "string", enum: ["A", "B", "tie"] }, reason: { type: "string" } },
137
+ required: ["verdict"],
138
+ additionalProperties: true,
139
+ };
140
+ const PAIRWISE_RUBRIC = "You are judging PAIRWISE PREFERENCE between two candidate outputs for ONE task — " +
141
+ "which is the better answer for VALUE (correctness and completeness), not merely which " +
142
+ "is better-formed. A is one candidate; B is the other.\n\nTASK INPUT:\n{{task_input}}\n\n" +
143
+ "OUTPUT A:\n{{output_a}}\n\nOUTPUT B:\n{{output_b}}\n\n" +
144
+ 'Respond with ONLY compact JSON: {"verdict": "A" | "B" | "tie", "reason": "<one sentence>"}. ' +
145
+ 'Answer "A" if A is better, "B" if B is better, "tie" if they are equally good.';
146
+ /** Build the synthetic pairwise-judge route for a base route + judge model. The
147
+ * judge model is declared WITHOUT its trial flag so it serves live here. */
148
+ function buildPairwiseJudgeRoute(baseRoute, model) {
149
+ return Object.freeze({
150
+ route: `__bakeoff_pairwise__:${baseRoute.route}`,
151
+ version: baseRoute.version,
152
+ outputSchema: Object.freeze({ ...PAIRWISE_SCHEMA }),
153
+ candidates: Object.freeze([{ provider: model.provider, model: model.model }]),
154
+ require: Object.freeze([]),
155
+ prefer: Object.freeze([]),
156
+ systemTemplate: PAIRWISE_RUBRIC,
157
+ variables: Object.freeze(["output_a", "output_b", "task_input"]),
158
+ policy: Object.freeze({ retries: Object.freeze({}), timeoutMs: baseRoute.policy.timeoutMs, tier: "standard", json: "json_mode" }),
159
+ variants: Object.freeze([]),
160
+ capture: false,
161
+ fingerprintCell: null,
162
+ });
163
+ }
164
+ function asText(v) {
165
+ return typeof v === "string" ? v : JSON.stringify(v ?? null);
166
+ }
167
+ /**
168
+ * Build a pairwise judge over the run loop (mirrors makeJudgeScorer). Each arm
169
+ * gets a FRESH judge so `maxSamples` is per arm. The comparison is presented in a
170
+ * FIXED order (A = served arm, B = reference); position bias is a known limit,
171
+ * noted in verification — a fixed order keeps the judge DETERMINISTIC for tests.
172
+ */
173
+ function makePairwiseJudge(deps, baseRoute, judge) {
174
+ const judgeRoute = buildPairwiseJudgeRoute(baseRoute, judge.model);
175
+ const routes = {
176
+ get: (r) => (r === judgeRoute.route ? judgeRoute : deps.routes.get(r)),
177
+ list: () => Object.freeze([...deps.routes.list(), judgeRoute]),
178
+ };
179
+ const judgeDeps = { ...deps, routes, captures: null, mode: "live" };
180
+ const envelope = `bakeoff-pairwise:${(0, node_crypto_1.randomUUID)()}`;
181
+ let used = 0;
182
+ return {
183
+ used: () => used,
184
+ async compare(armOutput, refOutput, taskInput) {
185
+ if (used >= judge.maxSamples)
186
+ return null;
187
+ used += 1;
188
+ try {
189
+ const result = await (0, run_1.run)(judgeDeps, judgeRoute.route, {
190
+ input: { output_a: asText(armOutput), output_b: asText(refOutput), task_input: asText(taskInput) },
191
+ // `bakeoff_pairwise` marks the row a pairwise-judge inference (the typed
192
+ // `rung` column is recorder-owned, so a distinct tag key is used); `lane:
193
+ // trial` keeps judge spend off the route's live rollups/odometer.
194
+ tags: { bakeoff_pairwise: "1", lane: "trial" },
195
+ budget: { envelope },
196
+ });
197
+ const verdict = result.output?.verdict;
198
+ if (verdict === "A")
199
+ return "win";
200
+ if (verdict === "B")
201
+ return "loss";
202
+ if (verdict === "tie")
203
+ return "tie";
204
+ return null;
205
+ }
206
+ catch (err) {
207
+ if (!(err instanceof failures_1.RigFailureError))
208
+ throw err;
209
+ // The metered attempt row(s) still stand; this sample gets no verdict.
210
+ return null;
211
+ }
212
+ },
213
+ };
214
+ }
@@ -30,6 +30,12 @@ export interface ReplayRequest {
30
30
  * sample for the human value review. Off by default (bounds memory + keeps
31
31
  * the pure-metrics path clean). */
32
32
  readonly keepOutputs?: boolean;
33
+ /** N5: retain `inputText` + `outputText` on each sample for the pairwise judge
34
+ * (arm-vs-reference comparison in the engine), WITHOUT the keepOutputs sink
35
+ * persistence. Set by the engine only when `bakeoff.pairwise.judge` is declared.
36
+ * A superset trigger of `keepOutputs` for retention; the sink stays gated on
37
+ * `keepOutputs` alone so pairwise retention never persists review samples. */
38
+ readonly retainOutputForPairwise?: boolean;
33
39
  /** RB-1: corrected/golden expected outputs keyed by captureId (eval-case path).
34
40
  * Presence in the map ⇒ this sample has an expected value; `value` may be any
35
41
  * JSON (including null). Quality tier 1 (eval case) when present. */
@@ -1 +1 @@
1
- {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/bakeoff/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;uCAEmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;yEAEqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrE;sFACkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B;;;;;iFAK6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;wEACoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;;oCAGgC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;yDACqD;IACrD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;sDACkD;IAClD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,EACtB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAoIxC"}
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/bakeoff/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;uCAEmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;kFAI8E;IAC9E,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;yEAEqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrE;sFACkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B;;;;;iFAK6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;wEACoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;;oCAGgC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;yDACqD;IACrD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;sDACkD;IAClD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,EACtB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAyIxC"}
@@ -49,6 +49,10 @@ async function replayVariant(deps, captures, req) {
49
49
  envelopeBudgetUsd: req.envelopeUsd,
50
50
  mode,
51
51
  };
52
+ // Retain per-sample input/output text when the caller asked (keepOutputs sink,
53
+ // or N5 pairwise judge). Same fields either way; the sink persist stays gated
54
+ // on keepOutputs alone (engine.ts).
55
+ const retainText = req.keepOutputs === true || req.retainOutputForPairwise === true;
52
56
  const results = [];
53
57
  for (const capture of rows) {
54
58
  const sampleTag = `${replayId}:${capture.inferenceId}`;
@@ -81,7 +85,7 @@ async function replayVariant(deps, captures, req) {
81
85
  repaired = result.meta.repairedBy !== null;
82
86
  outputValue = result.output;
83
87
  produced = true;
84
- if (req.keepOutputs)
88
+ if (retainText)
85
89
  outputText = JSON.stringify(result.output, null, 2);
86
90
  if (req.qualityGate !== undefined) {
87
91
  gated = { output: result.output, provider: result.meta.provider, model: result.meta.model, servedVariant: result.meta.servedVariant };
@@ -145,7 +149,7 @@ async function replayVariant(deps, captures, req) {
145
149
  pricingMissing,
146
150
  latencyMs,
147
151
  failureClass,
148
- ...(req.keepOutputs
152
+ ...(retainText
149
153
  ? { inputText: capture.variablesJson, outputText }
150
154
  : {}),
151
155
  });
@@ -36,6 +36,13 @@ export interface CacheAdjustedRow {
36
36
  * Empty input ⇒ no lines (the section is omitted entirely — back-compat).
37
37
  */
38
38
  export declare function formatCacheAdjustedSection(rows: readonly CacheAdjustedRow[]): string[];
39
+ /**
40
+ * N6 disagreement summary lines: the count of mined eval-case candidates (with
41
+ * the honest "of N found" when the list was capped), the per-reason breakdown,
42
+ * and the top candidates as captureId pointers an operator may promote. Pure
43
+ * (returns strings); empty when nothing disagreed. Exported for direct testing.
44
+ */
45
+ export declare function formatDisagreementSummary(artifact: BakeoffArtifact): string[];
39
46
  export declare function printMatrix(artifact: BakeoffArtifact, cacheAdjusted?: readonly CacheAdjustedRow[]): void;
40
47
  /**
41
48
  * `modelrig bakeoff --from-eval-cases <task> --variants a,b,c` — replay a task's
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bakeoff/report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAkCpD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAexE;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAgCtF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,GAAE,SAAS,gBAAgB,EAAO,GAAG,IAAI,CAkD5G;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,MAAM,EAAE,OAAO,EACf,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bakeoff/report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA2CpD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAexE;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAgCtF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,EAAE,CAiB7E;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,GAAE,SAAS,gBAAgB,EAAO,GAAG,IAAI,CAwD5G;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,MAAM,EAAE,OAAO,EACf,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
@@ -8,6 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.bakeoffHeadline = bakeoffHeadline;
10
10
  exports.formatCacheAdjustedSection = formatCacheAdjustedSection;
11
+ exports.formatDisagreementSummary = formatDisagreementSummary;
11
12
  exports.printMatrix = printMatrix;
12
13
  exports.runEvalCaseBakeoffCli = runEvalCaseBakeoffCli;
13
14
  const node_fs_1 = require("node:fs");
@@ -35,6 +36,16 @@ function fmtRegret(m) {
35
36
  function fmtQuality(m) {
36
37
  return m.qualityMean === null ? "—" : `${(m.qualityMean * 100).toFixed(0)}% ${m.qualityMetric}`;
37
38
  }
39
+ /** N5: an arm's pairwise-vs-reference cell — "63% [51…74] n=30", "ref", or "—". */
40
+ function fmtPairwise(m) {
41
+ if (m.isReference === true)
42
+ return "ref";
43
+ const p = m.pairwise;
44
+ if (p == null)
45
+ return "—";
46
+ const pct = (v) => (v * 100).toFixed(0);
47
+ return `${pct(p.winRate)}% [${pct(p.winRateCi95[0])}…${pct(p.winRateCi95[1])}] n=${p.nDecided}`;
48
+ }
38
49
  /** The reference $/1k or per-run ceiling from the objective, formatted, or null
39
50
  * when no ceiling is declared. */
40
51
  function fmtCeiling(artifact) {
@@ -107,14 +118,43 @@ function formatCacheAdjustedSection(rows) {
107
118
  }
108
119
  return lines;
109
120
  }
121
+ /**
122
+ * N6 disagreement summary lines: the count of mined eval-case candidates (with
123
+ * the honest "of N found" when the list was capped), the per-reason breakdown,
124
+ * and the top candidates as captureId pointers an operator may promote. Pure
125
+ * (returns strings); empty when nothing disagreed. Exported for direct testing.
126
+ */
127
+ function formatDisagreementSummary(artifact) {
128
+ const d = artifact.disagreement;
129
+ if (d === undefined || d.found === 0)
130
+ return [];
131
+ const reasons = Object.entries(d.counts)
132
+ .filter(([, n]) => n > 0)
133
+ .map(([r, n]) => `${r}=${n}`)
134
+ .join(", ");
135
+ const capNote = d.cappedAt !== null ? ` (showing top ${d.candidates.length} of ${d.found} — cap ${d.cappedAt})` : "";
136
+ const lines = [
137
+ `disagreement: ${d.found} eval-case candidate${d.found === 1 ? "" : "s"} mined from ${d.examined} samples${capNote} — ` +
138
+ "NOT auto-promoted; promote with artifact.promoteToEval.",
139
+ ];
140
+ if (reasons !== "")
141
+ lines.push(` reasons: ${reasons}`);
142
+ for (const c of d.candidates.slice(0, 5)) {
143
+ lines.push(` ${c.captureId} [${c.arm}] score ${c.score.toFixed(2)} — ${c.reasons.join(", ")}`);
144
+ }
145
+ return lines;
146
+ }
110
147
  function printMatrix(artifact, cacheAdjusted = []) {
111
148
  console.log(`bakeoff ${artifact.id}\nroute ${artifact.route}@${artifact.routeVersion} · replayed ${artifact.replayN} captured inputs · ${artifact.ranAt}`);
112
149
  // RB-1: a reference arm is present ⇒ show quality + regret columns and badge
113
150
  // the reference row; otherwise the pre-RB-1 matrix stands byte-for-byte.
114
151
  const hasReference = artifact.variants.some((m) => m.isReference === true);
152
+ const hasPairwise = artifact.variants.some((m) => m.pairwise != null);
115
153
  const header = ["variant", "n", "conform", "ci95", "repair", "repair$", "lat(ms)", "eff-$/1K-conf"];
116
154
  if (hasReference)
117
155
  header.push("quality", "regret(pts)");
156
+ if (hasPairwise)
157
+ header.push("pairwise-win");
118
158
  const rows = artifact.variants.map((m) => {
119
159
  const base = [
120
160
  m.isReference === true ? `${m.variant} [ref]` : m.variant,
@@ -128,6 +168,8 @@ function printMatrix(artifact, cacheAdjusted = []) {
128
168
  ];
129
169
  if (hasReference)
130
170
  base.push(fmtQuality(m), fmtRegret(m));
171
+ if (hasPairwise)
172
+ base.push(fmtPairwise(m));
131
173
  return base;
132
174
  });
133
175
  const widths = header.map((h, c) => Math.max(h.length, ...rows.map((r) => r[c].length)));
@@ -153,6 +195,10 @@ function printMatrix(artifact, cacheAdjusted = []) {
153
195
  else {
154
196
  console.log("proposal: none (no variant beats the incumbent's gates)");
155
197
  }
198
+ // N6 disagreement summary — printed only when candidates were mined; absent ⇒
199
+ // byte-identical to the pre-N6 matrix output.
200
+ for (const l of formatDisagreementSummary(artifact))
201
+ console.log(l);
156
202
  // Cache-adjusted section (spec §5.4) — printed only when the caller supplied
157
203
  // simulator rows; absent ⇒ the matrix output is byte-identical to pre-WS-B.
158
204
  for (const l of formatCacheAdjustedSection(cacheAdjusted))
@@ -5,6 +5,9 @@
5
5
  * value accuracy, paired regret vs a reference arm, and an objective-aware gate.
6
6
  */
7
7
  import type { ObjectiveMode } from "../types/route";
8
+ import type { PairwiseStats } from "./pairwise";
9
+ import type { DisagreementReport } from "./disagreement";
10
+ import type { MaskArmProof } from "../mask/proof";
8
11
  /** Which quality metric an arm's numbers are on (spec §2): value accuracy when a
9
12
  * golden / value check / judge scored it, else schema conformance. */
10
13
  export type QualityMetric = "value_accuracy" | "conformance";
@@ -57,6 +60,11 @@ export interface VariantMetrics {
57
60
  readonly regret?: RegretStats | null;
58
61
  /** RB-1: the reference arm hit its envelope — regret is on the paired subset. */
59
62
  readonly referencePartial?: boolean;
63
+ /** N5: this arm's pairwise standing vs the reference (win rate + seeded CI).
64
+ * Null on the reference arm and when no pairwise signal was decidable. A
65
+ * REPORTED signal beside regret; gates a proposal only when the route sets
66
+ * `objective.pairwiseAware`. */
67
+ readonly pairwise?: PairwiseStats | null;
60
68
  }
61
69
  /** RB-1: the resolved objective recorded on the artifact (evidence + report). */
62
70
  export interface BakeoffObjective {
@@ -67,6 +75,9 @@ export interface BakeoffObjective {
67
75
  readonly usdPerRun?: number;
68
76
  } | null;
69
77
  readonly lambda: number | null;
78
+ /** N5: the pairwise-aware gate knob (default false). Recorded on the artifact so
79
+ * the report/PR body/evidence say whether pairwise gated the proposal. */
80
+ readonly pairwiseAware?: boolean;
70
81
  }
71
82
  /** RB-1: the resolved reference arm recorded on the artifact. */
72
83
  export interface BakeoffReference {
@@ -104,5 +115,18 @@ export interface BakeoffArtifact {
104
115
  readonly reference?: BakeoffReference | null;
105
116
  /** RB-1: the metric the bake-off's quality numbers are on. */
106
117
  readonly qualityMetric?: QualityMetric;
118
+ /** N6: verdict-disagreement candidates mined from this bake-off's replay set —
119
+ * a ranked list of already-captured pointers an operator MAY promote to eval
120
+ * cases (never auto-promoted). Rides the LOCAL artifact only (captureIds are not
121
+ * mirrored). Absent on a pre-N5/N6 artifact or when no reference/arm signal
122
+ * disagreed. */
123
+ readonly disagreement?: DisagreementReport;
124
+ /** CS-1 Proven Masking (spec §7) — the masked-arm proof for a `--mask-policy`
125
+ * run: the Detector finds no span in the masked arm (`proven: true`), with the
126
+ * unmasked arm's detections as the Δ the mask removed. Counts by type only,
127
+ * never a value. Additive & optional (RB-1 fence — rides the matrix jsonb
128
+ * wholesale, does NOT restructure the replay engine); absent on an ordinary
129
+ * bake-off. The Chunk-B masked-vs-unmasked Δ-value-accuracy arm extends this. */
130
+ readonly maskArm?: MaskArmProof;
107
131
  }
108
132
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;sEACsE;AACtE,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAE7D;;8CAE8C;AAC9C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,wFAAwF;IACxF,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C;;sFAEkF;IAClF,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC3D;;sEAEkE;IAClE,QAAQ,CAAC,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD;6EACyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;iFAC6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,qDAAqD;IACrD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;6DAEyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;qCACiC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC,iFAAiF;IACjF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1F,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C;;8EAE0E;IAC1E,QAAQ,CAAC,QAAQ,EACb;QACE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;QACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;KACtC,GACD,IAAI,CAAC;IACT,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;CACxC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAgDH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAeD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAuBxD;AAqhBD,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCnE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAiDH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAeD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAuBxD;AAohBD,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCnE"}
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ const failures_1 = require("./types/failures");
30
30
  const failures_teaching_1 = require("./types/failures-teaching");
31
31
  const config_1 = require("./config");
32
32
  const index_1 = require("./index");
33
- const status_1 = require("./status");
33
+ const status_cli_1 = require("./status-cli");
34
34
  const engine_1 = require("./bakeoff/engine");
35
35
  const samples_1 = require("./bakeoff/samples");
36
36
  const report_1 = require("./bakeoff/report");
@@ -48,6 +48,7 @@ function objectiveOf(flags) {
48
48
  const cli_run_1 = require("./verify/cli-run");
49
49
  const loader_1 = require("./routes/loader");
50
50
  const rawonly_1 = require("./run/rawonly");
51
+ const mask_policy_cli_1 = require("./mask/mask-policy-cli");
51
52
  const validate_cli_1 = require("./validate-cli");
52
53
  const store_1 = require("./telemetry/store");
53
54
  const budget_1 = require("./watch/budget");
@@ -236,6 +237,11 @@ async function cmdBakeoff(flags) {
236
237
  throw new Error("--budget-usd must be positive");
237
238
  return (0, report_1.runEvalCaseBakeoffCli)(config, evalTask, vs, env, keepOutputsMode(flags), flags.bare.has("json"), objectiveOf(flags));
238
239
  }
240
+ // CS-1 (spec §7): --mask-policy proves no detected span leaves the masked arm
241
+ // (additive early-return; does not touch the RB-1 replay engine). Handler in mask/.
242
+ const maskPolicyRoute = flags.named.get("mask-policy");
243
+ if (maskPolicyRoute !== undefined)
244
+ return (0, mask_policy_cli_1.runMaskPolicyProofCli)(config, flags, maskPolicyRoute);
239
245
  const route = flags.named.get("route");
240
246
  const variantsRaw = flags.named.get("variants");
241
247
  const lastRaw = flags.named.get("replay-last");
@@ -486,13 +492,6 @@ async function cmdServe() {
486
492
  // The server owns the process from here (it holds the listener open).
487
493
  return 0;
488
494
  }
489
- /** `modelrig status` — print the runtime posture (§2): export mode, artifact
490
- * namespace on/off + WHY, content custody, OTLP. Reads config only (no rig, no
491
- * routes dir), so it works anywhere the env is set. */
492
- function cmdStatus() {
493
- console.log((0, status_1.formatStatusReport)((0, status_1.buildRigStatus)((0, config_1.loadConfigFromEnv)())));
494
- return 0;
495
- }
496
495
  /** `modelrig judge <run>` — E5 V.4: explicitly ask the bounded model-judge to
497
496
  * score a run. Goes through the API boundary (T12: the CLI never holds a DB
498
497
  * client) — POST /v1/runs/:id/judge on the configured server, authed with a
@@ -544,6 +543,7 @@ const USAGE = [
544
543
  " modelrig serve (needs the modelrig-server package)",
545
544
  " modelrig bakeoff --route <route> --variants a,b,c --replay-last N [--budget-usd usd] [--objective quality-first|balanced|cost-first] [--keep-outputs[=all]] [--json] [--no-mirror]",
546
545
  " modelrig bakeoff --from-proposal <id> [--replay-last N] [--keep-outputs[=all]] [--json]",
546
+ " modelrig bakeoff --mask-policy <route> [--replay-last N] (CS-1: prove the masked arm leaks no detected span)",
547
547
  " modelrig watch run [--events fixtures.json] [--json]",
548
548
  " modelrig verify-swap --route <route> --captures <fixture.json> [--budget-usd usd] [--out report.md] [--json]",
549
549
  " modelrig swap execute <proposalId> --approve [--approved-by name]",
@@ -564,7 +564,7 @@ async function main(argv) {
564
564
  const flags = parseArgs(rest);
565
565
  switch (command) {
566
566
  case "status":
567
- return cmdStatus();
567
+ return (0, status_cli_1.runStatusCli)();
568
568
  case "validate":
569
569
  return (0, validate_cli_1.runValidateCli)();
570
570
  case "bakeoff":