@sanity/ailf 4.6.0 → 6.0.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 (185) hide show
  1. package/canonical/grader-references/agent-harness-tools.yaml +42 -0
  2. package/canonical/grader-references/knowledge-probe-recall.yaml +36 -0
  3. package/canonical/grader-references/mcp-server-spec.yaml +51 -0
  4. package/canonical/grader-references/portable-text.yaml +48 -0
  5. package/config/diagnosis-cards.ts +318 -0
  6. package/config/models.ts +12 -0
  7. package/config/rubrics.ts +38 -2
  8. package/dist/_vendor/ailf-core/artifact-registry.d.ts +60 -2
  9. package/dist/_vendor/ailf-core/artifact-registry.js +288 -7
  10. package/dist/_vendor/ailf-core/examples/index.d.ts +125 -26
  11. package/dist/_vendor/ailf-core/examples/index.js +146 -47
  12. package/dist/_vendor/ailf-core/grader/failure-modes/agent-harness.d.ts +13 -0
  13. package/dist/_vendor/ailf-core/grader/failure-modes/agent-harness.js +16 -0
  14. package/dist/_vendor/ailf-core/grader/failure-modes/common.d.ts +14 -0
  15. package/dist/_vendor/ailf-core/grader/failure-modes/common.js +18 -0
  16. package/dist/_vendor/ailf-core/grader/failure-modes/index.d.ts +45 -0
  17. package/dist/_vendor/ailf-core/grader/failure-modes/index.js +109 -0
  18. package/dist/_vendor/ailf-core/grader/failure-modes/knowledge-probe.d.ts +13 -0
  19. package/dist/_vendor/ailf-core/grader/failure-modes/knowledge-probe.js +17 -0
  20. package/dist/_vendor/ailf-core/grader/failure-modes/literacy.d.ts +13 -0
  21. package/dist/_vendor/ailf-core/grader/failure-modes/literacy.js +17 -0
  22. package/dist/_vendor/ailf-core/grader/failure-modes/mcp.d.ts +13 -0
  23. package/dist/_vendor/ailf-core/grader/failure-modes/mcp.js +17 -0
  24. package/dist/_vendor/ailf-core/index.d.ts +1 -0
  25. package/dist/_vendor/ailf-core/index.js +4 -0
  26. package/dist/_vendor/ailf-core/ports/context.d.ts +8 -0
  27. package/dist/_vendor/ailf-core/ports/mode-handler.d.ts +15 -0
  28. package/dist/_vendor/ailf-core/schemas/branded-string.d.ts +40 -0
  29. package/dist/_vendor/ailf-core/schemas/branded-string.js +45 -0
  30. package/dist/_vendor/ailf-core/schemas/confidence-schema.d.ts +36 -0
  31. package/dist/_vendor/ailf-core/schemas/confidence-schema.js +32 -0
  32. package/dist/_vendor/ailf-core/schemas/eval-config.d.ts +1 -0
  33. package/dist/_vendor/ailf-core/schemas/eval-config.js +8 -4
  34. package/dist/_vendor/ailf-core/schemas/index.d.ts +2 -0
  35. package/dist/_vendor/ailf-core/schemas/index.js +9 -0
  36. package/dist/_vendor/ailf-core/schemas/pipeline-request.d.ts +1 -0
  37. package/dist/_vendor/ailf-core/schemas/pipeline-request.js +1 -0
  38. package/dist/_vendor/ailf-core/schemas/pipeline.d.ts +34 -8
  39. package/dist/_vendor/ailf-core/schemas/pipeline.js +23 -1
  40. package/dist/_vendor/ailf-core/services/diagnosis/card-validators.d.ts +41 -0
  41. package/dist/_vendor/ailf-core/services/diagnosis/card-validators.js +40 -0
  42. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/area-summary.test.d.ts +7 -0
  43. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/area-summary.test.js +131 -0
  44. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/failure-mode-summary.test.d.ts +7 -0
  45. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/failure-mode-summary.test.js +171 -0
  46. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/no-issues.test.d.ts +7 -0
  47. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/no-issues.test.js +155 -0
  48. package/dist/_vendor/ailf-core/services/diagnosis/cards/area-summary.d.ts +17 -0
  49. package/dist/_vendor/ailf-core/services/diagnosis/cards/area-summary.js +43 -0
  50. package/dist/_vendor/ailf-core/services/diagnosis/cards/doc-attribution-spotlight.d.ts +46 -0
  51. package/dist/_vendor/ailf-core/services/diagnosis/cards/doc-attribution-spotlight.js +104 -0
  52. package/dist/_vendor/ailf-core/services/diagnosis/cards/failure-mode-summary.d.ts +28 -0
  53. package/dist/_vendor/ailf-core/services/diagnosis/cards/failure-mode-summary.js +96 -0
  54. package/dist/_vendor/ailf-core/services/diagnosis/cards/index.d.ts +39 -0
  55. package/dist/_vendor/ailf-core/services/diagnosis/cards/index.js +52 -0
  56. package/dist/_vendor/ailf-core/services/diagnosis/cards/low-confidence-attribution.d.ts +27 -0
  57. package/dist/_vendor/ailf-core/services/diagnosis/cards/low-confidence-attribution.js +77 -0
  58. package/dist/_vendor/ailf-core/services/diagnosis/cards/no-issues.d.ts +32 -0
  59. package/dist/_vendor/ailf-core/services/diagnosis/cards/no-issues.js +71 -0
  60. package/dist/_vendor/ailf-core/services/diagnosis/cards/regression-vs-baseline.d.ts +44 -0
  61. package/dist/_vendor/ailf-core/services/diagnosis/cards/regression-vs-baseline.js +126 -0
  62. package/dist/_vendor/ailf-core/services/diagnosis/cards/top-recommendations.d.ts +41 -0
  63. package/dist/_vendor/ailf-core/services/diagnosis/cards/top-recommendations.js +107 -0
  64. package/dist/_vendor/ailf-core/services/diagnosis/cards/weakest-area.d.ts +43 -0
  65. package/dist/_vendor/ailf-core/services/diagnosis/cards/weakest-area.js +114 -0
  66. package/dist/_vendor/ailf-core/services/diagnosis/prompt-builders.d.ts +72 -0
  67. package/dist/_vendor/ailf-core/services/diagnosis/prompt-builders.js +273 -0
  68. package/dist/_vendor/ailf-core/services/diagnosis/prompts/doc-attribution-spotlight.system.d.ts +17 -0
  69. package/dist/_vendor/ailf-core/services/diagnosis/prompts/doc-attribution-spotlight.system.js +58 -0
  70. package/dist/_vendor/ailf-core/services/diagnosis/prompts/index.d.ts +10 -0
  71. package/dist/_vendor/ailf-core/services/diagnosis/prompts/index.js +10 -0
  72. package/dist/_vendor/ailf-core/services/diagnosis/prompts/low-confidence-attribution.system.d.ts +15 -0
  73. package/dist/_vendor/ailf-core/services/diagnosis/prompts/low-confidence-attribution.system.js +53 -0
  74. package/dist/_vendor/ailf-core/services/diagnosis/prompts/regression-vs-baseline.system.d.ts +14 -0
  75. package/dist/_vendor/ailf-core/services/diagnosis/prompts/regression-vs-baseline.system.js +63 -0
  76. package/dist/_vendor/ailf-core/services/diagnosis/prompts/top-recommendations.system.d.ts +16 -0
  77. package/dist/_vendor/ailf-core/services/diagnosis/prompts/top-recommendations.system.js +78 -0
  78. package/dist/_vendor/ailf-core/services/diagnosis/prompts/weakest-area.system.d.ts +16 -0
  79. package/dist/_vendor/ailf-core/services/diagnosis/prompts/weakest-area.system.js +86 -0
  80. package/dist/_vendor/ailf-core/services/diagnosis/registry.d.ts +50 -0
  81. package/dist/_vendor/ailf-core/services/diagnosis/registry.js +35 -0
  82. package/dist/_vendor/ailf-core/services/diagnosis-runner.d.ts +136 -0
  83. package/dist/_vendor/ailf-core/services/diagnosis-runner.js +153 -0
  84. package/dist/_vendor/ailf-core/services/index.d.ts +6 -0
  85. package/dist/_vendor/ailf-core/services/index.js +18 -0
  86. package/dist/_vendor/ailf-core/services/llm-client-factory.d.ts +64 -0
  87. package/dist/_vendor/ailf-core/services/llm-client-factory.js +54 -0
  88. package/dist/_vendor/ailf-core/services/report-to-markdown.js +3 -2
  89. package/dist/_vendor/ailf-core/types/attribution.d.ts +82 -0
  90. package/dist/_vendor/ailf-core/types/attribution.js +18 -0
  91. package/dist/_vendor/ailf-core/types/branded-ids.d.ts +26 -1
  92. package/dist/_vendor/ailf-core/types/branded-ids.js +80 -4
  93. package/dist/_vendor/ailf-core/types/confidence.d.ts +1 -1
  94. package/dist/_vendor/ailf-core/types/confidence.js +7 -0
  95. package/dist/_vendor/ailf-core/types/diagnosis.d.ts +271 -0
  96. package/dist/_vendor/ailf-core/types/diagnosis.js +19 -0
  97. package/dist/_vendor/ailf-core/types/generalized-task.d.ts +16 -1
  98. package/dist/_vendor/ailf-core/types/grader-judgment.d.ts +125 -0
  99. package/dist/_vendor/ailf-core/types/grader-judgment.js +30 -0
  100. package/dist/_vendor/ailf-core/types/index.d.ts +80 -29
  101. package/dist/_vendor/ailf-core/types/index.js +15 -1
  102. package/dist/_vendor/ailf-core/types/legacy-grader-judgment.d.ts +55 -0
  103. package/dist/_vendor/ailf-core/types/legacy-grader-judgment.js +30 -0
  104. package/dist/_vendor/ailf-core/types/pipeline-request.d.ts +1 -0
  105. package/dist/_vendor/ailf-core/types/repo-config.d.ts +8 -0
  106. package/dist/_vendor/ailf-shared/document-ref.d.ts +1 -1
  107. package/dist/adapters/api-client/build-request.d.ts +1 -0
  108. package/dist/adapters/api-client/build-request.js +3 -0
  109. package/dist/adapters/attribution/attribution-meta-writer.d.ts +35 -0
  110. package/dist/adapters/attribution/attribution-meta-writer.js +34 -0
  111. package/dist/adapters/attribution/index.d.ts +9 -0
  112. package/dist/adapters/attribution/index.js +8 -0
  113. package/dist/adapters/attribution/per-entry-attribution-writer.d.ts +56 -0
  114. package/dist/adapters/attribution/per-entry-attribution-writer.js +49 -0
  115. package/dist/adapters/config-sources/file-config-adapter.js +1 -0
  116. package/dist/adapters/grader-outputs/index.d.ts +10 -0
  117. package/dist/adapters/grader-outputs/index.js +8 -0
  118. package/dist/adapters/grader-outputs/legacy/index.d.ts +11 -0
  119. package/dist/adapters/grader-outputs/legacy/index.js +10 -0
  120. package/dist/adapters/grader-outputs/legacy/promptfoo-grader-output-legacy.d.ts +49 -0
  121. package/dist/adapters/grader-outputs/legacy/promptfoo-grader-output-legacy.js +48 -0
  122. package/dist/adapters/grader-outputs/promptfoo-grader-output.d.ts +102 -0
  123. package/dist/adapters/grader-outputs/promptfoo-grader-output.js +93 -0
  124. package/dist/adapters/index.d.ts +3 -0
  125. package/dist/adapters/index.js +4 -0
  126. package/dist/adapters/llm/fake-llm-client.d.ts +20 -0
  127. package/dist/adapters/llm/fake-llm-client.js +38 -1
  128. package/dist/adapters/llm/openai-llm-client.js +52 -3
  129. package/dist/adapters/task-sources/content-lake-task-source.d.ts +5 -1
  130. package/dist/adapters/task-sources/content-lake-task-source.js +28 -2
  131. package/dist/adapters/task-sources/repo-schemas.d.ts +79 -11
  132. package/dist/adapters/task-sources/repo-schemas.js +19 -2
  133. package/dist/cli-program.js +3 -0
  134. package/dist/commands/calculate-scores.js +1 -1
  135. package/dist/commands/explain-handler.js +1 -1
  136. package/dist/commands/interpret.d.ts +50 -0
  137. package/dist/commands/interpret.js +212 -0
  138. package/dist/commands/lookup-doc.d.ts +1 -1
  139. package/dist/commands/lookup-doc.js +3 -3
  140. package/dist/commands/pipeline-action.d.ts +6 -0
  141. package/dist/commands/pipeline-action.js +2 -0
  142. package/dist/commands/remote-pipeline.js +1 -0
  143. package/dist/composition-root.d.ts +57 -23
  144. package/dist/composition-root.js +155 -41
  145. package/dist/config/diagnosis-cards.ts +318 -0
  146. package/dist/config/models.ts +12 -0
  147. package/dist/config/rubrics.ts +38 -2
  148. package/dist/grader/agent-harness.d.ts +9 -0
  149. package/dist/grader/agent-harness.js +9 -0
  150. package/dist/grader/common.d.ts +9 -0
  151. package/dist/grader/common.js +9 -0
  152. package/dist/grader/index.d.ts +24 -0
  153. package/dist/grader/index.js +24 -0
  154. package/dist/grader/knowledge-probe.d.ts +9 -0
  155. package/dist/grader/knowledge-probe.js +9 -0
  156. package/dist/grader/literacy.d.ts +9 -0
  157. package/dist/grader/literacy.js +9 -0
  158. package/dist/grader/mcp.d.ts +9 -0
  159. package/dist/grader/mcp.js +9 -0
  160. package/dist/orchestration/build-app-context.js +1 -0
  161. package/dist/orchestration/build-step-sequence.js +5 -0
  162. package/dist/orchestration/steps/calculate-scores-step.js +23 -1
  163. package/dist/orchestration/steps/compute-attribution-step.d.ts +44 -0
  164. package/dist/orchestration/steps/compute-attribution-step.js +279 -0
  165. package/dist/orchestration/steps/gap-analysis-step.js +35 -7
  166. package/dist/orchestration/steps/index.d.ts +1 -0
  167. package/dist/orchestration/steps/index.js +1 -0
  168. package/dist/pipeline/attribution.d.ts +15 -0
  169. package/dist/pipeline/attribution.js +18 -9
  170. package/dist/pipeline/borderline-consensus-runner.d.ts +63 -0
  171. package/dist/pipeline/borderline-consensus-runner.js +124 -0
  172. package/dist/pipeline/borderline-detector.d.ts +24 -0
  173. package/dist/pipeline/borderline-detector.js +26 -0
  174. package/dist/pipeline/calculate-scores.d.ts +114 -3
  175. package/dist/pipeline/calculate-scores.js +426 -24
  176. package/dist/pipeline/compiler/literacy-bridge.d.ts +1 -1
  177. package/dist/pipeline/compiler/literacy-bridge.js +35 -17
  178. package/dist/pipeline/compiler/rubric-resolution.d.ts +15 -0
  179. package/dist/pipeline/compiler/rubric-resolution.js +9 -1
  180. package/dist/pipeline/compute-attribution.d.ts +80 -0
  181. package/dist/pipeline/compute-attribution.js +196 -0
  182. package/dist/pipeline/failure-modes.d.ts +52 -17
  183. package/dist/pipeline/failure-modes.js +178 -117
  184. package/dist/pipeline/map-request-to-config.js +1 -0
  185. package/package.json +7 -5
