modelrig 0.5.0 → 0.7.0

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 (159) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +54 -4
  3. package/dist/adapters/anthropic.d.ts.map +1 -1
  4. package/dist/adapters/anthropic.js +36 -7
  5. package/dist/adapters/attempt-meta-map.d.ts +63 -0
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
  7. package/dist/adapters/attempt-meta-map.js +154 -0
  8. package/dist/adapters/deepseek.d.ts.map +1 -1
  9. package/dist/adapters/deepseek.js +25 -2
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +29 -2
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +25 -2
  14. package/dist/adapters/openai-compat.d.ts.map +1 -1
  15. package/dist/adapters/openai-compat.js +25 -2
  16. package/dist/adapters/openai-stream.d.ts +2 -0
  17. package/dist/adapters/openai-stream.d.ts.map +1 -1
  18. package/dist/adapters/openai-stream.js +23 -3
  19. package/dist/adapters/openai.d.ts.map +1 -1
  20. package/dist/adapters/openai.js +36 -6
  21. package/dist/artifacts/artifact.d.ts.map +1 -1
  22. package/dist/artifacts/artifact.js +24 -5
  23. package/dist/artifacts/classification.d.ts +27 -0
  24. package/dist/artifacts/classification.d.ts.map +1 -0
  25. package/dist/artifacts/classification.js +38 -0
  26. package/dist/artifacts/index.d.ts +3 -1
  27. package/dist/artifacts/index.d.ts.map +1 -1
  28. package/dist/artifacts/index.js +10 -1
  29. package/dist/artifacts/run-context.d.ts +25 -3
  30. package/dist/artifacts/run-context.d.ts.map +1 -1
  31. package/dist/artifacts/run-context.js +61 -0
  32. package/dist/artifacts/scrub.d.ts +26 -0
  33. package/dist/artifacts/scrub.d.ts.map +1 -1
  34. package/dist/artifacts/scrub.js +53 -0
  35. package/dist/artifacts/step.d.ts +9 -0
  36. package/dist/artifacts/step.d.ts.map +1 -1
  37. package/dist/artifacts/step.js +9 -0
  38. package/dist/bakeoff/engine.d.ts +46 -4
  39. package/dist/bakeoff/engine.d.ts.map +1 -1
  40. package/dist/bakeoff/engine.js +269 -19
  41. package/dist/bakeoff/eval-cases.d.ts +5 -0
  42. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  43. package/dist/bakeoff/eval-cases.js +3 -0
  44. package/dist/bakeoff/judge.d.ts +39 -0
  45. package/dist/bakeoff/judge.d.ts.map +1 -0
  46. package/dist/bakeoff/judge.js +102 -0
  47. package/dist/bakeoff/quality.d.ts +44 -0
  48. package/dist/bakeoff/quality.d.ts.map +1 -0
  49. package/dist/bakeoff/quality.js +126 -0
  50. package/dist/bakeoff/reference.d.ts +34 -0
  51. package/dist/bakeoff/reference.d.ts.map +1 -0
  52. package/dist/bakeoff/reference.js +68 -0
  53. package/dist/bakeoff/replay.d.ts +25 -0
  54. package/dist/bakeoff/replay.d.ts.map +1 -1
  55. package/dist/bakeoff/replay.js +48 -1
  56. package/dist/bakeoff/report.d.ts +8 -1
  57. package/dist/bakeoff/report.d.ts.map +1 -1
  58. package/dist/bakeoff/report.js +83 -12
  59. package/dist/bakeoff/samples.d.ts +35 -2
  60. package/dist/bakeoff/samples.d.ts.map +1 -1
  61. package/dist/bakeoff/samples.js +67 -6
  62. package/dist/bakeoff/stats.d.ts +17 -1
  63. package/dist/bakeoff/stats.d.ts.map +1 -1
  64. package/dist/bakeoff/stats.js +67 -0
  65. package/dist/bakeoff/types.d.ts +76 -5
  66. package/dist/bakeoff/types.d.ts.map +1 -1
  67. package/dist/bakeoff/types.js +4 -3
  68. package/dist/cli-query.d.ts +14 -0
  69. package/dist/cli-query.d.ts.map +1 -0
  70. package/dist/cli-query.js +59 -0
  71. package/dist/cli.d.ts +2 -1
  72. package/dist/cli.d.ts.map +1 -1
  73. package/dist/cli.js +26 -23
  74. package/dist/config.d.ts +6 -0
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +14 -0
  77. package/dist/detect/detector.d.ts +82 -0
  78. package/dist/detect/detector.d.ts.map +1 -0
  79. package/dist/detect/detector.js +23 -0
  80. package/dist/index.d.ts +19 -14
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +38 -11
  83. package/dist/query/analytics.d.ts +84 -0
  84. package/dist/query/analytics.d.ts.map +1 -0
  85. package/dist/query/analytics.js +280 -0
  86. package/dist/query/columns.d.ts +59 -0
  87. package/dist/query/columns.d.ts.map +1 -0
  88. package/dist/query/columns.js +89 -0
  89. package/dist/query/grammar.d.ts +74 -7
  90. package/dist/query/grammar.d.ts.map +1 -1
  91. package/dist/query/grammar.js +179 -71
  92. package/dist/query/saved.d.ts +26 -0
  93. package/dist/query/saved.d.ts.map +1 -0
  94. package/dist/query/saved.js +51 -0
  95. package/dist/routes/loader.js +15 -10
  96. package/dist/routes/objective-loader.d.ts +35 -0
  97. package/dist/routes/objective-loader.d.ts.map +1 -0
  98. package/dist/routes/objective-loader.js +286 -0
  99. package/dist/run/accept.d.ts +43 -0
  100. package/dist/run/accept.d.ts.map +1 -0
  101. package/dist/run/accept.js +65 -0
  102. package/dist/run/cost.d.ts +13 -0
  103. package/dist/run/cost.d.ts.map +1 -1
  104. package/dist/run/cost.js +27 -0
  105. package/dist/run/dispatch.d.ts +24 -1
  106. package/dist/run/dispatch.d.ts.map +1 -1
  107. package/dist/run/dispatch.js +19 -0
  108. package/dist/run/ladder-key.d.ts +4 -0
  109. package/dist/run/ladder-key.d.ts.map +1 -1
  110. package/dist/run/ladder-key.js +3 -0
  111. package/dist/run/quality-gate.d.ts +39 -0
  112. package/dist/run/quality-gate.d.ts.map +1 -0
  113. package/dist/run/quality-gate.js +70 -0
  114. package/dist/run/raw.d.ts.map +1 -1
  115. package/dist/run/raw.js +22 -3
  116. package/dist/run/record-attempt.d.ts.map +1 -1
  117. package/dist/run/record-attempt.js +22 -1
  118. package/dist/run/routing-docs.d.ts.map +1 -1
  119. package/dist/run/routing-docs.js +11 -4
  120. package/dist/run/run.d.ts.map +1 -1
  121. package/dist/run/run.js +46 -39
  122. package/dist/settings/index.d.ts +1 -1
  123. package/dist/settings/index.d.ts.map +1 -1
  124. package/dist/settings/index.js +2 -1
  125. package/dist/settings/task-settings.d.ts +10 -0
  126. package/dist/settings/task-settings.d.ts.map +1 -1
  127. package/dist/settings/task-settings.js +17 -0
  128. package/dist/telemetry/attempt-meta.d.ts +178 -0
  129. package/dist/telemetry/attempt-meta.d.ts.map +1 -0
  130. package/dist/telemetry/attempt-meta.js +319 -0
  131. package/dist/telemetry/exporter.d.ts.map +1 -1
  132. package/dist/telemetry/exporter.js +6 -0
  133. package/dist/telemetry/ingest-protocol.d.ts +11 -0
  134. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  135. package/dist/telemetry/ingest-protocol.js +3 -0
  136. package/dist/telemetry/otel-map.d.ts +10 -0
  137. package/dist/telemetry/otel-map.d.ts.map +1 -1
  138. package/dist/telemetry/otel-map.js +64 -0
  139. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  140. package/dist/telemetry/store-migrations.js +15 -0
  141. package/dist/telemetry/store.d.ts.map +1 -1
  142. package/dist/telemetry/store.js +25 -7
  143. package/dist/types/failures-teaching.d.ts.map +1 -1
  144. package/dist/types/failures-teaching.js +20 -0
  145. package/dist/types/failures.d.ts +1 -1
  146. package/dist/types/failures.d.ts.map +1 -1
  147. package/dist/types/failures.js +1 -0
  148. package/dist/types/provider.d.ts +9 -0
  149. package/dist/types/provider.d.ts.map +1 -1
  150. package/dist/types/quality.d.ts +41 -0
  151. package/dist/types/quality.d.ts.map +1 -0
  152. package/dist/types/quality.js +12 -0
  153. package/dist/types/route.d.ts +89 -0
  154. package/dist/types/route.d.ts.map +1 -1
  155. package/dist/types/telemetry.d.ts +13 -0
  156. package/dist/types/telemetry.d.ts.map +1 -1
  157. package/dist/validate-cli.d.ts.map +1 -1
  158. package/dist/validate-cli.js +10 -0
  159. package/package.json +1 -1
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * Per-sample value-accuracy resolution for a bake-off (RB-1, near-optimal spec
4
+ * §4.3). DETERMINISTIC tiers only — the judge tier lives in judge.ts because it
5
+ * dispatches a metered inference. Precedence:
6
+ *
7
+ * 1. eval-case expected value (a corrected/golden artifact) ⇒ exact/normalized
8
+ * match for a primitive, or schema-aware field accuracy in [0,1] for an
9
+ * object/array.
10
+ * 2. declared `value_checks` (JSON-pointer assertions) ⇒ fraction passing.
11
+ * 3. null (the caller falls back to the judge, then to conformance).
12
+ *
13
+ * Pure; unit-tested directly. F5 (deterministic before judge) is exactly this
14
+ * ordering.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.fieldAccuracy = fieldAccuracy;
18
+ exports.resolveJsonPointer = resolveJsonPointer;
19
+ exports.checkPasses = checkPasses;
20
+ exports.resolveDeterministicQuality = resolveDeterministicQuality;
21
+ /** Normalize a scalar for comparison: trim strings; leave numbers/booleans as-is.
22
+ * "normalized match" (spec §4.3) means insensitivity to surrounding whitespace,
23
+ * never a fuzzy match — a wrong value is still wrong. */
24
+ function normalizeScalar(v) {
25
+ return typeof v === "string" ? v.trim() : v;
26
+ }
27
+ function scalarEqual(a, b) {
28
+ return normalizeScalar(a) === normalizeScalar(b);
29
+ }
30
+ function isObject(v) {
31
+ return typeof v === "object" && v !== null && !Array.isArray(v);
32
+ }
33
+ /**
34
+ * Schema-aware field accuracy in [0,1]: the fraction of the EXPECTED value's leaf
35
+ * fields that the actual output matches. Recurses into objects/arrays keyed by
36
+ * the expected shape (extra keys in `actual` are ignored — the golden defines
37
+ * what "correct" covers). A scalar expected ⇒ 1 or 0. An empty object/array
38
+ * expected ⇒ 1 (nothing to be wrong about).
39
+ */
40
+ function fieldAccuracy(expected, actual) {
41
+ if (isObject(expected)) {
42
+ const keys = Object.keys(expected);
43
+ if (keys.length === 0)
44
+ return 1;
45
+ let sum = 0;
46
+ for (const k of keys)
47
+ sum += fieldAccuracy(expected[k], isObject(actual) ? actual[k] : undefined);
48
+ return sum / keys.length;
49
+ }
50
+ if (Array.isArray(expected)) {
51
+ if (expected.length === 0)
52
+ return 1;
53
+ const act = Array.isArray(actual) ? actual : [];
54
+ let sum = 0;
55
+ for (let i = 0; i < expected.length; i += 1)
56
+ sum += fieldAccuracy(expected[i], act[i]);
57
+ return sum / expected.length;
58
+ }
59
+ return scalarEqual(expected, actual) ? 1 : 0;
60
+ }
61
+ /** Resolve an RFC 6901 JSON pointer against a value. Returns `undefined` when any
62
+ * segment is missing (a missing field fails its value check). */
63
+ function resolveJsonPointer(value, pointer) {
64
+ if (pointer === "")
65
+ return value;
66
+ const segments = pointer
67
+ .split("/")
68
+ .slice(1)
69
+ .map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
70
+ let cur = value;
71
+ for (const seg of segments) {
72
+ if (isObject(cur)) {
73
+ cur = cur[seg];
74
+ }
75
+ else if (Array.isArray(cur)) {
76
+ const idx = Number(seg);
77
+ cur = Number.isInteger(idx) && idx >= 0 ? cur[idx] : undefined;
78
+ }
79
+ else {
80
+ return undefined;
81
+ }
82
+ if (cur === undefined)
83
+ return undefined;
84
+ }
85
+ return cur;
86
+ }
87
+ /** True when `output` satisfies one value check (RFC 6901 pointer + literal|regex). */
88
+ function checkPasses(output, check) {
89
+ const actual = resolveJsonPointer(output, check.path);
90
+ if (actual === undefined)
91
+ return false;
92
+ if ("regex" in check.expect) {
93
+ return new RegExp(check.expect.regex).test(typeof actual === "string" ? actual : JSON.stringify(actual));
94
+ }
95
+ const lit = check.expect.literal;
96
+ if (isObject(lit) || Array.isArray(lit)) {
97
+ return JSON.stringify(normalizeDeep(lit)) === JSON.stringify(normalizeDeep(actual));
98
+ }
99
+ return scalarEqual(lit, actual);
100
+ }
101
+ function normalizeDeep(v) {
102
+ if (Array.isArray(v))
103
+ return v.map(normalizeDeep);
104
+ if (isObject(v)) {
105
+ const out = {};
106
+ for (const k of Object.keys(v).sort())
107
+ out[k] = normalizeDeep(v[k]);
108
+ return out;
109
+ }
110
+ return normalizeScalar(v);
111
+ }
112
+ /**
113
+ * Deterministic value accuracy for one sample, or null when neither an eval-case
114
+ * expected value nor a value check applies (the caller then tries the judge).
115
+ */
116
+ function resolveDeterministicQuality(input) {
117
+ if (input.hasExpected === true) {
118
+ return fieldAccuracy(input.expected, input.output);
119
+ }
120
+ const checks = input.valueChecks;
121
+ if (checks !== undefined && checks.length > 0) {
122
+ const passing = checks.filter((c) => checkPasses(input.output, c)).length;
123
+ return passing / checks.length;
124
+ }
125
+ return null;
126
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `reference: auto` resolution (RB-1, near-optimal spec §2/§3). The reference arm
3
+ * defines "optimal" for a bake-off: the strongest DECLARED candidate for the
4
+ * route's task.
5
+ *
6
+ * WHAT SIGNAL WE USE — and why it is what it is. The RigIndex Bradley-Terry layer
7
+ * (the truest per-task strength) is NOT shipped in the SDK package, and nothing
8
+ * at SDK runtime is keyed by a route's schema fingerprint cell. The strongest
9
+ * MEASURED quality signal that ships offline is each model's probed schema
10
+ * conformance in `registry.json` (surfaced on ModelSelectRow.conformRate). So the
11
+ * deterministic rule is:
12
+ *
13
+ * among the route's declared candidates with a probed conformance:
14
+ * rank by conformRate DESC, then effectiveUsdPer1k ASC, then provider/model ASC
15
+ * → the top row is the reference; null when no declared candidate is probed.
16
+ *
17
+ * This is pure, offline, deterministic, and reuses the exact registry number the
18
+ * capability gates already trust (select.ts CONFORMANCE_BAR). The report NAMES
19
+ * this rule and its source ("registry.json probed conformance") so the choice is
20
+ * never mistaken for a frontier oracle. An explicit `reference: provider/model`
21
+ * bypasses this entirely (validated in the loader).
22
+ */
23
+ import type { CandidateSpec, RouteBundle } from "../types/route";
24
+ import type { ModelSelectRow } from "../registry/select";
25
+ /** The rule name + source, surfaced on the report so `auto` is legible. */
26
+ export declare const REFERENCE_AUTO_RULE: string;
27
+ export declare function pickReferenceCandidate(candidates: readonly CandidateSpec[], rows: readonly ModelSelectRow[]): CandidateSpec | null;
28
+ /**
29
+ * A `reference: auto` resolver bound to the packaged registry (createRig wires
30
+ * this). Registry rows load once, lazily, and cache; a missing/unreadable
31
+ * registry ⇒ [] ⇒ null (no reference arm, graceful).
32
+ */
33
+ export declare function makeReferenceAutoResolver(registryPath?: string): (route: RouteBundle) => CandidateSpec | null;
34
+ //# sourceMappingURL=reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/bakeoff/reference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,QAEiC,CAAC;AAElE,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,IAAI,EAAE,SAAS,cAAc,EAAE,GAC9B,aAAa,GAAG,IAAI,CAmBtB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,CAAC,EAAE,MAAM,GACpB,CAAC,KAAK,EAAE,WAAW,KAAK,aAAa,GAAG,IAAI,CAY9C"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * `reference: auto` resolution (RB-1, near-optimal spec §2/§3). The reference arm
4
+ * defines "optimal" for a bake-off: the strongest DECLARED candidate for the
5
+ * route's task.
6
+ *
7
+ * WHAT SIGNAL WE USE — and why it is what it is. The RigIndex Bradley-Terry layer
8
+ * (the truest per-task strength) is NOT shipped in the SDK package, and nothing
9
+ * at SDK runtime is keyed by a route's schema fingerprint cell. The strongest
10
+ * MEASURED quality signal that ships offline is each model's probed schema
11
+ * conformance in `registry.json` (surfaced on ModelSelectRow.conformRate). So the
12
+ * deterministic rule is:
13
+ *
14
+ * among the route's declared candidates with a probed conformance:
15
+ * rank by conformRate DESC, then effectiveUsdPer1k ASC, then provider/model ASC
16
+ * → the top row is the reference; null when no declared candidate is probed.
17
+ *
18
+ * This is pure, offline, deterministic, and reuses the exact registry number the
19
+ * capability gates already trust (select.ts CONFORMANCE_BAR). The report NAMES
20
+ * this rule and its source ("registry.json probed conformance") so the choice is
21
+ * never mistaken for a frontier oracle. An explicit `reference: provider/model`
22
+ * bypasses this entirely (validated in the loader).
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.REFERENCE_AUTO_RULE = void 0;
26
+ exports.pickReferenceCandidate = pickReferenceCandidate;
27
+ exports.makeReferenceAutoResolver = makeReferenceAutoResolver;
28
+ const rows_1 = require("../registry/rows");
29
+ /** The rule name + source, surfaced on the report so `auto` is legible. */
30
+ exports.REFERENCE_AUTO_RULE = "strongest declared candidate by probed schema conformance " +
31
+ "(registry.json), tie-break effective $/1k then provider/model";
32
+ function pickReferenceCandidate(candidates, rows) {
33
+ const byKey = new Map(rows.map((r) => [r.key, r]));
34
+ const ranked = candidates
35
+ .map((c) => ({ candidate: c, row: byKey.get(`${c.provider}/${c.model}`) }))
36
+ .filter((x) => x.row !== undefined && x.row.conformRate !== null)
37
+ .sort((a, b) => {
38
+ const conf = (b.row.conformRate ?? 0) - (a.row.conformRate ?? 0);
39
+ if (conf !== 0)
40
+ return conf;
41
+ const ea = a.row.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY;
42
+ const eb = b.row.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY;
43
+ if (ea !== eb)
44
+ return ea - eb;
45
+ return `${a.candidate.provider}/${a.candidate.model}`.localeCompare(`${b.candidate.provider}/${b.candidate.model}`);
46
+ });
47
+ const top = ranked[0];
48
+ return top === undefined ? null : { provider: top.candidate.provider, model: top.candidate.model };
49
+ }
50
+ /**
51
+ * A `reference: auto` resolver bound to the packaged registry (createRig wires
52
+ * this). Registry rows load once, lazily, and cache; a missing/unreadable
53
+ * registry ⇒ [] ⇒ null (no reference arm, graceful).
54
+ */
55
+ function makeReferenceAutoResolver(registryPath) {
56
+ let rows = null;
57
+ return (route) => {
58
+ if (rows === null) {
59
+ try {
60
+ rows = (0, rows_1.loadRegistryRows)(registryPath);
61
+ }
62
+ catch {
63
+ rows = [];
64
+ }
65
+ }
66
+ return pickReferenceCandidate(route.candidates, rows);
67
+ };
68
+ }
@@ -19,6 +19,8 @@
19
19
  */
20
20
  import type { CaptureStore } from "../telemetry/capture";
21
21
  import type { RunDeps } from "../run/run";
22
+ import type { JudgeScorer } from "./judge";
23
+ import type { QualityGate } from "../types/quality";
22
24
  export interface ReplayRequest {
23
25
  readonly route: string;
24
26
  readonly variant: string;
@@ -28,12 +30,35 @@ export interface ReplayRequest {
28
30
  * sample for the human value review. Off by default (bounds memory + keeps
29
31
  * the pure-metrics path clean). */
30
32
  readonly keepOutputs?: boolean;
33
+ /** RB-1: corrected/golden expected outputs keyed by captureId (eval-case path).
34
+ * Presence in the map ⇒ this sample has an expected value; `value` may be any
35
+ * JSON (including null). Quality tier 1 (eval case) when present. */
36
+ readonly expected?: ReadonlyMap<string, {
37
+ readonly value: unknown;
38
+ }>;
39
+ /** RB-1: an opt-in judge scorer (tier 3), used only when neither an eval-case
40
+ * expected value nor a declared value check yields a score. Capped internally. */
41
+ readonly judge?: JudgeScorer;
42
+ /** Q1 quality gate (spec §4.2): scored on each replayed sample's output to
43
+ * populate `valueAccuracy` + `qualityPassed`. NOT passed into the inner
44
+ * `run()` — a bake-off MEASURES quality, it does not gate serving; injecting
45
+ * the gate into the run would reject samples and corrupt the conformance
46
+ * numbers it is trying to compare. Runs in the SAME value tier as the RB-1
47
+ * judge — whichever is supplied wins after the deterministic checks (F5). */
48
+ readonly qualityGate?: QualityGate;
31
49
  }
32
50
  export interface ReplaySampleResult {
33
51
  readonly captureId: string;
34
52
  readonly conform: boolean;
35
53
  readonly repaired: boolean;
54
+ /** Q1: the gate's score (or 1/0 from `ok` when it returned no score); null
55
+ * when no gate ran or the sample produced no output (a failure). */
36
56
  readonly valueAccuracy: number | null;
57
+ /** Q1: whether the gate PASSED this sample (`verdict.ok`); null when no gate
58
+ * ran or the sample failed. Drives the bake-off's quality pass-rate CI and
59
+ * `effectiveUsdPer1kPassing` (stats.ts) — distinct from `valueAccuracy`, which
60
+ * may be a continuous score. */
61
+ readonly qualityPassed: boolean | null;
37
62
  readonly costUsd: number;
38
63
  /** Portion of costUsd spent on repair calls (repaired_by rows). */
39
64
  readonly repairCostUsd: number;
@@ -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,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;CAChC;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,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,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,CAwFxC"}
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"}
@@ -24,6 +24,8 @@ const node_crypto_1 = require("node:crypto");
24
24
  const failures_1 = require("../types/failures");
25
25
  const run_1 = require("../run/run");
26
26
  const variant_1 = require("../run/variant");
27
+ const quality_1 = require("./quality");
28
+ const quality_gate_1 = require("../run/quality-gate");
27
29
  async function replayVariant(deps, captures, req) {
28
30
  const rows = captures.readLast(req.route, req.lastN);
29
31
  const replayId = (0, node_crypto_1.randomUUID)();
@@ -55,6 +57,12 @@ async function replayVariant(deps, captures, req) {
55
57
  let repaired = false;
56
58
  let failureClass = null;
57
59
  let outputText = null;
60
+ let outputValue = undefined;
61
+ let produced = false;
62
+ // Q1 (spec §4.2): the gate scores the arm's output OUTSIDE the run loop —
63
+ // captured here so it can be evaluated after the try without leaking the
64
+ // (envelope-typed) failure path into the gate.
65
+ let gated = null;
58
66
  try {
59
67
  // Through the REAL run loop: the variant's template re-renders against
60
68
  // the replay candidate's capability flags (never the captured prompt
@@ -71,8 +79,13 @@ async function replayVariant(deps, captures, req) {
71
79
  });
72
80
  conform = result.meta.validated;
73
81
  repaired = result.meta.repairedBy !== null;
82
+ outputValue = result.output;
83
+ produced = true;
74
84
  if (req.keepOutputs)
75
85
  outputText = JSON.stringify(result.output, null, 2);
86
+ if (req.qualityGate !== undefined) {
87
+ gated = { output: result.output, provider: result.meta.provider, model: result.meta.model, servedVariant: result.meta.servedVariant };
88
+ }
76
89
  }
77
90
  catch (err) {
78
91
  if (!(err instanceof failures_1.RigFailureError))
@@ -80,6 +93,39 @@ async function replayVariant(deps, captures, req) {
80
93
  failureClass = err.failure.class;
81
94
  }
82
95
  const latencyMs = Date.now() - started;
96
+ // Unified value tier (RB-1 §4.3 + Q1 §4.2, F5 deterministic-first). Only on a
97
+ // produced output; a failed sample stays unscored (its per-sample quality is
98
+ // then 1/0 conformance). Precedence for `valueAccuracy` (the regret quality):
99
+ // eval-case expected → declared value_checks → Q1 SDK gate score → RB-1
100
+ // declared judge → null. The Q1 gate, when supplied, ALWAYS runs to set
101
+ // `qualityPassed` (the boolean verdict that drives the cost-first pass-rate
102
+ // gate), independent of which tier won `valueAccuracy`.
103
+ let valueAccuracy = null;
104
+ let qualityPassed = null;
105
+ if (produced) {
106
+ const expected = req.expected?.get(capture.inferenceId);
107
+ const det = (0, quality_1.resolveDeterministicQuality)({
108
+ output: outputValue,
109
+ ...(route.valueChecks !== undefined ? { valueChecks: route.valueChecks } : {}),
110
+ ...(expected !== undefined ? { hasExpected: true, expected: expected.value } : {}),
111
+ });
112
+ let gateScore = null;
113
+ if (req.qualityGate !== undefined && gated !== null) {
114
+ const verdict = await (0, quality_gate_1.evaluateQualityGate)(req.qualityGate, gated.output, {
115
+ route: req.route, provider: gated.provider, model: gated.model,
116
+ candidateIndex: 0, attemptSeq: 1, servedVariant: gated.servedVariant,
117
+ });
118
+ qualityPassed = verdict.pass;
119
+ gateScore = verdict.score !== null ? verdict.score : verdict.pass ? 1 : 0;
120
+ }
121
+ if (det !== null)
122
+ valueAccuracy = det;
123
+ else if (gateScore !== null)
124
+ valueAccuracy = gateScore;
125
+ else if (req.judge !== undefined) {
126
+ valueAccuracy = await req.judge.score(outputValue, JSON.parse(capture.variablesJson));
127
+ }
128
+ }
83
129
  // Per-sample cost from the tagged attempt rows — base + retries + repair
84
130
  // + search, exactly what telemetry billed (F1-corrected).
85
131
  const attemptRows = deps.telemetry.queryByTag("replay_capture", sampleTag);
@@ -92,7 +138,8 @@ async function replayVariant(deps, captures, req) {
92
138
  captureId: capture.inferenceId,
93
139
  conform,
94
140
  repaired,
95
- valueAccuracy: null, // golden-set hook — expected values are Phase 4's judge/goldens work
141
+ valueAccuracy, // eval-case / value-check / Q1 gate / RB-1 judge, else null (→ conformance)
142
+ qualityPassed, // Q1: boolean gate verdict; null when no gate ran or the sample failed
96
143
  costUsd,
97
144
  repairCostUsd,
98
145
  pricingMissing,
@@ -8,6 +8,13 @@ import type { RigConfig } from "../config";
8
8
  import type { CacheSimAssumptions } from "./cache-sim";
9
9
  import type { KeepOutputsMode } from "./samples";
10
10
  import type { BakeoffArtifact } from "./types";
11
+ import type { ObjectiveMode } from "../types/route";
12
+ /**
13
+ * RB-1 headline (spec §4.5): one line stating the proposed arm's regret with CI
14
+ * vs the reference, its cost, and the ceiling. Returns null when there is no
15
+ * proposal or no reference (the plain matrix stands). Exported for the PR body.
16
+ */
17
+ export declare function bakeoffHeadline(artifact: BakeoffArtifact): string | null;
11
18
  /**
12
19
  * One variant's measured-cold vs cache-adjusted input cost (cache-aware-routing
13
20
  * spec §5.4). Built by the caller from `simulateCache` + the candidate's rates;
@@ -37,5 +44,5 @@ export declare function printMatrix(artifact: BakeoffArtifact, cacheAdjusted?: r
37
44
  * Returns the process exit code: 0 on a run (or an empty suite), 1 when cases
38
45
  * existed but NONE could be rehydrated (an actionable non-zero, not a crash).
39
46
  */
40
- export declare function runEvalCaseBakeoffCli(config: RigConfig, task: string, variants: readonly string[], envelope: number | undefined, keep: KeepOutputsMode | undefined, asJson: boolean): Promise<number>;
47
+ export declare function runEvalCaseBakeoffCli(config: RigConfig, task: string, variants: readonly string[], envelope: number | undefined, keep: KeepOutputsMode | undefined, asJson: boolean, objectiveMode?: ObjectiveMode): Promise<number>;
41
48
  //# sourceMappingURL=report.d.ts.map
@@ -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;AAM/D;;;;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,CAgC5G;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,GACd,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
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"}
@@ -6,6 +6,7 @@
6
6
  * with a reason before any bake-off runs — never a silent subset (spec §3.3).
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.bakeoffHeadline = bakeoffHeadline;
9
10
  exports.formatCacheAdjustedSection = formatCacheAdjustedSection;
10
11
  exports.printMatrix = printMatrix;
11
12
  exports.runEvalCaseBakeoffCli = runEvalCaseBakeoffCli;
@@ -17,6 +18,55 @@ const engine_1 = require("./engine");
17
18
  function fmtEff(usd) {
18
19
  return Number.isFinite(usd) ? `$${usd.toFixed(4)}` : "∞ (0 conformant)";
19
20
  }
21
+ /** RB-1: quality points (a 0..1 metric shown as pts, ×100). */
22
+ function fmtPts(v) {
23
+ return (v * 100).toFixed(1);
24
+ }
25
+ /** RB-1: an arm's regret cell — "0.8 [−0.4…2.1] n=212", "ref", or "—". */
26
+ function fmtRegret(m) {
27
+ if (m.isReference === true)
28
+ return "ref";
29
+ const r = m.regret;
30
+ if (r == null)
31
+ return "—";
32
+ return `${fmtPts(r.regretMean)} [${fmtPts(r.regretCi95[0])}…${fmtPts(r.regretCi95[1])}] n=${r.nPaired}`;
33
+ }
34
+ /** RB-1: an arm's quality cell — "94% value_accuracy" or "—". */
35
+ function fmtQuality(m) {
36
+ return m.qualityMean === null ? "—" : `${(m.qualityMean * 100).toFixed(0)}% ${m.qualityMetric}`;
37
+ }
38
+ /** The reference $/1k or per-run ceiling from the objective, formatted, or null
39
+ * when no ceiling is declared. */
40
+ function fmtCeiling(artifact) {
41
+ const c = artifact.objective?.priceCeiling;
42
+ if (c == null)
43
+ return null;
44
+ if (c.usdPer1k !== undefined)
45
+ return `$${c.usdPer1k.toFixed(2)}/1k`;
46
+ if (c.usdPerRun !== undefined)
47
+ return `$${c.usdPerRun.toFixed(4)}/run`;
48
+ return null;
49
+ }
50
+ /**
51
+ * RB-1 headline (spec §4.5): one line stating the proposed arm's regret with CI
52
+ * vs the reference, its cost, and the ceiling. Returns null when there is no
53
+ * proposal or no reference (the plain matrix stands). Exported for the PR body.
54
+ */
55
+ function bakeoffHeadline(artifact) {
56
+ const p = artifact.proposal;
57
+ const ref = artifact.reference;
58
+ if (p == null || ref == null || p.regret == null)
59
+ return null;
60
+ const arm = artifact.variants.find((v) => v.variant === p.variant);
61
+ const cost = arm !== undefined && Number.isFinite(arm.effectiveUsdPer1kConformant)
62
+ ? `$${arm.effectiveUsdPer1kConformant.toFixed(2)}/1k`
63
+ : "cost n/a";
64
+ const ceiling = fmtCeiling(artifact);
65
+ const r = p.regret;
66
+ return (`${p.variant} — regret ${fmtPts(r.regretMean)} pts ` +
67
+ `(CI ${fmtPts(r.regretCi95[0])}…${fmtPts(r.regretCi95[1])}, n=${r.nPaired}) vs reference ${ref.model}, ` +
68
+ `${cost}${ceiling !== null ? ` vs ceiling ${ceiling}` : ""} — proposed`);
69
+ }
20
70
  /**
21
71
  * Render the cache-adjusted section (spec §5.4) as lines — measured-cold AND
22
72
  * cache-adjusted input cost side by side, with the simulator's assumptions
@@ -59,27 +109,47 @@ function formatCacheAdjustedSection(rows) {
59
109
  }
60
110
  function printMatrix(artifact, cacheAdjusted = []) {
61
111
  console.log(`bakeoff ${artifact.id}\nroute ${artifact.route}@${artifact.routeVersion} · replayed ${artifact.replayN} captured inputs · ${artifact.ranAt}`);
112
+ // RB-1: a reference arm is present ⇒ show quality + regret columns and badge
113
+ // the reference row; otherwise the pre-RB-1 matrix stands byte-for-byte.
114
+ const hasReference = artifact.variants.some((m) => m.isReference === true);
62
115
  const header = ["variant", "n", "conform", "ci95", "repair", "repair$", "lat(ms)", "eff-$/1K-conf"];
63
- const rows = artifact.variants.map((m) => [
64
- m.variant,
65
- String(m.samples),
66
- `${(m.conformRate * 100).toFixed(0)}%`,
67
- `[${(m.conformCi95[0] * 100).toFixed(0)},${(m.conformCi95[1] * 100).toFixed(0)}]`,
68
- `${(m.repairRate * 100).toFixed(0)}%`,
69
- `$${m.meanRepairCostUsd.toFixed(4)}`,
70
- m.meanLatencyMs.toFixed(0),
71
- m.pricingMissing ? "UNPRICED (no ranking)" : fmtEff(m.effectiveUsdPer1kConformant),
72
- ]);
116
+ if (hasReference)
117
+ header.push("quality", "regret(pts)");
118
+ const rows = artifact.variants.map((m) => {
119
+ const base = [
120
+ m.isReference === true ? `${m.variant} [ref]` : m.variant,
121
+ String(m.samples),
122
+ `${(m.conformRate * 100).toFixed(0)}%`,
123
+ `[${(m.conformCi95[0] * 100).toFixed(0)},${(m.conformCi95[1] * 100).toFixed(0)}]`,
124
+ `${(m.repairRate * 100).toFixed(0)}%`,
125
+ `$${m.meanRepairCostUsd.toFixed(4)}`,
126
+ m.meanLatencyMs.toFixed(0),
127
+ m.pricingMissing ? "UNPRICED (no ranking)" : fmtEff(m.effectiveUsdPer1kConformant),
128
+ ];
129
+ if (hasReference)
130
+ base.push(fmtQuality(m), fmtRegret(m));
131
+ return base;
132
+ });
73
133
  const widths = header.map((h, c) => Math.max(h.length, ...rows.map((r) => r[c].length)));
74
134
  const line = (cells) => cells.map((cell, c) => cell.padEnd(widths[c])).join(" ");
75
135
  console.log(line(header));
76
136
  for (const row of rows)
77
137
  console.log(line(row));
78
- if (artifact.proposal !== null) {
138
+ const headline = bakeoffHeadline(artifact);
139
+ if (headline !== null) {
140
+ console.log(`PROPOSAL: ${headline} — human decision required; nothing is executed.`);
141
+ }
142
+ else if (artifact.proposal !== null) {
79
143
  console.log(`PROPOSAL: variant "${artifact.proposal.variant}" beats the incumbent by ` +
80
144
  `${artifact.proposal.vsIncumbentSavingsPct.toFixed(1)}% effective cost — human decision required; ` +
81
145
  "nothing is executed.");
82
146
  }
147
+ else if (hasReference && artifact.objective !== undefined) {
148
+ const bound = artifact.objective.maxRegret === null && artifact.objective.priceCeiling === null;
149
+ console.log(bound
150
+ ? "proposal: none — regret reported; declare objective.max_regret or price_ceiling to propose."
151
+ : "proposal: none (no arm is within the objective's bounds and better than the incumbent)");
152
+ }
83
153
  else {
84
154
  console.log("proposal: none (no variant beats the incumbent's gates)");
85
155
  }
@@ -95,7 +165,7 @@ function printMatrix(artifact, cacheAdjusted = []) {
95
165
  * Returns the process exit code: 0 on a run (or an empty suite), 1 when cases
96
166
  * existed but NONE could be rehydrated (an actionable non-zero, not a crash).
97
167
  */
98
- async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJson) {
168
+ async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJson, objectiveMode) {
99
169
  const mirrorTarget = config.export?.mode === "direct" ? config.export : undefined;
100
170
  const rig = (0, index_1.createRig)(config);
101
171
  try {
@@ -103,6 +173,7 @@ async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJ
103
173
  variants,
104
174
  ...(envelope !== undefined ? { envelopeUsdPerVariant: envelope } : {}),
105
175
  ...(keep !== undefined ? { keepOutputs: keep } : {}),
176
+ ...(objectiveMode !== undefined ? { objectiveMode } : {}),
106
177
  });
107
178
  console.log(`eval cases for "${task}": ${selection.total} total · ` +
108
179
  `${selection.rehydratable.length} rehydratable · ${selection.refusals.length} refused`);
@@ -8,8 +8,19 @@
8
8
  * key; read server-side only; deny-anon RLS. NEVER the anon staging posture.
9
9
  *
10
10
  * Transport is raw PostgREST over fetch (same pattern as the exporter).
11
+ *
12
+ * ── SCRUB BEFORE PERSIST (S7, screening B0-a) ───────────────────────────────
13
+ * `bakeoff_samples` is the largest server-side customer-content path, so the
14
+ * task's scrub setting — the SAME `ScrubMode` the content-custody + capture-write
15
+ * paths honor — runs on `inputText` AND `outputText` BEFORE any row is built or
16
+ * POSTed (mirrors src/run/capture-write.ts). off/detect ⇒ verbatim (detect still
17
+ * COUNTS findings onto the row's `arm_meta.scrub`, never values); redact ⇒ masked
18
+ * copy; block ⇒ a sample with any finding becomes metadata-only (input/output
19
+ * dropped) and counted. Findings are `{type,count}` only — never a matched value.
11
20
  */
12
21
  import type { BakeoffSamplesSink } from "./engine";
22
+ import { type ScrubMode } from "../artifacts/scrub";
23
+ import type { TaskSettingsResolver } from "../settings/task-settings";
13
24
  /** 256KB per output, per the architect's caps; TOAST compresses at rest. */
14
25
  export declare const OUTPUT_MAX_BYTES: number;
15
26
  export type KeepOutputsMode = "winner" | "all";
@@ -19,7 +30,9 @@ export interface BakeoffSample {
19
30
  readonly arm: string;
20
31
  readonly armMeta: Record<string, unknown> | null;
21
32
  readonly inputRef: string;
22
- readonly inputText: string;
33
+ /** Null when `block`-mode scrub dropped it (metadata-only sample). At persist
34
+ * the NOT-NULL `input_text` column coalesces null → "" (see the insert body). */
35
+ readonly inputText: string | null;
23
36
  readonly outputText: string | null;
24
37
  readonly conform: boolean;
25
38
  readonly costUsd: number;
@@ -45,7 +58,13 @@ export declare function truncateOutput(text: string | null): string | null;
45
58
  /** Default retention: 90 days from now (or pass a sooner stamp). Pure over
46
59
  * an injected `now` so it stays testable and workflow-safe. */
47
60
  export declare function retentionExpiry(nowMs: number, days?: number): string;
48
- export declare function buildBakeoffSamples(bakeoffId: string, route: string, arms: readonly ArmSamples[], expiresAt: string): BakeoffSample[];
61
+ /**
62
+ * Flatten arms×samples into rows, scrubbing input + output with the task's
63
+ * `mode` (S7, default `off` ⇒ verbatim, byte-identical). Scrub findings ride the
64
+ * row's `arm_meta.scrub` as counts-by-type (never values); a `block`-mode sample
65
+ * with any finding is stored metadata-only (input/output dropped).
66
+ */
67
+ export declare function buildBakeoffSamples(bakeoffId: string, route: string, arms: readonly ArmSamples[], expiresAt: string, mode?: ScrubMode): BakeoffSample[];
49
68
  export interface BakeoffSamplesClientConfig {
50
69
  readonly supabaseUrl: string;
51
70
  readonly serviceRoleKey: string;
@@ -73,5 +92,19 @@ export declare function createBakeoffSamplesClient(config: BakeoffSamplesClientC
73
92
  export declare function buildDirectSamplesSink(cfg: {
74
93
  readonly supabaseUrl: string;
75
94
  readonly serviceRoleKey: string;
95
+ /** Control-plane task-settings resolver (settings-plane §1.5). Null local-only
96
+ * / direct-mode rigs have none — then `defaultScrub` governs. */
97
+ readonly resolver?: TaskSettingsResolver | null;
98
+ /** The rig-level scrub default (config content-custody scrub) — the fallback
99
+ * when no resolver answers for the route. Defaults `off` ⇒ verbatim. */
100
+ readonly defaultScrub?: ScrubMode;
76
101
  }): BakeoffSamplesSink;
102
+ /**
103
+ * The effective scrub mode for a bake-off sample write — resolved EXACTLY as
104
+ * capture-write.ts does: a configured control plane WINS (`resolver.get(route)`),
105
+ * else the rig's config default. A local-only / direct-mode rig has no resolver,
106
+ * so it falls to `configDefault`. Pure and synchronous (the resolver read is
107
+ * cache-only, never a network hop).
108
+ */
109
+ export declare function resolveSampleScrubMode(resolver: TaskSettingsResolver | null, route: string, configDefault: ScrubMode): ScrubMode;
77
110
  //# sourceMappingURL=samples.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/bakeoff/samples.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAE3C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC,CAAC;CACJ;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,EAAE,CAKV;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED;+DAC+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,UAAU,EAAE,EAC3B,SAAS,EAAE,MAAM,GAChB,aAAa,EAAE,CAoBjB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,2EAA2E;IAC3E,KAAK,CAAC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACxE;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,oBAAoB,CAoEnG;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,GAAG,kBAAkB,CAQrB"}
1
+ {"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/bakeoff/samples.ts"],"names":[],"mappings":"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"}