modelrig 0.9.0 → 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 (223) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/README.md +51 -27
  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 +20 -0
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +207 -171
  29. package/dist/artifacts/index.d.ts +3 -1
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +8 -1
  32. package/dist/bakeoff/groundedness.d.ts +58 -0
  33. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  34. package/dist/bakeoff/groundedness.js +165 -0
  35. package/dist/bakeoff/quality.d.ts +14 -1
  36. package/dist/bakeoff/quality.d.ts.map +1 -1
  37. package/dist/bakeoff/quality.js +10 -0
  38. package/dist/bakeoff/replay.d.ts.map +1 -1
  39. package/dist/bakeoff/replay.js +4 -0
  40. package/dist/cli.d.ts.map +1 -1
  41. package/dist/cli.js +8 -0
  42. package/dist/config.d.ts +31 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +23 -0
  45. package/dist/doctor-cli.d.ts +7 -0
  46. package/dist/doctor-cli.d.ts.map +1 -0
  47. package/dist/doctor-cli.js +67 -0
  48. package/dist/doctor-preflight.d.ts +52 -0
  49. package/dist/doctor-preflight.d.ts.map +1 -0
  50. package/dist/doctor-preflight.js +112 -0
  51. package/dist/feedback/grade.d.ts +8 -1
  52. package/dist/feedback/grade.d.ts.map +1 -1
  53. package/dist/feedback/grade.js +4 -0
  54. package/dist/gateway/http-protocol.d.ts +19 -0
  55. package/dist/gateway/http-protocol.d.ts.map +1 -1
  56. package/dist/gateway/serve-client.d.ts +99 -0
  57. package/dist/gateway/serve-client.d.ts.map +1 -0
  58. package/dist/gateway/serve-client.js +360 -0
  59. package/dist/index.d.ts +25 -3
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +62 -8
  62. package/dist/prompts/manifest.d.ts +128 -0
  63. package/dist/prompts/manifest.d.ts.map +1 -0
  64. package/dist/prompts/manifest.js +214 -0
  65. package/dist/prompts/prompt-fetch.d.ts +84 -0
  66. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  67. package/dist/prompts/prompt-fetch.js +120 -0
  68. package/dist/prompts/prompt-resolve.d.ts +76 -0
  69. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  70. package/dist/prompts/prompt-resolve.js +93 -0
  71. package/dist/prompts/slot-resolver.d.ts +91 -0
  72. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  73. package/dist/prompts/slot-resolver.js +135 -0
  74. package/dist/prompts/tools-register.d.ts +60 -0
  75. package/dist/prompts/tools-register.d.ts.map +1 -0
  76. package/dist/prompts/tools-register.js +48 -0
  77. package/dist/prompts/version-hash.d.ts +29 -0
  78. package/dist/prompts/version-hash.d.ts.map +1 -0
  79. package/dist/prompts/version-hash.js +54 -0
  80. package/dist/prompts-export-cli.d.ts +21 -0
  81. package/dist/prompts-export-cli.d.ts.map +1 -0
  82. package/dist/prompts-export-cli.js +130 -0
  83. package/dist/registry/cache-policy.d.ts.map +1 -1
  84. package/dist/registry/cache-policy.js +22 -0
  85. package/dist/registry/pricing-shape.d.ts +38 -8
  86. package/dist/registry/pricing-shape.d.ts.map +1 -1
  87. package/dist/registry/pricing-shape.js +40 -12
  88. package/dist/registry/pricing.d.ts +6 -5
  89. package/dist/registry/pricing.d.ts.map +1 -1
  90. package/dist/registry/pricing.js +9 -5
  91. package/dist/routes/destination-loader.d.ts +16 -0
  92. package/dist/routes/destination-loader.d.ts.map +1 -0
  93. package/dist/routes/destination-loader.js +29 -0
  94. package/dist/routes/draft.d.ts +7 -0
  95. package/dist/routes/draft.d.ts.map +1 -1
  96. package/dist/routes/draft.js +12 -0
  97. package/dist/routes/loader.d.ts.map +1 -1
  98. package/dist/routes/loader.js +27 -54
  99. package/dist/routes/output-loader.d.ts +42 -0
  100. package/dist/routes/output-loader.d.ts.map +1 -0
  101. package/dist/routes/output-loader.js +112 -0
  102. package/dist/routes/prompt-loader.d.ts +49 -0
  103. package/dist/routes/prompt-loader.d.ts.map +1 -0
  104. package/dist/routes/prompt-loader.js +113 -0
  105. package/dist/routes/resolved-bundle.d.ts +19 -0
  106. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  107. package/dist/routes/resolved-bundle.js +24 -0
  108. package/dist/routes/screen-loader.d.ts +1 -1
  109. package/dist/routes/screen-loader.d.ts.map +1 -1
  110. package/dist/routes/screen-loader.js +8 -1
  111. package/dist/run/accept.d.ts +3 -0
  112. package/dist/run/accept.d.ts.map +1 -1
  113. package/dist/run/accept.js +1 -0
  114. package/dist/run/cost.d.ts +31 -2
  115. package/dist/run/cost.d.ts.map +1 -1
  116. package/dist/run/cost.js +57 -2
  117. package/dist/run/destination.d.ts +39 -0
  118. package/dist/run/destination.d.ts.map +1 -0
  119. package/dist/run/destination.js +45 -0
  120. package/dist/run/dispatch.d.ts +25 -3
  121. package/dist/run/dispatch.d.ts.map +1 -1
  122. package/dist/run/dispatch.js +26 -4
  123. package/dist/run/raw-defaults.d.ts.map +1 -1
  124. package/dist/run/raw-defaults.js +6 -1
  125. package/dist/run/raw-finalize.d.ts +50 -0
  126. package/dist/run/raw-finalize.d.ts.map +1 -0
  127. package/dist/run/raw-finalize.js +266 -0
  128. package/dist/run/raw-knobs.d.ts +36 -0
  129. package/dist/run/raw-knobs.d.ts.map +1 -0
  130. package/dist/run/raw-knobs.js +99 -0
  131. package/dist/run/raw-request.d.ts +24 -0
  132. package/dist/run/raw-request.d.ts.map +1 -0
  133. package/dist/run/raw-request.js +69 -0
  134. package/dist/run/raw-schema.d.ts +76 -0
  135. package/dist/run/raw-schema.d.ts.map +1 -0
  136. package/dist/run/raw-schema.js +166 -0
  137. package/dist/run/raw-search-cost.d.ts +48 -0
  138. package/dist/run/raw-search-cost.d.ts.map +1 -0
  139. package/dist/run/raw-search-cost.js +78 -0
  140. package/dist/run/raw-support.d.ts +64 -6
  141. package/dist/run/raw-support.d.ts.map +1 -1
  142. package/dist/run/raw-support.js +84 -11
  143. package/dist/run/raw.d.ts +25 -26
  144. package/dist/run/raw.d.ts.map +1 -1
  145. package/dist/run/raw.js +30 -351
  146. package/dist/run/record-attempt.d.ts +8 -0
  147. package/dist/run/record-attempt.d.ts.map +1 -1
  148. package/dist/run/record-attempt.js +26 -12
  149. package/dist/run/repair-attempt.d.ts +68 -0
  150. package/dist/run/repair-attempt.d.ts.map +1 -0
  151. package/dist/run/repair-attempt.js +180 -0
  152. package/dist/run/resolve-destination.d.ts +49 -0
  153. package/dist/run/resolve-destination.d.ts.map +1 -0
  154. package/dist/run/resolve-destination.js +76 -0
  155. package/dist/run/routing-docs.d.ts.map +1 -1
  156. package/dist/run/routing-docs.js +9 -4
  157. package/dist/run/run.d.ts +30 -1
  158. package/dist/run/run.d.ts.map +1 -1
  159. package/dist/run/run.js +75 -148
  160. package/dist/run/telemetry-row.d.ts +92 -0
  161. package/dist/run/telemetry-row.d.ts.map +1 -0
  162. package/dist/run/telemetry-row.js +81 -0
  163. package/dist/run/validate.d.ts +12 -1
  164. package/dist/run/validate.d.ts.map +1 -1
  165. package/dist/run/validate.js +31 -0
  166. package/dist/run/web-search-pricing.d.ts +43 -0
  167. package/dist/run/web-search-pricing.d.ts.map +1 -0
  168. package/dist/run/web-search-pricing.js +56 -0
  169. package/dist/settings/grading-guide.d.ts +27 -1
  170. package/dist/settings/grading-guide.d.ts.map +1 -1
  171. package/dist/status.d.ts +6 -0
  172. package/dist/status.d.ts.map +1 -1
  173. package/dist/status.js +4 -1
  174. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  175. package/dist/telemetry/artifact-store.js +11 -0
  176. package/dist/telemetry/attempt-meta.d.ts +55 -1
  177. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  178. package/dist/telemetry/attempt-meta.js +24 -1
  179. package/dist/telemetry/exporter-setup.d.ts +2 -0
  180. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  181. package/dist/telemetry/exporter-setup.js +6 -1
  182. package/dist/telemetry/grade-mappers.d.ts +5 -2
  183. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  184. package/dist/telemetry/grade-mappers.js +8 -2
  185. package/dist/telemetry/grades-sql.d.ts +4 -1
  186. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  187. package/dist/telemetry/grades-sql.js +26 -3
  188. package/dist/telemetry/ingest-protocol.d.ts +26 -2
  189. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  190. package/dist/telemetry/ingest-protocol.js +32 -29
  191. package/dist/telemetry/org-identity.d.ts +44 -0
  192. package/dist/telemetry/org-identity.d.ts.map +1 -0
  193. package/dist/telemetry/org-identity.js +98 -0
  194. package/dist/telemetry/route-registration.d.ts +48 -0
  195. package/dist/telemetry/route-registration.d.ts.map +1 -0
  196. package/dist/telemetry/route-registration.js +77 -0
  197. package/dist/telemetry/sinks.d.ts +10 -1
  198. package/dist/telemetry/sinks.d.ts.map +1 -1
  199. package/dist/telemetry/sinks.js +52 -6
  200. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  201. package/dist/telemetry/store-migrations.js +13 -0
  202. package/dist/tenancy/api-key-format.d.ts +16 -1
  203. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  204. package/dist/tenancy/api-key-format.js +17 -0
  205. package/dist/types/provider.d.ts +20 -3
  206. package/dist/types/provider.d.ts.map +1 -1
  207. package/dist/types/provider.js +1 -0
  208. package/dist/types/route.d.ts +76 -0
  209. package/dist/types/route.d.ts.map +1 -1
  210. package/dist/types/telemetry.d.ts +30 -2
  211. package/dist/types/telemetry.d.ts.map +1 -1
  212. package/docs/agent-skill/SKILL.md +73 -5
  213. package/docs/instrumentation.md +81 -36
  214. package/docs/llms.txt +95 -16
  215. package/docs/managed-serving.md +150 -0
  216. package/docs/mcp-oracle.md +75 -2
  217. package/docs/migration-playbook.md +5 -4
  218. package/docs/quickstart.md +169 -83
  219. package/package.json +3 -2
  220. package/registry/registry.json +82 -0
  221. package/dist/feedback/feedback.d.ts +0 -35
  222. package/dist/feedback/feedback.d.ts.map +0 -1
  223. package/dist/feedback/feedback.js +0 -39