@@ -79,7 +79,7 @@ export type WriteSource = WritePolicy;
79
79
  */
80
80
  export type ArtifactTruncationPolicy = "reject" | "trailing-truncate" | "fielded-truncate" | "trial-oversize";
81
81
  /** The union of every artifact type known to AILF. */
82
- export type ArtifactType = "runManifest" | "scoreSummary" | "pipelineResult" | "pipelineContext" | "documentManifest" | "prComment" | "readinessReport" | "reportSnapshot" | "autoComparison" | "gapReport" | "sinkResults" | "callbackRequest" | "callbackResponse" | "configSnapshot" | "evalConfigGenerated" | "comparisonReport" | "discoveryReport" | "failureModes" | "renderedPrompts" | "rawResults" | "testOutputs" | "graderPrompts" | "graderJudgments" | "symbolPreflight" | "traces";
82
+ export type ArtifactType = "runManifest" | "scoreSummary" | "pipelineResult" | "pipelineContext" | "documentManifest" | "prComment" | "readinessReport" | "reportSnapshot" | "autoComparison" | "gapReport" | "sinkResults" | "callbackRequest" | "callbackResponse" | "configSnapshot" | "evalConfigGenerated" | "comparisonReport" | "discoveryReport" | "failureModes" | "renderedPrompts" | "rawResults" | "testOutputs" | "graderPrompts" | "graderJudgments" | "symbolPreflight" | "traces" | "diagnosis" | "perEntryAttribution" | "attributionMeta";
83
83
  /**
84
84
  * Result of parsing a per-entry key into a sanitized filename component.
85
85
  * Success carries the sanitized value; failure carries a reason for 4xx responses.
@@ -114,6 +114,32 @@ export interface ManifestPreviewDeclaration<TPreview = unknown> {
114
114
  readonly extract: (entry: unknown) => TPreview;
115
115
  readonly capBytes: number;
116
116
  }
117
+ /**
118
+ * Callback signature for `ArtifactDescriptor.objectPath`. Aliased so
119
+ * external path builders (e.g. `diagnosisPathBuilder`) declare a named
120
+ * return type rather than re-typing the inline arrow signature.
121
+ */
122
+ export type ArtifactObjectPath = (runId: RunId, entryKey?: string, version?: string) => string;
123
+ /**
124
+ * Opt-in marker for the bulk-versioned-with-report-axis carve-out
125
+ * (Plan 01-03 / ITER2-BLOCKER-01). Any descriptor that needs to combine
126
+ * `layout: "bulk"` with a `report` axis (e.g. the post-hoc diagnosis
127
+ * artifact) must set
128
+ * `pathSafetyMarker: BULK_VERSIONED_WITH_REPORT_AXIS` to opt in to the
129
+ * carve-out in `assertValidArtifactDescriptor`.
130
+ *
131
+ * `Symbol.for(...)` is registry-keyed: it survives minification (the
132
+ * key is a string literal that minifiers cannot rename) and yields
133
+ * cross-module-instance equality by spec. This makes it bundler-stable
134
+ * — a deliberate replacement for any source-text reflection on the
135
+ * descriptor's `objectPath` closure body.
136
+ *
137
+ * The marker alone does NOT trigger the carve-out — the four structural
138
+ * conditions (bulk + versionedBy + run+report axes) must also hold.
139
+ * Conversely, the structural conditions alone do NOT trigger the
140
+ * carve-out without the marker. This is an explicit opt-in.
141
+ */
142
+ export declare const BULK_VERSIONED_WITH_REPORT_AXIS: unique symbol;
117
143
  /**
118
144
  * Per-type declaration consumed by writers, signers, and readers.
119
145
  *
@@ -157,6 +183,14 @@ export interface ArtifactDescriptor<TEntry = unknown, TPreview = unknown> {
157
183
  * When unset, the path is unversioned (legacy behavior).
158
184
  */
