modelrig 0.0.1 → 0.1.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 (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +115 -8
  3. package/dist/adapters/anthropic.d.ts +46 -0
  4. package/dist/adapters/anthropic.d.ts.map +1 -0
  5. package/dist/adapters/anthropic.js +202 -0
  6. package/dist/adapters/deepseek.d.ts +41 -0
  7. package/dist/adapters/deepseek.d.ts.map +1 -0
  8. package/dist/adapters/deepseek.js +163 -0
  9. package/dist/adapters/gemini.d.ts +44 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +222 -0
  12. package/dist/adapters/grok.d.ts +44 -0
  13. package/dist/adapters/grok.d.ts.map +1 -0
  14. package/dist/adapters/grok.js +168 -0
  15. package/dist/adapters/openai-compat.d.ts +74 -0
  16. package/dist/adapters/openai-compat.d.ts.map +1 -0
  17. package/dist/adapters/openai-compat.js +187 -0
  18. package/dist/adapters/openai.d.ts +49 -0
  19. package/dist/adapters/openai.d.ts.map +1 -0
  20. package/dist/adapters/openai.js +222 -0
  21. package/dist/bakeoff/engine.d.ts +52 -0
  22. package/dist/bakeoff/engine.d.ts.map +1 -0
  23. package/dist/bakeoff/engine.js +163 -0
  24. package/dist/bakeoff/replay.d.ts +52 -0
  25. package/dist/bakeoff/replay.d.ts.map +1 -0
  26. package/dist/bakeoff/replay.js +94 -0
  27. package/dist/bakeoff/samples.d.ts +62 -0
  28. package/dist/bakeoff/samples.d.ts.map +1 -0
  29. package/dist/bakeoff/samples.js +128 -0
  30. package/dist/bakeoff/stats.d.ts +13 -0
  31. package/dist/bakeoff/stats.d.ts.map +1 -0
  32. package/dist/bakeoff/stats.js +48 -0
  33. package/dist/bakeoff/types.d.ts +37 -0
  34. package/dist/bakeoff/types.d.ts.map +1 -0
  35. package/dist/bakeoff/types.js +10 -0
  36. package/dist/cli.d.ts +27 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +572 -0
  39. package/dist/config.d.ts +104 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/config.js +148 -0
  42. package/dist/feedback/coverage.d.ts +32 -0
  43. package/dist/feedback/coverage.d.ts.map +1 -0
  44. package/dist/feedback/coverage.js +95 -0
  45. package/dist/feedback/feedback.d.ts +35 -0
  46. package/dist/feedback/feedback.d.ts.map +1 -0
  47. package/dist/feedback/feedback.js +39 -0
  48. package/dist/index.d.ts +155 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +448 -0
  51. package/dist/init/finder.d.ts +79 -0
  52. package/dist/init/finder.d.ts.map +1 -0
  53. package/dist/init/finder.js +297 -0
  54. package/dist/init/run.d.ts +99 -0
  55. package/dist/init/run.d.ts.map +1 -0
  56. package/dist/init/run.js +281 -0
  57. package/dist/init/signatures.d.ts +39 -0
  58. package/dist/init/signatures.d.ts.map +1 -0
  59. package/dist/init/signatures.js +116 -0
  60. package/dist/matrix/aggregate.d.ts +89 -0
  61. package/dist/matrix/aggregate.d.ts.map +1 -0
  62. package/dist/matrix/aggregate.js +186 -0
  63. package/dist/query/grammar.d.ts +35 -0
  64. package/dist/query/grammar.d.ts.map +1 -0
  65. package/dist/query/grammar.js +279 -0
  66. package/dist/query/run.d.ts +8 -0
  67. package/dist/query/run.d.ts.map +1 -0
  68. package/dist/query/run.js +21 -0
  69. package/dist/registry/build.d.ts +126 -0
  70. package/dist/registry/build.d.ts.map +1 -0
  71. package/dist/registry/build.js +192 -0
  72. package/dist/registry/call-notes.d.ts +11 -0
  73. package/dist/registry/call-notes.d.ts.map +1 -0
  74. package/dist/registry/call-notes.js +72 -0
  75. package/dist/registry/claims.d.ts +63 -0
  76. package/dist/registry/claims.d.ts.map +1 -0
  77. package/dist/registry/claims.js +107 -0
  78. package/dist/registry/facts.d.ts +32 -0
  79. package/dist/registry/facts.d.ts.map +1 -0
  80. package/dist/registry/facts.js +120 -0
  81. package/dist/registry/fingerprint.d.ts +20 -0
  82. package/dist/registry/fingerprint.d.ts.map +1 -0
  83. package/dist/registry/fingerprint.js +150 -0
  84. package/dist/registry/parity.d.ts +26 -0
  85. package/dist/registry/parity.d.ts.map +1 -0
  86. package/dist/registry/parity.js +41 -0
  87. package/dist/registry/pricing.d.ts +19 -0
  88. package/dist/registry/pricing.d.ts.map +1 -0
  89. package/dist/registry/pricing.js +69 -0
  90. package/dist/registry/rows.d.ts +16 -0
  91. package/dist/registry/rows.d.ts.map +1 -0
  92. package/dist/registry/rows.js +57 -0
  93. package/dist/registry/seed.d.ts +43 -0
  94. package/dist/registry/seed.d.ts.map +1 -0
  95. package/dist/registry/seed.js +97 -0
  96. package/dist/registry/select.d.ts +68 -0
  97. package/dist/registry/select.d.ts.map +1 -0
  98. package/dist/registry/select.js +141 -0
  99. package/dist/registry/staleness.d.ts +18 -0
  100. package/dist/registry/staleness.d.ts.map +1 -0
  101. package/dist/registry/staleness.js +39 -0
  102. package/dist/routes/draft.d.ts +83 -0
  103. package/dist/routes/draft.d.ts.map +1 -0
  104. package/dist/routes/draft.js +135 -0
  105. package/dist/routes/loader.d.ts +26 -0
  106. package/dist/routes/loader.d.ts.map +1 -0
  107. package/dist/routes/loader.js +436 -0
  108. package/dist/routes/manifest.d.ts +24 -0
  109. package/dist/routes/manifest.d.ts.map +1 -0
  110. package/dist/routes/manifest.js +118 -0
  111. package/dist/routes/serveability.d.ts +52 -0
  112. package/dist/routes/serveability.d.ts.map +1 -0
  113. package/dist/routes/serveability.js +73 -0
  114. package/dist/routes/template.d.ts +18 -0
  115. package/dist/routes/template.d.ts.map +1 -0
  116. package/dist/routes/template.js +94 -0
  117. package/dist/run/cost.d.ts +26 -0
  118. package/dist/run/cost.d.ts.map +1 -0
  119. package/dist/run/cost.js +41 -0
  120. package/dist/run/envelope.d.ts +21 -0
  121. package/dist/run/envelope.d.ts.map +1 -0
  122. package/dist/run/envelope.js +45 -0
  123. package/dist/run/grounding.d.ts +56 -0
  124. package/dist/run/grounding.d.ts.map +1 -0
  125. package/dist/run/grounding.js +89 -0
  126. package/dist/run/repair.d.ts +36 -0
  127. package/dist/run/repair.d.ts.map +1 -0
  128. package/dist/run/repair.js +61 -0
  129. package/dist/run/resolve.d.ts +26 -0
  130. package/dist/run/resolve.d.ts.map +1 -0
  131. package/dist/run/resolve.js +81 -0
  132. package/dist/run/retry.d.ts +19 -0
  133. package/dist/run/retry.d.ts.map +1 -0
  134. package/dist/run/retry.js +50 -0
  135. package/dist/run/run.d.ts +41 -0
  136. package/dist/run/run.d.ts.map +1 -0
  137. package/dist/run/run.js +0 -0
  138. package/dist/run/validate.d.ts +15 -0
  139. package/dist/run/validate.d.ts.map +1 -0
  140. package/dist/run/validate.js +56 -0
  141. package/dist/savings/emit.d.ts +25 -0
  142. package/dist/savings/emit.d.ts.map +1 -0
  143. package/dist/savings/emit.js +75 -0
  144. package/dist/savings/odometer.d.ts +32 -0
  145. package/dist/savings/odometer.d.ts.map +1 -0
  146. package/dist/savings/odometer.js +70 -0
  147. package/dist/savings/swap-execute.d.ts +44 -0
  148. package/dist/savings/swap-execute.d.ts.map +1 -0
  149. package/dist/savings/swap-execute.js +87 -0
  150. package/dist/savings/swaps-client.d.ts +41 -0
  151. package/dist/savings/swaps-client.d.ts.map +1 -0
  152. package/dist/savings/swaps-client.js +81 -0
  153. package/dist/telemetry/capture.d.ts +28 -0
  154. package/dist/telemetry/capture.d.ts.map +1 -0
  155. package/dist/telemetry/capture.js +74 -0
  156. package/dist/telemetry/exporter.d.ts +62 -0
  157. package/dist/telemetry/exporter.d.ts.map +1 -0
  158. package/dist/telemetry/exporter.js +233 -0
  159. package/dist/telemetry/ingest-protocol.d.ts +161 -0
  160. package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
  161. package/dist/telemetry/ingest-protocol.js +163 -0
  162. package/dist/telemetry/sinks.d.ts +54 -0
  163. package/dist/telemetry/sinks.d.ts.map +1 -0
  164. package/dist/telemetry/sinks.js +89 -0
  165. package/dist/telemetry/store.d.ts +38 -0
  166. package/dist/telemetry/store.d.ts.map +1 -0
  167. package/dist/telemetry/store.js +332 -0
  168. package/dist/telemetry/supabase-exporter.d.ts +64 -0
  169. package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
  170. package/dist/telemetry/supabase-exporter.js +211 -0
  171. package/dist/tenancy/api-key-format.d.ts +67 -0
  172. package/dist/tenancy/api-key-format.d.ts.map +1 -0
  173. package/dist/tenancy/api-key-format.js +91 -0
  174. package/dist/tenancy/constants.d.ts +23 -0
  175. package/dist/tenancy/constants.d.ts.map +1 -0
  176. package/dist/tenancy/constants.js +25 -0
  177. package/dist/tenancy/scrub.d.ts +47 -0
  178. package/dist/tenancy/scrub.d.ts.map +1 -0
  179. package/dist/tenancy/scrub.js +85 -0
  180. package/dist/types/failures-teaching.d.ts +56 -0
  181. package/dist/types/failures-teaching.d.ts.map +1 -0
  182. package/dist/types/failures-teaching.js +177 -0
  183. package/dist/types/failures.d.ts +27 -0
  184. package/dist/types/failures.d.ts.map +1 -0
  185. package/dist/types/failures.js +41 -0
  186. package/dist/types/provider.d.ts +79 -0
  187. package/dist/types/provider.d.ts.map +1 -0
  188. package/dist/types/provider.js +20 -0
  189. package/dist/types/route.d.ts +127 -0
  190. package/dist/types/route.d.ts.map +1 -0
  191. package/dist/types/route.js +6 -0
  192. package/dist/types/telemetry.d.ts +89 -0
  193. package/dist/types/telemetry.d.ts.map +1 -0
  194. package/dist/types/telemetry.js +6 -0
  195. package/dist/watch/budget.d.ts +19 -0
  196. package/dist/watch/budget.d.ts.map +1 -0
  197. package/dist/watch/budget.js +35 -0
  198. package/dist/watch/evaluate.d.ts +66 -0
  199. package/dist/watch/evaluate.d.ts.map +1 -0
  200. package/dist/watch/evaluate.js +94 -0
  201. package/dist/watch/proposals.d.ts +73 -0
  202. package/dist/watch/proposals.d.ts.map +1 -0
  203. package/dist/watch/proposals.js +169 -0
  204. package/dist/watch/run-record.d.ts +64 -0
  205. package/dist/watch/run-record.d.ts.map +1 -0
  206. package/dist/watch/run-record.js +86 -0
  207. package/dist/watch/run-reported.d.ts +20 -0
  208. package/dist/watch/run-reported.d.ts.map +1 -0
  209. package/dist/watch/run-reported.js +53 -0
  210. package/dist/watch/run.d.ts +40 -0
  211. package/dist/watch/run.d.ts.map +1 -0
  212. package/dist/watch/run.js +44 -0
  213. package/dist/watch/state-store.d.ts +48 -0
  214. package/dist/watch/state-store.d.ts.map +1 -0
  215. package/dist/watch/state-store.js +86 -0
  216. package/dist/watch/state.d.ts +21 -0
  217. package/dist/watch/state.d.ts.map +1 -0
  218. package/dist/watch/state.js +93 -0
  219. package/package.json +50 -14
  220. package/index.js +0 -3
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * Bake-off engine — Phase 3 spec WS5 (closed core). Replays the last N
4
+ * captures through each named variant, aggregates per-variant metrics
5
+ * (stats.ts), and assembles the BakeoffArtifact with the proposal verdict.
6
+ *
7
+ * Proposal gate (builder decision, flagged): the incumbent is the "default"
8
+ * variant when compared, else the FIRST listed variant. A challenger
9
+ * proposes when (a) its conformance CI lower bound ≥ the incumbent's CI
10
+ * lower bound, and (b) its effective $/1K-conformant is strictly lower.
11
+ * Value gates beyond mechanical conformance arrive with Phase 4 judges —
12
+ * the artifact records value_accuracy where the hook supplies it.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.runBakeoff = runBakeoff;
16
+ exports.computeProposal = computeProposal;
17
+ exports.mirrorBakeoff = mirrorBakeoff;
18
+ const node_crypto_1 = require("node:crypto");
19
+ const loader_1 = require("../routes/loader");
20
+ const replay_1 = require("./replay");
21
+ const stats_1 = require("./stats");
22
+ const samples_1 = require("./samples");
23
+ /** Arm metadata for the reviewer: which model/rung/scaffold served it. */
24
+ function deriveArmMeta(route, arm) {
25
+ if (arm === "default") {
26
+ return { model: route.candidates[0]?.model ?? null, rung: route.policy.json };
27
+ }
28
+ const variant = route.variants.find((v) => v.name === arm);
29
+ const model = (variant?.candidates?.[0] ?? route.candidates[0])?.model ?? null;
30
+ return {
31
+ model,
32
+ rung: variant?.json ?? route.policy.json,
33
+ ...(variant?.scaffold !== undefined ? { scaffold: true } : {}),
34
+ };
35
+ }
36
+ async function runBakeoff(deps, captures, req, samplesSink) {
37
+ if (req.variants.length === 0) {
38
+ throw new loader_1.RouteConfigError("bakeoff needs at least one variant (use \"default\" for the base config)");
39
+ }
40
+ const route = deps.routes.get(req.route);
41
+ for (const name of req.variants) {
42
+ if (name !== "default" && !route.variants.some((v) => v.name === name)) {
43
+ throw new loader_1.RouteConfigError(`route "${req.route}" has no variant "${name}" — defined: [default, ${route.variants
44
+ .map((v) => v.name)
45
+ .join(", ")}]`);
46
+ }
47
+ }
48
+ const keepOutputs = req.keepOutputs !== undefined && samplesSink !== undefined;
49
+ const bakeoffId = (0, node_crypto_1.randomUUID)();
50
+ const metrics = [];
51
+ const rawByArm = new Map();
52
+ let replayN = 0;
53
+ for (const variant of req.variants) {
54
+ const samples = await (0, replay_1.replayVariant)(deps, captures, {
55
+ route: req.route,
56
+ variant,
57
+ lastN: req.replayLastN,
58
+ envelopeUsd: req.envelopeUsdPerVariant,
59
+ ...(keepOutputs ? { keepOutputs: true } : {}),
60
+ });
61
+ replayN = Math.max(replayN, samples.length);
62
+ metrics.push((0, stats_1.aggregateVariantMetrics)(variant, samples));
63
+ if (keepOutputs)
64
+ rawByArm.set(variant, samples);
65
+ }
66
+ const proposal = computeProposal(req.variants, metrics);
67
+ // Persist review samples (A3) — AFTER the winner is known, for the selected
68
+ // arms only. Never touches the artifact (payloads stay off the anon table).
69
+ if (keepOutputs && samplesSink !== undefined) {
70
+ const incumbentArm = req.variants.includes("default") ? "default" : req.variants[0];
71
+ const keptArms = (0, samples_1.selectArmsToKeep)(req.keepOutputs, incumbentArm, proposal?.variant ?? null, req.variants);
72
+ const arms = keptArms.map((arm) => ({
73
+ arm,
74
+ armMeta: deriveArmMeta(route, arm),
75
+ samples: (rawByArm.get(arm) ?? []).map((s) => ({
76
+ inputRef: s.captureId,
77
+ inputText: s.inputText ?? "",
78
+ outputText: s.outputText ?? null,
79
+ conform: s.conform,
80
+ costUsd: s.costUsd,
81
+ latencyMs: s.latencyMs,
82
+ })),
83
+ }));
84
+ // Best-effort: a hiccup persisting the SECONDARY review samples must not
85
+ // lose the PRIMARY metrics artifact of an expensive bake-off. Warn and
86
+ // continue; the artifact still returns, mirrors, and writes to disk.
87
+ try {
88
+ await samplesSink({ bakeoffId, route: route.route, arms });
89
+ }
90
+ catch (err) {
91
+ console.warn(`[modelrig] bake-off ${bakeoffId}: review samples not persisted ` +
92
+ `(${err instanceof Error ? err.message : String(err)}) — metrics unaffected; ` +
93
+ "re-run with --keep-outputs to retry the samples.");
94
+ }
95
+ }
96
+ return {
97
+ id: bakeoffId,
98
+ route: route.route,
99
+ routeVersion: route.version,
100
+ ranAt: new Date().toISOString(),
101
+ replayN,
102
+ variants: metrics,
103
+ proposal,
104
+ };
105
+ }
106
+ /** Winner among gate-passing challengers; null when nobody beats the
107
+ * incumbent. Exported for direct unit testing. */
108
+ function computeProposal(requested, metrics) {
109
+ const incumbentName = requested.includes("default") ? "default" : requested[0];
110
+ const incumbent = metrics.find((m) => m.variant === incumbentName);
111
+ // A1: unpriced cost columns under-count — an unpriced incumbent can't be
112
+ // compared against, and an unpriced challenger must never look cheap.
113
+ if (incumbent === undefined || incumbent.samples === 0 || incumbent.pricingMissing)
114
+ return null;
115
+ const challengers = metrics.filter((m) => m.variant !== incumbentName &&
116
+ m.samples > 0 &&
117
+ !m.pricingMissing &&
118
+ m.conformCi95[0] >= incumbent.conformCi95[0] &&
119
+ m.effectiveUsdPer1kConformant < incumbent.effectiveUsdPer1kConformant);
120
+ if (challengers.length === 0)
121
+ return null;
122
+ const winner = [...challengers].sort((a, b) => a.effectiveUsdPer1kConformant - b.effectiveUsdPer1kConformant)[0];
123
+ const savings = incumbent.effectiveUsdPer1kConformant === 0 ||
124
+ !Number.isFinite(incumbent.effectiveUsdPer1kConformant)
125
+ ? 0
126
+ : ((incumbent.effectiveUsdPer1kConformant - winner.effectiveUsdPer1kConformant) /
127
+ incumbent.effectiveUsdPer1kConformant) *
128
+ 100;
129
+ return { variant: winner.variant, vsIncumbentSavingsPct: savings };
130
+ }
131
+ /** Upsert the artifact into the `bakeoffs` mirror table (console read path).
132
+ * Same PostgREST transport pattern as the telemetry exporter; captures have
133
+ * no cloud surface — ONLY aggregate metrics travel. */
134
+ async function mirrorBakeoff(artifact, config) {
135
+ const fetchFn = config.fetchFn ?? fetch;
136
+ const base = config.supabaseUrl.replace(/\/$/, "");
137
+ const response = await fetchFn(`${base}/rest/v1/bakeoffs`, {
138
+ method: "POST",
139
+ headers: {
140
+ "content-type": "application/json",
141
+ apikey: config.serviceRoleKey,
142
+ authorization: `Bearer ${config.serviceRoleKey}`,
143
+ prefer: "resolution=merge-duplicates,return=minimal",
144
+ },
145
+ body: JSON.stringify([
146
+ {
147
+ id: artifact.id,
148
+ route: artifact.route,
149
+ route_version: artifact.routeVersion,
150
+ ran_at: artifact.ranAt,
151
+ variants: artifact.variants.map((m) => m.variant),
152
+ // JSON has no Infinity: a variant with zero conformant outputs
153
+ // serializes effectiveUsdPer1kConformant as null (console renders ∞).
154
+ matrix: JSON.parse(JSON.stringify(artifact.variants)),
155
+ replay_n: artifact.replayN,
156
+ source_id: config.sourceId,
157
+ },
158
+ ]),
159
+ });
160
+ if (!response.ok) {
161
+ throw new Error(`bakeoff mirror upsert → ${response.status} ${await response.text().catch(() => "")}`);
162
+ }
163
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Offline shadow replay — Phase 3 spec WS4/WS5. Re-runs locally captured
3
+ * inputs against candidate variants with ZERO production exposure. Replay
4
+ * dispatches are real (billed) adapter calls, but: envelope-guarded, tagged
5
+ * as replay (distinct from production telemetry), and never written to
6
+ * captures (no replay-of-replay).
7
+ *
8
+ * Builder decisions (flagged for architect review):
9
+ * - Replay rows go into the SAME local store under the `replay:<id>` TAG
10
+ * NAMESPACE (the scaffold's sanctioned option A): every replay attempt row
11
+ * carries tags {replay, replay_capture, variant} — production queries
12
+ * filter them out by tag, and per-sample cost is reconstructed from the
13
+ * same rows (base + retries + repair + search — F1-correct).
14
+ * - ReplaySampleResult gained `repairCostUsd` (additive) so the engine can
15
+ * report repair cost separately from total cost.
16
+ * - Envelope breach stops the pass typed-ly: the breaching sample is
17
+ * recorded with failureClass budget_exhausted and the PARTIAL result set
18
+ * is returned (honest Ns beat discarded spend).
19
+ */
20
+ import type { CaptureStore } from "../telemetry/capture";
21
+ import type { RunDeps } from "../run/run";
22
+ export interface ReplayRequest {
23
+ readonly route: string;
24
+ readonly variant: string;
25
+ readonly lastN: number;
26
+ readonly envelopeUsd: number;
27
+ /** Addendum A3: retain the rendered input + this arm's output on each
28
+ * sample for the human value review. Off by default (bounds memory + keeps
29
+ * the pure-metrics path clean). */
30
+ readonly keepOutputs?: boolean;
31
+ }
32
+ export interface ReplaySampleResult {
33
+ readonly captureId: string;
34
+ readonly conform: boolean;
35
+ readonly repaired: boolean;
36
+ readonly valueAccuracy: number | null;
37
+ readonly costUsd: number;
38
+ /** Portion of costUsd spent on repair calls (repaired_by rows). */
39
+ readonly repairCostUsd: number;
40
+ /** Addendum A1: any attempt row lacked pricing — costUsd under-counts and
41
+ * this sample must never rank a variant as cheap. */
42
+ readonly pricingMissing: boolean;
43
+ readonly latencyMs: number;
44
+ readonly failureClass: string | null;
45
+ /** Addendum A3 (keepOutputs only): the replayed input, verbatim. */
46
+ readonly inputText?: string;
47
+ /** Addendum A3 (keepOutputs only): this arm's output for the review, or
48
+ * null when the arm produced none (a failure). */
49
+ readonly outputText?: string | null;
50
+ }
51
+ export declare function replayVariant(deps: RunDeps, captures: CaptureStore, req: ReplayRequest): Promise<readonly ReplaySampleResult[]>;
52
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +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;AAE1C,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,CAsExC"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ /**
3
+ * Offline shadow replay — Phase 3 spec WS4/WS5. Re-runs locally captured
4
+ * inputs against candidate variants with ZERO production exposure. Replay
5
+ * dispatches are real (billed) adapter calls, but: envelope-guarded, tagged
6
+ * as replay (distinct from production telemetry), and never written to
7
+ * captures (no replay-of-replay).
8
+ *
9
+ * Builder decisions (flagged for architect review):
10
+ * - Replay rows go into the SAME local store under the `replay:<id>` TAG
11
+ * NAMESPACE (the scaffold's sanctioned option A): every replay attempt row
12
+ * carries tags {replay, replay_capture, variant} — production queries
13
+ * filter them out by tag, and per-sample cost is reconstructed from the
14
+ * same rows (base + retries + repair + search — F1-correct).
15
+ * - ReplaySampleResult gained `repairCostUsd` (additive) so the engine can
16
+ * report repair cost separately from total cost.
17
+ * - Envelope breach stops the pass typed-ly: the breaching sample is
18
+ * recorded with failureClass budget_exhausted and the PARTIAL result set
19
+ * is returned (honest Ns beat discarded spend).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.replayVariant = replayVariant;
23
+ const node_crypto_1 = require("node:crypto");
24
+ const failures_1 = require("../types/failures");
25
+ const run_1 = require("../run/run");
26
+ async function replayVariant(deps, captures, req) {
27
+ const rows = captures.readLast(req.route, req.lastN);
28
+ const replayId = (0, node_crypto_1.randomUUID)();
29
+ const envelopeKey = `replay:${replayId}`;
30
+ // Zero production exposure knobs: no capture writes (no replay-of-replay),
31
+ // and the whole pass shares one hard envelope.
32
+ const replayDeps = {
33
+ ...deps,
34
+ captures: null,
35
+ envelopeBudgetUsd: req.envelopeUsd,
36
+ };
37
+ const results = [];
38
+ for (const capture of rows) {
39
+ const sampleTag = `${replayId}:${capture.inferenceId}`;
40
+ const started = Date.now();
41
+ let conform = false;
42
+ let repaired = false;
43
+ let failureClass = null;
44
+ let outputText = null;
45
+ try {
46
+ // Through the REAL run loop: the variant's template re-renders against
47
+ // the replay candidate's capability flags (never the captured prompt
48
+ // text — plan §10 replay-validity risk), repair/grounding rungs and
49
+ // retry budgets all behave exactly as production serving would.
50
+ const result = await (0, run_1.run)(replayDeps, req.route, {
51
+ input: JSON.parse(capture.variablesJson),
52
+ tags: { replay: replayId, replay_capture: sampleTag, variant: req.variant },
53
+ ...(req.variant !== "default" ? { variant: req.variant } : {}),
54
+ budget: { envelope: envelopeKey },
55
+ });
56
+ conform = result.meta.validated;
57
+ repaired = result.meta.repairedBy !== null;
58
+ if (req.keepOutputs)
59
+ outputText = JSON.stringify(result.output, null, 2);
60
+ }
61
+ catch (err) {
62
+ if (!(err instanceof failures_1.RigFailureError))
63
+ throw err;
64
+ failureClass = err.failure.class;
65
+ }
66
+ const latencyMs = Date.now() - started;
67
+ // Per-sample cost from the tagged attempt rows — base + retries + repair
68
+ // + search, exactly what telemetry billed (F1-corrected).
69
+ const attemptRows = deps.telemetry.queryByTag("replay_capture", sampleTag);
70
+ const costUsd = attemptRows.reduce((a, r) => a + r.costEstimateUsd, 0);
71
+ const repairCostUsd = attemptRows
72
+ .filter((r) => r.repairedBy !== null)
73
+ .reduce((a, r) => a + r.costEstimateUsd, 0);
74
+ const pricingMissing = attemptRows.some((r) => r.pricingMissing);
75
+ results.push({
76
+ captureId: capture.inferenceId,
77
+ conform,
78
+ repaired,
79
+ valueAccuracy: null, // golden-set hook — expected values are Phase 4's judge/goldens work
80
+ costUsd,
81
+ repairCostUsd,
82
+ pricingMissing,
83
+ latencyMs,
84
+ failureClass,
85
+ ...(req.keepOutputs
86
+ ? { inputText: capture.variablesJson, outputText }
87
+ : {}),
88
+ });
89
+ if (failureClass === "budget_exhausted") {
90
+ break; // typed stop at the envelope; partial results stand
91
+ }
92
+ }
93
+ return results;
94
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Bake-off review samples — Phase 4 addendum A3 (value-review-storage.md §11).
3
+ *
4
+ * A SEPARATE store from captures: rows exist only because a human ran
5
+ * `modelrig bakeoff --keep-outputs` intending a value review. They carry the
6
+ * rendered input + each arm's output so the human-in-the-loop diff (eval
7
+ * rung 1.5) happens in the console. Written by the CLI with the service-role
8
+ * key; read server-side only; deny-anon RLS. NEVER the anon staging posture.
9
+ *
10
+ * Transport is raw PostgREST over fetch (same pattern as the exporter).
11
+ */
12
+ /** 256KB per output, per the architect's caps; TOAST compresses at rest. */
13
+ export declare const OUTPUT_MAX_BYTES: number;
14
+ export type KeepOutputsMode = "winner" | "all";
15
+ export interface BakeoffSample {
16
+ readonly bakeoffId: string;
17
+ readonly route: string;
18
+ readonly arm: string;
19
+ readonly armMeta: Record<string, unknown> | null;
20
+ readonly inputRef: string;
21
+ readonly inputText: string;
22
+ readonly outputText: string | null;
23
+ readonly conform: boolean;
24
+ readonly costUsd: number;
25
+ readonly latencyMs: number;
26
+ readonly expiresAt: string;
27
+ }
28
+ /** One arm's per-input samples, ready to persist. */
29
+ export interface ArmSamples {
30
+ readonly arm: string;
31
+ readonly armMeta: Record<string, unknown> | null;
32
+ readonly samples: ReadonlyArray<{
33
+ readonly inputRef: string;
34
+ readonly inputText: string;
35
+ readonly outputText: string | null;
36
+ readonly conform: boolean;
37
+ readonly costUsd: number;
38
+ readonly latencyMs: number;
39
+ }>;
40
+ }
41
+ /** Default persists incumbent + winner only; "all" persists every arm. */
42
+ export declare function selectArmsToKeep(mode: KeepOutputsMode, incumbentArm: string, winnerArm: string | null, allArms: readonly string[]): string[];
43
+ export declare function truncateOutput(text: string | null): string | null;
44
+ /** Default retention: 90 days from now (or pass a sooner stamp). Pure over
45
+ * an injected `now` so it stays testable and workflow-safe. */
46
+ export declare function retentionExpiry(nowMs: number, days?: number): string;
47
+ export declare function buildBakeoffSamples(bakeoffId: string, route: string, arms: readonly ArmSamples[], expiresAt: string): BakeoffSample[];
48
+ export interface BakeoffSamplesClientConfig {
49
+ readonly supabaseUrl: string;
50
+ readonly serviceRoleKey: string;
51
+ readonly fetchFn?: typeof fetch;
52
+ }
53
+ export interface BakeoffSamplesClient {
54
+ insert(samples: readonly BakeoffSample[]): Promise<void>;
55
+ /** Purge by bakeoff id, or everything past its expiry when no id given. */
56
+ purge(opts?: {
57
+ bakeoffId?: string;
58
+ nowIso?: string;
59
+ }): Promise<number>;
60
+ }
61
+ export declare function createBakeoffSamplesClient(config: BakeoffSamplesClientConfig): BakeoffSamplesClient;
62
+ //# sourceMappingURL=samples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/bakeoff/samples.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,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"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * Bake-off review samples — Phase 4 addendum A3 (value-review-storage.md §11).
4
+ *
5
+ * A SEPARATE store from captures: rows exist only because a human ran
6
+ * `modelrig bakeoff --keep-outputs` intending a value review. They carry the
7
+ * rendered input + each arm's output so the human-in-the-loop diff (eval
8
+ * rung 1.5) happens in the console. Written by the CLI with the service-role
9
+ * key; read server-side only; deny-anon RLS. NEVER the anon staging posture.
10
+ *
11
+ * Transport is raw PostgREST over fetch (same pattern as the exporter).
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.OUTPUT_MAX_BYTES = void 0;
15
+ exports.selectArmsToKeep = selectArmsToKeep;
16
+ exports.truncateOutput = truncateOutput;
17
+ exports.retentionExpiry = retentionExpiry;
18
+ exports.buildBakeoffSamples = buildBakeoffSamples;
19
+ exports.createBakeoffSamplesClient = createBakeoffSamplesClient;
20
+ /** 256KB per output, per the architect's caps; TOAST compresses at rest. */
21
+ exports.OUTPUT_MAX_BYTES = 256 * 1024;
22
+ /** Default persists incumbent + winner only; "all" persists every arm. */
23
+ function selectArmsToKeep(mode, incumbentArm, winnerArm, allArms) {
24
+ if (mode === "all")
25
+ return [...allArms];
26
+ const keep = [incumbentArm];
27
+ if (winnerArm !== null && winnerArm !== incumbentArm)
28
+ keep.push(winnerArm);
29
+ return keep;
30
+ }
31
+ function truncateOutput(text) {
32
+ if (text === null)
33
+ return null;
34
+ if (Buffer.byteLength(text, "utf8") <= exports.OUTPUT_MAX_BYTES)
35
+ return text;
36
+ // Cut on a byte budget, then tag it so the reviewer knows it's clipped.
37
+ const marker = "\n\n[truncated at 256KB for review storage]";
38
+ const budget = exports.OUTPUT_MAX_BYTES - Buffer.byteLength(marker, "utf8");
39
+ return Buffer.from(text, "utf8").subarray(0, budget).toString("utf8") + marker;
40
+ }
41
+ /** Default retention: 90 days from now (or pass a sooner stamp). Pure over
42
+ * an injected `now` so it stays testable and workflow-safe. */
43
+ function retentionExpiry(nowMs, days = 90) {
44
+ return new Date(nowMs + days * 24 * 3600 * 1000).toISOString();
45
+ }
46
+ function buildBakeoffSamples(bakeoffId, route, arms, expiresAt) {
47
+ const rows = [];
48
+ for (const arm of arms) {
49
+ for (const s of arm.samples) {
50
+ rows.push({
51
+ bakeoffId,
52
+ route,
53
+ arm: arm.arm,
54
+ armMeta: arm.armMeta,
55
+ inputRef: s.inputRef,
56
+ inputText: s.inputText,
57
+ outputText: truncateOutput(s.outputText),
58
+ conform: s.conform,
59
+ costUsd: s.costUsd,
60
+ latencyMs: s.latencyMs,
61
+ expiresAt,
62
+ });
63
+ }
64
+ }
65
+ return rows;
66
+ }
67
+ function createBakeoffSamplesClient(config) {
68
+ const fetchFn = config.fetchFn ?? fetch;
69
+ const base = config.supabaseUrl.replace(/\/$/, "");
70
+ async function request(path, init) {
71
+ const response = await fetchFn(`${base}/rest/v1/${path}`, {
72
+ ...init,
73
+ headers: {
74
+ "content-type": "application/json",
75
+ apikey: config.serviceRoleKey,
76
+ authorization: `Bearer ${config.serviceRoleKey}`,
77
+ ...(init.headers ?? {}),
78
+ },
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`bakeoff_samples ${init.method ?? "GET"} ${path} → ${response.status} ` +
82
+ `${await response.text().catch(() => "")}`);
83
+ }
84
+ return response;
85
+ }
86
+ return {
87
+ async insert(samples) {
88
+ if (samples.length === 0)
89
+ return;
90
+ // Chunk the POST: at the ≤50-inputs cap, large-input routes (e.g. the
91
+ // cutover steps pass ~600KB assembled prompts) would make a single body
92
+ // tens of MB and blow past PostgREST's limit. Small batches keep every
93
+ // body well under it, at any cap.
94
+ const CHUNK = 8;
95
+ for (let i = 0; i < samples.length; i += CHUNK) {
96
+ const batch = samples.slice(i, i + CHUNK);
97
+ await request("bakeoff_samples", {
98
+ method: "POST",
99
+ headers: { prefer: "return=minimal" },
100
+ body: JSON.stringify(batch.map((s) => ({
101
+ bakeoff_id: s.bakeoffId,
102
+ route: s.route,
103
+ arm: s.arm,
104
+ arm_meta: s.armMeta,
105
+ input_ref: s.inputRef,
106
+ input_text: s.inputText,
107
+ output_text: s.outputText,
108
+ conform: s.conform,
109
+ cost_usd: s.costUsd,
110
+ latency_ms: s.latencyMs,
111
+ expires_at: s.expiresAt,
112
+ }))),
113
+ });
114
+ }
115
+ },
116
+ async purge(opts) {
117
+ const filter = opts?.bakeoffId !== undefined
118
+ ? `bakeoff_id=eq.${encodeURIComponent(opts.bakeoffId)}`
119
+ : `expires_at=lt.${encodeURIComponent(opts?.nowIso ?? new Date().toISOString())}`;
120
+ const response = await request(`bakeoff_samples?${filter}`, {
121
+ method: "DELETE",
122
+ headers: { prefer: "return=representation" },
123
+ });
124
+ const deleted = (await response.json().catch(() => []));
125
+ return Array.isArray(deleted) ? deleted.length : 0;
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Bake-off statistics — Phase 3 spec WS5. Wilson 95% intervals ported from
3
+ * the probe suite's summarize math (same constants — results comparable);
4
+ * no cross-package import (the probes package is standalone since P2).
5
+ *
6
+ * The headline: effective $ per 1K conformant outputs — TOTAL sample cost
7
+ * (base + retries + repair + search, F1-corrected) ÷ conformant count × 1000.
8
+ */
9
+ import type { ReplaySampleResult } from "./replay";
10
+ import type { VariantMetrics } from "./types";
11
+ export declare function wilson95(successes: number, n: number): readonly [number, number];
12
+ export declare function aggregateVariantMetrics(variant: string, samples: readonly ReplaySampleResult[]): VariantMetrics;
13
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/bakeoff/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAShF;AAMD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,kBAAkB,EAAE,GACrC,cAAc,CAuBhB"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /**
3
+ * Bake-off statistics — Phase 3 spec WS5. Wilson 95% intervals ported from
4
+ * the probe suite's summarize math (same constants — results comparable);
5
+ * no cross-package import (the probes package is standalone since P2).
6
+ *
7
+ * The headline: effective $ per 1K conformant outputs — TOTAL sample cost
8
+ * (base + retries + repair + search, F1-corrected) ÷ conformant count × 1000.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.wilson95 = wilson95;
12
+ exports.aggregateVariantMetrics = aggregateVariantMetrics;
13
+ function wilson95(successes, n) {
14
+ if (n === 0)
15
+ return [0, 1];
16
+ const z = 1.959963984540054;
17
+ const p = successes / n;
18
+ const z2 = z * z;
19
+ const denominator = 1 + z2 / n;
20
+ const center = (p + z2 / (2 * n)) / denominator;
21
+ const half = (z * Math.sqrt((p * (1 - p)) / n + z2 / (4 * n * n))) / denominator;
22
+ return [Math.max(0, center - half), Math.min(1, center + half)];
23
+ }
24
+ function mean(values) {
25
+ return values.length === 0 ? 0 : values.reduce((a, b) => a + b, 0) / values.length;
26
+ }
27
+ function aggregateVariantMetrics(variant, samples) {
28
+ const n = samples.length;
29
+ const conformant = samples.filter((s) => s.conform);
30
+ const repaired = samples.filter((s) => s.repaired);
31
+ const accuracies = samples
32
+ .map((s) => s.valueAccuracy)
33
+ .filter((a) => a !== null);
34
+ const totalCostUsd = samples.reduce((a, s) => a + s.costUsd, 0);
35
+ return {
36
+ variant,
37
+ samples: n,
38
+ conformRate: n === 0 ? 0 : conformant.length / n,
39
+ conformCi95: wilson95(conformant.length, n),
40
+ repairRate: n === 0 ? 0 : repaired.length / n,
41
+ meanRepairCostUsd: mean(repaired.map((s) => s.repairCostUsd)),
42
+ valueAccuracyMean: accuracies.length === 0 ? null : mean(accuracies),
43
+ meanLatencyMs: mean(samples.map((s) => s.latencyMs)),
44
+ meanCostUsd: n === 0 ? 0 : totalCostUsd / n,
45
+ effectiveUsdPer1kConformant: conformant.length === 0 ? Number.POSITIVE_INFINITY : (totalCostUsd / conformant.length) * 1000,
46
+ pricingMissing: samples.some((s) => s.pricingMissing),
47
+ };
48
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Conformance bake-off contracts — Phase 3 spec WS5 (closed core).
3
+ * The headline metric: effective $ per 1K conformant outputs, with repair
4
+ * and search costs included (F1-corrected accounting).
5
+ */
6
+ export interface VariantMetrics {
7
+ readonly variant: string;
8
+ readonly samples: number;
9
+ readonly conformRate: number;
10
+ readonly conformCi95: readonly [number, number];
11
+ readonly repairRate: number;
12
+ readonly meanRepairCostUsd: number;
13
+ /** Golden-set value accuracy where the route defines expectedValues; null otherwise. */
14
+ readonly valueAccuracyMean: number | null;
15
+ readonly meanLatencyMs: number;
16
+ readonly meanCostUsd: number;
17
+ /** (base + repair + search costs) / conformant outputs × 1000 — the number. */
18
+ readonly effectiveUsdPer1kConformant: number;
19
+ /** Addendum A1: any sample served by a model with no pricing entry —
20
+ * cost columns under-count; this variant is never ranked or proposed. */
21
+ readonly pricingMissing: boolean;
22
+ }
23
+ export interface BakeoffArtifact {
24
+ readonly id: string;
25
+ readonly route: string;
26
+ readonly routeVersion: number;
27
+ readonly ranAt: string;
28
+ readonly replayN: number;
29
+ readonly variants: readonly VariantMetrics[];
30
+ /** Winner by effectiveUsdPer1kConformant among variants meeting the gates;
31
+ * null when no variant beats the incumbent's gates. */
32
+ readonly proposal: {
33
+ readonly variant: string;
34
+ readonly vsIncumbentSavingsPct: number;
35
+ } | null;
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,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;6EACyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;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;2DACuD;IACvD,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAChG"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Conformance bake-off contracts — Phase 3 spec WS5 (closed core).
4
+ * The headline metric: effective $ per 1K conformant outputs, with repair
5
+ * and search costs included (F1-corrected accounting).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ // Implemented: engine.ts assembles this from replay results; stats.ts owns
9
+ // the math (Wilson CI — ported from the probes' summarize approach, no
10
+ // cross-package import).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * modelrig CLI — Phase 3 plan §3 + Phase 4 plan §3:
4
+ * modelrig bakeoff --route X --variants a,b,c --replay-last N
5
+ * [--envelope usd] [--json] [--no-mirror]
6
+ * modelrig bakeoff --from-proposal <id> [--replay-last N] [--json]
7
+ * modelrig watch run [--events fixtures.json] [--json]
8
+ *
9
+ * Bake-offs replay locally captured traffic, write the artifact JSON next
10
+ * to the telemetry db (.modelrig/bakeoffs/<id>.json), upsert the `bakeoffs`
11
+ * mirror when the Supabase sink is configured, and print the matrix.
12
+ * Captures never travel — only aggregate metrics. Watch cycles spend $0;
13
+ * --from-proposal runs draw from the monthly watch envelope and attach
14
+ * their evidence to the proposal (suggested → evidenced). Nothing here
15
+ * executes a swap — that is `modelrig swap execute --approve`, a separate,
16
+ * human command.
17
+ */
18
+ import type { BakeoffArtifact } from "./bakeoff/types";
19
+ export interface Flags {
20
+ readonly positional: readonly string[];
21
+ readonly named: ReadonlyMap<string, string>;
22
+ readonly bare: ReadonlySet<string>;
23
+ }
24
+ export declare function parseArgs(argv: readonly string[]): Flags;
25
+ export declare function printMatrix(artifact: BakeoffArtifact): void;
26
+ export declare function main(argv: readonly string[]): Promise<number>;
27
+ //# sourceMappingURL=cli.d.ts.map