@sanity/ailf 2.9.0 → 3.1.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 (72) hide show
  1. package/dist/_vendor/ailf-core/artifact-capture/association.d.ts +37 -0
  2. package/dist/_vendor/ailf-core/artifact-capture/association.js +19 -0
  3. package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
  4. package/dist/_vendor/ailf-core/artifact-registry.js +1 -18
  5. package/dist/_vendor/ailf-core/batch-signing.d.ts +64 -0
  6. package/dist/_vendor/ailf-core/batch-signing.js +23 -0
  7. package/dist/_vendor/ailf-core/index.d.ts +2 -2
  8. package/dist/_vendor/ailf-core/index.js +2 -2
  9. package/dist/_vendor/ailf-core/ports/context.d.ts +12 -20
  10. package/dist/_vendor/ailf-core/ports/index.d.ts +2 -2
  11. package/dist/_vendor/ailf-core/ports/index.js +1 -0
  12. package/dist/_vendor/ailf-core/ports/progress-reporter.d.ts +74 -0
  13. package/dist/_vendor/ailf-core/ports/progress-reporter.js +26 -0
  14. package/dist/_vendor/ailf-core/services/slim-report-summary.js +1 -16
  15. package/dist/adapters/config-sources/file-config-adapter.js +0 -4
  16. package/dist/adapters/progress/console-progress-reporter.d.ts +35 -0
  17. package/dist/adapters/progress/console-progress-reporter.js +110 -0
  18. package/dist/artifact-capture/api-gateway-artifact-writer.d.ts +8 -1
  19. package/dist/artifact-capture/api-gateway-artifact-writer.js +79 -42
  20. package/dist/artifact-capture/batching-api-gateway-artifact-writer.d.ts +108 -0
  21. package/dist/artifact-capture/batching-api-gateway-artifact-writer.js +492 -0
  22. package/dist/artifact-capture/fanout-artifact-writer.d.ts +14 -2
  23. package/dist/artifact-capture/fanout-artifact-writer.js +25 -4
  24. package/dist/artifact-capture/gcs-artifact-writer.d.ts +27 -1
  25. package/dist/artifact-capture/gcs-artifact-writer.js +168 -38
  26. package/dist/artifact-capture/instrumented-artifact-writer.d.ts +32 -0
  27. package/dist/artifact-capture/instrumented-artifact-writer.js +151 -0
  28. package/dist/artifact-capture/local-fs-artifact-writer.d.ts +8 -1
  29. package/dist/artifact-capture/local-fs-artifact-writer.js +23 -4
  30. package/dist/artifact-capture/parallel-emit.d.ts +43 -0
  31. package/dist/artifact-capture/parallel-emit.js +84 -0
  32. package/dist/artifact-capture/redact-artifact.d.ts +3 -5
  33. package/dist/artifact-capture/redact-artifact.js +3 -5
  34. package/dist/artifact-capture/upload-metrics.d.ts +62 -0
  35. package/dist/artifact-capture/upload-metrics.js +125 -0
  36. package/dist/cli.js +56 -2
  37. package/dist/commands/explain-handler.js +1 -5
  38. package/dist/commands/pipeline-action.d.ts +0 -4
  39. package/dist/commands/pipeline-action.js +11 -45
  40. package/dist/commands/pipeline.d.ts +1 -5
  41. package/dist/commands/pipeline.js +1 -5
  42. package/dist/commands/runs.d.ts +18 -0
  43. package/dist/commands/runs.js +71 -0
  44. package/dist/composition-root.d.ts +2 -2
  45. package/dist/composition-root.js +98 -38
  46. package/dist/orchestration/build-app-context.js +4 -7
  47. package/dist/orchestration/pipeline-orchestrator.js +100 -24
  48. package/dist/orchestration/steps/calculate-scores-step.js +1 -1
  49. package/dist/orchestration/steps/finalize-run-step.js +33 -2
  50. package/dist/pipeline/emit-eval-results.js +29 -11
  51. package/dist/pipeline/map-request-to-config.js +0 -4
  52. package/dist/pipeline/upload-test-outputs.d.ts +12 -5
  53. package/dist/pipeline/upload-test-outputs.js +27 -10
  54. package/package.json +3 -3
  55. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.d.ts +0 -14
  56. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.js +0 -25
  57. package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +0 -94
  58. package/dist/_vendor/ailf-core/ports/artifact-collector.js +0 -13
  59. package/dist/_vendor/ailf-core/ports/capture-comparator.d.ts +0 -138
  60. package/dist/_vendor/ailf-core/ports/capture-comparator.js +0 -10
  61. package/dist/artifact-capture/comparator.d.ts +0 -22
  62. package/dist/artifact-capture/comparator.js +0 -493
  63. package/dist/artifact-capture/filesystem-collector.d.ts +0 -60
  64. package/dist/artifact-capture/filesystem-collector.js +0 -262
  65. package/dist/artifact-capture/gcs-collector.d.ts +0 -55
  66. package/dist/artifact-capture/gcs-collector.js +0 -117
  67. package/dist/commands/capture-compare.d.ts +0 -15
  68. package/dist/commands/capture-compare.js +0 -253
  69. package/dist/commands/capture-list.d.ts +0 -12
  70. package/dist/commands/capture-list.js +0 -150
  71. package/dist/commands/capture.d.ts +0 -9
  72. package/dist/commands/capture.js +0 -16