159
185
  readonly versionedBy?: VersionedBy;
186
+ /**
187
+ * Optional opt-in marker for the bulk-versioned-with-report-axis
188
+ * carve-out in `assertValidArtifactDescriptor` (Plan 01-03 /
189
+ * ITER2-BLOCKER-01). See `BULK_VERSIONED_WITH_REPORT_AXIS` for full
190
+ * semantics. Descriptors without this marker are subject to the
191
+ * standard bounded-axis rule.
192
+ */
193
+ readonly pathSafetyMarker?: typeof BULK_VERSIONED_WITH_REPORT_AXIS;
160
194
  /**
161
195
  * Build the GCS object path for this artifact.
162
196
  * - bulk: returns `runs/{runId}/{slug}.{ext}`; `entryKey` is ignored.
@@ -165,7 +199,7 @@ export interface ArtifactDescriptor<TEntry = unknown, TPreview = unknown> {
165
199
  * `runs/{runId}/{slug}-{version}.{ext}` for bulk-shaped versioned
166
200
  * descriptors. `entryKey` is ignored on versioned bulk paths.
167
201
  */
168
- readonly objectPath: (runId: RunId, entryKey?: string, version?: string) => string;
202
+ readonly objectPath: ArtifactObjectPath;
169
203
  /**
170
204
  * Build a filename-safe entry key from association values. Only meaningful
171
205
  * for `layout === "per-entry"` — bulk descriptors omit it.
@@ -201,6 +235,30 @@ export interface ArtifactDescriptor<TEntry = unknown, TPreview = unknown> {
201
235
  * collapsed to an unversioned path.
202
236
  */
