@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.
- package/dist/_vendor/ailf-core/artifact-capture/association.d.ts +37 -0
- package/dist/_vendor/ailf-core/artifact-capture/association.js +19 -0
- package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
- package/dist/_vendor/ailf-core/artifact-registry.js +1 -18
- package/dist/_vendor/ailf-core/batch-signing.d.ts +64 -0
- package/dist/_vendor/ailf-core/batch-signing.js +23 -0
- package/dist/_vendor/ailf-core/index.d.ts +2 -2
- package/dist/_vendor/ailf-core/index.js +2 -2
- package/dist/_vendor/ailf-core/ports/context.d.ts +12 -20
- package/dist/_vendor/ailf-core/ports/index.d.ts +2 -2
- package/dist/_vendor/ailf-core/ports/index.js +1 -0
- package/dist/_vendor/ailf-core/ports/progress-reporter.d.ts +74 -0
- package/dist/_vendor/ailf-core/ports/progress-reporter.js +26 -0
- package/dist/_vendor/ailf-core/services/slim-report-summary.js +1 -16
- package/dist/adapters/config-sources/file-config-adapter.js +0 -4
- package/dist/adapters/progress/console-progress-reporter.d.ts +35 -0
- package/dist/adapters/progress/console-progress-reporter.js +110 -0
- package/dist/artifact-capture/api-gateway-artifact-writer.d.ts +8 -1
- package/dist/artifact-capture/api-gateway-artifact-writer.js +79 -42
- package/dist/artifact-capture/batching-api-gateway-artifact-writer.d.ts +108 -0
- package/dist/artifact-capture/batching-api-gateway-artifact-writer.js +492 -0
- package/dist/artifact-capture/fanout-artifact-writer.d.ts +14 -2
- package/dist/artifact-capture/fanout-artifact-writer.js +25 -4
- package/dist/artifact-capture/gcs-artifact-writer.d.ts +27 -1
- package/dist/artifact-capture/gcs-artifact-writer.js +168 -38
- package/dist/artifact-capture/instrumented-artifact-writer.d.ts +32 -0
- package/dist/artifact-capture/instrumented-artifact-writer.js +151 -0
- package/dist/artifact-capture/local-fs-artifact-writer.d.ts +8 -1
- package/dist/artifact-capture/local-fs-artifact-writer.js +23 -4
- package/dist/artifact-capture/parallel-emit.d.ts +43 -0
- package/dist/artifact-capture/parallel-emit.js +84 -0
- package/dist/artifact-capture/redact-artifact.d.ts +3 -5
- package/dist/artifact-capture/redact-artifact.js +3 -5
- package/dist/artifact-capture/upload-metrics.d.ts +62 -0
- package/dist/artifact-capture/upload-metrics.js +125 -0
- package/dist/cli.js +56 -2
- package/dist/commands/explain-handler.js +1 -5
- package/dist/commands/pipeline-action.d.ts +0 -4
- package/dist/commands/pipeline-action.js +11 -45
- package/dist/commands/pipeline.d.ts +1 -5
- package/dist/commands/pipeline.js +1 -5
- package/dist/commands/runs.d.ts +18 -0
- package/dist/commands/runs.js +71 -0
- package/dist/composition-root.d.ts +2 -2
- package/dist/composition-root.js +98 -38
- package/dist/orchestration/build-app-context.js +4 -7
- package/dist/orchestration/pipeline-orchestrator.js +100 -24
- package/dist/orchestration/steps/calculate-scores-step.js +1 -1
- package/dist/orchestration/steps/finalize-run-step.js +33 -2
- package/dist/pipeline/emit-eval-results.js +29 -11
- package/dist/pipeline/map-request-to-config.js +0 -4
- package/dist/pipeline/upload-test-outputs.d.ts +12 -5
- package/dist/pipeline/upload-test-outputs.js +27 -10
- package/package.json +3 -3
- package/dist/_vendor/ailf-core/artifact-capture/noop-collector.d.ts +0 -14
- package/dist/_vendor/ailf-core/artifact-capture/noop-collector.js +0 -25
- package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +0 -94
- package/dist/_vendor/ailf-core/ports/artifact-collector.js +0 -13
- package/dist/_vendor/ailf-core/ports/capture-comparator.d.ts +0 -138
- package/dist/_vendor/ailf-core/ports/capture-comparator.js +0 -10
- package/dist/artifact-capture/comparator.d.ts +0 -22
- package/dist/artifact-capture/comparator.js +0 -493
- package/dist/artifact-capture/filesystem-collector.d.ts +0 -60
- package/dist/artifact-capture/filesystem-collector.js +0 -262
- package/dist/artifact-capture/gcs-collector.d.ts +0 -55
- package/dist/artifact-capture/gcs-collector.js +0 -117
- package/dist/commands/capture-compare.d.ts +0 -15
- package/dist/commands/capture-compare.js +0 -253
- package/dist/commands/capture-list.d.ts +0 -12
- package/dist/commands/capture-list.js +0 -150
- package/dist/commands/capture.d.ts +0 -9
- 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/
|
|
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/
|
|
76
|
-
return this.putJson(uploadUrlPath, manifest, {
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
}
|