@@ -8,7 +8,7 @@
8
8
  * Endpoints:
9
9
  * - Bulk: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/upload-url
10
10
  * - Per-entry: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/{entryKey}/upload-url
11
- * - Manifest: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/manifest/upload-url
11
+ * - Manifest: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/upload-url
12
12
  *
13
13
  * ## W0049 API surface
14
14
  *
@@ -28,10 +28,13 @@
28
28
  * @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md
29
29
  */
30
30
  import { ARTIFACT_REGISTRY, NotImplementedError, } from "../_vendor/ailf-core/index.js";
31
+ import { NO_OP_UPLOAD_METRICS, } from "./upload-metrics.js";
31
32
  export class ApiGatewayArtifactWriter {
32
33
  options;
34
+ metrics;
33
35
  constructor(options) {
34
36
  this.options = options;
37
+ this.metrics = options.metrics ?? NO_OP_UPLOAD_METRICS;
35
38
  }
36
39
  // ---- Canonical W0049 API ------------------------------------------------
37
40
  async emit(type, association, payload) {
@@ -46,12 +49,13 @@ export class ApiGatewayArtifactWriter {
46
49
  return this.putJson(uploadUrlPath, payload, {
47
50
  layout: "bulk",
48
51
  entryCount: entryCountOf(payload),
52
+ type,
49
53
  });
50
54
  }
51
55
  // per-entry
52
56
  const entryKey = descriptor.formatEntryKey(association);
53
57
  const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(type)}/${encodeURIComponent(entryKey)}/upload-url`;
54
- const result = await this.putJsonRaw(uploadUrlPath, payload);
58
+ const result = await this.putJsonRaw(uploadUrlPath, payload, type);
55
59
  if (!result)
56
60
  return null;
57
61
  return {
@@ -72,8 +76,11 @@ export class ApiGatewayArtifactWriter {
72
76
  "the batch route.");
73
77
  }
74
78
  async writeManifest(runId, manifest) {
75
- const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/manifest/upload-url`;
76
- return this.putJson(uploadUrlPath, manifest, { layout: "bulk" });
79
+ const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/upload-url`;
80
+ return this.putJson(uploadUrlPath, manifest, {
81
+ layout: "bulk",
82
+ type: "manifest",
83
+ });
77
84
  }
78
85
  // ---- Deprecated legacy surface (W0052) ----------------------------------
79
86
  /** @deprecated Use `emit()` instead. */
@@ -82,6 +89,7 @@ export class ApiGatewayArtifactWriter {
82
89
  return this.putJson(uploadUrlPath, data, {
83
90
  layout: "bulk",
84
91
  entryCount: entryCountOf(data),
92
+ type,
85
93
  });
86
94
  }
87
95
  /** @deprecated Use `emit()` per entry instead. */
@@ -101,7 +109,7 @@ export class ApiGatewayArtifactWriter {
101
109
  continue;
102
110
  }
103
111
  const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(type)}/${encodeURIComponent(entry.key)}/upload-url`;
104
- const result = await this.putJsonRaw(uploadUrlPath, entry.data);
112
+ const result = await this.putJsonRaw(uploadUrlPath, entry.data, type);
105
113
  if (!result)
106
114
  continue;
107
115
  bucket = result.bucket;
@@ -122,7 +130,7 @@ export class ApiGatewayArtifactWriter {
122
130
  }
123
131
  // ---- Internals ----------------------------------------------------------
124
132
  async putJson(uploadUrlPath, data, meta) {
125
- const result = await this.putJsonRaw(uploadUrlPath, data);
133
+ const result = await this.putJsonRaw(uploadUrlPath, data, meta.type);
126
134
  if (!result)
127
135
  return null;
128
136
  return {
@@ -134,23 +142,38 @@ export class ApiGatewayArtifactWriter {
134
142
  layout: meta.layout,
135
143
  };
136
144
  }
137
- async putJsonRaw(uploadUrlPath, data) {
145
+ async putJsonRaw(uploadUrlPath, data, type) {
138
146
  const json = JSON.stringify(data);
139
147
  const bytes = Buffer.byteLength(json, "utf-8");
140
148
  try {
141
- const signed = await this.fetchSignedUrl(uploadUrlPath);
149
+ const signed = await this.fetchSignedUrl(uploadUrlPath, type);
142
150
  if (!signed)
143
151
  return null;
144
- const putRes = await fetch(signed.url, {
145
- body: json,
146
- headers: signed.requiredHeaders,
147
- method: "PUT",
148
- });
149
- if (!putRes.ok) {
150
- console.warn(` ⚠️ Artifact upload failed (non-blocking): ${signed.path} — GCS PUT ${putRes.status} ${putRes.statusText}`);
151
- return null;
152
+ const putStart = Date.now();
153
+ let putSuccess = false;
154
+ try {
155
+ const putRes = await fetch(signed.url, {
156
+ body: json,
157
+ headers: signed.requiredHeaders,
158
+ method: "PUT",
159
+ });
160
+ if (!putRes.ok) {
161
+ console.warn(` ⚠️ Artifact upload failed (non-blocking): ${signed.path} — GCS PUT ${putRes.status} ${putRes.statusText}`);
162
+ return null;
163
+ }
164
+ putSuccess = true;
165
+ return { bucket: signed.bucket, path: signed.path, bytes };
166
+ }
167
+ finally {
168
+ this.metrics.record({
169
+ phase: "put",
170
+ writer: "ApiGatewayArtifactWriter",
171
+ type,
172
+ ms: Date.now() - putStart,
173
+ bytes,
174
+ success: putSuccess,
175
+ });
152
176
  }
153
- return { bucket: signed.bucket, path: signed.path, bytes };
154
177
  }
155
178
  catch (err) {
156
179
  const message = err instanceof Error ? err.message : String(err);
@@ -158,33 +181,47 @@ export class ApiGatewayArtifactWriter {
158
181
  return null;
159
182
  }
160
183
  }
161
- async fetchSignedUrl(uploadUrlPath) {
162
- const url = `${this.options.apiBaseUrl.replace(/\/$/, "")}${uploadUrlPath}`;
163
- const res = await fetch(url, {
164
- headers: { Authorization: `Bearer ${this.options.apiKey}` },
165
- method: "GET",
166
- });
167
- if (!res.ok) {
168
- console.warn(` ⚠️ Signed-URL request failed: ${res.status} ${res.statusText}`);
169
- return null;
184
+ async fetchSignedUrl(uploadUrlPath, type) {
185
+ const start = Date.now();
186
+ let success = false;
187
+ try {
188
+ const url = `${this.options.apiBaseUrl.replace(/\/$/, "")}${uploadUrlPath}`;
189
+ const res = await fetch(url, {
190
+ headers: { Authorization: `Bearer ${this.options.apiKey}` },
191
+ method: "GET",
192
+ });
193
+ if (!res.ok) {
194
+ console.warn(` ⚠️ Signed-URL request failed: ${res.status} ${res.statusText}`);
195
+ return null;
196
+ }
197
+ const body = (await res.json());
198
+ if (body.object !== "signed_upload_url" ||
199
+ typeof body.url !== "string" ||
200
+ typeof body.path !== "string" ||
201
+ typeof body.bucket !== "string" ||
202
+ !body.requiredHeaders) {
203
+ console.warn(` ⚠️ Signed-URL response was malformed`);
204
+ return null;
205
+ }
206
+ success = true;
207
+ return {
208
+ bucket: body.bucket,
209
+ method: "PUT",
210
+ object: "signed_upload_url",
211
+ path: body.path,
212
+ requiredHeaders: body.requiredHeaders,
213
+ url: body.url,
214
+ };
170
215
  }
171
- const body = (await res.json());
172
- if (body.object !== "signed_upload_url" ||
173
- typeof body.url !== "string" ||
174
- typeof body.path !== "string" ||
175
- typeof body.bucket !== "string" ||
176
- !body.requiredHeaders) {
177
- console.warn(` ⚠️ Signed-URL response was malformed`);
178
- return null;
216
+ finally {
217
+ this.metrics.record({
218
+ phase: "sign",
219
+ writer: "ApiGatewayArtifactWriter",
220
+ type,
221
+ ms: Date.now() - start,
222
+ success,
223
+ });
179
224
  }
180
- return {
181
- bucket: body.bucket,
182
- method: "PUT",
183
- object: "signed_upload_url",
184
- path: body.path,
185
- requiredHeaders: body.requiredHeaders,
186
- url: body.url,
187
- };
188
225
  }
189
226
  }
190
227
  function entryCountOf(data) {
@@ -0,0 +1,108 @@
1
+ /**
2
+ * BatchingApiGatewayArtifactWriter — W0056 prototype B.
3
+ *
4
+ * Drop-in replacement for `ApiGatewayArtifactWriter` that coalesces `emit()`
5
+ * calls into batches, signs many URLs per Vercel round trip via
6
+ * `POST /v1/runs/:runId/artifacts/batch/upload-urls` (W0052), and PUTs to GCS
7
+ * with bounded concurrency.
8
+ *
9
+ * Why: the baseline in `docs/design-docs/artifact-upload-throughput.md` showed
10
+ * that parallel single-URL signs trigger `429 Too Many Requests` on the Vercel
11
+ * signing function. Batch signing eliminates one Vercel round trip per
12
+ * artifact and makes client-side parallelism safe on the API Gateway path.
13
+ *
14
+ * Flushing:
15
+ * - An `emit()` call pushes a `PendingEmit` onto a queue and returns a
16
+ * deferred Promise. The queue self-flushes via `queueMicrotask` when
17
+ * the first entry lands, and again whenever the buffer reaches
18
+ * `batchSize`.
19
+ * - `writeManifest(...)` awaits any in-flight flush and drains the queue
20
+ * before writing the manifest through a single-URL sign (the manifest
21
+ * isn't in the registry's per-entry scheme).
22
+ *
23
+ * NDJSON streaming for `traces` is still not implemented here — the
24
+ * upstream `ApiGatewayArtifactWriter` throws `NotImplementedError` and so
25
+ * does this writer. Traces flow through the GCS-direct writer when ADC
26
+ * credentials are present.
27
+ */
28
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type AssociationValues, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
29
+ import { type UploadMetricsSink } from "./upload-metrics.js";
30
+ export interface BatchingApiGatewayArtifactWriterOptions {
31
+ /** Base URL of the API gateway (e.g., "https://ailf-api.sanity.build"). */
32
+ apiBaseUrl: string;
33
+ /** AILF API key with the `artifact:write` scope. */
34
+ apiKey: string;
35
+ /** GCS bucket name — included in the returned ArtifactRef. */
36
+ bucket: string;
37
+ /**
38
+ * Maximum entries per `/batch/upload-urls` request. The existing Vercel
39
+ * route signs URLs in parallel inside a single Function invocation, so
40
+ * the cap protects the Function's CPU budget. Defaults to 256 (see
41
+ * `DEFAULT_BATCH_SIZE` for the rationale and the W0058 measurement
42
+ * that motivated the bump from 32).
43
+ */
44
+ batchSize?: number;
45
+ /** Bounded concurrency for the PUT fan-out after a batch sign resolves. */
46
+ putConcurrency?: number;
47
+ /** Optional metrics sink; defaults to no-op. */
48
+ metrics?: UploadMetricsSink;
49
+ }
50
+ export declare class BatchingApiGatewayArtifactWriter implements ArtifactWriter {
51
+ private readonly options;
52
+ private readonly pending;
53
+ private flushing;
54
+ private microtaskScheduled;
55
+ constructor(options: BatchingApiGatewayArtifactWriterOptions);
56
+ emit<T extends ArtifactType>(type: T, association: AssociationValues, payload: unknown): Promise<ArtifactRef | null>;
57
+ appendNdjson(): Promise<ArtifactRef | null>;
58
+ writeManifest(runId: RunId, manifest: RunManifest): Promise<ArtifactRef | null>;
59
+ /** @deprecated — routes through `emit()` for backward compat. */
60
+ writeBulk(type: ArtifactType, runId: RunId, data: unknown): Promise<ArtifactRef | null>;
61
+ /**
62
+ * @deprecated The legacy `writePerEntry` surface requires back-deriving an
63
+ * `AssociationValues` from a wire entryKey, which the registry no longer
64
+ * exposes. Producers on this writer must use `emit()`. All current producers
65
+ * have already migrated.
66
+ */
67
+ writePerEntry(type: ArtifactType, _runId: RunId, _entries: readonly ArtifactEntry[]): Promise<ArtifactRef | null>;
68
+ private scheduleFlush;
69
+ /**
70
+ * Drain the pending queue with overlapped sign + PUT.
71
+ *
72
+ * Pre-pipeline behaviour was `sign(N) → put(N) → sign(N+1) → put(N+1)`.
73
+ * After this change the order becomes
74
+ * `sign(N) → (put(N) || sign(N+1)) → put(N+1)`:
75
+ *
76
+ * - We wait for stage N's sign to resolve, then launch stage N+1's sign
77
+ * immediately (before awaiting stage N's PUT phase). This is the moment
78
+ * emits that arrived while stage N's sign was in flight first become
79
+ * visible to `startNextStage`.
80
+ * - Stage N's PUT fan-out then runs concurrently with stage N+1's sign
81
+ * request, removing the per-batch sign-RTT pre-amble that made the
82
+ * W0056 prototype ~10 % slower than the (unsafe) single-URL parallel
83
+ * variant.
84
+ *
85
+ * Invariants preserved from the pre-pipeline implementation:
86
+ * - Emits resolve in batch-commit order within a stage.
87
+ * - All entries in a `Stage` share one `runId` (enforced by `startNextStage`).
88
+ * - A sign failure fails only that stage's group; later stages proceed.
89
+ * - run() keeps looping until `pending` is empty AND no prefetched stage
90
+ * remains — so emits that arrive during a PUT phase are picked up in the
91
+ * next iteration.
92
+ */
93
+ private drain;
94
+ /**
95
+ * Pop the next same-runId batch (up to `batchSize` entries) off the queue
96
+ * and launch its batch-sign request immediately. Returns a `Stage` whose
97
+ * `signPromise` is already in flight, so the caller can start the sign for
98
+ * the *next* batch while this one's PUTs are still pending.
99
+ *
100
+ * Returning `null` means the queue is empty.
101
+ */
102
+ private startNextStage;
103
+ private buildSignBody;
104
+ private batchSign;
105
+ private putToSignedUrl;
106
+ /** Single-URL sign + PUT — used for the manifest. */
107
+ private writeSingle;
108
+ }