203
237
  export declare function versionedPathBuilder(slug: string, mime: ArtifactMime): (runId: RunId, _entryKey?: string, version?: string) => string;
238
+ /**
239
+ * Two-axis post-hoc path builder for the diagnosis descriptor (Plan 01-03,
240
+ * D-09). Filename: `diagnosis-{diagnosisVersion}-{cardSetShortHash}.json`
241
+ * where `cardSetShortHash = sha256(cardVersion).slice(0, 12)`.
242
+ *
243
+ * The compound version argument is `${diagnosisVersion}|${cardVersion}`
244
+ * to fit the existing 3-arg `objectPath` signature without growing the
245
+ * shared `ArtifactObjectPath` shape. Callers should use
246
+ * `encodeDiagnosisPathVersion()` rather than building the compound
247
+ * string by hand.
248
+ *
249
+ * The diagnosis descriptor opts into the bulk-versioned-with-report-axis
250
+ * carve-out via `pathSafetyMarker: BULK_VERSIONED_WITH_REPORT_AXIS`
251
+ * (ITER2-BLOCKER-01). The builder itself does not need to know about
252
+ * the marker — it just builds the path; the carve-out is a registration-
253
+ * time concern in `assertValidArtifactDescriptor`.
254
+ */
255
+ export declare function diagnosisPathBuilder(): ArtifactObjectPath;
256
+ /**
257
+ * Pack the diagnosis descriptor's two version segments into the existing
258
+ * single-string `version` slot of `ArtifactObjectPath`. Separator is `|`;
259
+ * `diagnosisVersion` MUST NOT contain `|` (the function rejects that case).
260
+ */
261
+ export declare function encodeDiagnosisPathVersion(diagnosisVersion: string, cardVersion: string): string;
204
262
  /** Test-only reset for the legacy-key warning flag. Not exported publicly. */
