modelrig 0.8.1 → 0.10.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 (382) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/README.md +55 -29
  3. package/data/PROVENANCE.md +14 -0
  4. package/data/pricing-map.json +26 -0
  5. package/dist/adapters/attempt-meta-map.d.ts +8 -1
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
  7. package/dist/adapters/attempt-meta-map.js +4 -0
  8. package/dist/adapters/build-adapters.js +2 -2
  9. package/dist/adapters/gemini.d.ts +28 -1
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +38 -9
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +53 -4
  14. package/dist/adapters/openai-compat.d.ts +8 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +8 -1
  17. package/dist/adapters/openai-stream.d.ts +10 -2
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +5 -1
  20. package/dist/adapters/openai.d.ts +10 -1
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +57 -7
  23. package/dist/adapters/responses-api.d.ts +61 -0
  24. package/dist/adapters/responses-api.d.ts.map +1 -0
  25. package/dist/adapters/responses-api.js +124 -0
  26. package/dist/artifacts/artifact.d.ts +31 -12
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +218 -188
  29. package/dist/artifacts/index.d.ts +15 -5
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +19 -5
  32. package/dist/artifacts/run-context.d.ts +3 -24
  33. package/dist/artifacts/run-context.d.ts.map +1 -1
  34. package/dist/artifacts/run-context.js +3 -39
  35. package/dist/bakeoff/engine.d.ts +29 -0
  36. package/dist/bakeoff/engine.d.ts.map +1 -1
  37. package/dist/bakeoff/engine.js +47 -0
  38. package/dist/bakeoff/eval-cases-rig.d.ts +36 -0
  39. package/dist/bakeoff/eval-cases-rig.d.ts.map +1 -0
  40. package/dist/bakeoff/eval-cases-rig.js +27 -0
  41. package/dist/bakeoff/eval-cases.d.ts +19 -0
  42. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  43. package/dist/bakeoff/eval-cases.js +18 -1
  44. package/dist/bakeoff/golden-judge.d.ts +47 -0
  45. package/dist/bakeoff/golden-judge.d.ts.map +1 -0
  46. package/dist/bakeoff/golden-judge.js +49 -0
  47. package/dist/bakeoff/groundedness.d.ts +58 -0
  48. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  49. package/dist/bakeoff/groundedness.js +165 -0
  50. package/dist/bakeoff/quality.d.ts +43 -0
  51. package/dist/bakeoff/quality.d.ts.map +1 -1
  52. package/dist/bakeoff/quality.js +31 -0
  53. package/dist/bakeoff/replay.d.ts +11 -0
  54. package/dist/bakeoff/replay.d.ts.map +1 -1
  55. package/dist/bakeoff/replay.js +12 -0
  56. package/dist/bakeoff/report.d.ts +7 -0
  57. package/dist/bakeoff/report.d.ts.map +1 -1
  58. package/dist/bakeoff/report.js +26 -0
  59. package/dist/cli-judge.d.ts +12 -0
  60. package/dist/cli-judge.d.ts.map +1 -0
  61. package/dist/cli-judge.js +48 -0
  62. package/dist/cli.d.ts.map +1 -1
  63. package/dist/cli.js +22 -42
  64. package/dist/config.d.ts +85 -29
  65. package/dist/config.d.ts.map +1 -1
  66. package/dist/config.js +109 -47
  67. package/dist/detect/detector.d.ts +4 -2
  68. package/dist/detect/detector.d.ts.map +1 -1
  69. package/dist/detect/safe-harbor.d.ts +57 -0
  70. package/dist/detect/safe-harbor.d.ts.map +1 -0
  71. package/dist/detect/safe-harbor.js +278 -0
  72. package/dist/doctor-cli.d.ts +7 -0
  73. package/dist/doctor-cli.d.ts.map +1 -0
  74. package/dist/doctor-cli.js +67 -0
  75. package/dist/doctor-preflight.d.ts +52 -0
  76. package/dist/doctor-preflight.d.ts.map +1 -0
  77. package/dist/doctor-preflight.js +112 -0
  78. package/dist/feedback/coverage.d.ts +11 -8
  79. package/dist/feedback/coverage.d.ts.map +1 -1
  80. package/dist/feedback/coverage.js +29 -16
  81. package/dist/feedback/grade.d.ts +58 -0
  82. package/dist/feedback/grade.d.ts.map +1 -0
  83. package/dist/feedback/grade.js +67 -0
  84. package/dist/gateway/http-protocol.d.ts +20 -1
  85. package/dist/gateway/http-protocol.d.ts.map +1 -1
  86. package/dist/gateway/http-protocol.js +1 -1
  87. package/dist/gateway/serve-client.d.ts +99 -0
  88. package/dist/gateway/serve-client.d.ts.map +1 -0
  89. package/dist/gateway/serve-client.js +360 -0
  90. package/dist/index.d.ts +68 -29
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +167 -46
  93. package/dist/init/observe-cli.js +1 -1
  94. package/dist/init/observe.d.ts +2 -2
  95. package/dist/init/observe.d.ts.map +1 -1
  96. package/dist/init/observe.js +11 -7
  97. package/dist/mask/arm.d.ts +112 -0
  98. package/dist/mask/arm.d.ts.map +1 -0
  99. package/dist/mask/arm.js +179 -0
  100. package/dist/mask/index.d.ts +2 -0
  101. package/dist/mask/index.d.ts.map +1 -1
  102. package/dist/mask/index.js +7 -1
  103. package/dist/mask/mask-policy-cli.d.ts +12 -4
  104. package/dist/mask/mask-policy-cli.d.ts.map +1 -1
  105. package/dist/mask/mask-policy-cli.js +34 -9
  106. package/dist/prompts/manifest.d.ts +128 -0
  107. package/dist/prompts/manifest.d.ts.map +1 -0
  108. package/dist/prompts/manifest.js +214 -0
  109. package/dist/prompts/prompt-fetch.d.ts +84 -0
  110. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  111. package/dist/prompts/prompt-fetch.js +120 -0
  112. package/dist/prompts/prompt-resolve.d.ts +76 -0
  113. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  114. package/dist/prompts/prompt-resolve.js +93 -0
  115. package/dist/prompts/slot-resolver.d.ts +91 -0
  116. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  117. package/dist/prompts/slot-resolver.js +135 -0
  118. package/dist/prompts/tools-register.d.ts +60 -0
  119. package/dist/prompts/tools-register.d.ts.map +1 -0
  120. package/dist/prompts/tools-register.js +48 -0
  121. package/dist/prompts/version-hash.d.ts +29 -0
  122. package/dist/prompts/version-hash.d.ts.map +1 -0
  123. package/dist/prompts/version-hash.js +54 -0
  124. package/dist/prompts-export-cli.d.ts +21 -0
  125. package/dist/prompts-export-cli.d.ts.map +1 -0
  126. package/dist/prompts-export-cli.js +130 -0
  127. package/dist/query/grammar.d.ts +2 -2
  128. package/dist/query/grammar.d.ts.map +1 -1
  129. package/dist/query/grammar.js +3 -1
  130. package/dist/query/saved.d.ts +13 -0
  131. package/dist/query/saved.d.ts.map +1 -1
  132. package/dist/query/saved.js +67 -1
  133. package/dist/receipts/types.d.ts +1 -1
  134. package/dist/receipts/types.d.ts.map +1 -1
  135. package/dist/receipts/types.js +1 -0
  136. package/dist/registry/build.d.ts +43 -1
  137. package/dist/registry/build.d.ts.map +1 -1
  138. package/dist/registry/build.js +52 -0
  139. package/dist/registry/cache-policy.d.ts.map +1 -1
  140. package/dist/registry/cache-policy.js +22 -0
  141. package/dist/registry/pricing-shape.d.ts +67 -8
  142. package/dist/registry/pricing-shape.d.ts.map +1 -1
  143. package/dist/registry/pricing-shape.js +50 -13
  144. package/dist/registry/pricing.d.ts +6 -5
  145. package/dist/registry/pricing.d.ts.map +1 -1
  146. package/dist/registry/pricing.js +9 -5
  147. package/dist/registry/rows.d.ts.map +1 -1
  148. package/dist/registry/rows.js +1 -0
  149. package/dist/registry/select.d.ts +6 -1
  150. package/dist/registry/select.d.ts.map +1 -1
  151. package/dist/registry/select.js +11 -0
  152. package/dist/routes/call-config.d.ts +199 -0
  153. package/dist/routes/call-config.d.ts.map +1 -0
  154. package/dist/routes/call-config.js +343 -0
  155. package/dist/routes/destination-loader.d.ts +16 -0
  156. package/dist/routes/destination-loader.d.ts.map +1 -0
  157. package/dist/routes/destination-loader.js +29 -0
  158. package/dist/routes/draft.d.ts +7 -0
  159. package/dist/routes/draft.d.ts.map +1 -1
  160. package/dist/routes/draft.js +12 -0
  161. package/dist/routes/loader.d.ts.map +1 -1
  162. package/dist/routes/loader.js +31 -56
  163. package/dist/routes/objective-loader.d.ts +12 -0
  164. package/dist/routes/objective-loader.d.ts.map +1 -1
  165. package/dist/routes/objective-loader.js +82 -0
  166. package/dist/routes/output-loader.d.ts +42 -0
  167. package/dist/routes/output-loader.d.ts.map +1 -0
  168. package/dist/routes/output-loader.js +112 -0
  169. package/dist/routes/prompt-loader.d.ts +49 -0
  170. package/dist/routes/prompt-loader.d.ts.map +1 -0
  171. package/dist/routes/prompt-loader.js +113 -0
  172. package/dist/routes/resolved-bundle.d.ts +69 -0
  173. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  174. package/dist/routes/resolved-bundle.js +76 -0
  175. package/dist/routes/screen-loader.d.ts +29 -1
  176. package/dist/routes/screen-loader.d.ts.map +1 -1
  177. package/dist/routes/screen-loader.js +59 -1
  178. package/dist/run/accept.d.ts +5 -0
  179. package/dist/run/accept.d.ts.map +1 -1
  180. package/dist/run/accept.js +2 -0
  181. package/dist/run/cost.d.ts +31 -2
  182. package/dist/run/cost.d.ts.map +1 -1
  183. package/dist/run/cost.js +57 -2
  184. package/dist/run/destination.d.ts +39 -0
  185. package/dist/run/destination.d.ts.map +1 -0
  186. package/dist/run/destination.js +45 -0
  187. package/dist/run/dispatch.d.ts +67 -16
  188. package/dist/run/dispatch.d.ts.map +1 -1
  189. package/dist/run/dispatch.js +133 -29
  190. package/dist/run/grounding.d.ts +28 -2
  191. package/dist/run/grounding.d.ts.map +1 -1
  192. package/dist/run/grounding.js +54 -10
  193. package/dist/run/raw-defaults.d.ts.map +1 -1
  194. package/dist/run/raw-defaults.js +6 -1
  195. package/dist/run/raw-finalize.d.ts +50 -0
  196. package/dist/run/raw-finalize.d.ts.map +1 -0
  197. package/dist/run/raw-finalize.js +266 -0
  198. package/dist/run/raw-knobs.d.ts +36 -0
  199. package/dist/run/raw-knobs.d.ts.map +1 -0
  200. package/dist/run/raw-knobs.js +99 -0
  201. package/dist/run/raw-request.d.ts +24 -0
  202. package/dist/run/raw-request.d.ts.map +1 -0
  203. package/dist/run/raw-request.js +69 -0
  204. package/dist/run/raw-schema.d.ts +76 -0
  205. package/dist/run/raw-schema.d.ts.map +1 -0
  206. package/dist/run/raw-schema.js +166 -0
  207. package/dist/run/raw-search-cost.d.ts +48 -0
  208. package/dist/run/raw-search-cost.d.ts.map +1 -0
  209. package/dist/run/raw-search-cost.js +78 -0
  210. package/dist/run/raw-support.d.ts +64 -6
  211. package/dist/run/raw-support.d.ts.map +1 -1
  212. package/dist/run/raw-support.js +84 -11
  213. package/dist/run/raw.d.ts +25 -26
  214. package/dist/run/raw.d.ts.map +1 -1
  215. package/dist/run/raw.js +30 -351
  216. package/dist/run/record-attempt.d.ts +12 -0
  217. package/dist/run/record-attempt.d.ts.map +1 -1
  218. package/dist/run/record-attempt.js +30 -12
  219. package/dist/run/repair-attempt.d.ts +68 -0
  220. package/dist/run/repair-attempt.d.ts.map +1 -0
  221. package/dist/run/repair-attempt.js +180 -0
  222. package/dist/run/resolve-destination.d.ts +49 -0
  223. package/dist/run/resolve-destination.d.ts.map +1 -0
  224. package/dist/run/resolve-destination.js +76 -0
  225. package/dist/run/routing-docs.d.ts.map +1 -1
  226. package/dist/run/routing-docs.js +10 -5
  227. package/dist/run/run.d.ts +30 -1
  228. package/dist/run/run.d.ts.map +1 -1
  229. package/dist/run/run.js +94 -172
  230. package/dist/run/telemetry-row.d.ts +92 -0
  231. package/dist/run/telemetry-row.d.ts.map +1 -0
  232. package/dist/run/telemetry-row.js +81 -0
  233. package/dist/run/validate.d.ts +12 -1
  234. package/dist/run/validate.d.ts.map +1 -1
  235. package/dist/run/validate.js +31 -0
  236. package/dist/run/web-search-pricing.d.ts +43 -0
  237. package/dist/run/web-search-pricing.d.ts.map +1 -0
  238. package/dist/run/web-search-pricing.js +56 -0
  239. package/dist/savings/odometer.d.ts +4 -0
  240. package/dist/savings/odometer.d.ts.map +1 -1
  241. package/dist/savings/odometer.js +20 -1
  242. package/dist/savings/swap-execute.d.ts +7 -0
  243. package/dist/savings/swap-execute.d.ts.map +1 -1
  244. package/dist/savings/swap-execute.js +30 -0
  245. package/dist/savings/swaps-client.d.ts +10 -0
  246. package/dist/savings/swaps-client.d.ts.map +1 -1
  247. package/dist/screen/fixtures/probe-corpora.d.ts +23 -0
  248. package/dist/screen/fixtures/probe-corpora.d.ts.map +1 -0
  249. package/dist/screen/fixtures/probe-corpora.js +69 -0
  250. package/dist/screen/index.d.ts +21 -0
  251. package/dist/screen/index.d.ts.map +1 -0
  252. package/dist/screen/index.js +64 -0
  253. package/dist/screen/model-screen.d.ts +111 -0
  254. package/dist/screen/model-screen.d.ts.map +1 -0
  255. package/dist/screen/model-screen.js +178 -0
  256. package/dist/screen/patterns.d.ts +51 -0
  257. package/dist/screen/patterns.d.ts.map +1 -0
  258. package/dist/screen/patterns.js +151 -0
  259. package/dist/screen/phi-gate.d.ts +52 -0
  260. package/dist/screen/phi-gate.d.ts.map +1 -0
  261. package/dist/screen/phi-gate.js +86 -0
  262. package/dist/screen/policy.d.ts +71 -0
  263. package/dist/screen/policy.d.ts.map +1 -0
  264. package/dist/screen/policy.js +334 -0
  265. package/dist/screen/probe.d.ts +118 -0
  266. package/dist/screen/probe.d.ts.map +1 -0
  267. package/dist/screen/probe.js +132 -0
  268. package/dist/screen/screen-cli.d.ts +16 -0
  269. package/dist/screen/screen-cli.d.ts.map +1 -0
  270. package/dist/screen/screen-cli.js +178 -0
  271. package/dist/screen/screen.d.ts +99 -0
  272. package/dist/screen/screen.d.ts.map +1 -0
  273. package/dist/screen/screen.js +0 -0
  274. package/dist/screen/sidecar.d.ts +69 -0
  275. package/dist/screen/sidecar.d.ts.map +1 -0
  276. package/dist/screen/sidecar.js +145 -0
  277. package/dist/screen/types.d.ts +43 -2
  278. package/dist/screen/types.d.ts.map +1 -1
  279. package/dist/settings/api-fetcher.d.ts +14 -3
  280. package/dist/settings/api-fetcher.d.ts.map +1 -1
  281. package/dist/settings/api-fetcher.js +61 -10
  282. package/dist/settings/grading-guide.d.ts +102 -0
  283. package/dist/settings/grading-guide.d.ts.map +1 -0
  284. package/dist/settings/grading-guide.js +23 -0
  285. package/dist/settings/index.d.ts +3 -2
  286. package/dist/settings/index.d.ts.map +1 -1
  287. package/dist/settings/index.js +2 -1
  288. package/dist/settings/task-settings.d.ts +44 -7
  289. package/dist/settings/task-settings.d.ts.map +1 -1
  290. package/dist/settings/task-settings.js +53 -12
  291. package/dist/status.d.ts +32 -20
  292. package/dist/status.d.ts.map +1 -1
  293. package/dist/status.js +51 -30
  294. package/dist/telemetry/artifact-store.d.ts +5 -6
  295. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  296. package/dist/telemetry/artifact-store.js +21 -24
  297. package/dist/telemetry/attempt-meta.d.ts +76 -1
  298. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  299. package/dist/telemetry/attempt-meta.js +33 -1
  300. package/dist/telemetry/exporter-setup.d.ts +2 -0
  301. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  302. package/dist/telemetry/exporter-setup.js +6 -1
  303. package/dist/telemetry/exporter.d.ts +2 -2
  304. package/dist/telemetry/exporter.d.ts.map +1 -1
  305. package/dist/telemetry/exporter.js +22 -41
  306. package/dist/telemetry/grade-mappers.d.ts +25 -0
  307. package/dist/telemetry/grade-mappers.d.ts.map +1 -0
  308. package/dist/telemetry/grade-mappers.js +64 -0
  309. package/dist/telemetry/grades-sql.d.ts +56 -0
  310. package/dist/telemetry/grades-sql.d.ts.map +1 -0
  311. package/dist/telemetry/grades-sql.js +156 -0
  312. package/dist/telemetry/ingest-protocol.d.ts +55 -48
  313. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  314. package/dist/telemetry/ingest-protocol.js +48 -83
  315. package/dist/telemetry/org-identity.d.ts +44 -0
  316. package/dist/telemetry/org-identity.d.ts.map +1 -0
  317. package/dist/telemetry/org-identity.js +98 -0
  318. package/dist/telemetry/route-registration.d.ts +48 -0
  319. package/dist/telemetry/route-registration.d.ts.map +1 -0
  320. package/dist/telemetry/route-registration.js +77 -0
  321. package/dist/telemetry/sinks.d.ts +10 -1
  322. package/dist/telemetry/sinks.d.ts.map +1 -1
  323. package/dist/telemetry/sinks.js +52 -6
  324. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  325. package/dist/telemetry/store-migrations.js +64 -0
  326. package/dist/telemetry/store.d.ts +9 -6
  327. package/dist/telemetry/store.d.ts.map +1 -1
  328. package/dist/telemetry/store.js +8 -50
  329. package/dist/tenancy/api-key-format.d.ts +45 -1
  330. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  331. package/dist/tenancy/api-key-format.js +50 -1
  332. package/dist/types/provider.d.ts +23 -3
  333. package/dist/types/provider.d.ts.map +1 -1
  334. package/dist/types/provider.js +26 -0
  335. package/dist/types/route.d.ts +154 -2
  336. package/dist/types/route.d.ts.map +1 -1
  337. package/dist/types/telemetry.d.ts +73 -48
  338. package/dist/types/telemetry.d.ts.map +1 -1
  339. package/dist/validate-cli.d.ts.map +1 -1
  340. package/dist/validate-cli.js +32 -0
  341. package/dist/verify/verify.d.ts +5 -0
  342. package/dist/verify/verify.d.ts.map +1 -1
  343. package/dist/verify/verify.js +2 -0
  344. package/dist/watch/evaluate.d.ts +84 -1
  345. package/dist/watch/evaluate.d.ts.map +1 -1
  346. package/dist/watch/evaluate.js +28 -0
  347. package/dist/watch/monitor.d.ts +8 -2
  348. package/dist/watch/monitor.d.ts.map +1 -1
  349. package/dist/watch/monitor.js +13 -5
  350. package/dist/watch/regret-monitor-cli.d.ts +16 -0
  351. package/dist/watch/regret-monitor-cli.d.ts.map +1 -0
  352. package/dist/watch/regret-monitor-cli.js +137 -0
  353. package/dist/watch/regret-monitor.d.ts +168 -0
  354. package/dist/watch/regret-monitor.d.ts.map +1 -0
  355. package/dist/watch/regret-monitor.js +304 -0
  356. package/dist/watch/upgrade.d.ts +62 -0
  357. package/dist/watch/upgrade.d.ts.map +1 -0
  358. package/dist/watch/upgrade.js +57 -0
  359. package/docs/agent-skill/SKILL.md +181 -9
  360. package/docs/how-it-fits.md +3 -2
  361. package/docs/instrumentation.md +219 -46
  362. package/docs/llms.txt +158 -35
  363. package/docs/managed-serving.md +150 -0
  364. package/docs/mcp-oracle.md +322 -0
  365. package/docs/migration-playbook.md +5 -4
  366. package/docs/quickstart.md +263 -89
  367. package/package.json +18 -4
  368. package/registry/leaderboard.json +74 -0
  369. package/registry/registry.json +232 -112
  370. package/screen.d.ts +16 -0
  371. package/templates/call-disposition-qa.json +77 -0
  372. package/templates/catalog-cleansing.json +77 -0
  373. package/templates/compliance-review.json +76 -0
  374. package/templates/cs-next-action.json +76 -0
  375. package/templates/document-extraction.json +78 -0
  376. package/templates/financial-classification.json +75 -0
  377. package/templates/lead-qualification.json +76 -0
  378. package/templates/medical-classification.json +71 -0
  379. package/templates/ticket-triage.json +76 -0
  380. package/dist/feedback/feedback.d.ts +0 -35
  381. package/dist/feedback/feedback.d.ts.map +0 -1
  382. package/dist/feedback/feedback.js +0 -39
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ /**
3
+ * SDK-side live call-config resolution (live-routing-evals spec §4, R-1; LRE-1a).
4
+ *
5
+ * A route's live call-config (which model/provider + sampling) can be edited in
6
+ * the console WITHOUT a redeploy. The SDK reads it just-before-call through
7
+ * `GET /v1/routes/:route/call-config`, caches the policy (short TTL, ETag-
8
+ * revalidated), and MERGES the pin over the resolved (mirrored/deploy-time)
9
+ * bundle before `run()`. This is R-1's cached-policy model: no per-call network
10
+ * cost when fresh, and outage-safe.
11
+ *
12
+ * ── FAIL-OPEN, ALWAYS — the hot path never throws ─────────────────────────
13
+ * `getCallConfig` NEVER throws. On any error it returns the LAST-KNOWN-GOOD
14
+ * cached policy; if there is none, it returns null — and a null resolution means
15
+ * "serve the mirrored/pinned bundle unchanged". A route with no override
16
+ * resolves BYTE-IDENTICAL to today (the golden invariant): `applyCallConfig`
17
+ * returns the SAME bundle reference when there is nothing to merge.
18
+ *
19
+ * ── PHASE 1 = PIN ONLY ────────────────────────────────────────────────────
20
+ * `mode='experiment'` (local weighted arm draw) and `mode='live'` (strict per-
21
+ * call read) are TYPED here so the wire shape is complete, but they are INERT:
22
+ * `applyCallConfig` acts on `mode='pin'` only and passes everything else through
23
+ * unchanged. Experiments ship in LRE-2.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.parseCallConfig = parseCallConfig;
27
+ exports.narrowPin = narrowPin;
28
+ exports.pinFromResolved = pinFromResolved;
29
+ exports.applyCallConfig = applyCallConfig;
30
+ exports.armName = armName;
31
+ exports.narrowExperiment = narrowExperiment;
32
+ exports.hashUnitKey = hashUnitKey;
33
+ exports.drawArm = drawArm;
34
+ exports.resolveCallConfigForRun = resolveCallConfigForRun;
35
+ exports.createCallConfigResolver = createCallConfigResolver;
36
+ exports.createApiCallConfigFetcher = createApiCallConfigFetcher;
37
+ exports.getCallConfig = getCallConfig;
38
+ function isMode(v) {
39
+ return v === "pin" || v === "experiment" || v === "live";
40
+ }
41
+ /** Narrow an unknown endpoint body into a `ResolvedCallConfig`, or null when the
42
+ * shape is not what the endpoint promises (a malformed 200 is a miss, not
43
+ * trusted — we never fabricate an override from junk). */
44
+ function parseCallConfig(body) {
45
+ if (typeof body !== "object" || body === null)
46
+ return null;
47
+ const b = body;
48
+ if (typeof b.version !== "number" || !Number.isInteger(b.version))
49
+ return null;
50
+ if (typeof b.etag !== "string")
51
+ return null;
52
+ const mode = b.mode === null ? null : isMode(b.mode) ? b.mode : undefined;
53
+ if (mode === undefined)
54
+ return null;
55
+ return { version: b.version, mode, config: b.config ?? null, etag: b.etag };
56
+ }
57
+ /** Narrow a config to a `CallConfigPin`, or null (T7 — jsonb is not proof of
58
+ * shape). The single place a pin's shape is trusted on the SDK side. */
59
+ function narrowPin(config) {
60
+ if (typeof config !== "object" || config === null || Array.isArray(config))
61
+ return null;
62
+ const c = config;
63
+ if (typeof c.provider !== "string" || c.provider === "")
64
+ return null;
65
+ if (typeof c.model !== "string" || c.model === "")
66
+ return null;
67
+ const pin = {
68
+ provider: c.provider,
69
+ model: c.model,
70
+ };
71
+ if (typeof c.sampling === "object" && c.sampling !== null && !Array.isArray(c.sampling)) {
72
+ const s = c.sampling;
73
+ const sampling = {};
74
+ if (typeof s.temperature === "number")
75
+ sampling.temperature = s.temperature;
76
+ if (typeof s.top_p === "number")
77
+ sampling.top_p = s.top_p;
78
+ if (typeof s.max_output_tokens === "number")
79
+ sampling.max_output_tokens = s.max_output_tokens;
80
+ pin.sampling = sampling;
81
+ }
82
+ if (typeof c.variant === "string")
83
+ pin.variant = c.variant;
84
+ return pin;
85
+ }
86
+ /** The pin carried by a resolved config, or null when there is no pin override
87
+ * (no override, or a non-pin mode — inert in Phase 1). */
88
+ function pinFromResolved(resolved) {
89
+ if (resolved === null || resolved.mode !== "pin")
90
+ return null;
91
+ return narrowPin(resolved.config);
92
+ }
93
+ /** Map the wire sampling (snake_case) to the SDK's `SamplingParams` (camelCase),
94
+ * merged over the bundle's existing sampling. Only present keys override. */
95
+ function mergeSampling(base, over) {
96
+ if (over === undefined)
97
+ return base;
98
+ const out = { ...(base ?? {}) };
99
+ if (over.temperature !== undefined)
100
+ out.temperature = over.temperature;
101
+ if (over.top_p !== undefined)
102
+ out.topP = over.top_p;
103
+ if (over.max_output_tokens !== undefined)
104
+ out.maxOutputTokens = over.max_output_tokens;
105
+ return out;
106
+ }
107
+ /** Merge a single narrowed pin over a bundle — REPLACES the candidate set with
108
+ * the pinned model (eligibility was gated at write) and folds its sampling into
109
+ * the policy. The one place a pin becomes a runnable bundle; shared by the pin
110
+ * path and the experiment arm-draw so both merge identically. */
111
+ function applyPin(bundle, pin) {
112
+ const pinnedCandidate = {
113
+ provider: pin.provider,
114
+ model: pin.model,
115
+ trial: false,
116
+ };
117
+ const sampling = mergeSampling(bundle.policy.sampling, pin.sampling);
118
+ return {
119
+ ...bundle,
120
+ candidates: [pinnedCandidate],
121
+ policy: { ...bundle.policy, ...(sampling !== undefined ? { sampling } : {}) },
122
+ };
123
+ }
124
+ /**
125
+ * Merge a resolved call-config over a bundle, returning a runnable bundle.
126
+ *
127
+ * INERT BY DEFAULT: when `resolved` is null, or its mode is not `pin`, or the pin
128
+ * does not narrow, the SAME `bundle` reference is returned — byte-identical to
129
+ * today (the golden invariant). Otherwise the pin REPLACES the candidate set with
130
+ * the single pinned model (eligibility was gated at write, so it satisfies the
131
+ * route `require`) and merges its sampling into the policy.
132
+ *
133
+ * `mode='experiment'` is INERT here (returns the bundle unchanged): an experiment
134
+ * resolves through {@link resolveCallConfigForRun}, which draws an arm and returns
135
+ * the arm name for `inferences.meta.arm`. `applyCallConfig` stays pin-only so the
136
+ * pin/golden path is byte-identical.
137
+ */
138
+ function applyCallConfig(bundle, resolved) {
139
+ const pin = pinFromResolved(resolved);
140
+ if (pin === null)
141
+ return bundle;
142
+ return applyPin(bundle, pin);
143
+ }
144
+ /** The stable arm name stamped on `inferences.meta.arm` — the caller-declared
145
+ * name, else the pin's variant, else `provider/model`. Kept meta-safe (short, no
146
+ * whitespace) by construction: providers/models/variants are identifiers. */
147
+ function armName(arm) {
148
+ return arm.name ?? arm.ref.variant ?? `${arm.ref.provider}/${arm.ref.model}`;
149
+ }
150
+ /** Narrow a jsonb `config` to a `CallConfigExperiment`, or null (T7 — jsonb is
151
+ * not proof of shape). Requires ≥1 well-formed arm (each `ref` a valid pin, each
152
+ * `weight` a positive finite number) and a fallback pin. Fail-open: a malformed
153
+ * experiment narrows to null and the caller serves the mirrored bundle. */
154
+ function narrowExperiment(config) {
155
+ if (typeof config !== "object" || config === null || Array.isArray(config))
156
+ return null;
157
+ const c = config;
158
+ if (!Array.isArray(c.arms) || c.arms.length === 0)
159
+ return null;
160
+ const fallback = narrowPin(c.fallback);
161
+ if (fallback === null)
162
+ return null;
163
+ const arms = [];
164
+ for (const raw of c.arms) {
165
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
166
+ return null;
167
+ const r = raw;
168
+ const ref = narrowPin(r.ref);
169
+ if (ref === null)
170
+ return null;
171
+ if (typeof r.weight !== "number" || !Number.isFinite(r.weight) || r.weight <= 0)
172
+ return null;
173
+ const arm = { ref, weight: r.weight };
174
+ if (typeof r.name === "string" && r.name !== "")
175
+ arm.name = r.name;
176
+ arms.push(arm);
177
+ }
178
+ const exp = { arms, fallback };
179
+ if (typeof c.stickyKey === "string" && c.stickyKey !== "")
180
+ exp.stickyKey = c.stickyKey;
181
+ return exp;
182
+ }
183
+ /** FNV-1a 32-bit hash of a string → a stable uint32. Pure, dependency-free — the
184
+ * sticky-assignment key so the SAME unit key always draws the SAME arm (an A/B is
185
+ * measured honestly). Not cryptographic; a fast, well-distributed bucket hash. */
186
+ function hashUnitKey(key) {
187
+ let h = 0x811c9dc5;
188
+ for (let i = 0; i < key.length; i += 1) {
189
+ h ^= key.charCodeAt(i);
190
+ h = Math.imul(h, 0x01000193);
191
+ }
192
+ return h >>> 0;
193
+ }
194
+ /**
195
+ * Draw one arm from an experiment (spec §4). Weighted by `weight`: an arm's draw
196
+ * probability is `weight / Σweights`. DETERMINISTIC by `hash(unitKey)` when a
197
+ * unit key is supplied (sticky assignment); weighted-random otherwise. Returns
198
+ * null only when there are no positive-weight arms (⇒ the caller serves the
199
+ * fallback). Pure over its inputs (the RNG/hash are the only entropy).
200
+ */
201
+ function drawArm(experiment, opts = {}) {
202
+ const arms = experiment.arms.filter((a) => a.weight > 0);
203
+ if (arms.length === 0)
204
+ return null;
205
+ const total = arms.reduce((sum, a) => sum + a.weight, 0);
206
+ // A point in [0,total): a deterministic fraction of `total` from the unit-key
207
+ // hash (sticky), else a random fraction. NEVER lands exactly on `total` (the
208
+ // hash is divided by 2^32, and rng() < 1), so the last arm is not over-picked.
209
+ const fraction = opts.unitKey !== undefined ? hashUnitKey(opts.unitKey) / 0x100000000 : (opts.rng ?? Math.random)();
210
+ let point = fraction * total;
211
+ for (const arm of arms) {
212
+ point -= arm.weight;
213
+ if (point < 0)
214
+ return arm;
215
+ }
216
+ return arms[arms.length - 1]; // floating-point guard — the last arm owns the tail.
217
+ }
218
+ /**
219
+ * Resolve a route's call-config into a runnable bundle for ONE call, drawing an
220
+ * experiment arm locally when `mode='experiment'` (spec §4, R-1). FAIL-OPEN and
221
+ * byte-identical for pin: `mode='pin'` merges exactly like {@link applyCallConfig}
222
+ * (arm null), a null/`live`/malformed resolution returns the bundle unchanged
223
+ * (arm null), and a malformed experiment falls back to the experiment's own
224
+ * fallback pin, else the mirrored bundle. NEVER throws.
225
+ *
226
+ * For an experiment, the stickiness unit key is `opts.unitKey` when given, else
227
+ * the value of `input[experiment.stickyKey]` (resolved from the run's input) —
228
+ * so the SAME unit is served the SAME arm across calls.
229
+ */
230
+ function resolveCallConfigForRun(bundle, resolved, opts = {}) {
231
+ try {
232
+ if (resolved === null || resolved.mode === "live")
233
+ return { bundle, arm: null };
234
+ if (resolved.mode === "pin")
235
+ return { bundle: applyCallConfig(bundle, resolved), arm: null };
236
+ // mode === "experiment"
237
+ const experiment = narrowExperiment(resolved.config);
238
+ if (experiment === null)
239
+ return { bundle, arm: null }; // fail-open: inert.
240
+ const unitKey = opts.unitKey ??
241
+ (experiment.stickyKey !== undefined && opts.input !== undefined && opts.input[experiment.stickyKey] !== undefined
242
+ ? String(opts.input[experiment.stickyKey])
243
+ : undefined);
244
+ const arm = drawArm(experiment, {
245
+ ...(unitKey !== undefined ? { unitKey } : {}),
246
+ ...(opts.rng !== undefined ? { rng: opts.rng } : {}),
247
+ });
248
+ if (arm === null)
249
+ return { bundle: applyPin(bundle, experiment.fallback), arm: null };
250
+ return { bundle: applyPin(bundle, arm.ref), arm: armName(arm) };
251
+ }
252
+ catch {
253
+ // The hot path never throws — an unexpected shape serves the mirrored bundle.
254
+ return { bundle, arm: null };
255
+ }
256
+ }
257
+ /**
258
+ * Build the cached call-config resolver. Holds a per-route cache with a fetch
259
+ * timestamp + ETag; a read within `ttlMs` returns the cache with no network; a
260
+ * stale read revalidates (If-None-Match) and folds the result in. Every fetch is
261
+ * fail-open: a rejection or a null leaves the last-known value in place, and a
262
+ * route never yet resolved returns null (⇒ serve the mirrored bundle unchanged).
263
+ */
264
+ function createCallConfigResolver(config) {
265
+ const now = config.now ?? (() => Date.now());
266
+ const cache = new Map();
267
+ return {
268
+ async getCallConfig(route) {
269
+ const entry = cache.get(route);
270
+ if (entry !== undefined && now() - entry.fetchedAt < config.ttlMs) {
271
+ return entry.value;
272
+ }
273
+ try {
274
+ const result = await config.fetcher.fetch(route, entry?.value.etag);
275
+ if (result === "not-modified") {
276
+ // Fresh again — bump the timestamp, keep the value.
277
+ if (entry !== undefined)
278
+ cache.set(route, { value: entry.value, fetchedAt: now() });
279
+ return entry?.value ?? null;
280
+ }
281
+ if (result === null) {
282
+ // Miss/error — fail-open to last-known-good (or null the first time).
283
+ return entry?.value ?? null;
284
+ }
285
+ cache.set(route, { value: result, fetchedAt: now() });
286
+ return result;
287
+ }
288
+ catch {
289
+ // The hot path never throws — fail-open to last-known-good.
290
+ return entry?.value ?? null;
291
+ }
292
+ },
293
+ snapshot() {
294
+ const out = new Map();
295
+ for (const [route, entry] of cache)
296
+ out.set(route, entry.value);
297
+ return out;
298
+ },
299
+ };
300
+ }
301
+ /**
302
+ * The api-mode call-config fetcher: one authenticated, ETag-aware GET per route.
303
+ * Mirrors the api sink's leak discipline (status only, never the body/URL in an
304
+ * error — C2). NEVER throws: a network/HTTP failure resolves null, a 304 resolves
305
+ * "not-modified", a malformed 200 resolves null — so the resolver stays fail-open.
306
+ */
307
+ function createApiCallConfigFetcher(config) {
308
+ const base = config.ingestUrl.replace(/\/$/, "");
309
+ const fetchFn = config.fetchFn ?? fetch;
310
+ return {
311
+ async fetch(route, etag) {
312
+ const url = `${base}/v1/routes/${encodeURIComponent(route)}/call-config`;
313
+ let response;
314
+ try {
315
+ response = await fetchFn(url, {
316
+ method: "GET",
317
+ headers: {
318
+ authorization: `Bearer ${config.apiKey}`,
319
+ ...(etag !== undefined ? { "if-none-match": etag } : {}),
320
+ },
321
+ });
322
+ }
323
+ catch {
324
+ return null; // network throw — fail-open, keep last-known.
325
+ }
326
+ if (response.status === 304)
327
+ return "not-modified";
328
+ if (!response.ok)
329
+ return null;
330
+ const body = (await response.json().catch(() => null));
331
+ return parseCallConfig(body);
332
+ },
333
+ };
334
+ }
335
+ /**
336
+ * Convenience: resolve `route`'s config through the resolver, then merge it over
337
+ * `bundle`. NEVER throws (delegates to `getCallConfig`'s fail-open) and returns
338
+ * the SAME bundle reference when there is no override (byte-identical).
339
+ */
340
+ async function getCallConfig(route, deps) {
341
+ const resolved = await deps.resolver.getCallConfig(route);
342
+ return applyCallConfig(deps.bundle, resolved);
343
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Serving-`destination` route-field validation (Unified Route U2, ruling D-2).
3
+ * Extracted from loader.ts so loader.ts holds under the 700-LOC cap (same reason
4
+ * loader-warnings.ts / screen-loader.ts were carved out). Hand-written validation
5
+ * mirroring the loader's PROVIDERS/TIERS pattern: a route's `destination:` is
6
+ * checked against the DESTINATIONS vocabulary (the ONE source of truth in
7
+ * run/destination.ts — no second list to drift); an unknown value pushes a
8
+ * teaching problem (the caller turns it into a RouteConfigError). Absent ⇒
9
+ * undefined ⇒ resolves to `direct` at run time ⇒ BYTE-IDENTICAL.
10
+ */
11
+ import type { Destination } from "../run/destination";
12
+ /** Validate a route bundle's `destination:` value. Absent/null ⇒ undefined
13
+ * (defaults to `direct`); a known value ⇒ that `Destination`; an unknown value ⇒
14
+ * a pushed problem and undefined. */
15
+ export declare function parseDestination(raw: unknown, problems: string[]): Destination | undefined;
16
+ //# sourceMappingURL=destination-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destination-loader.d.ts","sourceRoot":"","sources":["../../src/routes/destination-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAMtD;;qCAEqC;AACrC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,GAAG,SAAS,CAO1F"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * Serving-`destination` route-field validation (Unified Route U2, ruling D-2).
4
+ * Extracted from loader.ts so loader.ts holds under the 700-LOC cap (same reason
5
+ * loader-warnings.ts / screen-loader.ts were carved out). Hand-written validation
6
+ * mirroring the loader's PROVIDERS/TIERS pattern: a route's `destination:` is
7
+ * checked against the DESTINATIONS vocabulary (the ONE source of truth in
8
+ * run/destination.ts — no second list to drift); an unknown value pushes a
9
+ * teaching problem (the caller turns it into a RouteConfigError). Absent ⇒
10
+ * undefined ⇒ resolves to `direct` at run time ⇒ BYTE-IDENTICAL.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.parseDestination = parseDestination;
14
+ const destination_1 = require("../run/destination");
15
+ /** The serving-`destination` vocabulary as a lookup set, mirroring the loader's
16
+ * PROVIDERS/TIERS ReadonlySets. */
17
+ const DESTINATIONS = new Set(destination_1.DESTINATIONS);
18
+ /** Validate a route bundle's `destination:` value. Absent/null ⇒ undefined
19
+ * (defaults to `direct`); a known value ⇒ that `Destination`; an unknown value ⇒
20
+ * a pushed problem and undefined. */
21
+ function parseDestination(raw, problems) {
22
+ if (raw === undefined || raw === null)
23
+ return undefined;
24
+ if (typeof raw !== "string" || !DESTINATIONS.has(raw)) {
25
+ problems.push(`destination "${String(raw)}" is not one of [${[...DESTINATIONS].join(", ")}]`);
26
+ return undefined;
27
+ }
28
+ return raw;
29
+ }
@@ -34,6 +34,13 @@ export interface RouteDraftSpec {
34
34
  readonly variables: readonly string[];
35
35
  /** JSON Schema for the output; null drafts an unstructured route. */
36
36
  readonly outputSchema: object | null;
37
+ /** `output: text` (issue #1) — a prose/markdown route, returned verbatim.
38
+ * Absent / `"json"` ⇒ nothing is emitted (the loader default). `text` with a
39
+ * non-null `outputSchema` is refused here, as the loader would refuse it. */
40
+ readonly outputKind?: "json" | "text";
41
+ /** `prompt.user` (issue #5, R1) — the per-call user-turn template, written
42
+ * to `prompts/<name>.user.md` and declared beside `prompt.system`. */
43
+ readonly userPrompt?: string;
37
44
  readonly require?: readonly string[];
38
45
  readonly prefer?: readonly string[];
39
46
  readonly policy?: {
@@ -1 +1 @@
1
- {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/routes/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C;wCACoC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACrD,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;6EACyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAmBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAgGhE;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE;IACF,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1D,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D,EACD,IAAI,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAclB"}
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/routes/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C;wCACoC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;iFAE6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC;0EACsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACrD,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;6EACyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAmBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CA6GhE;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE;IACF,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1D,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D,EACD,IAAI,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAclB"}
@@ -40,10 +40,15 @@ function buildRouteDraft(spec) {
40
40
  throw new Error(`route draft "${spec.route}" has no candidates — a route with nothing allowed to serve it ` +
41
41
  "cannot run. Name at least one provider/model.");
42
42
  }
43
+ if (spec.outputKind === "text" && spec.outputSchema !== null) {
44
+ throw new Error(`route draft "${spec.route}" declares output: text AND a schema — a prose route has no schema. ` +
45
+ "Drop the schema, or draft a JSON route.");
46
+ }
43
47
  const name = slug(spec.route);
44
48
  const files = [];
45
49
  const schemaRef = spec.outputSchema === null ? null : `./schemas/${name}.schema.json`;
46
50
  const promptRef = `./prompts/${name}.system.md`;
51
+ const userPromptRef = spec.userPrompt === undefined ? null : `./prompts/${name}.user.md`;
47
52
  const lines = [
48
53
  "# ModelRig route bundle — DRAFT. Nothing in your app calls this yet.",
49
54
  "# Read it, edit it, move it up one directory into modelrig/routes/, then",
@@ -55,6 +60,8 @@ function buildRouteDraft(spec) {
55
60
  lines.push(`# ${line}`);
56
61
  }
57
62
  lines.push("#", "# Candidates are ordered incumbent-first: the model you already use leads,", "# so the first run reproduces today's behavior. The cheaper probed", "# alternatives below it are what a bake-off would put to the test.", "#", "# `prefer` is empty ON PURPOSE. Adding `cost` re-sorts these candidates by", "# price, which would hand the first run to the cheapest model rather than", "# to your incumbent — the opposite of a like-for-like migration. Prove a", "# cheaper model with a bake-off, then switch to it deliberately.", `route: ${yamlString(spec.route)}`, "version: 1");
63
+ if (spec.outputKind === "text")
64
+ lines.push("output: text # prose/markdown, returned verbatim (no schema:)");
58
65
  if (schemaRef !== null)
59
66
  lines.push(`schema: ${schemaRef}`);
60
67
  lines.push("candidates:");
@@ -78,6 +85,8 @@ function buildRouteDraft(spec) {
78
85
  lines.push(`prefer: [${(spec.prefer ?? []).map(yamlString).join(", ")}]`);
79
86
  lines.push("prompt:");
80
87
  lines.push(` system: ${promptRef}`);
88
+ if (userPromptRef !== null)
89
+ lines.push(` user: ${userPromptRef} # per-call user turn (the delta next to a cache handle)`);
81
90
  lines.push(` variables: [${spec.variables.map(yamlString).join(", ")}]` +
82
91
  (spec.variables.length === 0 ? " # no {{variables}} detected — the prompt is static" : ""));
83
92
  const policy = spec.policy ?? {};
@@ -105,6 +114,9 @@ function buildRouteDraft(spec) {
105
114
  });
106
115
  }
107
116
  files.push({ path: `prompts/${name}.system.md`, contents: ensureTrailingNewline(spec.systemPrompt) });
117
+ if (spec.userPrompt !== undefined) {
118
+ files.push({ path: `prompts/${name}.user.md`, contents: ensureTrailingNewline(spec.userPrompt) });
119
+ }
108
120
  return { route: spec.route, files };
109
121
  }
110
122
  function ensureTrailingNewline(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/routes/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAKV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;AAWxB,OAAO,EAAE,sBAAsB,EAAE,kCAAkC,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAE/H;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO;CAOxF;AAiCD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC,IAAI,IAAI,SAAS,WAAW,EAAE,CAAC;CAChC;AAscD;;;;;;;GAOG;AACH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAItF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAyB1F;AAED;;yBAEyB;AACzB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA6CtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CA2C1D"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/routes/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAKV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;AAaxB,OAAO,EAAE,sBAAsB,EAAE,kCAAkC,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAE/H;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO;CAOxF;AA+BD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC,IAAI,IAAI,SAAS,WAAW,EAAE,CAAC;CAChC;AA2aD;;;;;;;GAOG;AACH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAItF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAyB1F;AAED;;yBAEyB;AACzB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA6CtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CA2C1D"}
@@ -14,13 +14,17 @@ const node_fs_1 = require("node:fs");
14
14
  const node_path_1 = require("node:path");
15
15
  const yaml_1 = require("yaml");
16
16
  const failures_1 = require("../types/failures");
17
+ const provider_1 = require("../types/provider");
17
18
  const fingerprint_1 = require("../registry/fingerprint");
18
19
  const cache_lint_1 = require("./cache-lint");
20
+ const destination_loader_1 = require("./destination-loader"); // U2 (D-2): validated `destination` field (carved out for the LOC cap)
19
21
  const sampling_config_1 = require("./sampling-config");
20
22
  const loader_warnings_1 = require("./loader-warnings");
21
23
  const screen_loader_1 = require("./screen-loader");
24
+ const prompt_loader_1 = require("./prompt-loader"); // issue #5 (R1): `prompt.user` + the prompt block (moved out for the LOC cap)
22
25
  const mask_loader_1 = require("./mask-loader");
23
26
  const objective_loader_1 = require("./objective-loader");
27
+ const output_loader_1 = require("./output-loader"); // issue #1: `output: text` (prose) + the schema reader (moved out for the LOC cap)
24
28
  // Re-exported so existing importers of "./loader" keep resolving these advisories
25
29
  // (they live in ./loader-warnings to hold loader.ts under the 700-LOC cap).
26
30
  var loader_warnings_2 = require("./loader-warnings");
@@ -43,13 +47,11 @@ class RouteConfigError extends Error {
43
47
  }
44
48
  }
45
49
  exports.RouteConfigError = RouteConfigError;
46
- const PROVIDERS = new Set([
47
- "gemini",
48
- "openai",
49
- "deepseek",
50
- "anthropic",
51
- "grok",
52
- ]);
50
+ // Provider allow-set for routable candidates and repair models (M-S1) — every
51
+ // known ProviderId, now INCLUDING the ModelRig-hosted aggregator hosts
52
+ // (fireworks/deepinfra/zai). Sourced from PROVIDER_IDS so it stays in lockstep
53
+ // with the union (compacted from an inline literal to hold the 700-LOC cap).
54
+ const PROVIDERS = new Set(provider_1.PROVIDER_IDS);
53
55
  const REQUIREMENTS = new Set([
54
56
  "schema_conformant",
55
57
  "grounded",
@@ -314,31 +316,6 @@ function parseVariants(raw, routeCandidates, problems) {
314
316
  }
315
317
  return out;
316
318
  }
317
- function readSchema(schemaRaw, bundleDir, problems) {
318
- if (schemaRaw === null || schemaRaw === undefined)
319
- return null;
320
- if (typeof schemaRaw !== "string" || schemaRaw === "") {
321
- problems.push("schema must be a relative path or null");
322
- return null;
323
- }
324
- const schemaPath = (0, node_path_1.resolve)(bundleDir, schemaRaw);
325
- if (!(0, node_fs_1.existsSync)(schemaPath)) {
326
- problems.push(`schema file not found: ${schemaRaw}`);
327
- return null;
328
- }
329
- try {
330
- const parsed = JSON.parse((0, node_fs_1.readFileSync)(schemaPath, "utf8"));
331
- if (!isRecord(parsed)) {
332
- problems.push(`schema file ${schemaRaw} must contain a JSON object`);
333
- return null;
334
- }
335
- return parsed;
336
- }
337
- catch (err) {
338
- problems.push(`schema file ${schemaRaw} is not valid JSON: ${err.message}`);
339
- return null;
340
- }
341
- }
342
319
  function loadBundleFile(filePath) {
343
320
  const problems = [];
344
321
  let doc;
@@ -365,12 +342,14 @@ function loadBundleFile(filePath) {
365
342
  const candidates = parseCandidates(doc.candidates, problems);
366
343
  const require = parseStringList(doc.require, "require", REQUIREMENTS, problems);
367
344
  const prefer = parseStringList(doc.prefer, "prefer", PREFERENCES, problems);
368
- const outputSchema = readSchema(doc.schema, bundleDir, problems);
345
+ const outputSchema = (0, output_loader_1.readSchema)(doc.schema, bundleDir, problems);
369
346
  const policy = parsePolicy(doc.policy, problems);
370
347
  // Phase 3 (bundle v2, all additive): variants, repair, capture opt-in,
371
348
  // grounding-inject query template.
372
349
  const variants = parseVariants(doc.variants, candidates, problems);
373
350
  const repair = parseRepair(doc.repair, "repair", candidates, problems);
351
+ // Issue #1: `output: text` (prose) — a load error when it contradicts schema:/require:/repair:.
352
+ const output = (0, output_loader_1.parseOutputBlock)(doc.output, { outputSchema, schemaDeclared: doc.schema !== undefined && doc.schema !== null, require, repair, variants }, problems);
374
353
  const capture = doc.capture ?? false;
375
354
  if (typeof capture !== "boolean") {
376
355
  problems.push("capture must be a boolean");
@@ -404,30 +383,16 @@ function loadBundleFile(filePath) {
404
383
  // RB-1 (near-optimal spec §3): objective knob, reference arm + judge, value
405
384
  // checks — all additive; candidate-set invariant enforced in parseRb1Fields.
406
385
  const rb1 = (0, objective_loader_1.parseRb1Fields)(doc, route, candidates, problems);
407
- // CS-1 Proven Masking (spec §3): resolve the `mask:` block + its local
408
- // dictionary here so an unreadable dictionary or unknown type is a LOAD error.
409
- // Absent / `mode: off` ⇒ undefined ⇒ byte-identical (the golden test).
386
+ // CS-1 `mask:` (spec §3) + SC-1a `screen:` (spec §4.1) + SC-2 `classification:`/`phi` posture gate (spec-sc2 §4): resolved here so an unreadable dictionary / unknown detector-or-action / loosening sources key / mis-postured PHI route is a LOAD error (absent / `mode: off` ⇒ undefined ⇒ byte-identical). Classification + phi-gate live in screen-loader.ts (LOC cap).
410
387
  const mask = (0, mask_loader_1.parseMaskBlock)(doc.mask, bundleDir, problems);
411
- let systemTemplate = "";
412
- let variables = [];
413
- let variableTrust;
414
- if (!isRecord(doc.prompt) || typeof doc.prompt.system !== "string") {
415
- problems.push("prompt.system must be a relative path");
416
- }
417
- else {
418
- const promptPath = (0, node_path_1.resolve)(bundleDir, doc.prompt.system);
419
- if (!(0, node_fs_1.existsSync)(promptPath)) {
420
- problems.push(`prompt.system file not found: ${doc.prompt.system}`);
421
- }
422
- else {
423
- systemTemplate = (0, node_fs_1.readFileSync)(promptPath, "utf8");
424
- }
425
- // Screening S2 (SC-0 B3): parse names + optional per-variable x-trust.
426
- const parsed = (0, screen_loader_1.parseVariables)(doc.prompt.variables ?? [], problems);
427
- variables = parsed.names;
428
- variableTrust = parsed.trust;
429
- }
430
- (0, screen_loader_1.pushUndeclaredVariableProblems)(systemTemplate, groundingQuery, variables, problems);
388
+ const screen = (0, screen_loader_1.parseScreenBlock)(doc.screen, problems);
389
+ const classification = (0, screen_loader_1.parseClassificationAndPhiGate)(doc.classification, { mask, screen, require }, problems);
390
+ const destination = (0, destination_loader_1.parseDestination)(doc.destination, problems); // Unified Route U2 (D-2)
391
+ // `prompt.system` / `prompt.user` (issue #5, R1) / `prompt.variables` (+ S2
392
+ // x-trust) — parsed in prompt-loader.ts; both templates are files relative to
393
+ // the bundle and share one declared-variable list.
394
+ const { systemTemplate, userTemplate, variables, variableTrust } = (0, prompt_loader_1.parsePromptBlock)(doc.prompt, bundleDir, problems);
395
+ (0, screen_loader_1.pushUndeclaredVariableProblems)(systemTemplate, groundingQuery, variables, problems, userTemplate);
431
396
  if (problems.length > 0) {
432
397
  throw new RouteConfigError("invalid route bundle", { file: filePath, problems });
433
398
  }
@@ -462,14 +427,22 @@ function loadBundleFile(filePath) {
462
427
  const xTrustWarning = (0, loader_warnings_1.untrustedShapedVariableWarning)(route, variables, variableTrust);
463
428
  if (xTrustWarning !== null)
464
429
  console.warn(`modelrig: ${xTrustWarning}`);
430
+ // Issue #5 (R1) advisory: a cached route with a Gemini candidate, a non-empty
431
+ // system template and no `prompt.user` — a handle would deliver the template
432
+ // as the user turn (never dropped); name the rule so the shape is deliberate.
433
+ const cachedPromptWarning = (0, prompt_loader_1.cachedRouteSystemOnlyWarning)(route, cache, candidates, systemTemplate, userTemplate);
434
+ if (cachedPromptWarning !== null)
435
+ console.warn(`modelrig: ${cachedPromptWarning}`);
465
436
  return Object.freeze({
466
437
  route: route,
467
438
  version: version,
468
439
  outputSchema: outputSchema === null ? null : Object.freeze(outputSchema),
440
+ ...output, // outputKind/outputMinChars — present ONLY for `output: text` (json ⇒ nothing added)
469
441
  candidates: Object.freeze(candidates),
470
442
  require: Object.freeze(require),
471
443
  prefer: Object.freeze(prefer),
472
444
  systemTemplate,
445
+ ...(userTemplate !== undefined ? { userTemplate } : {}), // issue #5 (R1): present ONLY when declared (absent ⇒ unchanged bundle_sha)
473
446
  variables: Object.freeze(variables),
474
447
  policy,
475
448
  ...(repair !== undefined ? { repair } : {}),
@@ -480,6 +453,8 @@ function loadBundleFile(filePath) {
480
453
  ...(groundingAssembly !== undefined ? { groundingAssembly } : {}),
481
454
  ...(variableTrust !== undefined ? { variableTrust } : {}),
482
455
  ...(mask !== undefined ? { mask } : {}),
456
+ ...(screen !== undefined ? { screen } : {}),
457
+ ...(classification !== undefined ? { classification } : {}), ...(destination !== undefined ? { destination } : {}),
483
458
  // Network-effect instrumentation (spec WS6): computed once at load,
484
459
  // stamped on every telemetry row for this route.
485
460
  fingerprintCell: (0, fingerprint_1.fingerprintSchema)(outputSchema)?.cellKey ?? null,
@@ -13,7 +13,19 @@ export declare function parseRb1Fields(doc: Record<string, unknown>, route: stri
13
13
  objective?: RouteObjective;
14
14
  bakeoff?: BakeoffConfig;
15
15
  valueChecks?: readonly ValueCheck[];
16
+ grade?: {
17
+ passThreshold: number;
18
+ };
16
19
  };
20
+ /** Default grade pass threshold when a route declares no `grade:` block (R2). */
21
+ export declare const DEFAULT_GRADE_PASS_THRESHOLD = 0.5;
22
+ /** Parse the optional `grade:` block (grade primitive, R2). Currently one knob,
23
+ * `pass_threshold` (0–1). Declared + validated here but UNREAD until GP-2 wires
24
+ * verdict derivation. Absent ⇒ undefined (⇒ the default 0.5 is applied downstream).
25
+ * A malformed value pushes a teaching problem, exactly like every other field. */
26
+ export declare function parseGradeBlock(raw: unknown, problems: string[]): {
27
+ passThreshold: number;
28
+ } | undefined;
17
29
  /** Parse the `objective:` block. Absent ⇒ undefined (the engine defaults to
18
30
  * quality-first with no bound: regret shown, nothing proposed). */
19
31
  export declare function parseObjective(raw: unknown, problems: string[]): RouteObjective | undefined;