modelrig 0.1.0 → 0.3.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 (310) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +53 -11
  3. package/data/PROVENANCE.md +19 -0
  4. package/data/pricing-manual.json +22 -0
  5. package/data/pricing-map.json +46362 -0
  6. package/data/pricing-overlay.json +419 -0
  7. package/dist/adapters/anthropic.d.ts +50 -6
  8. package/dist/adapters/anthropic.d.ts.map +1 -1
  9. package/dist/adapters/anthropic.js +123 -29
  10. package/dist/adapters/build-adapters.d.ts +11 -0
  11. package/dist/adapters/build-adapters.d.ts.map +1 -0
  12. package/dist/adapters/build-adapters.js +45 -0
  13. package/dist/adapters/deepseek.d.ts +6 -0
  14. package/dist/adapters/deepseek.d.ts.map +1 -1
  15. package/dist/adapters/deepseek.js +54 -1
  16. package/dist/adapters/gemini.d.ts +10 -0
  17. package/dist/adapters/gemini.d.ts.map +1 -1
  18. package/dist/adapters/gemini.js +78 -7
  19. package/dist/adapters/grok.d.ts +9 -0
  20. package/dist/adapters/grok.d.ts.map +1 -1
  21. package/dist/adapters/grok.js +67 -3
  22. package/dist/adapters/openai-compat.d.ts +6 -0
  23. package/dist/adapters/openai-compat.d.ts.map +1 -1
  24. package/dist/adapters/openai-compat.js +58 -1
  25. package/dist/adapters/openai-stream.d.ts +58 -0
  26. package/dist/adapters/openai-stream.d.ts.map +1 -0
  27. package/dist/adapters/openai-stream.js +120 -0
  28. package/dist/adapters/openai.d.ts +6 -0
  29. package/dist/adapters/openai.d.ts.map +1 -1
  30. package/dist/adapters/openai.js +60 -1
  31. package/dist/adapters/sampling.d.ts +28 -0
  32. package/dist/adapters/sampling.d.ts.map +1 -0
  33. package/dist/adapters/sampling.js +34 -0
  34. package/dist/adapters/tool-mapping.d.ts +92 -0
  35. package/dist/adapters/tool-mapping.d.ts.map +1 -0
  36. package/dist/adapters/tool-mapping.js +143 -0
  37. package/dist/artifacts/artifact.d.ts +137 -0
  38. package/dist/artifacts/artifact.d.ts.map +1 -0
  39. package/dist/artifacts/artifact.js +377 -0
  40. package/dist/artifacts/content-uploader.d.ts +87 -0
  41. package/dist/artifacts/content-uploader.d.ts.map +1 -0
  42. package/dist/artifacts/content-uploader.js +124 -0
  43. package/dist/artifacts/index.d.ts +63 -0
  44. package/dist/artifacts/index.d.ts.map +1 -0
  45. package/dist/artifacts/index.js +80 -0
  46. package/dist/artifacts/run-context.d.ts +129 -0
  47. package/dist/artifacts/run-context.d.ts.map +1 -0
  48. package/dist/artifacts/run-context.js +208 -0
  49. package/dist/artifacts/scrub.d.ts +60 -0
  50. package/dist/artifacts/scrub.d.ts.map +1 -0
  51. package/dist/artifacts/scrub.js +139 -0
  52. package/dist/artifacts/step.d.ts +26 -0
  53. package/dist/artifacts/step.d.ts.map +1 -0
  54. package/dist/artifacts/step.js +37 -0
  55. package/dist/artifacts/taint.d.ts +63 -0
  56. package/dist/artifacts/taint.d.ts.map +1 -0
  57. package/dist/artifacts/taint.js +76 -0
  58. package/dist/bakeoff/cache-sim.d.ts +99 -0
  59. package/dist/bakeoff/cache-sim.d.ts.map +1 -0
  60. package/dist/bakeoff/cache-sim.js +126 -0
  61. package/dist/bakeoff/eval-cases.d.ts +96 -0
  62. package/dist/bakeoff/eval-cases.d.ts.map +1 -0
  63. package/dist/bakeoff/eval-cases.js +105 -0
  64. package/dist/bakeoff/report.d.ts +41 -0
  65. package/dist/bakeoff/report.d.ts.map +1 -0
  66. package/dist/bakeoff/report.js +142 -0
  67. package/dist/bakeoff/samples.d.ts +15 -0
  68. package/dist/bakeoff/samples.d.ts.map +1 -1
  69. package/dist/bakeoff/samples.js +20 -0
  70. package/dist/cli.d.ts +1 -3
  71. package/dist/cli.d.ts.map +1 -1
  72. package/dist/cli.js +134 -116
  73. package/dist/config.d.ts +100 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/config.js +104 -0
  76. package/dist/deprecations.d.ts +32 -0
  77. package/dist/deprecations.d.ts.map +1 -0
  78. package/dist/deprecations.js +59 -0
  79. package/dist/gateway/http-protocol.d.ts +175 -0
  80. package/dist/gateway/http-protocol.d.ts.map +1 -0
  81. package/dist/gateway/http-protocol.js +105 -0
  82. package/dist/index.d.ts +106 -19
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +266 -138
  85. package/dist/init/finder.d.ts +15 -0
  86. package/dist/init/finder.d.ts.map +1 -1
  87. package/dist/init/finder.js +46 -0
  88. package/dist/init/init-cli.d.ts +10 -0
  89. package/dist/init/init-cli.d.ts.map +1 -0
  90. package/dist/init/init-cli.js +86 -0
  91. package/dist/init/observe-cli.d.ts +19 -0
  92. package/dist/init/observe-cli.d.ts.map +1 -0
  93. package/dist/init/observe-cli.js +73 -0
  94. package/dist/init/observe.d.ts +81 -0
  95. package/dist/init/observe.d.ts.map +1 -0
  96. package/dist/init/observe.js +245 -0
  97. package/dist/registry/build.d.ts +94 -1
  98. package/dist/registry/build.d.ts.map +1 -1
  99. package/dist/registry/build.js +125 -4
  100. package/dist/registry/cache-policy.d.ts +94 -0
  101. package/dist/registry/cache-policy.d.ts.map +1 -0
  102. package/dist/registry/cache-policy.js +238 -0
  103. package/dist/registry/call-notes.d.ts.map +1 -1
  104. package/dist/registry/call-notes.js +40 -2
  105. package/dist/registry/effective-rate.d.ts +151 -0
  106. package/dist/registry/effective-rate.d.ts.map +1 -0
  107. package/dist/registry/effective-rate.js +156 -0
  108. package/dist/registry/facts.d.ts.map +1 -1
  109. package/dist/registry/facts.js +16 -0
  110. package/dist/registry/parity.d.ts +26 -0
  111. package/dist/registry/parity.d.ts.map +1 -1
  112. package/dist/registry/parity.js +43 -2
  113. package/dist/registry/pricing-shape.d.ts +113 -0
  114. package/dist/registry/pricing-shape.d.ts.map +1 -0
  115. package/dist/registry/pricing-shape.js +123 -0
  116. package/dist/registry/pricing.d.ts +25 -1
  117. package/dist/registry/pricing.d.ts.map +1 -1
  118. package/dist/registry/pricing.js +64 -6
  119. package/dist/rigindex/bootstrap.d.ts +36 -0
  120. package/dist/rigindex/bootstrap.d.ts.map +1 -0
  121. package/dist/rigindex/bootstrap.js +97 -0
  122. package/dist/rigindex/fit.d.ts +24 -0
  123. package/dist/rigindex/fit.d.ts.map +1 -0
  124. package/dist/rigindex/fit.js +76 -0
  125. package/dist/rigindex/gates.d.ts +52 -0
  126. package/dist/rigindex/gates.d.ts.map +1 -0
  127. package/dist/rigindex/gates.js +109 -0
  128. package/dist/rigindex/graph.d.ts +31 -0
  129. package/dist/rigindex/graph.d.ts.map +1 -0
  130. package/dist/rigindex/graph.js +120 -0
  131. package/dist/rigindex/index.d.ts +23 -0
  132. package/dist/rigindex/index.d.ts.map +1 -0
  133. package/dist/rigindex/index.js +112 -0
  134. package/dist/rigindex/types.d.ts +76 -0
  135. package/dist/rigindex/types.d.ts.map +1 -0
  136. package/dist/rigindex/types.js +8 -0
  137. package/dist/routes/cache-lint.d.ts +41 -0
  138. package/dist/routes/cache-lint.d.ts.map +1 -0
  139. package/dist/routes/cache-lint.js +81 -0
  140. package/dist/routes/loader.d.ts +48 -0
  141. package/dist/routes/loader.d.ts.map +1 -1
  142. package/dist/routes/loader.js +167 -0
  143. package/dist/routes/resolved-bundle.d.ts +79 -0
  144. package/dist/routes/resolved-bundle.d.ts.map +1 -0
  145. package/dist/routes/resolved-bundle.js +148 -0
  146. package/dist/routes/sampling-config.d.ts +32 -0
  147. package/dist/routes/sampling-config.d.ts.map +1 -0
  148. package/dist/routes/sampling-config.js +91 -0
  149. package/dist/run/ajv.d.ts +22 -0
  150. package/dist/run/ajv.d.ts.map +1 -0
  151. package/dist/run/ajv.js +32 -0
  152. package/dist/run/cache-handle.d.ts +58 -0
  153. package/dist/run/cache-handle.d.ts.map +1 -0
  154. package/dist/run/cache-handle.js +54 -0
  155. package/dist/run/cache-stamp.d.ts +65 -0
  156. package/dist/run/cache-stamp.d.ts.map +1 -0
  157. package/dist/run/cache-stamp.js +0 -0
  158. package/dist/run/capture-write.d.ts +33 -0
  159. package/dist/run/capture-write.d.ts.map +1 -0
  160. package/dist/run/capture-write.js +55 -0
  161. package/dist/run/cost.d.ts.map +1 -1
  162. package/dist/run/cost.js +1 -1
  163. package/dist/run/dispatch.d.ts +99 -0
  164. package/dist/run/dispatch.d.ts.map +1 -0
  165. package/dist/run/dispatch.js +191 -0
  166. package/dist/run/raw.d.ts +130 -0
  167. package/dist/run/raw.d.ts.map +1 -0
  168. package/dist/run/raw.js +363 -0
  169. package/dist/run/record-attempt.d.ts +40 -0
  170. package/dist/run/record-attempt.d.ts.map +1 -0
  171. package/dist/run/record-attempt.js +99 -0
  172. package/dist/run/resolve.d.ts +75 -2
  173. package/dist/run/resolve.d.ts.map +1 -1
  174. package/dist/run/resolve.js +126 -4
  175. package/dist/run/run.d.ts +14 -0
  176. package/dist/run/run.d.ts.map +1 -1
  177. package/dist/run/run.js +0 -0
  178. package/dist/run/stream.d.ts +49 -0
  179. package/dist/run/stream.d.ts.map +1 -0
  180. package/dist/run/stream.js +87 -0
  181. package/dist/run/validate.d.ts.map +1 -1
  182. package/dist/run/validate.js +6 -7
  183. package/dist/savings/cache-ledger.d.ts +108 -0
  184. package/dist/savings/cache-ledger.d.ts.map +1 -0
  185. package/dist/savings/cache-ledger.js +174 -0
  186. package/dist/savings/cache-reconcile.d.ts +64 -0
  187. package/dist/savings/cache-reconcile.d.ts.map +1 -0
  188. package/dist/savings/cache-reconcile.js +59 -0
  189. package/dist/savings/cache-stats.d.ts +63 -0
  190. package/dist/savings/cache-stats.d.ts.map +1 -0
  191. package/dist/savings/cache-stats.js +108 -0
  192. package/dist/savings/emit.d.ts +13 -0
  193. package/dist/savings/emit.d.ts.map +1 -1
  194. package/dist/savings/emit.js +25 -0
  195. package/dist/savings/swap-execute.d.ts +14 -0
  196. package/dist/savings/swap-execute.d.ts.map +1 -1
  197. package/dist/savings/swap-execute.js +44 -1
  198. package/dist/savings/swaps-client.d.ts +14 -0
  199. package/dist/savings/swaps-client.d.ts.map +1 -1
  200. package/dist/savings/swaps-client.js +16 -2
  201. package/dist/settings/api-fetcher.d.ts +39 -0
  202. package/dist/settings/api-fetcher.d.ts.map +1 -0
  203. package/dist/settings/api-fetcher.js +92 -0
  204. package/dist/settings/index.d.ts +10 -0
  205. package/dist/settings/index.d.ts.map +1 -0
  206. package/dist/settings/index.js +15 -0
  207. package/dist/settings/task-settings.d.ts +91 -0
  208. package/dist/settings/task-settings.d.ts.map +1 -0
  209. package/dist/settings/task-settings.js +116 -0
  210. package/dist/status.d.ts +77 -0
  211. package/dist/status.d.ts.map +1 -0
  212. package/dist/status.js +113 -0
  213. package/dist/telemetry/artifact-store.d.ts +48 -0
  214. package/dist/telemetry/artifact-store.d.ts.map +1 -0
  215. package/dist/telemetry/artifact-store.js +189 -0
  216. package/dist/telemetry/cache-miss-streak.d.ts +79 -0
  217. package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
  218. package/dist/telemetry/cache-miss-streak.js +99 -0
  219. package/dist/telemetry/capture.d.ts +3 -0
  220. package/dist/telemetry/capture.d.ts.map +1 -1
  221. package/dist/telemetry/capture.js +15 -8
  222. package/dist/telemetry/exporter-setup.d.ts +28 -0
  223. package/dist/telemetry/exporter-setup.d.ts.map +1 -0
  224. package/dist/telemetry/exporter-setup.js +34 -0
  225. package/dist/telemetry/exporter.d.ts +75 -0
  226. package/dist/telemetry/exporter.d.ts.map +1 -1
  227. package/dist/telemetry/exporter.js +324 -47
  228. package/dist/telemetry/health.d.ts +76 -0
  229. package/dist/telemetry/health.d.ts.map +1 -0
  230. package/dist/telemetry/health.js +92 -0
  231. package/dist/telemetry/ingest-protocol.d.ts +152 -3
  232. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  233. package/dist/telemetry/ingest-protocol.js +204 -4
  234. package/dist/telemetry/load-error-report.d.ts +24 -0
  235. package/dist/telemetry/load-error-report.d.ts.map +1 -0
  236. package/dist/telemetry/load-error-report.js +50 -0
  237. package/dist/telemetry/otel-map.d.ts +121 -0
  238. package/dist/telemetry/otel-map.d.ts.map +1 -0
  239. package/dist/telemetry/otel-map.js +224 -0
  240. package/dist/telemetry/otel-normalize.d.ts +104 -0
  241. package/dist/telemetry/otel-normalize.d.ts.map +1 -0
  242. package/dist/telemetry/otel-normalize.js +530 -0
  243. package/dist/telemetry/otlp-emitter.d.ts +52 -0
  244. package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
  245. package/dist/telemetry/otlp-emitter.js +144 -0
  246. package/dist/telemetry/otlp-proto.d.ts +29 -0
  247. package/dist/telemetry/otlp-proto.d.ts.map +1 -0
  248. package/dist/telemetry/otlp-proto.js +266 -0
  249. package/dist/telemetry/sinks.d.ts +37 -1
  250. package/dist/telemetry/sinks.d.ts.map +1 -1
  251. package/dist/telemetry/sinks.js +121 -30
  252. package/dist/telemetry/store.d.ts +28 -1
  253. package/dist/telemetry/store.d.ts.map +1 -1
  254. package/dist/telemetry/store.js +218 -3
  255. package/dist/tenancy/api-key-format.d.ts +15 -5
  256. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  257. package/dist/tenancy/api-key-format.js +24 -5
  258. package/dist/types/config-teaching.d.ts +62 -0
  259. package/dist/types/config-teaching.d.ts.map +1 -0
  260. package/dist/types/config-teaching.js +183 -0
  261. package/dist/types/provider.d.ts +147 -1
  262. package/dist/types/provider.d.ts.map +1 -1
  263. package/dist/types/provider.js +21 -1
  264. package/dist/types/route.d.ts +79 -1
  265. package/dist/types/route.d.ts.map +1 -1
  266. package/dist/types/telemetry.d.ts +117 -0
  267. package/dist/types/telemetry.d.ts.map +1 -1
  268. package/dist/validate-cli.d.ts +12 -0
  269. package/dist/validate-cli.d.ts.map +1 -0
  270. package/dist/validate-cli.js +57 -0
  271. package/dist/verify/capture.d.ts +66 -0
  272. package/dist/verify/capture.d.ts.map +1 -0
  273. package/dist/verify/capture.js +180 -0
  274. package/dist/verify/cli-run.d.ts +23 -0
  275. package/dist/verify/cli-run.d.ts.map +1 -0
  276. package/dist/verify/cli-run.js +57 -0
  277. package/dist/verify/diff.d.ts +48 -0
  278. package/dist/verify/diff.d.ts.map +1 -0
  279. package/dist/verify/diff.js +157 -0
  280. package/dist/verify/index.d.ts +17 -0
  281. package/dist/verify/index.d.ts.map +1 -0
  282. package/dist/verify/index.js +24 -0
  283. package/dist/verify/report.d.ts +14 -0
  284. package/dist/verify/report.d.ts.map +1 -0
  285. package/dist/verify/report.js +88 -0
  286. package/dist/verify/types.d.ts +101 -0
  287. package/dist/verify/types.d.ts.map +1 -0
  288. package/dist/verify/types.js +17 -0
  289. package/dist/verify/verify.d.ts +29 -0
  290. package/dist/verify/verify.d.ts.map +1 -0
  291. package/dist/verify/verify.js +148 -0
  292. package/dist/watch/evaluate.d.ts +162 -3
  293. package/dist/watch/evaluate.d.ts.map +1 -1
  294. package/dist/watch/evaluate.js +61 -0
  295. package/dist/watch/monitor.d.ts +116 -0
  296. package/dist/watch/monitor.d.ts.map +1 -0
  297. package/dist/watch/monitor.js +199 -0
  298. package/dist/watch/proposals.d.ts +20 -4
  299. package/dist/watch/proposals.d.ts.map +1 -1
  300. package/dist/watch/proposals.js +31 -3
  301. package/dist/watch/run.d.ts +33 -0
  302. package/dist/watch/run.d.ts.map +1 -1
  303. package/dist/watch/run.js +39 -0
  304. package/package.json +7 -1
  305. package/registry/LICENSE +20 -0
  306. package/registry/leaderboard.json +3639 -0
  307. package/registry/registry.json +7263 -0
  308. package/dist/telemetry/supabase-exporter.d.ts +0 -64
  309. package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
  310. package/dist/telemetry/supabase-exporter.js +0 -211
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * The diff engine — the honest verdict vocabulary that makes the verifier's
4
+ * report trustworthy. Pure (no adapters, no run loop): it compares an
5
+ * original output against a routed one and returns one of three verdicts.
6
+ *
7
+ * Design decision (flagged for architect review): the routed output arrives
8
+ * as PARSED JSON — `run()` only returns after the output validated against the
9
+ * route schema (or it throws), so a routed sample is guaranteed schema-
10
+ * conformant and JSON. We therefore compare STRUCTURE, not raw bytes. For JSON,
11
+ * key order and whitespace are not behavior, so "byte-identical where
12
+ * deterministic" is operationalized as CANONICAL identity (sorted-key
13
+ * serialization). Structured-field equality — with declared nondeterministic
14
+ * fields excluded — is the fallback, exactly as the spec's §6 evaluation
15
+ * contract prescribes.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.canonicalJson = canonicalJson;
19
+ exports.firstDivergence = firstDivergence;
20
+ exports.diffOutputs = diffOutputs;
21
+ /** Deterministic JSON serialization: object keys sorted recursively, arrays
22
+ * left in order (order is semantic). The operative notion of "byte-identical"
23
+ * for JSON outputs. */
24
+ function canonicalJson(value) {
25
+ return JSON.stringify(sortKeys(value));
26
+ }
27
+ function sortKeys(value) {
28
+ if (Array.isArray(value))
29
+ return value.map(sortKeys);
30
+ if (value !== null && typeof value === "object") {
31
+ const out = {};
32
+ for (const key of Object.keys(value).sort()) {
33
+ out[key] = sortKeys(value[key]);
34
+ }
35
+ return out;
36
+ }
37
+ return value;
38
+ }
39
+ /** True when `path` is exactly, or nested under, any declared nondeterministic
40
+ * path. `/meta` masks `/meta` and `/meta/gen`; it never masks `/metadata`. */
41
+ function isMasked(path, nondeterministicPaths) {
42
+ return nondeterministicPaths.some((nd) => path === nd || path.startsWith(`${nd}/`));
43
+ }
44
+ /**
45
+ * First point at which `original` and `routed` structurally differ, in a
46
+ * stable depth-first order, or null when they are deep-equal after masking the
47
+ * declared nondeterministic paths. Object keys are visited in sorted order so
48
+ * the "first" differing field is deterministic across runs.
49
+ */
50
+ function firstDivergence(original, routed, nondeterministicPaths, path = "") {
51
+ const here = path === "" ? "" : path;
52
+ if (isMasked(here || "/", nondeterministicPaths))
53
+ return null;
54
+ if (Array.isArray(original) || Array.isArray(routed)) {
55
+ if (!Array.isArray(original) || !Array.isArray(routed) || original.length !== routed.length) {
56
+ return { path: here || "/", originalValue: original, routedValue: routed };
57
+ }
58
+ for (let i = 0; i < original.length; i += 1) {
59
+ const d = firstDivergence(original[i], routed[i], nondeterministicPaths, `${here}/${i}`);
60
+ if (d !== null)
61
+ return d;
62
+ }
63
+ return null;
64
+ }
65
+ if (isObject(original) || isObject(routed)) {
66
+ if (!isObject(original) || !isObject(routed)) {
67
+ return { path: here || "/", originalValue: original, routedValue: routed };
68
+ }
69
+ const keys = [...new Set([...Object.keys(original), ...Object.keys(routed)])].sort();
70
+ for (const key of keys) {
71
+ const childPath = `${here}/${escapePointer(key)}`;
72
+ if (isMasked(childPath, nondeterministicPaths))
73
+ continue;
74
+ const inO = Object.prototype.hasOwnProperty.call(original, key);
75
+ const inR = Object.prototype.hasOwnProperty.call(routed, key);
76
+ if (!inO || !inR) {
77
+ return { path: childPath, originalValue: original[key], routedValue: routed[key] };
78
+ }
79
+ const d = firstDivergence(original[key], routed[key], nondeterministicPaths, childPath);
80
+ if (d !== null)
81
+ return d;
82
+ }
83
+ return null;
84
+ }
85
+ // Scalars (incl. null): a strict, type-aware compare.
86
+ if (original !== routed) {
87
+ return { path: here || "/", originalValue: original, routedValue: routed };
88
+ }
89
+ return null;
90
+ }
91
+ function isObject(v) {
92
+ return v !== null && typeof v === "object" && !Array.isArray(v);
93
+ }
94
+ /** RFC-6901 escaping for a path segment (`~`→`~0`, `/`→`~1`). */
95
+ function escapePointer(key) {
96
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
97
+ }
98
+ function shortValue(v) {
99
+ const s = typeof v === "string" ? v : JSON.stringify(v);
100
+ return s !== undefined && s.length > 60 ? `${s.slice(0, 57)}…` : String(s);
101
+ }
102
+ function tryParse(text) {
103
+ try {
104
+ return { ok: true, value: JSON.parse(text) };
105
+ }
106
+ catch {
107
+ return { ok: false };
108
+ }
109
+ }
110
+ /**
111
+ * Diff an original output (raw text) against a routed output (already parsed by
112
+ * `run()`), returning the verdict. `routedParsed` is guaranteed schema-
113
+ * conformant JSON by the run loop; `originalText` is whatever the original call
114
+ * site emitted.
115
+ *
116
+ * - byte / canonical identity ⇒ `equivalent`
117
+ * - structurally equal after masking declared nd-paths ⇒
118
+ * `equivalent-modulo-nondeterminism`
119
+ * - a real structured field differs ⇒ `divergent` (first field named)
120
+ * - the original is not JSON ⇒ `divergent` (a free-text call site — this
121
+ * isn't route-shaped work; T0/T1 territory, honestly flagged not forced)
122
+ */
123
+ function diffOutputs(originalText, routedParsed, routedText, nondeterministicPaths) {
124
+ // True byte-identity first — the strongest, cheapest signal.
125
+ if (originalText === routedText) {
126
+ return { verdict: "equivalent", firstDivergentPath: null, detail: "byte-identical output" };
127
+ }
128
+ const parsedOriginal = tryParse(originalText);
129
+ if (!parsedOriginal.ok) {
130
+ return {
131
+ verdict: "divergent",
132
+ firstDivergentPath: null,
133
+ detail: "original output is not JSON — this is a free-text call site; equivalence is not " +
134
+ "provable through a task route (T0/T1 territory, not a Tier-2 replacement)",
135
+ };
136
+ }
137
+ // Canonical identity: for JSON, key order and whitespace are not behavior.
138
+ if (canonicalJson(parsedOriginal.value) === canonicalJson(routedParsed)) {
139
+ return { verdict: "equivalent", firstDivergentPath: null, detail: "canonically identical JSON" };
140
+ }
141
+ const d = firstDivergence(parsedOriginal.value, routedParsed, nondeterministicPaths);
142
+ if (d === null) {
143
+ return {
144
+ verdict: "equivalent-modulo-nondeterminism",
145
+ firstDivergentPath: null,
146
+ detail: nondeterministicPaths.length > 0
147
+ ? `structurally equal after excluding nondeterministic field(s): ${nondeterministicPaths.join(", ")}`
148
+ : "structurally equal (serialization differs only)",
149
+ };
150
+ }
151
+ return {
152
+ verdict: "divergent",
153
+ firstDivergentPath: d.path,
154
+ detail: `structured field ${d.path} differs: original=${shortValue(d.originalValue)} ` +
155
+ `routed=${shortValue(d.routedValue)}`,
156
+ };
157
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shadow-equivalence verifier (Expansion §9, autonomy-ladder Tier 2) — the
3
+ * public barrel. Proves a same-model call replacement is behavior-preserving
4
+ * before a human merges the Tier-2 PR: capture (local) → replay through the
5
+ * routed replacement (reusing the run loop) → diff (honest verdict vocabulary)
6
+ * → report (machine + human). Refusing an uncapturable call site is a correct
7
+ * output, never a failure.
8
+ */
9
+ export { verifySwap } from "./verify";
10
+ export type { VerifySwapRequest } from "./verify";
11
+ export { diffOutputs, firstDivergence, canonicalJson } from "./diff";
12
+ export type { Divergence } from "./diff";
13
+ export { captureOriginal, openShadowCaptureStore, parseFixture, serializeFixture, } from "./capture";
14
+ export type { OriginalCallSite, OriginalCallFn, CaptureOptions, ShadowCaptureStore, ShadowCaptureRecord, } from "./capture";
15
+ export { renderReportMarkdown } from "./report";
16
+ export type { ShadowCapture, ShadowCaptureFixture, CaptureRefusal, SampleVerdict, EquivalenceVerdict, SampleDiff, SampleReport, EquivalenceReport, } from "./types";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verify/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Shadow-equivalence verifier (Expansion §9, autonomy-ladder Tier 2) — the
4
+ * public barrel. Proves a same-model call replacement is behavior-preserving
5
+ * before a human merges the Tier-2 PR: capture (local) → replay through the
6
+ * routed replacement (reusing the run loop) → diff (honest verdict vocabulary)
7
+ * → report (machine + human). Refusing an uncapturable call site is a correct
8
+ * output, never a failure.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.renderReportMarkdown = exports.serializeFixture = exports.parseFixture = exports.openShadowCaptureStore = exports.captureOriginal = exports.canonicalJson = exports.firstDivergence = exports.diffOutputs = exports.verifySwap = void 0;
12
+ var verify_1 = require("./verify");
13
+ Object.defineProperty(exports, "verifySwap", { enumerable: true, get: function () { return verify_1.verifySwap; } });
14
+ var diff_1 = require("./diff");
15
+ Object.defineProperty(exports, "diffOutputs", { enumerable: true, get: function () { return diff_1.diffOutputs; } });
16
+ Object.defineProperty(exports, "firstDivergence", { enumerable: true, get: function () { return diff_1.firstDivergence; } });
17
+ Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return diff_1.canonicalJson; } });
18
+ var capture_1 = require("./capture");
19
+ Object.defineProperty(exports, "captureOriginal", { enumerable: true, get: function () { return capture_1.captureOriginal; } });
20
+ Object.defineProperty(exports, "openShadowCaptureStore", { enumerable: true, get: function () { return capture_1.openShadowCaptureStore; } });
21
+ Object.defineProperty(exports, "parseFixture", { enumerable: true, get: function () { return capture_1.parseFixture; } });
22
+ Object.defineProperty(exports, "serializeFixture", { enumerable: true, get: function () { return capture_1.serializeFixture; } });
23
+ var report_1 = require("./report");
24
+ Object.defineProperty(exports, "renderReportMarkdown", { enumerable: true, get: function () { return report_1.renderReportMarkdown; } });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Report rendering — the shadow-equivalence report the skill's Tier-2 PR
3
+ * embeds. Two forms, one source: the machine-readable form is the
4
+ * `EquivalenceReport` object serialized as JSON (stable, `schemaVersion: 1`);
5
+ * the human-readable form is the Markdown below, written for a PR reviewer who
6
+ * must decide whether the same-model replacement is safe to merge.
7
+ *
8
+ * The rendering never editorializes the verdict — it prints exactly what the
9
+ * diff decided, always alongside the plumbing-not-model-choice guarantee so a
10
+ * reader cannot mistake equivalence for a model-quality claim.
11
+ */
12
+ import type { EquivalenceReport } from "./types";
13
+ export declare function renderReportMarkdown(report: EquivalenceReport): string;
14
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/verify/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAoC,MAAM,SAAS,CAAC;AAwCnF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA+CtE"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * Report rendering — the shadow-equivalence report the skill's Tier-2 PR
4
+ * embeds. Two forms, one source: the machine-readable form is the
5
+ * `EquivalenceReport` object serialized as JSON (stable, `schemaVersion: 1`);
6
+ * the human-readable form is the Markdown below, written for a PR reviewer who
7
+ * must decide whether the same-model replacement is safe to merge.
8
+ *
9
+ * The rendering never editorializes the verdict — it prints exactly what the
10
+ * diff decided, always alongside the plumbing-not-model-choice guarantee so a
11
+ * reader cannot mistake equivalence for a model-quality claim.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.renderReportMarkdown = renderReportMarkdown;
15
+ const VERDICT_LABEL = {
16
+ equivalent: "EQUIVALENT",
17
+ "equivalent-modulo-nondeterminism": "EQUIVALENT (modulo nondeterminism)",
18
+ divergent: "DIVERGENT",
19
+ refused: "REFUSED",
20
+ };
21
+ /** The one-line headline a reviewer reads first. */
22
+ function headline(report) {
23
+ const label = VERDICT_LABEL[report.overall];
24
+ switch (report.overall) {
25
+ case "equivalent":
26
+ return `✅ **${label}** — all ${report.n} replayed input(s) reproduced the original output.`;
27
+ case "equivalent-modulo-nondeterminism":
28
+ return `🟡 **${label}** — all ${report.n} input(s) matched once declared nondeterministic fields were excluded.`;
29
+ case "divergent":
30
+ return `❌ **${label}** — ${report.counts.divergent} of ${report.n} input(s) did not reproduce; do not merge.`;
31
+ case "refused":
32
+ return `⛔ **${label}** — not a Tier-2 replacement. ${report.refusalReason ?? ""}`.trim();
33
+ }
34
+ }
35
+ /** Escape `|` so a field value never breaks the markdown table (the report is
36
+ * embedded in a PR body — a stray pipe from an odd JSON key must not corrupt it). */
37
+ function cell(text) {
38
+ return text.replace(/\|/g, "\\|");
39
+ }
40
+ function sampleRow(s) {
41
+ const detail = s.failureClass !== null
42
+ ? `replay failed (${s.failureClass})`
43
+ : s.firstDivergentPath !== null
44
+ ? `first differs at \`${cell(s.firstDivergentPath)}\``
45
+ : cell(s.detail);
46
+ return `| ${s.index} | ${s.verdict} | ${cell(s.servedModel ?? "—")} | $${s.costUsd.toFixed(4)} | ${detail} |`;
47
+ }
48
+ function renderReportMarkdown(report) {
49
+ const lines = [];
50
+ lines.push("## Shadow-equivalence verification");
51
+ lines.push("");
52
+ lines.push(headline(report));
53
+ lines.push("");
54
+ lines.push(`- **Call site:** \`${report.callSiteId}\``);
55
+ lines.push(`- **Route:** \`${report.route}@${report.routeVersion}\``);
56
+ lines.push(`- **Pinned model:** \`${report.pinnedModel}\` (original == replacement)`);
57
+ lines.push(`- **Ran at:** ${report.ranAt}`);
58
+ lines.push(`- **Tally:** ${report.counts.equivalent} equivalent · ` +
59
+ `${report.counts.equivalentModulo} modulo-nondeterminism · ${report.counts.divergent} divergent`);
60
+ lines.push("");
61
+ lines.push(`> ${report.guarantee}`);
62
+ lines.push("");
63
+ if (report.samples.length > 0) {
64
+ lines.push("| # | verdict | served model | cost | detail |");
65
+ lines.push("|---|---------|--------------|------|--------|");
66
+ for (const s of report.samples)
67
+ lines.push(sampleRow(s));
68
+ lines.push("");
69
+ }
70
+ if (report.refusals.length > 0) {
71
+ lines.push("### Refusals (honest, not failures)");
72
+ lines.push("");
73
+ for (const r of report.refusals)
74
+ lines.push(`- **${r.reason}** — ${r.detail}`);
75
+ lines.push("");
76
+ lines.push("Refused call sites are T0/T1 territory (gateway-pointing or instrumentation), " +
77
+ "not same-model call replacement — this is a correct outcome, not a gap.");
78
+ lines.push("");
79
+ }
80
+ lines.push(report.overall === "divergent"
81
+ ? "**Rollback is git-native:** revert this PR to restore the original call byte-for-byte."
82
+ : report.overall === "refused"
83
+ ? "No change is proposed for this call site."
84
+ : "Safe to merge on this evidence: the routed replacement preserves behavior. " +
85
+ "Rollback stays git-native (revert this PR) — same-model means it is also a route-YAML flip.");
86
+ lines.push("");
87
+ return lines.join("\n");
88
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shadow-equivalence verifier contracts (Expansion §9, autonomy-ladder Tier 2).
3
+ *
4
+ * The verifier proves a SAME-MODEL call replacement is behavior-preserving
5
+ * BEFORE a human merges the Tier-2 PR: capture N invocations of the original
6
+ * call site's inputs/outputs locally, replay the SAME inputs through the
7
+ * ModelRig-routed replacement (same provider/model PINNED — this verifies the
8
+ * PLUMBING, never the model choice), diff the outputs, and emit a report the
9
+ * skill embeds in the PR.
10
+ *
11
+ * Honest refusal is a first-class output: a call site that can't be captured
12
+ * or replayed (streaming, dynamically-assembled prompts, or a route that does
13
+ * not pin the original model) is REFUSED, not forced — those sites are T0/T1
14
+ * territory per the spec's stated limits.
15
+ */
16
+ import type { CandidateSpec } from "../types/route";
17
+ /** One recorded original invocation: the extracted template variables that
18
+ * reconstruct the original prompt through the drafted route, and the original
19
+ * call's raw output text. */
20
+ export interface ShadowCapture {
21
+ /** The extracted template variables (Tier-2 turns a prompt into
22
+ * `systemTemplate` + variables); replayed through the route unchanged. */
23
+ readonly input: Record<string, unknown>;
24
+ /** The original call site's raw output text, verbatim. */
25
+ readonly output: string;
26
+ }
27
+ /** A call site (or a single input) that could NOT be captured/replayed.
28
+ * Refusing IS a correct output — these are T0/T1 territory, never a failure. */
29
+ export interface CaptureRefusal {
30
+ readonly reason: "streaming" | "dynamic-prompt" | "uncapturable";
31
+ readonly detail: string;
32
+ }
33
+ /** The portable capture fixture the skill writes and the verifier reads. Stays
34
+ * local (SQLite/JSON in the SDK process) — nothing here uploads. */
35
+ export interface ShadowCaptureFixture {
36
+ /** Where the original call lives, e.g. "src/pipeline/summarize.ts:42". */
37
+ readonly callSiteId: string;
38
+ /** The provider+model of the ORIGINAL call — the route MUST pin exactly this
39
+ * (same-model replacement; the verifier refuses otherwise). */
40
+ readonly original: CandidateSpec;
41
+ readonly capturedAt: string;
42
+ readonly samples: readonly ShadowCapture[];
43
+ /** Inputs/sites the capture step honestly declined (streaming, dynamic). */
44
+ readonly refusals?: readonly CaptureRefusal[];
45
+ /** JSON-pointer paths (exact, or subtree prefixes) whose value is allowed to
46
+ * differ without counting as a divergence — the migration's declared
47
+ * free-text / nondeterministic fields (e.g. "/summary"). Absent = every
48
+ * field must match (the conservative default). */
49
+ readonly nondeterministicPaths?: readonly string[];
50
+ }
51
+ /** Per-sample verdict — the honest three-way vocabulary. */
52
+ export type SampleVerdict = "equivalent" | "equivalent-modulo-nondeterminism" | "divergent";
53
+ /** Whole-run verdict: the per-sample vocabulary plus `refused` (the run never
54
+ * ran — pin violation, or nothing capturable). */
55
+ export type EquivalenceVerdict = SampleVerdict | "refused";
56
+ /** The result of diffing one original/routed output pair. */
57
+ export interface SampleDiff {
58
+ readonly verdict: SampleVerdict;
59
+ /** First differing structured field (JSON-pointer) when divergent on
60
+ * structured output; null when equivalent, modulo, or divergent for a
61
+ * non-field reason (bad JSON, plumbing failure, model mismatch). */
62
+ readonly firstDivergentPath: string | null;
63
+ readonly detail: string;
64
+ }
65
+ export interface SampleReport extends SampleDiff {
66
+ readonly index: number;
67
+ /** "provider/model" that actually served the replay — null when the replay
68
+ * itself failed before any model served. */
69
+ readonly servedModel: string | null;
70
+ readonly costUsd: number;
71
+ /** Set when the routed replay threw a typed failure (the plumbing could not
72
+ * produce a conformant output for this input) — itself a divergence. */
73
+ readonly failureClass: string | null;
74
+ }
75
+ export interface EquivalenceReport {
76
+ readonly schemaVersion: 1;
77
+ readonly kind: "shadow-equivalence";
78
+ readonly callSiteId: string;
79
+ readonly route: string;
80
+ readonly routeVersion: number;
81
+ /** "provider/model" the route pins — equal to the original by construction
82
+ * (the verifier refuses when it isn't). */
83
+ readonly pinnedModel: string;
84
+ readonly ranAt: string;
85
+ readonly n: number;
86
+ readonly overall: EquivalenceVerdict;
87
+ readonly counts: {
88
+ readonly equivalent: number;
89
+ readonly equivalentModulo: number;
90
+ readonly divergent: number;
91
+ };
92
+ readonly samples: readonly SampleReport[];
93
+ /** Capture-time refusals, surfaced verbatim (never a silent subset). */
94
+ readonly refusals: readonly CaptureRefusal[];
95
+ /** Set when `overall === "refused"` — why the run never ran. */
96
+ readonly refusalReason: string | null;
97
+ /** The plumbing-not-model-choice statement, embedded in the report so a PR
98
+ * reader cannot mistake equivalence for a model-quality claim. */
99
+ readonly guarantee: string;
100
+ }
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/verify/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;6BAE6B;AAC7B,MAAM,WAAW,aAAa;IAC5B;8EAC0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;gFACgF;AAChF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,GAAG,cAAc,CAAC;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;oEACoE;AACpE,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;mEAC+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C;;;sDAGkD;IAClD,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GACrB,YAAY,GACZ,kCAAkC,GAClC,WAAW,CAAC;AAEhB;kDACkD;AAClD,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,SAAS,CAAC;AAE3D,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC;;wEAEoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;gDAC4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;4EACwE;IACxE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;+CAC2C;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;sEACkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * Shadow-equivalence verifier contracts (Expansion §9, autonomy-ladder Tier 2).
4
+ *
5
+ * The verifier proves a SAME-MODEL call replacement is behavior-preserving
6
+ * BEFORE a human merges the Tier-2 PR: capture N invocations of the original
7
+ * call site's inputs/outputs locally, replay the SAME inputs through the
8
+ * ModelRig-routed replacement (same provider/model PINNED — this verifies the
9
+ * PLUMBING, never the model choice), diff the outputs, and emit a report the
10
+ * skill embeds in the PR.
11
+ *
12
+ * Honest refusal is a first-class output: a call site that can't be captured
13
+ * or replayed (streaming, dynamically-assembled prompts, or a route that does
14
+ * not pin the original model) is REFUSED, not forced — those sites are T0/T1
15
+ * territory per the spec's stated limits.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shadow-equivalence orchestrator (Expansion §9, Tier 2) — the replay-and-diff
3
+ * pass that proves a same-model call replacement is behavior-preserving before
4
+ * a human merges the Tier-2 PR.
5
+ *
6
+ * Reuse, not twin (dev-rule S1): the EXECUTOR is the run loop — the same
7
+ * verified pipeline `rig.run()` drives — and the replay POSTURE is the
8
+ * bake-off's (`bakeoff/replay.ts`): captures severed (no capture-of-verify),
9
+ * one hard envelope across the pass, per-sample cost reconstructed from the
10
+ * tagged telemetry rows. What differs, and why this is a sibling of
11
+ * `replayVariant` rather than a call into it: shadow-equivalence pins a
12
+ * SPECIFIC captured input (not "last N by route"), needs the routed OUTPUT for
13
+ * a pairwise diff (not an aggregate metric), and asserts the served model
14
+ * equals the pinned original (a plumbing check variant comparison never makes).
15
+ */
16
+ import type { RunDeps } from "../run/run";
17
+ import type { EquivalenceReport, ShadowCaptureFixture } from "./types";
18
+ export interface VerifySwapRequest {
19
+ /** The drafted replacement route (must pin the original model — see below). */
20
+ readonly route: string;
21
+ /** The local capture fixture (original inputs/outputs). Never uploaded. */
22
+ readonly fixture: ShadowCaptureFixture;
23
+ /** Hard cap for the whole replay pass (default $10). */
24
+ readonly envelopeUsd?: number;
25
+ /** Injectable clock for a deterministic `ranAt` (tests). */
26
+ readonly now?: () => string;
27
+ }
28
+ export declare function verifySwap(deps: RunDeps, req: VerifySwapRequest): Promise<EquivalenceReport>;
29
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/verify/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,OAAO,KAAK,EACV,iBAAiB,EAIjB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAWjB,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAiCD,wBAAsB,UAAU,CAC9B,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAsH5B"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * Shadow-equivalence orchestrator (Expansion §9, Tier 2) — the replay-and-diff
4
+ * pass that proves a same-model call replacement is behavior-preserving before
5
+ * a human merges the Tier-2 PR.
6
+ *
7
+ * Reuse, not twin (dev-rule S1): the EXECUTOR is the run loop — the same
8
+ * verified pipeline `rig.run()` drives — and the replay POSTURE is the
9
+ * bake-off's (`bakeoff/replay.ts`): captures severed (no capture-of-verify),
10
+ * one hard envelope across the pass, per-sample cost reconstructed from the
11
+ * tagged telemetry rows. What differs, and why this is a sibling of
12
+ * `replayVariant` rather than a call into it: shadow-equivalence pins a
13
+ * SPECIFIC captured input (not "last N by route"), needs the routed OUTPUT for
14
+ * a pairwise diff (not an aggregate metric), and asserts the served model
15
+ * equals the pinned original (a plumbing check variant comparison never makes).
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.verifySwap = verifySwap;
19
+ const run_1 = require("../run/run");
20
+ const failures_1 = require("../types/failures");
21
+ const diff_1 = require("./diff");
22
+ /** One hard envelope for the whole verification pass when the caller sets none
23
+ * — replay is real, billed dispatch and must stay bounded (spec §9). */
24
+ const DEFAULT_VERIFY_ENVELOPE_USD = 10;
25
+ const GUARANTEE = "Same provider/model pinned on both sides: this verifies the PLUMBING of the " +
26
+ "ModelRig-routed replacement, never the model choice. Model optimization is a " +
27
+ "separate, human-gated bake-off decision.";
28
+ function candidateEquals(a, b) {
29
+ return a.provider === b.provider && a.model === b.model;
30
+ }
31
+ /** Build a refused report — the run never ran (pin violation, or nothing to
32
+ * replay). Refusing IS a correct output; it costs nothing. */
33
+ function refused(req, routeVersion, pinnedModel, reason, nowIso) {
34
+ return {
35
+ schemaVersion: 1,
36
+ kind: "shadow-equivalence",
37
+ callSiteId: req.fixture.callSiteId,
38
+ route: req.route,
39
+ routeVersion,
40
+ pinnedModel,
41
+ ranAt: nowIso,
42
+ n: 0,
43
+ overall: "refused",
44
+ counts: { equivalent: 0, equivalentModulo: 0, divergent: 0 },
45
+ samples: [],
46
+ refusals: req.fixture.refusals ?? [],
47
+ refusalReason: reason,
48
+ guarantee: GUARANTEE,
49
+ };
50
+ }
51
+ async function verifySwap(deps, req) {
52
+ const nowIso = (req.now ?? (() => new Date().toISOString()))();
53
+ const bundle = deps.routes.get(req.route);
54
+ const original = req.fixture.original;
55
+ const pinnedModel = `${original.provider}/${original.model}`;
56
+ // Same-model pin (the C2-exit gateway-substitution ruling): the route must
57
+ // pin EXACTLY the original model and nothing else. A route that offers other
58
+ // candidates would introduce model choice — which is not what this verifies.
59
+ const pins = bundle.candidates.length > 0 &&
60
+ bundle.candidates.every((c) => candidateEquals(c, original));
61
+ if (!pins) {
62
+ const declared = bundle.candidates.map((c) => `${c.provider}/${c.model}`).join(", ") || "none";
63
+ return refused(req, bundle.version, pinnedModel, `route "${req.route}" declares candidates [${declared}]; shadow-equivalence requires it to ` +
64
+ `pin exactly the original model ${pinnedModel} — this verifies plumbing, not model choice. ` +
65
+ "A multi-model route is a bake-off, not a Tier-2 replacement.", nowIso);
66
+ }
67
+ if (req.fixture.samples.length === 0) {
68
+ const hadRefusals = (req.fixture.refusals ?? []).length > 0;
69
+ return refused(req, bundle.version, pinnedModel, hadRefusals
70
+ ? "no replayable captures — every invocation was honestly refused (streaming / dynamic prompt); " +
71
+ "this call site is T0/T1 territory, not a Tier-2 replacement"
72
+ : "no captures to replay — capture N original invocations first", nowIso);
73
+ }
74
+ // Replay posture: sever captures (no capture-of-verify) and share one hard
75
+ // envelope, exactly as bakeoff/replay.ts does.
76
+ const envelopeUsd = req.envelopeUsd ?? DEFAULT_VERIFY_ENVELOPE_USD;
77
+ const replayDeps = { ...deps, captures: null, envelopeBudgetUsd: envelopeUsd };
78
+ const envelopeKey = `verify:${req.route}:${nowIso}`;
79
+ const nd = req.fixture.nondeterministicPaths ?? [];
80
+ const samples = [];
81
+ for (let index = 0; index < req.fixture.samples.length; index += 1) {
82
+ const capture = req.fixture.samples[index];
83
+ const sampleTag = `${envelopeKey}#${index}`;
84
+ let servedModel = null;
85
+ let failureClass = null;
86
+ let diff = { verdict: "divergent", firstDivergentPath: null, detail: "" };
87
+ try {
88
+ const result = await (0, run_1.run)(replayDeps, req.route, {
89
+ input: capture.input,
90
+ tags: { shadow_verify: envelopeKey, shadow_verify_sample: sampleTag },
91
+ budget: { envelope: envelopeKey, budgetUsd: envelopeUsd },
92
+ });
93
+ servedModel = `${result.meta.provider}/${result.meta.model}`;
94
+ if (servedModel !== pinnedModel) {
95
+ // A declared candidate served something other than the original — the
96
+ // plumbing does not preserve the model. Divergent, not equivalent.
97
+ diff = {
98
+ verdict: "divergent",
99
+ firstDivergentPath: null,
100
+ detail: `routed run served ${servedModel}, not the pinned original ${pinnedModel} — a fallback fired`,
101
+ };
102
+ }
103
+ else {
104
+ const routedText = JSON.stringify(result.output);
105
+ diff = (0, diff_1.diffOutputs)(capture.output, result.output, routedText, nd);
106
+ }
107
+ }
108
+ catch (err) {
109
+ if (!(err instanceof failures_1.RigFailureError))
110
+ throw err;
111
+ failureClass = err.failure.class;
112
+ diff = {
113
+ verdict: "divergent",
114
+ firstDivergentPath: null,
115
+ detail: `routed replacement did not return a conformant output (${err.failure.class}): ` +
116
+ `${err.failure.message} — the plumbing regressed for this input`,
117
+ };
118
+ }
119
+ // Per-sample cost from the tagged rows — base + retries + repair, exactly
120
+ // what telemetry billed (the bake-off's F1-correct accounting).
121
+ const costUsd = deps.telemetry
122
+ .queryByTag("shadow_verify_sample", sampleTag)
123
+ .reduce((a, r) => a + r.costEstimateUsd, 0);
124
+ samples.push({ index, ...diff, servedModel, costUsd, failureClass });
125
+ }
126
+ const counts = {
127
+ equivalent: samples.filter((s) => s.verdict === "equivalent").length,
128
+ equivalentModulo: samples.filter((s) => s.verdict === "equivalent-modulo-nondeterminism").length,
129
+ divergent: samples.filter((s) => s.verdict === "divergent").length,
130
+ };
131
+ const overall = counts.divergent > 0 ? "divergent" : counts.equivalentModulo > 0 ? "equivalent-modulo-nondeterminism" : "equivalent";
132
+ return {
133
+ schemaVersion: 1,
134
+ kind: "shadow-equivalence",
135
+ callSiteId: req.fixture.callSiteId,
136
+ route: req.route,
137
+ routeVersion: bundle.version,
138
+ pinnedModel,
139
+ ranAt: nowIso,
140
+ n: samples.length,
141
+ overall,
142
+ counts,
143
+ samples,
144
+ refusals: req.fixture.refusals ?? [],
145
+ refusalReason: null,
146
+ guarantee: GUARANTEE,
147
+ };
148
+ }