205
263
  export declare function __resetLegacyTestOutputsWarning(): void;
206
264
  /**
@@ -24,6 +24,26 @@
24
24
  * @see docs/design-docs/unified-run-artifacts.md (§ M1, § M5)
25
25
  */
26
26
  import { z } from "zod";
27
+ /**
28
+ * Opt-in marker for the bulk-versioned-with-report-axis carve-out
29
+ * (Plan 01-03 / ITER2-BLOCKER-01). Any descriptor that needs to combine
30
+ * `layout: "bulk"` with a `report` axis (e.g. the post-hoc diagnosis
31
+ * artifact) must set
32
+ * `pathSafetyMarker: BULK_VERSIONED_WITH_REPORT_AXIS` to opt in to the
33
+ * carve-out in `assertValidArtifactDescriptor`.
34
+ *
35
+ * `Symbol.for(...)` is registry-keyed: it survives minification (the
36
+ * key is a string literal that minifiers cannot rename) and yields
37
+ * cross-module-instance equality by spec. This makes it bundler-stable
38
+ * — a deliberate replacement for any source-text reflection on the
39
+ * descriptor's `objectPath` closure body.
40
+ *
41
+ * The marker alone does NOT trigger the carve-out — the four structural
42
+ * conditions (bulk + versionedBy + run+report axes) must also hold.
43
+ * Conversely, the structural conditions alone do NOT trigger the
44
+ * carve-out without the marker. This is an explicit opt-in.
45
+ */
46
+ export const BULK_VERSIONED_WITH_REPORT_AXIS = Symbol.for("ailf.artifactRegistry.bulkVersionedWithReportAxis");
27
47
  // ---------------------------------------------------------------------------
28
48
  // Path + key helpers
29
49
  // ---------------------------------------------------------------------------
@@ -87,6 +107,193 @@ export function versionedPathBuilder(slug, mime) {
87
107
  return `runs/${runId}/${slug}-${sanitized}.${ext}`;
88
108
  };
89
109
  }
