modelrig 0.9.0 → 0.11.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 (231) hide show
  1. package/CHANGELOG.md +227 -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 +68 -0
  48. package/dist/doctor-preflight.d.ts +64 -0
  49. package/dist/doctor-preflight.d.ts.map +1 -0
  50. package/dist/doctor-preflight.js +167 -0
  51. package/dist/feedback/coverage.d.ts.map +1 -1
  52. package/dist/feedback/coverage.js +9 -2
  53. package/dist/feedback/grade.d.ts +8 -1
  54. package/dist/feedback/grade.d.ts.map +1 -1
  55. package/dist/feedback/grade.js +4 -0
  56. package/dist/gateway/http-protocol.d.ts +19 -0
  57. package/dist/gateway/http-protocol.d.ts.map +1 -1
  58. package/dist/gateway/serve-client.d.ts +99 -0
  59. package/dist/gateway/serve-client.d.ts.map +1 -0
  60. package/dist/gateway/serve-client.js +360 -0
  61. package/dist/index.d.ts +31 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +71 -8
  64. package/dist/prompts/manifest.d.ts +128 -0
  65. package/dist/prompts/manifest.d.ts.map +1 -0
  66. package/dist/prompts/manifest.js +214 -0
  67. package/dist/prompts/prompt-fetch.d.ts +84 -0
  68. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  69. package/dist/prompts/prompt-fetch.js +120 -0
  70. package/dist/prompts/prompt-resolve.d.ts +76 -0
  71. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  72. package/dist/prompts/prompt-resolve.js +93 -0
  73. package/dist/prompts/slot-resolver.d.ts +91 -0
  74. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  75. package/dist/prompts/slot-resolver.js +135 -0
  76. package/dist/prompts/tools-register.d.ts +60 -0
  77. package/dist/prompts/tools-register.d.ts.map +1 -0
  78. package/dist/prompts/tools-register.js +48 -0
  79. package/dist/prompts/version-hash.d.ts +29 -0
  80. package/dist/prompts/version-hash.d.ts.map +1 -0
  81. package/dist/prompts/version-hash.js +54 -0
  82. package/dist/prompts-export-cli.d.ts +21 -0
  83. package/dist/prompts-export-cli.d.ts.map +1 -0
  84. package/dist/prompts-export-cli.js +130 -0
  85. package/dist/registry/cache-policy.d.ts.map +1 -1
  86. package/dist/registry/cache-policy.js +22 -0
  87. package/dist/registry/pricing-shape.d.ts +38 -8
  88. package/dist/registry/pricing-shape.d.ts.map +1 -1
  89. package/dist/registry/pricing-shape.js +40 -12
  90. package/dist/registry/pricing.d.ts +6 -5
  91. package/dist/registry/pricing.d.ts.map +1 -1
  92. package/dist/registry/pricing.js +9 -5
  93. package/dist/routes/destination-loader.d.ts +16 -0
  94. package/dist/routes/destination-loader.d.ts.map +1 -0
  95. package/dist/routes/destination-loader.js +29 -0
  96. package/dist/routes/draft.d.ts +7 -0
  97. package/dist/routes/draft.d.ts.map +1 -1
  98. package/dist/routes/draft.js +12 -0
  99. package/dist/routes/loader.d.ts.map +1 -1
  100. package/dist/routes/loader.js +27 -54
  101. package/dist/routes/output-loader.d.ts +42 -0
  102. package/dist/routes/output-loader.d.ts.map +1 -0
  103. package/dist/routes/output-loader.js +112 -0
  104. package/dist/routes/prompt-loader.d.ts +49 -0
  105. package/dist/routes/prompt-loader.d.ts.map +1 -0
  106. package/dist/routes/prompt-loader.js +113 -0
  107. package/dist/routes/resolved-bundle.d.ts +19 -0
  108. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  109. package/dist/routes/resolved-bundle.js +24 -0
  110. package/dist/routes/screen-loader.d.ts +1 -1
  111. package/dist/routes/screen-loader.d.ts.map +1 -1
  112. package/dist/routes/screen-loader.js +8 -1
  113. package/dist/run/accept.d.ts +3 -0
  114. package/dist/run/accept.d.ts.map +1 -1
  115. package/dist/run/accept.js +1 -0
  116. package/dist/run/cost.d.ts +31 -2
  117. package/dist/run/cost.d.ts.map +1 -1
  118. package/dist/run/cost.js +57 -2
  119. package/dist/run/destination.d.ts +39 -0
  120. package/dist/run/destination.d.ts.map +1 -0
  121. package/dist/run/destination.js +45 -0
  122. package/dist/run/dispatch.d.ts +25 -3
  123. package/dist/run/dispatch.d.ts.map +1 -1
  124. package/dist/run/dispatch.js +26 -4
  125. package/dist/run/raw-defaults.d.ts.map +1 -1
  126. package/dist/run/raw-defaults.js +6 -1
  127. package/dist/run/raw-finalize.d.ts +96 -0
  128. package/dist/run/raw-finalize.d.ts.map +1 -0
  129. package/dist/run/raw-finalize.js +283 -0
  130. package/dist/run/raw-knobs.d.ts +36 -0
  131. package/dist/run/raw-knobs.d.ts.map +1 -0
  132. package/dist/run/raw-knobs.js +99 -0
  133. package/dist/run/raw-request.d.ts +24 -0
  134. package/dist/run/raw-request.d.ts.map +1 -0
  135. package/dist/run/raw-request.js +69 -0
  136. package/dist/run/raw-schema.d.ts +76 -0
  137. package/dist/run/raw-schema.d.ts.map +1 -0
  138. package/dist/run/raw-schema.js +166 -0
  139. package/dist/run/raw-search-cost.d.ts +49 -0
  140. package/dist/run/raw-search-cost.d.ts.map +1 -0
  141. package/dist/run/raw-search-cost.js +79 -0
  142. package/dist/run/raw-support.d.ts +64 -6
  143. package/dist/run/raw-support.d.ts.map +1 -1
  144. package/dist/run/raw-support.js +84 -11
  145. package/dist/run/raw.d.ts +25 -26
  146. package/dist/run/raw.d.ts.map +1 -1
  147. package/dist/run/raw.js +30 -351
  148. package/dist/run/record-attempt.d.ts +8 -0
  149. package/dist/run/record-attempt.d.ts.map +1 -1
  150. package/dist/run/record-attempt.js +26 -12
  151. package/dist/run/record-call.d.ts +104 -0
  152. package/dist/run/record-call.d.ts.map +1 -0
  153. package/dist/run/record-call.js +277 -0
  154. package/dist/run/repair-attempt.d.ts +68 -0
  155. package/dist/run/repair-attempt.d.ts.map +1 -0
  156. package/dist/run/repair-attempt.js +180 -0
  157. package/dist/run/resolve-destination.d.ts +49 -0
  158. package/dist/run/resolve-destination.d.ts.map +1 -0
  159. package/dist/run/resolve-destination.js +76 -0
  160. package/dist/run/routing-docs.d.ts.map +1 -1
  161. package/dist/run/routing-docs.js +9 -4
  162. package/dist/run/run.d.ts +30 -1
  163. package/dist/run/run.d.ts.map +1 -1
  164. package/dist/run/run.js +75 -148
  165. package/dist/run/telemetry-row.d.ts +92 -0
  166. package/dist/run/telemetry-row.d.ts.map +1 -0
  167. package/dist/run/telemetry-row.js +81 -0
  168. package/dist/run/validate.d.ts +12 -1
  169. package/dist/run/validate.d.ts.map +1 -1
  170. package/dist/run/validate.js +31 -0
  171. package/dist/run/web-search-pricing.d.ts +43 -0
  172. package/dist/run/web-search-pricing.d.ts.map +1 -0
  173. package/dist/run/web-search-pricing.js +56 -0
  174. package/dist/settings/grading-guide.d.ts +27 -1
  175. package/dist/settings/grading-guide.d.ts.map +1 -1
  176. package/dist/status.d.ts +6 -0
  177. package/dist/status.d.ts.map +1 -1
  178. package/dist/status.js +4 -1
  179. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  180. package/dist/telemetry/artifact-store.js +11 -0
  181. package/dist/telemetry/attempt-meta.d.ts +66 -1
  182. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  183. package/dist/telemetry/attempt-meta.js +27 -1
  184. package/dist/telemetry/exporter-setup.d.ts +2 -0
  185. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  186. package/dist/telemetry/exporter-setup.js +6 -1
  187. package/dist/telemetry/grade-mappers.d.ts +5 -2
  188. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  189. package/dist/telemetry/grade-mappers.js +8 -2
  190. package/dist/telemetry/grades-sql.d.ts +4 -1
  191. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  192. package/dist/telemetry/grades-sql.js +26 -3
  193. package/dist/telemetry/ingest-protocol.d.ts +26 -2
  194. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  195. package/dist/telemetry/ingest-protocol.js +32 -29
  196. package/dist/telemetry/lanes.d.ts +31 -0
  197. package/dist/telemetry/lanes.d.ts.map +1 -0
  198. package/dist/telemetry/lanes.js +41 -0
  199. package/dist/telemetry/org-identity.d.ts +47 -0
  200. package/dist/telemetry/org-identity.d.ts.map +1 -0
  201. package/dist/telemetry/org-identity.js +100 -0
  202. package/dist/telemetry/route-registration.d.ts +48 -0
  203. package/dist/telemetry/route-registration.d.ts.map +1 -0
  204. package/dist/telemetry/route-registration.js +77 -0
  205. package/dist/telemetry/sinks.d.ts +10 -1
  206. package/dist/telemetry/sinks.d.ts.map +1 -1
  207. package/dist/telemetry/sinks.js +52 -6
  208. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  209. package/dist/telemetry/store-migrations.js +13 -0
  210. package/dist/tenancy/api-key-format.d.ts +16 -1
  211. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  212. package/dist/tenancy/api-key-format.js +17 -0
  213. package/dist/types/provider.d.ts +20 -3
  214. package/dist/types/provider.d.ts.map +1 -1
  215. package/dist/types/provider.js +1 -0
  216. package/dist/types/route.d.ts +76 -0
  217. package/dist/types/route.d.ts.map +1 -1
  218. package/dist/types/telemetry.d.ts +30 -2
  219. package/dist/types/telemetry.d.ts.map +1 -1
  220. package/docs/agent-skill/SKILL.md +75 -5
  221. package/docs/instrumentation.md +98 -36
  222. package/docs/llms.txt +96 -16
  223. package/docs/managed-serving.md +150 -0
  224. package/docs/mcp-oracle.md +75 -2
  225. package/docs/migration-playbook.md +5 -4
  226. package/docs/quickstart.md +169 -83
  227. package/package.json +3 -2
  228. package/registry/registry.json +82 -0
  229. package/dist/feedback/feedback.d.ts +0 -35
  230. package/dist/feedback/feedback.d.ts.map +0 -1
  231. package/dist/feedback/feedback.js +0 -39
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Responses-API dispatch path (Native Web Search lane, spec N1–N2) — the SHARED
4
+ * request/response mapping for providers whose native `web_search` tool lives only
5
+ * on `/v1/responses` (OpenAI + xAI/Grok today; same shape). This is a NEW dispatch
6
+ * path, NOT an extension of the Chat Completions `tools` block — see research §2.
7
+ *
8
+ * DOCTRINE (inherited from CE-9):
9
+ * - ABSENT ⇒ BYTE-IDENTICAL. This path is entered ONLY for a grounded request
10
+ * (`req.grounding === true`). A non-grounded request stays on the adapter's
11
+ * existing `chat.completions.create` call, unchanged. The per-adapter branch
12
+ * lives in openai.ts / grok.ts (Step 2); this module owns only the Responses shape.
13
+ * - FORWARD-VERBATIM + CLASSIFY-TEACHABLY: forward the web_search tool + sampling/
14
+ * reasoning under their Responses-dialect names; let the provider enforce; map a
15
+ * rejection onto an EXISTING FailureClass with a teaching fixHint (no new class).
16
+ * - CITATIONS: map `response` citations onto the `url_citation` shape already used
17
+ * by the inject rung (src/run/grounding.ts) — do NOT invent a second type.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.buildResponsesParams = buildResponsesParams;
21
+ exports.mapResponsesResult = mapResponsesResult;
22
+ const sampling_1 = require("./sampling");
23
+ const raw_support_1 = require("../run/raw-support");
24
+ /** Parity with the Chat Completions default output cap (openai.ts / grok.ts). */
25
+ const DEFAULT_MAX_OUTPUT_TOKENS = 32000;
26
+ /**
27
+ * Build the `responses.create` params for a grounded request. Appends the
28
+ * `web_search` tool; forwards sampling/reasoning. Callers MUST only invoke this when
29
+ * `req.grounding === true` (the guard already enforced provider support pre-dispatch).
30
+ *
31
+ * Reasoning maps 1:1 for OpenAI (`level` → `reasoning.effort`); for grok the neutral
32
+ * four-level `level` is coarsened to xAI's `low|high` via `coarsenGrokReasoningEffort`
33
+ * (the SAME disclosed mapping the Chat Completions path uses — never a silent drop).
34
+ */
35
+ function buildResponsesParams(req) {
36
+ const input = [
37
+ ...(req.systemPrompt !== "" ? [{ role: "system", content: req.systemPrompt }] : []),
38
+ { role: "user", content: req.userPrompt },
39
+ ];
40
+ const reasoning = req.reasoning !== undefined
41
+ ? {
42
+ reasoning: {
43
+ effort: req.candidate.provider === "grok"
44
+ ? (0, raw_support_1.coarsenGrokReasoningEffort)(req.reasoning.level)
45
+ : req.reasoning.level,
46
+ },
47
+ }
48
+ : {};
49
+ return {
50
+ model: req.candidate.model,
51
+ input,
52
+ // Native web-search server tool, left at defaults. As of openai@7.x the
53
+ // `web_search` tool type also accepts `filters`, `external_web_access`, and
54
+ // `search_context_size` (a token/context-budget knob) — NOT wired here on
55
+ // purpose (they need product knobs); available for a follow-up mission.
56
+ tools: [{ type: "web_search" }],
57
+ max_output_tokens: (0, sampling_1.effectiveMaxOutputTokens)(req, DEFAULT_MAX_OUTPUT_TOKENS),
58
+ // temperature / top_p share the Chat Completions dialect names on the
59
+ // Responses API — reuse the one sampling mapper (absent ⇒ no keys added).
60
+ ...(0, sampling_1.openAISamplingFields)(req),
61
+ ...reasoning,
62
+ };
63
+ }
64
+ /**
65
+ * Map a raw `responses.create` result onto ModelRig's normalized shape: output text,
66
+ * AdapterUsage (cache-read is a SUBSET of `tokensIn`, per the WS-A invariant), and
67
+ * citations in the `url_citation` shape. A zero-citation grounded response is
68
+ * SURFACED (empty array), never thrown (spec N2).
69
+ */
70
+ function mapResponsesResult(response) {
71
+ const r = (typeof response === "object" && response !== null ? response : {});
72
+ const items = Array.isArray(r.output) ? r.output : [];
73
+ // Text: prefer the SDK convenience aggregate, else concatenate output_text parts.
74
+ const aggregate = typeof r.output_text === "string" ? r.output_text : "";
75
+ const text = aggregate !== ""
76
+ ? aggregate
77
+ : items
78
+ .filter((it) => it.type === "message")
79
+ .flatMap((it) => it.content ?? [])
80
+ .filter((c) => c.type === "output_text" && typeof c.text === "string")
81
+ .map((c) => c.text)
82
+ .join("");
83
+ // Citations: url_citation annotations on output_text parts, deduped by URL,
84
+ // first-seen title wins. Reuses the inject rung's UrlCitation shape.
85
+ const seen = new Set();
86
+ const citations = [];
87
+ for (const it of items) {
88
+ if (it.type !== "message")
89
+ continue;
90
+ for (const part of it.content ?? []) {
91
+ if (part.type !== "output_text")
92
+ continue;
93
+ for (const ann of part.annotations ?? []) {
94
+ if (ann.type !== "url_citation" || typeof ann.url !== "string" || ann.url === "")
95
+ continue;
96
+ if (seen.has(ann.url))
97
+ continue;
98
+ seen.add(ann.url);
99
+ citations.push({
100
+ type: "url_citation",
101
+ url: ann.url,
102
+ ...(typeof ann.title === "string" && ann.title !== "" ? { title: ann.title } : {}),
103
+ });
104
+ }
105
+ }
106
+ }
107
+ // usage: input_tokens already INCLUDES cached_tokens → tokensIn is subset-correct
108
+ // (WS-A). The Responses API reports no cache-write on these providers ⇒ 0/omit.
109
+ const usage = {
110
+ tokensIn: r.usage?.input_tokens ?? 0,
111
+ tokensOut: r.usage?.output_tokens ?? 0,
112
+ tokensCached: r.usage?.input_tokens_details?.cached_tokens ?? 0,
113
+ servedTier: "standard",
114
+ };
115
+ // searchCalls: count the web_search_call output items (spec N3). None ⇒ undefined
116
+ // (the raw success path assumes 1, disclosed).
117
+ const searchCalls = items.filter((it) => it.type === "web_search_call").length;
118
+ return {
119
+ text,
120
+ usage,
121
+ citations,
122
+ ...(searchCalls > 0 ? { searchCalls } : {}),
123
+ };
124
+ }
@@ -18,6 +18,7 @@ import type { EvalCaseOrigin } from "../types/telemetry";
18
18
  import { type ScrubMode } from "./scrub";