@@ -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");
@@ -408,26 +387,12 @@ function loadBundleFile(filePath) {
408
387
  const mask = (0, mask_loader_1.parseMaskBlock)(doc.mask, bundleDir, problems);
409
388
  const screen = (0, screen_loader_1.parseScreenBlock)(doc.screen, problems);
410
389
  const classification = (0, screen_loader_1.parseClassificationAndPhiGate)(doc.classification, { mask, screen, require }, 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);
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 } : {}),
@@ -481,7 +454,7 @@ function loadBundleFile(filePath) {
481
454
  ...(variableTrust !== undefined ? { variableTrust } : {}),
482
455
  ...(mask !== undefined ? { mask } : {}),
483
456
  ...(screen !== undefined ? { screen } : {}),
484
- ...(classification !== undefined ? { classification } : {}),
457
+ ...(classification !== undefined ? { classification } : {}), ...(destination !== undefined ? { destination } : {}),
485
458
  // Network-effect instrumentation (spec WS6): computed once at load,
486
459
  // stamped on every telemetry row for this route.
487
460
  fingerprintCell: (0, fingerprint_1.fingerprintSchema)(outputSchema)?.cellKey ?? null,
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Route OUTPUT contract at load — the `schema:` file reader and the `output:`
3
+ * block (GitLab issue #1, incident 2026-10-11: a prose route had no way to say
4
+ * "my output is not JSON" and was rejected `content_invalid` on every call).
5
+ *
6
+ * Carved out of routes/loader.ts (which sits at the 700-LOC cap, dev-rule P1):
7
+ * `readSchema` is a PURE MOVE; `parseOutputBlock` is new.
8
+ *
9
+ * YAML forms accepted for `output:`:
10
+ * (absent) → json (the default; byte-identical)
11
+ * output: json → json (explicit; identical to absent)
12
+ * output: text → text (prose/markdown, returned verbatim)
13
+ * output: { kind: text, min_chars: N } → text with a declared length floor
14
+ * output: { kind: json } → json
15
+ *
16
+ * The parsed result OMITS `outputKind` for json so a JSON route's bundle (and
17
+ * its `bundle_sha`) is unchanged; only `text` is ever carried.
18
+ */
19
+ import type { RepairPolicy, Requirement, RouteVariant } from "../types/route";
20
+ export declare const OUTPUT_KINDS: ReadonlySet<string>;
21
+ export declare const PROSE_DOCS_ANCHOR = "route-bundles.md#prose-and-markdown-output-output-text";
22
+ /** Resolve + parse the bundle's `schema:` file (null / absent ⇒ schema-less). */
23
+ export declare function readSchema(schemaRaw: unknown, bundleDir: string, problems: string[]): object | null;
24
+ export interface ParsedOutputBlock {
25
+ /** Present ONLY for `text` (json ⇒ omitted ⇒ byte-identical bundle). */
26
+ readonly outputKind?: "text";
27
+ readonly outputMinChars?: number;
28
+ }
29
+ /** What the loader already parsed that an `output: text` declaration must be
30
+ * consistent with — each conflict is a LOAD error that teaches, never a run-time
31
+ * surprise. */
32
+ export interface OutputBlockContext {
33
+ readonly outputSchema: object | null;
34
+ readonly schemaDeclared: boolean;
35
+ readonly require: readonly Requirement[];
36
+ readonly repair: RepairPolicy | undefined;
37
+ readonly variants: readonly RouteVariant[];
38
+ }
39
+ /** Parse the bundle's `output:` key. Pushes teaching problems; returns `{}` for
40
+ * json (absent / explicit) so the bundle spread adds nothing. */
41
+ export declare function parseOutputBlock(raw: unknown, ctx: OutputBlockContext, problems: string[]): ParsedOutputBlock;
42
+ //# sourceMappingURL=output-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-loader.d.ts","sourceRoot":"","sources":["../../src/routes/output-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAc,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE1F,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAAyC,CAAC;AAGvF,eAAO,MAAM,iBAAiB,2DAA2D,CAAC;AAM1F,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAsBnG;AAED,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;eAEe;AACf,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED;iEACiE;AACjE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAqD7G"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * Route OUTPUT contract at load — the `schema:` file reader and the `output:`
4
+ * block (GitLab issue #1, incident 2026-10-11: a prose route had no way to say
5
+ * "my output is not JSON" and was rejected `content_invalid` on every call).
6
+ *
7
+ * Carved out of routes/loader.ts (which sits at the 700-LOC cap, dev-rule P1):
8
+ * `readSchema` is a PURE MOVE; `parseOutputBlock` is new.
9
+ *
10
+ * YAML forms accepted for `output:`:
11
+ * (absent) → json (the default; byte-identical)
12
+ * output: json → json (explicit; identical to absent)
13
+ * output: text → text (prose/markdown, returned verbatim)
14
+ * output: { kind: text, min_chars: N } → text with a declared length floor
15
+ * output: { kind: json } → json
16
+ *
17
+ * The parsed result OMITS `outputKind` for json so a JSON route's bundle (and
18
+ * its `bundle_sha`) is unchanged; only `text` is ever carried.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.PROSE_DOCS_ANCHOR = exports.OUTPUT_KINDS = void 0;
22
+ exports.readSchema = readSchema;
23
+ exports.parseOutputBlock = parseOutputBlock;
24
+ const node_fs_1 = require("node:fs");
25
+ const node_path_1 = require("node:path");
26
+ exports.OUTPUT_KINDS = new Set(["json", "text"]);
27
+ const OUTPUT_BLOCK_KEYS = new Set(["kind", "min_chars"]);
28
+ exports.PROSE_DOCS_ANCHOR = "route-bundles.md#prose-and-markdown-output-output-text";
29
+ function isRecord(v) {
30
+ return typeof v === "object" && v !== null && !Array.isArray(v);
31
+ }
32
+ /** Resolve + parse the bundle's `schema:` file (null / absent ⇒ schema-less). */
33
+ function readSchema(schemaRaw, bundleDir, problems) {
34
+ if (schemaRaw === null || schemaRaw === undefined)
35
+ return null;
36
+ if (typeof schemaRaw !== "string" || schemaRaw === "") {
37
+ problems.push("schema must be a relative path or null");
38
+ return null;
39
+ }
40
+ const schemaPath = (0, node_path_1.resolve)(bundleDir, schemaRaw);
41
+ if (!(0, node_fs_1.existsSync)(schemaPath)) {
42
+ problems.push(`schema file not found: ${schemaRaw}`);
43
+ return null;
44
+ }
45
+ try {
46
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(schemaPath, "utf8"));
47
+ if (!isRecord(parsed)) {
48
+ problems.push(`schema file ${schemaRaw} must contain a JSON object`);
49
+ return null;
50
+ }
51
+ return parsed;
52
+ }
53
+ catch (err) {
54
+ problems.push(`schema file ${schemaRaw} is not valid JSON: ${err.message}`);
55
+ return null;
56
+ }
57
+ }
58
+ /** Parse the bundle's `output:` key. Pushes teaching problems; returns `{}` for
59
+ * json (absent / explicit) so the bundle spread adds nothing. */
60
+ function parseOutputBlock(raw, ctx, problems) {
61
+ if (raw === undefined || raw === null)
62
+ return {};
63
+ let kind;
64
+ let minChars;
65
+ if (typeof raw === "string") {
66
+ kind = raw;
67
+ }
68
+ else if (isRecord(raw)) {
69
+ for (const key of Object.keys(raw)) {
70
+ if (!OUTPUT_BLOCK_KEYS.has(key)) {
71
+ problems.push(`output.${key} is not a known key — the block is {kind: json|text, min_chars?: integer >= 1}`);
72
+ }
73
+ }
74
+ kind = raw.kind;
75
+ if (raw.min_chars !== undefined) {
76
+ if (typeof raw.min_chars !== "number" || !Number.isInteger(raw.min_chars) || raw.min_chars < 1) {
77
+ problems.push("output.min_chars must be an integer >= 1");
78
+ }
79
+ else {
80
+ minChars = raw.min_chars;
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ problems.push(`output must be "json", "text", or {kind, min_chars?} — got ${JSON.stringify(raw)}`);
86
+ return {};
87
+ }
88
+ if (typeof kind !== "string" || !exports.OUTPUT_KINDS.has(kind)) {
89
+ problems.push(`output "${String(kind)}" is not one of [json, text] — write \`output: text\` for a prose/markdown route, ` +
90
+ `or omit it for JSON (the default)`);
91
+ return {};
92
+ }
93
+ if (kind === "json") {
94
+ if (minChars !== undefined)
95
+ problems.push("output.min_chars applies to output: text only — a JSON route conforms to its schema instead");
96
+ return {};
97
+ }
98
+ // kind === "text": the prose contract must not contradict the JSON machinery.
99
+ if (ctx.schemaDeclared || ctx.outputSchema !== null) {
100
+ problems.push("output: text cannot be combined with schema: — a prose route has no JSON Schema to conform to. " +
101
+ "Remove `schema:` (or set it to null), or drop `output: text` to serve JSON");
102
+ }
103
+ if (ctx.require.includes("schema_conformant")) {
104
+ problems.push("output: text cannot require schema_conformant — there is no schema; remove it from `require`");
105
+ }
106
+ const repairDeclared = ctx.repair !== undefined || ctx.variants.some((v) => v.repair !== undefined);
107
+ if (repairDeclared) {
108
+ problems.push("repair: is the JSON repair rung and never runs on an output: text route — remove it (a prose " +
109
+ "response that fails min_chars is retried from the content_invalid budget instead)");
110
+ }
111
+ return { outputKind: "text", ...(minChars !== undefined ? { outputMinChars: minChars } : {}) };
112
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The bundle's `prompt:` block at load — `prompt.system`, the per-call
3
+ * `prompt.user` (GitLab issue #5, R1), and `prompt.variables`.
4
+ *
5
+ * Carved out of routes/loader.ts (which sits at the 700-LOC cap, dev-rule P1):
6
+ * the `prompt.system` + `variables` handling is a PURE MOVE; `prompt.user` and
7
+ * the cache/system-only advisory are new.
8
+ *
9
+ * YAML forms accepted for `prompt:`:
10
+ * prompt: { system: <path>, variables: [...] } → today (byte-identical)
11
+ * prompt: { system: <path>, user: <path>, variables: [...] } → system + per-call user turn
12
+ * prompt: { user: <path>, variables: [...] } → user turn only (empty system)
13
+ *
14
+ * Both templates are files relative to the bundle (same rule as `schema:`);
15
+ * both render with the same variables, capability conditionals and `x-trust`
16
+ * removal. `prompt.user` must be a non-empty file — an empty user turn next to
17
+ * a cache handle would send the handle and nothing else, the exact trap R1
18
+ * closes.
19
+ */
20
+ import type { CandidateSpec } from "../types/route";
21
+ export declare const CACHE_PROMPT_DOCS_ANCHOR = "caching-lifecycle.md#a-cache-handle-changes-where-the-template-is-delivered";
22
+ export interface ParsedPromptBlock {
23
+ readonly systemTemplate: string;
24
+ /** Present ONLY when `prompt.user` was declared (absent ⇒ byte-identical bundle). */
25
+ readonly userTemplate?: string;
26
+ readonly variables: readonly string[];
27
+ readonly variableTrust?: Record<string, "untrusted">;
28
+ }
29
+ /**
30
+ * Parse the bundle's `prompt:` block. `prompt.system` stays REQUIRED unless
31
+ * `prompt.user` is declared (a user-turn-only route has an empty system
32
+ * template — the shape a Gemini cache handle wants, since the cache already
33
+ * holds the system content). Returns `systemTemplate: ""` on any problem so the
34
+ * caller's later checks stay total; the collected problems fail the load.
35
+ */
36
+ export declare function parsePromptBlock(raw: unknown, bundleDir: string, problems: string[]): ParsedPromptBlock;
37
+ /**
38
+ * Issue #5 (R1) advisory, P4 "errors teach": a route that opts into caching
39
+ * (`cache: auto`) AND has a Gemini candidate AND renders a non-empty system
40
+ * template with NO `prompt.user` is the shape that loses content the moment a
41
+ * customer passes a Gemini cache handle on `rig.run` — the handle forbids
42
+ * `systemInstruction`, so the rendered template is delivered as the user turn
43
+ * (never dropped), but the author almost certainly meant the cache to hold the
44
+ * prefix and `prompt.user` to hold the delta. Warn once at load naming the
45
+ * rule. Non-fatal: the route loads and serves as declared. Pure — unit-tested
46
+ * directly. Returns the warning line, or null.
47
+ */
48
+ export declare function cachedRouteSystemOnlyWarning(route: string, cache: boolean, candidates: readonly CandidateSpec[], systemTemplate: string, userTemplate: string | undefined): string | null;
49
+ //# sourceMappingURL=prompt-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-loader.d.ts","sourceRoot":"","sources":["../../src/routes/prompt-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,eAAO,MAAM,wBAAwB,gFAAgF,CAAC;AAMtH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,qFAAqF;IACrF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACtD;AAkBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAiCvG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,MAAM,GAAG,IAAI,CAYf"}
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * The bundle's `prompt:` block at load — `prompt.system`, the per-call
4
+ * `prompt.user` (GitLab issue #5, R1), and `prompt.variables`.
5
+ *
6
+ * Carved out of routes/loader.ts (which sits at the 700-LOC cap, dev-rule P1):
7
+ * the `prompt.system` + `variables` handling is a PURE MOVE; `prompt.user` and
8
+ * the cache/system-only advisory are new.
9
+ *
10
+ * YAML forms accepted for `prompt:`:
11
+ * prompt: { system: <path>, variables: [...] } → today (byte-identical)
12
+ * prompt: { system: <path>, user: <path>, variables: [...] } → system + per-call user turn
13
+ * prompt: { user: <path>, variables: [...] } → user turn only (empty system)
14
+ *
15
+ * Both templates are files relative to the bundle (same rule as `schema:`);
16
+ * both render with the same variables, capability conditionals and `x-trust`
17
+ * removal. `prompt.user` must be a non-empty file — an empty user turn next to
18
+ * a cache handle would send the handle and nothing else, the exact trap R1
19
+ * closes.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.CACHE_PROMPT_DOCS_ANCHOR = void 0;
23
+ exports.parsePromptBlock = parsePromptBlock;
24
+ exports.cachedRouteSystemOnlyWarning = cachedRouteSystemOnlyWarning;
25
+ const node_fs_1 = require("node:fs");
26
+ const node_path_1 = require("node:path");
27
+ const screen_loader_1 = require("./screen-loader");
28
+ exports.CACHE_PROMPT_DOCS_ANCHOR = "caching-lifecycle.md#a-cache-handle-changes-where-the-template-is-delivered";
29
+ function isRecord(v) {
30
+ return typeof v === "object" && v !== null && !Array.isArray(v);
31
+ }
32
+ /** Read a template file named by `prompt.<key>` relative to the bundle. Pushes
33
+ * a teaching problem and returns null when the key is not a path or the file is
34
+ * missing. */
35
+ function readTemplateFile(raw, key, bundleDir, problems) {
36
+ if (typeof raw !== "string" || raw === "") {
37
+ problems.push(`prompt.${key} must be a relative path`);
38
+ return null;
39
+ }
40
+ const path = (0, node_path_1.resolve)(bundleDir, raw);
41
+ if (!(0, node_fs_1.existsSync)(path)) {
42
+ problems.push(`prompt.${key} file not found: ${raw}`);
43
+ return null;
44
+ }
45
+ return (0, node_fs_1.readFileSync)(path, "utf8");
46
+ }
47
+ /**
48
+ * Parse the bundle's `prompt:` block. `prompt.system` stays REQUIRED unless
49
+ * `prompt.user` is declared (a user-turn-only route has an empty system
50
+ * template — the shape a Gemini cache handle wants, since the cache already
51
+ * holds the system content). Returns `systemTemplate: ""` on any problem so the
52
+ * caller's later checks stay total; the collected problems fail the load.
53
+ */
54
+ function parsePromptBlock(raw, bundleDir, problems) {
55
+ if (!isRecord(raw)) {
56
+ problems.push("prompt.system must be a relative path");
57
+ return { systemTemplate: "", variables: [] };
58
+ }
59
+ const userDeclared = raw.user !== undefined && raw.user !== null;
60
+ let systemTemplate = "";
61
+ if (raw.system !== undefined && raw.system !== null) {
62
+ systemTemplate = readTemplateFile(raw.system, "system", bundleDir, problems) ?? "";
63
+ }
64
+ else if (!userDeclared) {
65
+ problems.push("prompt.system must be a relative path");
66
+ }
67
+ let userTemplate;
68
+ if (userDeclared) {
69
+ const text = readTemplateFile(raw.user, "user", bundleDir, problems);
70
+ if (text !== null) {
71
+ if (text.trim() === "") {
72
+ problems.push(`prompt.user file is empty: ${String(raw.user)} — the user turn must carry content (with a cache handle ` +
73
+ `it is the ONLY per-call content the model sees; omit prompt.user to keep the fixed trigger)`);
74
+ }
75
+ else {
76
+ userTemplate = text;
77
+ }
78
+ }
79
+ }
80
+ const parsed = (0, screen_loader_1.parseVariables)(raw.variables ?? [], problems);
81
+ return {
82
+ systemTemplate,
83
+ ...(userTemplate !== undefined ? { userTemplate } : {}),
84
+ variables: parsed.names,
85
+ ...(parsed.trust !== undefined ? { variableTrust: parsed.trust } : {}),
86
+ };
87
+ }
88
+ /**
89
+ * Issue #5 (R1) advisory, P4 "errors teach": a route that opts into caching
90
+ * (`cache: auto`) AND has a Gemini candidate AND renders a non-empty system
91
+ * template with NO `prompt.user` is the shape that loses content the moment a
92
+ * customer passes a Gemini cache handle on `rig.run` — the handle forbids
93
+ * `systemInstruction`, so the rendered template is delivered as the user turn
94
+ * (never dropped), but the author almost certainly meant the cache to hold the
95
+ * prefix and `prompt.user` to hold the delta. Warn once at load naming the
96
+ * rule. Non-fatal: the route loads and serves as declared. Pure — unit-tested
97
+ * directly. Returns the warning line, or null.
98
+ */
99
+ function cachedRouteSystemOnlyWarning(route, cache, candidates, systemTemplate, userTemplate) {
100
+ if (!cache)
101
+ return null;
102
+ if (userTemplate !== undefined)
103
+ return null;
104
+ if (systemTemplate.trim() === "")
105
+ return null;
106
+ if (!candidates.some((c) => c.provider === "gemini"))
107
+ return null;
108
+ return (`route "${route}" declares cache: with a Gemini candidate, a non-empty prompt.system and no prompt.user. ` +
109
+ `A Gemini cache handle (rig.run(..., { cache: { key, provider: "gemini" } })) forbids systemInstruction, so ` +
110
+ `on a cached dispatch the rendered system template is delivered AS THE USER TURN (prompt_delivery: ` +
111
+ `"user+cache") — never dropped, but probably not what you meant. Put the shared prefix in the cache ` +
112
+ `resource and the per-call delta in a \`prompt.user\` template (see ${exports.CACHE_PROMPT_DOCS_ANCHOR}).`);
113
+ }
@@ -27,6 +27,7 @@
27
27
  import type { LoadedRoutes } from "./loader";
28
28
  import type { ScreenPolicy } from "../screen/types";
29
29
  import type { ResolvedMaskPolicy } from "../mask/types";
30
+ import type { Manifest } from "../prompts/manifest";
30
31
  import type { CandidateSpec, Preference, RepairPolicy, Requirement, RouteBundle, RoutePolicy, RouteVariant } from "../types/route";
31
32
  /**
32
33
  * The canonical, JSON-serialisable form of a resolved route bundle. Every field
@@ -39,11 +40,22 @@ export interface ResolvedRouteBundle {
39
40
  readonly version: number;
40
41
  /** Resolved, parsed JSON Schema (null = unstructured route). Inlined at sync. */
41
42
  readonly outputSchema: object | null;
43
+ /** Issue #1 — `output: text`. Carried ONLY as `"text"` (a json route omits the
44
+ * key, so its `bundle_sha` is unchanged); the hosted/mirror lane rebuilds the
45
+ * same prose contract and never JSON-parses the response. `outputMinChars` is
46
+ * the declared floor, text routes only. Absent ⇒ json ⇒ byte-identical. */
47
+ readonly outputKind?: "text";
48
+ readonly outputMinChars?: number;
42
49
  readonly candidates: readonly CandidateSpec[];
43
50
  readonly require: readonly Requirement[];
44
51
  readonly prefer: readonly Preference[];
45
52
  /** Raw template text — the prompt FILE, inlined at sync. */
46
53
  readonly systemTemplate: string;
54
+ /** Issue #5 (R1) — the per-call `prompt.user` template, inlined at sync and
55
+ * carried ONLY when declared (absent ⇒ the fixed trigger ⇒ unchanged
56
+ * `bundle_sha`), so the hosted/mirror lane renders the same user turn and a
57
+ * cache handle composes there too. */
58
+ readonly userTemplate?: string;
47
59
  readonly variables: readonly string[];
48
60
  readonly policy: RoutePolicy;
49
61
  readonly repair?: RepairPolicy;
@@ -81,6 +93,13 @@ export interface ResolvedRouteBundle {
81
93
  * always re-resolves on the mirror and needs no export refusal. Absent ⇒ no
82
94
  * masking (byte-identical). */
83
95
  readonly mask?: ResolvedMaskPolicy;
96
+ /** PSR-2 (spec §2.1/§3) — the served prompt's assembly manifest (structure-only).
97
+ * A promote writes the promoted `prompt_versions` row's `manifest` through into
98
+ * `routes.bundle` alongside the `systemTemplate`, and recomputes `bundle_sha`
99
+ * over this whole object — so the served projection carries the manifest the
100
+ * client-fill engine (PSR-2b) resolves against. Absent ⇒ no PSR manifest
101
+ * (byte-identical to a pre-PSR mirror). Additive & optional. */
102
+ readonly manifest?: Manifest;
84
103
  }
85
104
  /** Project an already-loaded (resolved) bundle to its serialisable form. Pure —
86
105
  * no filesystem; the loader already followed every file reference. */
@@ -1 +1 @@
1
- {"version":3,"file":"resolved-bundle.d.ts","sourceRoot":"","sources":["../../src/routes/resolved-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;iFAC6E;IAC7E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACpD;;;;2BAIuB;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/D;;;;;;;;;;;mBAWe;IACf,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;;;;mCAO+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED;sEACsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,CAuB3E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAe1D;AAiBD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEvE;AAED;gFACgF;AAChF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEtE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAkD5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAclE"}
1
+ {"version":3,"file":"resolved-bundle.d.ts","sourceRoot":"","sources":["../../src/routes/resolved-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;;+EAG2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;0CAGsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;iFAC6E;IAC7E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACpD;;;;2BAIuB;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/D;;;;;;;;;;;mBAWe;IACf,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;;;;mCAO+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IACnC;;;;;oEAKgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAED;sEACsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,CA8B3E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAe1D;AAiBD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEvE;AAED;gFACgF;AAChF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEtE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAoE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAclE"}
@@ -43,10 +43,14 @@ function resolveRouteBundle(bundle) {
43
43
  route: bundle.route,
44
44
  version: bundle.version,
45
45
  outputSchema: bundle.outputSchema,
46
+ // Issue #1: prose contract crosses the wire (text only; json adds no key).
47
+ ...(bundle.outputKind === "text" ? { outputKind: "text" } : {}),
48
+ ...(bundle.outputKind === "text" && bundle.outputMinChars !== undefined ? { outputMinChars: bundle.outputMinChars } : {}),
46
49
  candidates: bundle.candidates,
47
50
  require: bundle.require,
48
51
  prefer: bundle.prefer,
49
52
  systemTemplate: bundle.systemTemplate,
53
+ ...(bundle.userTemplate !== undefined ? { userTemplate: bundle.userTemplate } : {}), // issue #5 (R1)
50
54
  variables: bundle.variables,
51
55
  policy: bundle.policy,
52
56
  ...(bundle.repair !== undefined ? { repair: bundle.repair } : {}),
@@ -60,6 +64,9 @@ function resolveRouteBundle(bundle) {
60
64
  // silently inert on the hosted lane. Absent ⇒ omitted ⇒ byte-identical.
61
65
  ...(bundle.screen !== undefined ? { screenPolicy: bundle.screen.policy } : {}),
62
66
  ...(bundle.mask !== undefined ? { mask: bundle.mask } : {}),
67
+ // PSR-2: carry the served prompt's manifest so promote's write-through and the
68
+ // round-trip both preserve it. Absent ⇒ omitted ⇒ byte-identical.
69
+ ...(bundle.manifest !== undefined ? { manifest: bundle.manifest } : {}),
63
70
  };
64
71
  }
65
72
  /**
@@ -145,14 +152,28 @@ function bundleFromResolved(raw) {
145
152
  throw new loader_1.RouteConfigError(`mirrored route bundle "${b.route}" is missing \`policy\``);
146
153
  }
147
154
  const outputSchema = b.outputSchema ?? null;
155
+ // Issue #1: a mirrored `output: text` route rebuilds as text (never JSON-parsed
156
+ // on the hosted lane). Anything but the literal "text" ⇒ json (byte-identical);
157
+ // text + a schema is a corrupt mirror (the loader refuses that pair at author).
158
+ const outputKind = b.outputKind === "text" ? "text" : undefined;
159
+ if (outputKind === "text" && outputSchema !== null) {
160
+ throw new loader_1.RouteConfigError(`mirrored route bundle "${b.route}" declares output: text AND a schema — remove one`);
161
+ }
162
+ const minChars = b.outputMinChars;
163
+ const outputMinChars = outputKind === "text" && typeof minChars === "number" && Number.isInteger(minChars) && minChars >= 1 ? minChars : undefined;
148
164
  return Object.freeze({
149
165
  route: b.route,
150
166
  version: b.version,
151
167
  outputSchema: outputSchema === null ? null : Object.freeze(outputSchema),
168
+ ...(outputKind !== undefined ? { outputKind } : {}),
169
+ ...(outputMinChars !== undefined ? { outputMinChars } : {}),
152
170
  candidates: Object.freeze([...b.candidates]),
153
171
  require: Object.freeze([...(b.require ?? [])]),
154
172
  prefer: Object.freeze([...(b.prefer ?? [])]),
155
173
  systemTemplate: b.systemTemplate,
174
+ // Issue #5 (R1): a mirrored `prompt.user` rebuilds as the user turn; anything
175
+ // but a non-empty string ⇒ absent ⇒ the fixed trigger (byte-identical).
176
+ ...(typeof b.userTemplate === "string" && b.userTemplate !== "" ? { userTemplate: b.userTemplate } : {}),
156
177
  variables: Object.freeze([...(b.variables ?? [])]),
157
178
  policy: b.policy,
158
179
  ...(b.repair !== undefined ? { repair: b.repair } : {}),
@@ -171,6 +192,9 @@ function bundleFromResolved(raw) {
171
192
  // Mask carries its FULLY-RESOLVED policy (entries inlined) — passed through,
172
193
  // re-materialising byte-identically so `mask: block` is never inert.
173
194
  ...(b.mask !== undefined ? { mask: b.mask } : {}),
195
+ // PSR-2: the served prompt's structure-only manifest, passed through whole
196
+ // (no re-resolve — it carries no filesystem/closure state). Absent ⇒ omitted.
197
+ ...(b.manifest !== undefined ? { manifest: b.manifest } : {}),
174
198
  // Recomputed, never trusted from the wire — cannot drift from the schema.
175
199
  fingerprintCell: (0, fingerprint_1.fingerprintSchema)(outputSchema)?.cellKey ?? null,
176
200
  });
@@ -35,7 +35,7 @@ export declare function parseClassificationAndPhiGate(raw: unknown, ctx: {
35
35
  /** Push a problem for every `{{var}}` referenced in the system template or the
36
36
  * grounding query that was not declared in `prompt.variables`. Colocated with
37
37
  * the variable parser it validates. */
38
- export declare function pushUndeclaredVariableProblems(systemTemplate: string, groundingQuery: string | undefined, variables: readonly string[], problems: string[]): void;
38
+ export declare function pushUndeclaredVariableProblems(systemTemplate: string, groundingQuery: string | undefined, variables: readonly string[], problems: string[], userTemplate?: string): void;
39
39
  /** Parse `prompt.variables` — each entry is a bare name (semi, stays in the
40
40
  * system slot — the default) or `{name, "x-trust": "untrusted"}` (spotlighted out
41
41
  * of the system slot, S2). Returns the declared names and, when any variable is
@@ -1 +1 @@
1
- {"version":3,"file":"screen-loader.d.ts","sourceRoot":"","sources":["../../src/routes/screen-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAMlD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,oBAAoB,GAAG,SAAS,CAUnG;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,EAClG,QAAQ,EAAE,MAAM,EAAE,GACjB,kBAAkB,GAAG,SAAS,CAYhC;AAED;;uCAEuC;AACvC,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,EAAE,MAAM,EAAE,GACjB,IAAI,CAcN;AAED;;;iFAGiF;AACjF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,GACjB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAwB5E;AAED;;iFAEiF;AACjF,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,QAAQ,EAAE,MAAM,EAAE,GACjB,QAAQ,GAAG,WAAW,GAAG,SAAS,CAQpC"}
1
+ {"version":3,"file":"screen-loader.d.ts","sourceRoot":"","sources":["../../src/routes/screen-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAMlD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,oBAAoB,GAAG,SAAS,CAUnG;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,EAClG,QAAQ,EAAE,MAAM,EAAE,GACjB,kBAAkB,GAAG,SAAS,CAYhC;AAED;;uCAEuC;AACvC,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,EAAE,MAAM,EAAE,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAqBN;AAED;;;iFAGiF;AACjF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,GACjB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAwB5E;AAED;;iFAEiF;AACjF,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,QAAQ,EAAE,MAAM,EAAE,GACjB,QAAQ,GAAG,WAAW,GAAG,SAAS,CAQpC"}