110
+ /**
111
+ * Synchronous SHA-256 over a UTF-8-encoded string returning a lowercase
112
+ * hex digest. Implemented inline because `@sanity/ailf-core` is the
113
+ * domain kernel — it intentionally avoids a `@types/node` dependency
114
+ * (matching the existing `byteLengthUtf8` rationale below) and the
115
+ * Web Crypto `crypto.subtle.digest` API is async-only, which would
116
+ * force `ArtifactObjectPath` to become async.
117
+ *
118
+ * Used only for the diagnosis descriptor's `cardSetShortHash` (the
119
+ * filename suffix) — collision resistance, not authentication, is the
120
+ * relevant property. FIPS 180-4 reference implementation.
121
+ */
122
+ function sha256Hex(input) {
123
+ const K = [
124
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
125
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
126
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
127
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
128
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
129
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
130
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
131
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
132
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
133
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
134
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
135
+ ];
136
+ // UTF-8 encode (without TextEncoder reliance — kernel runs in both Node
137
+ // and the browser; both have TextEncoder by spec, but staying explicit
138
+ // mirrors the byteLengthUtf8 helper below).
139
+ const bytes = [];
140
+ for (let i = 0; i < input.length; i++) {
141
+ let c = input.charCodeAt(i);
142
+ if (c < 0x80) {
143
+ bytes.push(c);
144
+ }
145
+ else if (c < 0x800) {
146
+ bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
147
+ }
148
+ else if (c >= 0xd800 && c < 0xdc00 && i + 1 < input.length) {
149
+ // surrogate pair
150
+ const c2 = input.charCodeAt(i + 1);
151
+ const cp = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff));
152
+ bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
153
+ i++;
154
+ }
155
+ else {
156
+ bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
157
+ }
158
+ }
159
+ // Pre-processing: padding
160
+ const bitLen = bytes.length * 8;
161
+ bytes.push(0x80);
162
+ while (bytes.length % 64 !== 56)
163
+ bytes.push(0);
164
+ // 64-bit big-endian length (high 32 bits zero — input < 2^32 bits is the
165
+ // realistic ceiling here).
166
+ bytes.push(0, 0, 0, 0);
167
+ bytes.push((bitLen >>> 24) & 0xff, (bitLen >>> 16) & 0xff, (bitLen >>> 8) & 0xff, bitLen & 0xff);
168
+ let h0 = 0x6a09e667;
169
+ let h1 = 0xbb67ae85;
170
+ let h2 = 0x3c6ef372;
171
+ let h3 = 0xa54ff53a;
172
+ let h4 = 0x510e527f;
173
+ let h5 = 0x9b05688c;
174
+ let h6 = 0x1f83d9ab;
175
+ let h7 = 0x5be0cd19;
176
+ const W = new Array(64);
177
+ for (let chunk = 0; chunk < bytes.length; chunk += 64) {
178
+ for (let i = 0; i < 16; i++) {
179
+ const j = chunk + i * 4;
180
+ W[i] =
181
+ ((bytes[j] << 24) |
182
+ (bytes[j + 1] << 16) |
183
+ (bytes[j + 2] << 8) |
184
+ bytes[j + 3]) >>>
185
+ 0;
186
+ }
187
+ for (let i = 16; i < 64; i++) {
188
+ const a = W[i - 15];
189
+ const b = W[i - 2];
190
+ const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
191
+ const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
192
+ W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0;
193
+ }
194
+ let a = h0;
195
+ let b = h1;
196
+ let c = h2;
197
+ let d = h3;
198
+ let e = h4;
199
+ let f = h5;
200
+ let g = h6;
201
+ let h = h7;
202
+ for (let i = 0; i < 64; i++) {
203
+ const S1 = ((e >>> 6) | (e << 26)) ^
204
+ ((e >>> 11) | (e << 21)) ^
205
+ ((e >>> 25) | (e << 7));
206
+ const ch = (e & f) ^ (~e & g);
207
+ const t1 = (h + S1 + ch + K[i] + W[i]) >>> 0;
208
+ const S0 = ((a >>> 2) | (a << 30)) ^
209
+ ((a >>> 13) | (a << 19)) ^
210
+ ((a >>> 22) | (a << 10));
211
+ const mj = (a & b) ^ (a & c) ^ (b & c);
212
+ const t2 = (S0 + mj) >>> 0;
213
+ h = g;
214
+ g = f;
215
+ f = e;
216
+ e = (d + t1) >>> 0;
217
+ d = c;
218
+ c = b;
219
+ b = a;
220
+ a = (t1 + t2) >>> 0;
221
+ }
222
+ h0 = (h0 + a) >>> 0;
223
+ h1 = (h1 + b) >>> 0;
224
+ h2 = (h2 + c) >>> 0;
225
+ h3 = (h3 + d) >>> 0;
226
+ h4 = (h4 + e) >>> 0;
227
+ h5 = (h5 + f) >>> 0;
228
+ h6 = (h6 + g) >>> 0;
229
+ h7 = (h7 + h) >>> 0;
230
+ }
231
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
232
+ return (toHex(h0) +
233
+ toHex(h1) +
234
+ toHex(h2) +
235
+ toHex(h3) +
236
+ toHex(h4) +
237
+ toHex(h5) +
238
+ toHex(h6) +
239
+ toHex(h7));
240
+ }
241
+ /**
242
+ * Two-axis post-hoc path builder for the diagnosis descriptor (Plan 01-03,
243
+ * D-09). Filename: `diagnosis-{diagnosisVersion}-{cardSetShortHash}.json`
244
+ * where `cardSetShortHash = sha256(cardVersion).slice(0, 12)`.
245
+ *
246
+ * The compound version argument is `${diagnosisVersion}|${cardVersion}`
247
+ * to fit the existing 3-arg `objectPath` signature without growing the
248
+ * shared `ArtifactObjectPath` shape. Callers should use
249
+ * `encodeDiagnosisPathVersion()` rather than building the compound
250
+ * string by hand.
251
+ *
252
+ * The diagnosis descriptor opts into the bulk-versioned-with-report-axis
253
+ * carve-out via `pathSafetyMarker: BULK_VERSIONED_WITH_REPORT_AXIS`
254
+ * (ITER2-BLOCKER-01). The builder itself does not need to know about
255
+ * the marker — it just builds the path; the carve-out is a registration-
256
+ * time concern in `assertValidArtifactDescriptor`.
257
+ */
258
+ export function diagnosisPathBuilder() {
259
+ return (runId, reportId, version) => {
260
+ if (!reportId)
261
+ throw new Error("diagnosisPathBuilder requires reportId axis");
262
+ if (!version)
263
+ throw new Error("diagnosisPathBuilder requires compound version");
264
+ if (hasControlChars(reportId))
265
+ throw new Error("diagnosisPathBuilder reportId must not contain control characters");
266
+ const sep = version.indexOf("|");
267
+ if (sep <= 0)
268
+ throw new Error("diagnosisPathBuilder version must be ${diagnosisVersion}|${cardVersion}");
269
+ const dv = version.slice(0, sep);
270
+ const cv = version.slice(sep + 1);
271
+ if (hasControlChars(dv))
272
+ throw new Error("diagnosisPathBuilder diagnosisVersion must not contain control characters");
273
+ // Sanitize reportId and diagnosisVersion segments — `/` would otherwise
274
+ // create unintended GCS subdirectories and break the
275
+ // `runs/{runId}/{reportId}/diagnosis-…` path-prefix containment
276
+ // guarantee. Mirrors `versionedPathBuilder` (above).
277
+ const sanitizedReportId = sanitizeEntryKey(reportId);
278
+ const sanitizedDv = sanitizeEntryKey(dv);
279
+ const cardSetShortHash = sha256Hex(cv).slice(0, 12);
280
+ return `runs/${runId}/${sanitizedReportId}/diagnosis-${sanitizedDv}-${cardSetShortHash}.json`;
281
+ };
282
+ }
283
+ /**
284
+ * Pack the diagnosis descriptor's two version segments into the existing
285
+ * single-string `version` slot of `ArtifactObjectPath`. Separator is `|`;
286
+ * `diagnosisVersion` MUST NOT contain `|` (the function rejects that case).
287
+ */
288
+ export function encodeDiagnosisPathVersion(diagnosisVersion, cardVersion) {
289
+ if (diagnosisVersion === "")
290
+ throw new Error("encodeDiagnosisPathVersion: diagnosisVersion must be a non-empty string");
291
+ if (cardVersion === "")
292
+ throw new Error("encodeDiagnosisPathVersion: cardVersion must be a non-empty string");
293
+ if (diagnosisVersion.includes("|"))
294
+ throw new Error("diagnosisVersion must not contain '|'");
295
+ return `${diagnosisVersion}|${cardVersion}`;
296
+ }
90
297
  /**
91
298
  * Convert an entry key (wire format, e.g. `{taskId}::{modelId}`) to a
92
299
  * filename-safe component.
@@ -443,11 +650,12 @@ function titleCaseCategory(id) {
443
650
  .join(" ");
444
651
  }
445
652
  function buildDescriptor(input) {
446
- const objectPath = input.versionedBy
447
- ? versionedPathBuilder(input.slug, input.mime)
448
- : input.layout === "bulk"
449
- ? bulkPathBuilder(input.slug, input.mime)
450
- : perEntryPathBuilder(input.slug, input.mime);
653
+ const objectPath = input.objectPath ??
654
+ (input.versionedBy
655
+ ? versionedPathBuilder(input.slug, input.mime)
656
+ : input.layout === "bulk"
657
+ ? bulkPathBuilder(input.slug, input.mime)
658
+ : perEntryPathBuilder(input.slug, input.mime));
451
659
  const formatEntryKey = input.layout === "per-entry" ? formatKeyFromAxes(input.axes) : undefined;
452
660
  const parseEntryKey = input.layout === "per-entry"
453
661
  ? (input.parseEntryKey ?? parseKeyByAxes(input.type, input.axes))
@@ -464,6 +672,7 @@ function buildDescriptor(input) {
464
672
  optional: input.optional,
465
673
  writePolicy: input.writePolicy,
466
674
  versionedBy: input.versionedBy,
675
+ pathSafetyMarker: input.pathSafetyMarker,
467
676
  objectPath,
468
677
  formatEntryKey,
469
678
  parseEntryKey,
@@ -964,6 +1173,49 @@ export const ARTIFACT_REGISTRY = {
964
1173
  capBytes: 10_000_000,
965
1174
  truncation: "trial-oversize",
966
1175
  }),
1176
+ // -- Plan 01-03 — Actionability ladder Phase 1 ---------------------------
1177
+ diagnosis: buildDescriptor({
1178
+ type: "diagnosis",
1179
+ slug: "diagnosis",
1180
+ layout: "bulk",
1181
+ axes: ["run", "report"],
1182
+ entrySchema: unknownEntry,
1183
+ mime: "application/json",
1184
+ capBytes: 5 * 1024 * 1024,
1185
+ writePolicy: "post-hoc",
1186
+ versionedBy: "diagnosisVersion",
1187
+ objectPath: diagnosisPathBuilder(),
1188
+ // Defense-in-depth: this descriptor's axes (`run`, `report`) are both
1189
+ // bounded, so the `assertValidArtifactDescriptor` unbounded-axis rule
1190
+ // does not fire and the carve-out is never consulted at module load
1191
+ // for THIS descriptor. The marker is set so any future additions to
1192
+ // this descriptor (or a new diagnosis-shaped descriptor that gains an
1193
+ // unbounded axis like `task`) opt into the carve-out by default rather
1194
+ // than tripping the bounded-axis rule. The marker also documents
1195
+ // intent: this descriptor is the canonical reference for the
1196
+ // bulk-versioned-with-report-axis pattern (ITER2-BLOCKER-01).
1197
+ pathSafetyMarker: BULK_VERSIONED_WITH_REPORT_AXIS, // ITER2-BLOCKER-01 — explicit opt-in
1198
+ }),
1199
+ perEntryAttribution: buildDescriptor({
1200
+ type: "perEntryAttribution",
1201
+ slug: "attribution",
1202
+ layout: "per-entry",
1203
+ axes: ["run"],
1204
+ entrySchema: unknownEntry,
1205
+ mime: "application/json",
1206
+ capBytes: 1 * 1024 * 1024,
1207
+ writePolicy: "pipeline",
1208
+ }),
1209
+ attributionMeta: buildDescriptor({
1210
+ type: "attributionMeta",
1211
+ slug: "attribution-meta",
1212
+ layout: "bulk",
1213
+ axes: ["run"],
1214
+ entrySchema: unknownEntry,
1215
+ mime: "application/json",
1216
+ capBytes: 1 * 1024 * 1024,
1217
+ writePolicy: "pipeline",
1218
+ }),
967
1219
  };
968
1220
  /** All artifact types in declaration order. */