19
19
  import { type TaskSettingsResolver } from "../settings/task-settings";
20
20
  import type { ContentUploadInput, ContentUploadOutcome } from "./content-uploader";
21
+ import { type Manifest } from "../prompts/manifest";
21
22
  /** The upload transport `save` fires content through — injected so the SDK's
22
23
  * networked implementation and the tests share one seam. */
23
24
  export type ArtifactContentUploader = (input: ContentUploadInput) => Promise<ContentUploadOutcome>;
@@ -64,6 +65,15 @@ export interface ArtifactSaveOptions {
64
65
  readonly supersedes?: ArtifactHandle;
65
66
  readonly expiresAt?: string;
66
67
  }
68
+ /** Options for {@link ArtifactApi.saveManifest} — the grouping keys the manifest
69
+ * artifact inherits from its prompt (task/step), mirroring a normal save. */
70
+ export interface ArtifactManifestOptions {
71
+ /** Artifact name; defaults to `<prompt name>.manifest` / `"manifest"`. */
72
+ readonly name?: string;
73
+ readonly task?: string;
74
+ readonly stepKey?: string;
75
+ readonly attemptNo?: number;
76
+ }
67
77
  export interface ArtifactPromoteOptions {
68
78
  readonly note?: string;
69
79
  /** The route/task this case belongs to. Falls back to the task the source
@@ -81,6 +91,16 @@ export interface ArtifactApi {
81
91
  * a handle. Returns null when the namespace is disabled or a zeroRetention run
82
92
  * refuses it (fail-closed). */
83
93
  save(value: unknown, opts: ArtifactSaveOptions): ArtifactHandle | null;
94
+ /** PSR-1: attach a **structure-only** assembly manifest (spec §2.1) to a
95
+ * `prompt` artifact. Validates the manifest, saves it as a linked free-text
96
+ * `type: "manifest"` artifact (ZERO-DDL — the `type` column is open), and
97
+ * links `prompt → manifest` with a `derived_from` edge so the manifest joins
98
+ * the existing prompt lineage and stays addressable for PSR-2 ingest. Returns
99
+ * the manifest handle, or null when the manifest is invalid (a counted,
100
+ * teaching warn — never throws) or custody/zeroRetention refuses the save.
101
+ * A no-op unless explicitly called, so `save` stays byte-identical (the emit
102
+ * is inert without a manifest). */
103
+ saveManifest(prompt: ArtifactHandle | string, manifest: Manifest, opts?: ArtifactManifestOptions): ArtifactHandle | null;
84
104
  /** Mark an artifact consumed by the current run (feeds derived_from lineage). */
85
105
  load(ref: ArtifactHandle | string): void;
86
106
  /** Explicit lineage edge the runtime cannot infer. */
@@ -1 +1 @@
1
- {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../src/artifacts/artifact.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../src/artifacts/artifact.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,7 @@ const run_context_1 = require("./run-context");
22
22
  const scrub_1 = require("./scrub");
23
23
  const classification_1 = require("./classification");
24
24
  const task_settings_1 = require("../settings/task-settings");
25
+ const manifest_1 = require("../prompts/manifest");
25
26
  /** The configured scrub mode for one artifact save (BEFORE the S8-c PII/PHI
26
27
  * floor). Resolved EXACTLY as capture-write.ts / samples.ts do: a configured
27
28
  * control plane WINS (`resolver.get(task).scrub`), else the rig's default. A
@@ -43,6 +44,9 @@ function contentTypeFor(type) {
43
44
  return "text/csv";
44
45
  if (t === "html")
45
46
  return "text/html; charset=utf-8";
47
+ // PSR-1: a structure-only assembly manifest is stored as a linked JSON artifact.
48
+ if (t === "manifest")
49
+ return "application/json";
46
50
  return "application/octet-stream";
47
51
  }
48
52
  function idOf(ref) {
@@ -56,6 +60,7 @@ function warn(scope, err) {
56
60
  function inertApi() {
57
61
  return {
58
62
  save: () => null,
63
+ saveManifest: () => null,
59
64
  load: () => { },
60
65
  link: () => { },
61
66
  promoteToEval: () => null,
@@ -160,188 +165,219 @@ function createArtifactApi(deps) {
160
165
  }
161
166
  }
162
167
  }
163
- return {
164
- save(value, opts) {
165
- const ctx = (0, run_context_1.getActiveRunContext)();
166
- // zeroRetention: refuse fail-closed BEFORE hashing or building any row.
167
- if (ctx !== null && ctx.zeroRetention) {
168
- skippedZeroRetention += 1;
169
- console.warn(`[modelrig artifacts] zeroRetention run — artifact "${opts.name}" NOT recorded ` +
170
- "(fail-closed at the SDK gate; no metadata, no hash persisted).");
171
- return null;
172
- }
173
- // Hash the serialization, then DISCARD the bytes — metadata + hash only.
174
- const serialized = typeof value === "string" ? value : JSON.stringify(value ?? null);
175
- const bytes = Buffer.byteLength(serialized, "utf8");
176
- const contentHash = `sha256:${(0, node_crypto_1.createHash)("sha256").update(serialized).digest("hex")}`;
177
- const schema = opts.schema ?? null;
178
- const id = (0, node_crypto_1.randomUUID)();
179
- const version = opts.supersedes !== undefined ? opts.supersedes.version + 1 : 1;
180
- // G1 (spec §3): an explicit stepKey wins; absent, the artifact lands in the
181
- // AMBIENT step — the most recent auto-step in this run context — so
182
- // `artifact.save` and the run/@step graph compose. Null outside a step (or
183
- // outside a run context), exactly as before.
184
- const stepKey = opts.stepKey ?? ctx?.currentStepKey() ?? null;
185
- // S8-b: validate data_classification against the shared enum. A bad value
186
- // would fail the DB CHECK (00058) and drop the row at ingest, so catch it
187
- // here with a teaching warning and drop the LABEL (the artifact still
188
- // saves — a metadata typo must not lose the artifact). Normalize first so
189
- // "PHI"/" pii " keep their meaning (and their scrub floor) rather than
190
- // silently dropping.
191
- let classification = (0, classification_1.normalizeClassification)(opts.classification);
192
- if (opts.classification !== undefined && classification !== null && !(0, classification_1.isDataClassification)(classification)) {
193
- console.warn(`[modelrig artifacts] artifact "${opts.name}" has an unknown data_classification ` +
194
- `${JSON.stringify(opts.classification)} — label dropped (artifact still saved). ` +
195
- `fix_hint: use one of ${classification_1.DATA_CLASSIFICATIONS.join(", ")}.`);
196
- classification = null;
197
- }
198
- // Custody applies only when a rig content policy exists AND the caller left
199
- // the bytes to us (a caller-supplied contentRef = their own store; managed
200
- // custody must not double-home it).
201
- const custodyApplies = content !== undefined && opts.contentRef === undefined;
202
- // Scrub is computed HERE (before the row is written) so its counts-only
203
- // summary can ride the immutable artifact row (00039). Only when custody
204
- // applies; null otherwise (custody off ⇒ nothing was scanned). The
205
- // configured mode comes from the per-task/account control-plane setting
206
- // (P15 CP-3b — resolver wins; `content.defaultScrub` fallback local/direct).
207
- // S8-c: a `pii`/`phi` artifact forces the scrub mode to a floor of
208
- // `redact`, so PII/PHI content is never uploaded verbatim even when the
209
- // task's configured mode is `off`/`detect`. A stronger `block` stays.
210
- const effScrub = content !== undefined
211
- ? (0, task_settings_1.effectiveScrubMode)(resolveContentScrubMode(content, opts.task), classification)
212
- : null;
213
- const scrubbed = custodyApplies && content !== undefined && effScrub !== null ? (0, scrub_1.scrubContent)(serialized, effScrub) : null;
214
- const scrubSummary = scrubbed !== null && effScrub !== null ? (0, scrub_1.summarizeScrub)(effScrub, scrubbed) : null;
215
- if (scrubbed !== null)
216
- scrubFindings += scrubbed.findings.reduce((n, f) => n + f.count, 0);
217
- try {
218
- store.recordArtifact({
219
- id,
220
- runId: ctx?.runId ?? null,
221
- // E3: step attribution is an explicit caller-supplied key; G1 adds the
222
- // ambient-step fallback (resolved above) so a save with no stepKey joins
223
- // the current run/@step. Absent both ⇒ null, exactly as before.
224
- stepKey,
225
- attemptNo: opts.attemptNo ?? null,
226
- task: opts.task ?? null,
227
- name: opts.name,
228
- type: opts.type,
229
- version,
230
- supersedesArtifactId: opts.supersedes?.id ?? null,
231
- declaredSchema: schema,
232
- fingerprintCell: (0, fingerprint_1.fingerprintSchema)(schema)?.cellKey ?? null,
233
- contentRef: opts.contentRef ?? null,
234
- contentHash,
235
- contentBytes: bytes,
236
- dataClassification: classification,
237
- expiresAt: opts.expiresAt ?? null,
238
- // Per-row scrub findings ride the original (scanned) row (00039).
239
- scrub: scrubSummary,
240
- });
241
- saves += 1;
242
- if (opts.task !== undefined)
243
- savedTasks.set(id, opts.task); // for promoteToEval (chunk A)
244
- // Lineage v0: derived_from edges to everything loaded since the last save.
245
- if (ctx !== null) {
246
- for (const loadedId of ctx.takeLoadedIds()) {
247
- if (loadedId === id)
248
- continue; // edges_no_self
249
- store.recordArtifactEdge({ fromArtifactId: id, toArtifactId: loadedId, kind: "derived_from" });
250
- inferredEdges += 1;
251
- }
168
+ // Extracted so `saveManifest` (PSR-1) can compose them without re-implementing
169
+ // the custody/lineage save path or the edges_no_self link. The bodies are the
170
+ // original `save`/`link` verbatim — the returned object references these.
171
+ const save = (value, opts) => {
172
+ const ctx = (0, run_context_1.getActiveRunContext)();
173
+ // zeroRetention: refuse fail-closed BEFORE hashing or building any row.
174
+ if (ctx !== null && ctx.zeroRetention) {
175
+ skippedZeroRetention += 1;
176
+ console.warn(`[modelrig artifacts] zeroRetention run — artifact "${opts.name}" NOT recorded ` +
177
+ "(fail-closed at the SDK gate; no metadata, no hash persisted).");
178
+ return null;
179
+ }
180
+ // Hash the serialization, then DISCARD the bytes — metadata + hash only.
181
+ const serialized = typeof value === "string" ? value : JSON.stringify(value ?? null);
182
+ const bytes = Buffer.byteLength(serialized, "utf8");
183
+ const contentHash = `sha256:${(0, node_crypto_1.createHash)("sha256").update(serialized).digest("hex")}`;
184
+ const schema = opts.schema ?? null;
185
+ const id = (0, node_crypto_1.randomUUID)();
186
+ const version = opts.supersedes !== undefined ? opts.supersedes.version + 1 : 1;
187
+ // G1 (spec §3): an explicit stepKey wins; absent, the artifact lands in the
188
+ // AMBIENT step — the most recent auto-step in this run context — so
189
+ // `artifact.save` and the run/@step graph compose. Null outside a step (or
190
+ // outside a run context), exactly as before.
191
+ const stepKey = opts.stepKey ?? ctx?.currentStepKey() ?? null;
192
+ // S8-b: validate data_classification against the shared enum. A bad value
193
+ // would fail the DB CHECK (00058) and drop the row at ingest, so catch it
194
+ // here with a teaching warning and drop the LABEL (the artifact still
195
+ // saves — a metadata typo must not lose the artifact). Normalize first so
196
+ // "PHI"/" pii " keep their meaning (and their scrub floor) rather than
197
+ // silently dropping.
198
+ let classification = (0, classification_1.normalizeClassification)(opts.classification);
199
+ if (opts.classification !== undefined && classification !== null && !(0, classification_1.isDataClassification)(classification)) {
200
+ console.warn(`[modelrig artifacts] artifact "${opts.name}" has an unknown data_classification ` +
201
+ `${JSON.stringify(opts.classification)} — label dropped (artifact still saved). ` +
202
+ `fix_hint: use one of ${classification_1.DATA_CLASSIFICATIONS.join(", ")}.`);
203
+ classification = null;
204
+ }
205
+ // Custody applies only when a rig content policy exists AND the caller left
206
+ // the bytes to us (a caller-supplied contentRef = their own store; managed
207
+ // custody must not double-home it).
208
+ const custodyApplies = content !== undefined && opts.contentRef === undefined;
209
+ // Scrub is computed HERE (before the row is written) so its counts-only
210
+ // summary can ride the immutable artifact row (00039). Only when custody
211
+ // applies; null otherwise (custody off ⇒ nothing was scanned). The
212
+ // configured mode comes from the per-task/account control-plane setting
213
+ // (P15 CP-3b — resolver wins; `content.defaultScrub` fallback local/direct).
214
+ // S8-c: a `pii`/`phi` artifact forces the scrub mode to a floor of
215
+ // `redact`, so PII/PHI content is never uploaded verbatim even when the
216
+ // task's configured mode is `off`/`detect`. A stronger `block` stays.
217
+ const effScrub = content !== undefined
218
+ ? (0, task_settings_1.effectiveScrubMode)(resolveContentScrubMode(content, opts.task), classification)
219
+ : null;
220
+ const scrubbed = custodyApplies && content !== undefined && effScrub !== null ? (0, scrub_1.scrubContent)(serialized, effScrub) : null;
221
+ const scrubSummary = scrubbed !== null && effScrub !== null ? (0, scrub_1.summarizeScrub)(effScrub, scrubbed) : null;
222
+ if (scrubbed !== null)
223
+ scrubFindings += scrubbed.findings.reduce((n, f) => n + f.count, 0);
224
+ try {
225
+ store.recordArtifact({
226
+ id,
227
+ runId: ctx?.runId ?? null,
228
+ // E3: step attribution is an explicit caller-supplied key; G1 adds the
229
+ // ambient-step fallback (resolved above) so a save with no stepKey joins
230
+ // the current run/@step. Absent both ⇒ null, exactly as before.
231
+ stepKey,
232
+ attemptNo: opts.attemptNo ?? null,
233
+ task: opts.task ?? null,
234
+ name: opts.name,
235
+ type: opts.type,
236
+ version,
237
+ supersedesArtifactId: opts.supersedes?.id ?? null,
238
+ declaredSchema: schema,
239
+ fingerprintCell: (0, fingerprint_1.fingerprintSchema)(schema)?.cellKey ?? null,
240
+ contentRef: opts.contentRef ?? null,
241
+ contentHash,
242
+ contentBytes: bytes,
243
+ dataClassification: classification,
244
+ expiresAt: opts.expiresAt ?? null,
245
+ // Per-row scrub findings ride the original (scanned) row (00039).
246
+ scrub: scrubSummary,
247
+ });
248
+ saves += 1;
249
+ if (opts.task !== undefined)
250
+ savedTasks.set(id, opts.task); // for promoteToEval (chunk A)
251
+ // Lineage v0: derived_from edges to everything loaded since the last save.
252
+ if (ctx !== null) {
253
+ for (const loadedId of ctx.takeLoadedIds()) {
254
+ if (loadedId === id)
255
+ continue; // edges_no_self
256
+ store.recordArtifactEdge({ fromArtifactId: id, toArtifactId: loadedId, kind: "derived_from" });
257
+ inferredEdges += 1;
252
258
  }
253
259
  }
254
- catch (err) {
255
- warn("save", err);
256
- return null;
260
+ }
261
+ catch (err) {
262
+ warn("save", err);
263
+ return null;
264
+ }
265
+ // ── Content custody (E2-C) ──────────────────────────────────────────
266
+ // Off by default: only when a rig has configured content custody. The
267
+ // metadata row above is written regardless — content is layered on, never
268
+ // a precondition. zeroRetention already returned above, so we never reach
269
+ // here for a zeroRetention run (structural, not re-checked).
270
+ if (content !== undefined && scrubbed !== null) {
271
+ if (scrubbed.blocked) {
272
+ // `block` mode found PII/PHI → content upload refused (counted). The
273
+ // artifact keeps its metadata + hash; it simply stores no content.
274
+ contentBlocked += 1;
275
+ console.warn(`[modelrig artifacts] scrub blocked content for "${opts.name}" ` +
276
+ `(${scrubbed.findings.length} finding type(s)); metadata + hash kept, no content stored.`);
257
277
  }
258
- // ── Content custody (E2-C) ──────────────────────────────────────────
259
- // Off by default: only when a rig has configured content custody. The
260
- // metadata row above is written regardless — content is layered on, never
261
- // a precondition. zeroRetention already returned above, so we never reach
262
- // here for a zeroRetention run (structural, not re-checked).
263
- if (content !== undefined && scrubbed !== null) {
264
- if (scrubbed.blocked) {
265
- // `block` mode found PII/PHI → content upload refused (counted). The
266
- // artifact keeps its metadata + hash; it simply stores no content.
267
- contentBlocked += 1;
268
- console.warn(`[modelrig artifacts] scrub blocked content for "${opts.name}" ` +
269
- `(${scrubbed.findings.length} finding type(s)); metadata + hash kept, no content stored.`);
270
- }
271
- else if (effScrub === "redact" && scrubbed.findings.length > 0 && scrubbed.text !== null) {
272
- // Redaction is a SUPERSEDING version (immutability): the original row
273
- // keeps its hash and stores NO content; a new version records the
274
- // masked content's hash and uploads the masked bytes.
275
- const maskedSerialized = scrubbed.text;
276
- const maskedBytes = Buffer.byteLength(maskedSerialized, "utf8");
277
- const maskedHash = `sha256:${(0, node_crypto_1.createHash)("sha256").update(maskedSerialized).digest("hex")}`;
278
- const redactedId = (0, node_crypto_1.randomUUID)();
279
- const redactedVersion = version + 1;
280
- try {
281
- store.recordArtifact({
282
- id: redactedId,
283
- runId: ctx?.runId ?? null,
284
- // Same ambient-step resolution as the original row it supersedes.
285
- stepKey,
286
- attemptNo: opts.attemptNo ?? null,
287
- task: opts.task ?? null,
288
- name: opts.name,
289
- type: opts.type,
290
- version: redactedVersion,
291
- supersedesArtifactId: id, // supersedes the original (ruling c column)
292
- declaredSchema: schema,
293
- fingerprintCell: (0, fingerprint_1.fingerprintSchema)(schema)?.cellKey ?? null,
294
- contentRef: null,
295
- contentHash: maskedHash,
296
- contentBytes: maskedBytes,
297
- dataClassification: classification,
298
- expiresAt: opts.expiresAt ?? null,
299
- // Findings ride the original scanned row; the masked version stores
300
- // no PII, so its own scrub is null (avoid double-counting).
301
- scrub: null,
302
- });
303
- saves += 1;
304
- fireUpload({
305
- artifactId: redactedId,
306
- contentType: contentTypeFor(opts.type),
307
- body: Buffer.from(maskedSerialized, "utf8"),
308
- contentHash: maskedHash,
309
- }, content.upload);
310
- // The live version is the redacted one — hand its handle back.
311
- return { id: redactedId, name: opts.name, type: opts.type, version: redactedVersion };
312
- }
313
- catch (err) {
314
- warn("save (redacted version)", err);
315
- // Fall through to returning the original handle (metadata-only).
316
- }
317
- }
318
- else {
319
- // off / detect / redact-with-no-findings: upload the bytes verbatim.
278
+ else if (effScrub === "redact" && scrubbed.findings.length > 0 && scrubbed.text !== null) {
279
+ // Redaction is a SUPERSEDING version (immutability): the original row
280
+ // keeps its hash and stores NO content; a new version records the
281
+ // masked content's hash and uploads the masked bytes.
282
+ const maskedSerialized = scrubbed.text;
283
+ const maskedBytes = Buffer.byteLength(maskedSerialized, "utf8");
284
+ const maskedHash = `sha256:${(0, node_crypto_1.createHash)("sha256").update(maskedSerialized).digest("hex")}`;
285
+ const redactedId = (0, node_crypto_1.randomUUID)();
286
+ const redactedVersion = version + 1;
287
+ try {
288
+ store.recordArtifact({
289
+ id: redactedId,
290
+ runId: ctx?.runId ?? null,
291
+ // Same ambient-step resolution as the original row it supersedes.
292
+ stepKey,
293
+ attemptNo: opts.attemptNo ?? null,
294
+ task: opts.task ?? null,
295
+ name: opts.name,
296
+ type: opts.type,
297
+ version: redactedVersion,
298
+ supersedesArtifactId: id, // supersedes the original (ruling c column)
299
+ declaredSchema: schema,
300
+ fingerprintCell: (0, fingerprint_1.fingerprintSchema)(schema)?.cellKey ?? null,
301
+ contentRef: null,
302
+ contentHash: maskedHash,
303
+ contentBytes: maskedBytes,
304
+ dataClassification: classification,
305
+ expiresAt: opts.expiresAt ?? null,
306
+ // Findings ride the original scanned row; the masked version stores
307
+ // no PII, so its own scrub is null (avoid double-counting).
308
+ scrub: null,
309
+ });
310
+ saves += 1;
320
311
  fireUpload({
321
- artifactId: id,
312
+ artifactId: redactedId,
322
313
  contentType: contentTypeFor(opts.type),
323
- body: Buffer.from(scrubbed.text ?? serialized, "utf8"),
324
- contentHash,
314
+ body: Buffer.from(maskedSerialized, "utf8"),
315
+ contentHash: maskedHash,
325
316
  }, content.upload);
317
+ // The live version is the redacted one — hand its handle back.
318
+ return { id: redactedId, name: opts.name, type: opts.type, version: redactedVersion };
319
+ }
320
+ catch (err) {
321
+ warn("save (redacted version)", err);
322
+ // Fall through to returning the original handle (metadata-only).
326
323
  }
327
324
  }
328
- return { id, name: opts.name, type: opts.type, version };
329
- },
325
+ else {
326
+ // off / detect / redact-with-no-findings: upload the bytes verbatim.
327
+ fireUpload({
328
+ artifactId: id,
329
+ contentType: contentTypeFor(opts.type),
330
+ body: Buffer.from(scrubbed.text ?? serialized, "utf8"),
331
+ contentHash,
332
+ }, content.upload);
333
+ }
334
+ }
335
+ return { id, name: opts.name, type: opts.type, version };
336
+ };
337
+ const link = (from, to, kind) => {
338
+ const fromId = idOf(from);
339
+ const toId = idOf(to);
340
+ if (fromId === toId)
341
+ return; // edges_no_self
342
+ try {
343
+ store.recordArtifactEdge({ fromArtifactId: fromId, toArtifactId: toId, kind });
344
+ }
345
+ catch (err) {
346
+ warn("link", err);
347
+ }
348
+ };
349
+ // PSR-1: validate a structure-only manifest, save it as a linked `manifest`
350
+ // artifact (ZERO-DDL free-text type), and chain `prompt → manifest` via
351
+ // derived_from. Never throws; an invalid manifest is a counted, teaching warn.
352
+ const saveManifest = (prompt, manifest, opts) => {
353
+ const result = (0, manifest_1.validateManifest)(manifest);
354
+ if (!result.ok) {
355
+ console.warn(`[modelrig artifacts] manifest for prompt "${idOf(prompt)}" is invalid — not emitted: ` +
356
+ `${result.errors.join("; ")}`);
357
+ return null;
358
+ }
359
+ const promptName = typeof prompt === "string" ? undefined : prompt.name;
360
+ const name = opts?.name ?? (promptName !== undefined ? `${promptName}.manifest` : "manifest");
361
+ const handle = save(JSON.stringify(result.manifest), {
362
+ name,
363
+ type: "manifest",
364
+ ...(opts?.task !== undefined ? { task: opts.task } : {}),
365
+ ...(opts?.stepKey !== undefined ? { stepKey: opts.stepKey } : {}),
366
+ ...(opts?.attemptNo !== undefined ? { attemptNo: opts.attemptNo } : {}),
367
+ });
368
+ if (handle === null)
369
+ return null; // custody/zeroRetention refused — no edge.
370
+ // Chain prompt → manifest: the prompt is assembled per this manifest recipe.
371
+ link(prompt, handle, "derived_from");
372
+ return handle;
373
+ };
374
+ return {
375
+ save,
376
+ saveManifest,
330
377
  load(ref) {
331
378
  (0, run_context_1.getActiveRunContext)()?.noteLoad(idOf(ref));
332
379
  },
333
- link(from, to, kind) {
334
- const fromId = idOf(from);
335
- const toId = idOf(to);
336
- if (fromId === toId)
337
- return; // edges_no_self
338
- try {
339
- store.recordArtifactEdge({ fromArtifactId: fromId, toArtifactId: toId, kind });
340
- }
341
- catch (err) {
342
- warn("link", err);
343
- }
344
- },
380
+ link,
345
381
  promoteToEval(ref, opts) {
346
382
  const sourceArtifactId = idOf(ref);
347
383
  // Resolve the task: explicit wins, else the task this artifact was saved
@@ -16,7 +16,9 @@ export { step } from "./step";
16
16
  export { createArtifactApi } from "./artifact";
17
17
  export { openArtifactStore } from "../telemetry/artifact-store";
18
18
  export type { ArtifactStore } from "../telemetry/artifact-store";
19
- export type { ArtifactApi, ArtifactHandle, ArtifactSaveOptions, ArtifactPromoteOptions, ArtifactContentDeps, ArtifactContentUploader, } from "./artifact";
19
+ export type { ArtifactApi, ArtifactHandle, ArtifactSaveOptions, ArtifactManifestOptions, ArtifactPromoteOptions, ArtifactContentDeps, ArtifactContentUploader, } from "./artifact";
20
+ export { validateManifest, assertValidManifest, parseAuthoringDoc } from "../prompts/manifest";
21
+ export type { Slot, SlotKind, Manifest, DynamicSource, DynamicSourceKind, CachePolicy, StaticFrom, ManifestValidation, AuthoringDoc, } from "../prompts/manifest";
20
22
  export { uploadArtifactContent } from "./content-uploader";
21
23
  export type { ContentUploadConfig, ContentUploadInput, ContentUploadOutcome, } from "./content-uploader";
22
24
  export { scrubContent, luhnValid, detectSpans, scrubDetectors } from "./scrub";