969
1221
  export const ARTIFACT_TYPES = Object.keys(ARTIFACT_REGISTRY);
@@ -996,13 +1248,40 @@ const UNBOUNDED_AXES = [
996
1248
  "model",
997
1249
  "trial",
998
1250
  ];
1251
+ /**
1252
+ * The bulk-versioned-with-report-axis carve-out (Plan 01-03 /
1253
+ * ITER2-BLOCKER-01) is an explicit opt-in: a descriptor must set
1254
+ * `pathSafetyMarker === BULK_VERSIONED_WITH_REPORT_AXIS` AND meet the
1255
+ * four structural conditions:
1256
+ *
1257
+ * 1. desc.pathSafetyMarker === BULK_VERSIONED_WITH_REPORT_AXIS (gate)
1258
+ * 2. desc.layout === "bulk" (sanity)
1259
+ * 3. Boolean(desc.versionedBy) (sanity)
1260
+ * 4. desc.association.axes.includes("run") (sanity)
1261
+ * 5. desc.association.axes.includes("report") (sanity)
1262
+ *
1263
+ * Marker without structure → fails (the descriptor misapplied the marker).
1264
+ * Structure without marker → fails (the descriptor must opt in).
1265
+ *
1266
+ * The marker is the SOLE detection mechanism. There is no source-text
1267
+ * reflection on the descriptor's path-builder closure body — `Symbol.for(...)`
1268
+ * is bundler-stable by spec and survives minification.
1269
+ */
1270
+ function isBulkVersionedWithReportAxisCarveOut(desc) {
1271
+ return (desc.pathSafetyMarker === BULK_VERSIONED_WITH_REPORT_AXIS &&
1272
+ desc.layout === "bulk" &&
1273
+ Boolean(desc.versionedBy) &&
1274
+ desc.association.axes.includes("run") &&
1275
+ desc.association.axes.includes("report"));
1276
+ }
999
1277
  /**
1000
1278
  * Structural check run against a single descriptor. Exported so L1 contract
1001
1279
  * tests can construct an invalid descriptor inline and assert the throw.
1002
1280
  */
1003
1281
  export function assertValidArtifactDescriptor(desc) {
1282
+ const carveOutApplies = isBulkVersionedWithReportAxisCarveOut(desc);
1004
1283
  const hasUnboundedAxis = desc.association.axes.some((a) => UNBOUNDED_AXES.includes(a));
1005
- if (hasUnboundedAxis && desc.layout !== "per-entry") {
1284
+ if (hasUnboundedAxis && desc.layout !== "per-entry" && !carveOutApplies) {
1006
1285
  throw new Error(`Artifact ${desc.type}: association contains unbounded axis (${desc.association.axes
1007
1286
  .filter((a) => UNBOUNDED_AXES.includes(a))
1008
1287
  .join(", ")}) but layout is "${desc.layout}". Unbounded axes require layout "per-entry".`);
@@ -1015,7 +1294,9 @@ export function assertValidArtifactDescriptor(desc) {
1015
1294
  }
1016
1295
  // D0050 — versioned descriptors are bulk-shaped only in v0; per-entry +
1017
1296
  // versioned is a future extension and rejected at module load so a
1018
- // half-wired descriptor doesn't ship by accident.
1297
+ // half-wired descriptor doesn't ship by accident. Bulk-versioned-with-
1298
+ // report-axis (the diagnosis case) is permitted only via the explicit
1299
+ // pathSafetyMarker opt-in checked above.
1019
1300
  if (desc.versionedBy && desc.layout !== "bulk") {
1020
1301
  throw new Error(`Artifact ${desc.type}: versionedBy is only supported on bulk descriptors (got layout "${desc.layout}")`);
1021
1302
  }