@sanity/ailf 2.7.1 → 2.9.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 +35 -0
- package/dist/_vendor/ailf-core/artifact-capture/association.js +28 -0
- package/dist/_vendor/ailf-core/artifact-registry.d.ts +173 -0
- package/dist/_vendor/ailf-core/artifact-registry.js +811 -0
- package/dist/_vendor/ailf-core/index.d.ts +3 -1
- package/dist/_vendor/ailf-core/index.js +3 -1
- package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +3 -3
- package/dist/_vendor/ailf-core/ports/artifact-writer.d.ts +95 -0
- package/dist/_vendor/ailf-core/ports/artifact-writer.js +51 -0
- package/dist/_vendor/ailf-core/ports/context.d.ts +32 -3
- package/dist/_vendor/ailf-core/ports/index.d.ts +3 -3
- package/dist/_vendor/ailf-core/ports/index.js +1 -1
- package/dist/_vendor/ailf-core/schemas/pipeline.d.ts +6 -6
- package/dist/_vendor/ailf-core/services/index.d.ts +1 -0
- package/dist/_vendor/ailf-core/services/index.js +1 -0
- package/dist/_vendor/ailf-core/services/slim-report-summary.d.ts +31 -0
- package/dist/_vendor/ailf-core/services/slim-report-summary.js +217 -0
- package/dist/_vendor/ailf-core/types/branded-ids.d.ts +42 -0
- package/dist/_vendor/ailf-core/types/branded-ids.js +21 -0
- package/dist/_vendor/ailf-core/types/index.d.ts +298 -77
- package/dist/_vendor/ailf-core/types/index.js +1 -1
- package/dist/_vendor/ailf-shared/index.d.ts +2 -0
- package/dist/_vendor/ailf-shared/index.js +2 -0
- package/dist/_vendor/ailf-shared/run-context.d.ts +55 -0
- package/dist/_vendor/ailf-shared/run-context.js +17 -0
- package/dist/_vendor/ailf-shared/run-trigger.d.ts +30 -0
- package/dist/_vendor/ailf-shared/run-trigger.js +13 -0
- package/dist/artifact-capture/accumulating-artifact-writer.d.ts +50 -0
- package/dist/artifact-capture/accumulating-artifact-writer.js +111 -0
- package/dist/artifact-capture/api-gateway-artifact-writer.d.ts +52 -0
- package/dist/artifact-capture/api-gateway-artifact-writer.js +199 -0
- package/dist/artifact-capture/emit-file.d.ts +28 -0
- package/dist/artifact-capture/emit-file.js +56 -0
- package/dist/artifact-capture/fanout-artifact-writer.d.ts +39 -0
- package/dist/artifact-capture/fanout-artifact-writer.js +76 -0
- package/dist/artifact-capture/filesystem-collector.d.ts +22 -4
- package/dist/artifact-capture/filesystem-collector.js +48 -23
- package/dist/artifact-capture/gcs-artifact-writer.d.ts +67 -0
- package/dist/artifact-capture/gcs-artifact-writer.js +343 -0
- package/dist/artifact-capture/local-fs-artifact-writer.d.ts +71 -0
- package/dist/artifact-capture/local-fs-artifact-writer.js +273 -0
- package/dist/commands/explain-handler.js +4 -0
- package/dist/commands/pipeline-action.d.ts +5 -0
- package/dist/commands/pipeline-action.js +56 -5
- package/dist/commands/pipeline.d.ts +4 -0
- package/dist/commands/pipeline.js +6 -2
- package/dist/commands/publish.js +7 -3
- package/dist/composition-root.d.ts +14 -11
- package/dist/composition-root.js +90 -31
- package/dist/orchestration/build-step-sequence.js +6 -1
- package/dist/orchestration/pipeline-orchestrator.d.ts +1 -1
- package/dist/orchestration/pipeline-orchestrator.js +41 -30
- package/dist/orchestration/steps/calculate-scores-step.d.ts +1 -1
- package/dist/orchestration/steps/calculate-scores-step.js +50 -10
- package/dist/orchestration/steps/callback-step.d.ts +1 -1
- package/dist/orchestration/steps/callback-step.js +6 -4
- package/dist/orchestration/steps/compare-step.d.ts +1 -1
- package/dist/orchestration/steps/compare-step.js +4 -2
- package/dist/orchestration/steps/discovery-report-step.d.ts +1 -1
- package/dist/orchestration/steps/discovery-report-step.js +4 -1
- package/dist/orchestration/steps/fetch-docs-step.js +9 -15
- package/dist/orchestration/steps/finalize-run-step.d.ts +29 -0
- package/dist/orchestration/steps/finalize-run-step.js +117 -0
- package/dist/orchestration/steps/gap-analysis-step.js +34 -6
- package/dist/orchestration/steps/generate-configs-step.d.ts +1 -1
- package/dist/orchestration/steps/generate-configs-step.js +11 -11
- package/dist/orchestration/steps/publish-report-step.d.ts +1 -1
- package/dist/orchestration/steps/publish-report-step.js +40 -55
- package/dist/orchestration/steps/readiness-step.d.ts +1 -1
- package/dist/orchestration/steps/readiness-step.js +4 -1
- package/dist/orchestration/steps/report-step.d.ts +1 -1
- package/dist/orchestration/steps/report-step.js +6 -3
- package/dist/orchestration/steps/run-eval-step.js +14 -9
- package/dist/pipeline/calculate-scores.js +13 -2
- package/dist/pipeline/compare.d.ts +2 -2
- package/dist/pipeline/emit-eval-results.d.ts +38 -0
- package/dist/pipeline/emit-eval-results.js +100 -0
- package/dist/pipeline/provenance.d.ts +24 -44
- package/dist/pipeline/provenance.js +17 -165
- package/dist/pipeline/report-title.d.ts +2 -2
- package/dist/pipeline/run-context.d.ts +57 -0
- package/dist/pipeline/run-context.js +156 -0
- package/dist/pipeline/upload-test-outputs.d.ts +26 -0
- package/dist/pipeline/upload-test-outputs.js +34 -0
- package/dist/report-store.js +4 -2
- package/package.json +3 -3
- package/dist/_vendor/ailf-core/ports/artifact-uploader.d.ts +0 -35
- package/dist/_vendor/ailf-core/ports/artifact-uploader.js +0 -18
- package/dist/artifact-capture/api-gateway-artifact-uploader.d.ts +0 -41
- package/dist/artifact-capture/api-gateway-artifact-uploader.js +0 -123
- package/dist/artifact-capture/gcs-report-artifact-uploader.d.ts +0 -31
- package/dist/artifact-capture/gcs-report-artifact-uploader.js +0 -66
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* upload-test-outputs.ts — shared helper for the testOutputs artifact upload.
|
|
3
|
+
*
|
|
4
|
+
* CalculateScoresStep calls this once its score-summary.json is complete.
|
|
5
|
+
* Each {taskId, modelId} pair becomes one GCS object under
|
|
6
|
+
* `runs/{runId}/test-outputs/{taskId}--{modelId}.json` carrying the full
|
|
7
|
+
* response output and truncation flag. The returned ArtifactRef's
|
|
8
|
+
* `entries[]` catalog lists every uploaded entry so Studio can render
|
|
9
|
+
* drill-down state without a second listing call.
|
|
10
|
+
*
|
|
11
|
+
* PublishReportStep later strips responseOutput from the inline
|
|
12
|
+
* testResults[] when this upload succeeds, so the Content Lake document
|
|
13
|
+
* stays slim — the full output lives in GCS and is fetched per-entry
|
|
14
|
+
* on click.
|
|
15
|
+
*
|
|
16
|
+
* @see docs/decisions/D0032-run-anchored-artifact-store.md
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Upload testOutputs as per-entry GCS objects under
|
|
20
|
+
* `runs/{runId}/test-outputs/`, one per `{taskId}::{modelId}` pair.
|
|
21
|
+
*
|
|
22
|
+
* Returns the `ArtifactRef` on success, or `null` when upload is skipped or
|
|
23
|
+
* fails (P5: non-blocking).
|
|
24
|
+
*/
|
|
25
|
+
export async function uploadTestOutputs(writer, runId, testResults) {
|
|
26
|
+
const entries = testResults.map((tr) => ({
|
|
27
|
+
key: `${tr.taskId}::${tr.modelId}`,
|
|
28
|
+
data: {
|
|
29
|
+
responseOutput: tr.responseOutput ?? "",
|
|
30
|
+
responseOutputTruncated: tr.responseOutputTruncated ?? false,
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
return writer.writePerEntry("testOutputs", runId, entries);
|
|
34
|
+
}
|
package/dist/report-store.js
CHANGED
|
@@ -211,8 +211,10 @@ export class ReportStore {
|
|
|
211
211
|
summary: {
|
|
212
212
|
...report.summary,
|
|
213
213
|
// Artifact references live inside summary in Sanity so they're
|
|
214
|
-
// projected automatically by the reportDetailQuery (
|
|
215
|
-
...(report.
|
|
214
|
+
// projected automatically by the reportDetailQuery (D0032)
|
|
215
|
+
...(report.artifactManifest
|
|
216
|
+
? { artifactManifest: report.artifactManifest }
|
|
217
|
+
: {}),
|
|
216
218
|
},
|
|
217
219
|
tag: report.tag ?? null,
|
|
218
220
|
title: report.title ?? null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ailf",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@types/node": "^22.13.1",
|
|
53
53
|
"tsx": "^4.19.2",
|
|
54
54
|
"typescript": "^5.7.3",
|
|
55
|
-
"@sanity/ailf-
|
|
56
|
-
"@sanity/ailf-
|
|
55
|
+
"@sanity/ailf-core": "0.1.0",
|
|
56
|
+
"@sanity/ailf-shared": "0.1.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsc && tsx scripts/bundle-workspace-deps.ts",
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Port: ArtifactUploader — uploads report artifacts to external object storage.
|
|
3
|
-
*
|
|
4
|
-
* Separate from ArtifactCollector (which captures forensic archives).
|
|
5
|
-
* This port puts structured files at known paths so Studio can fetch
|
|
6
|
-
* them on demand via signed URLs.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
9
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
10
|
-
*/
|
|
11
|
-
import type { ArtifactRef } from "../types/index.js";
|
|
12
|
-
/**
|
|
13
|
-
* Uploads report artifacts to external storage.
|
|
14
|
-
*
|
|
15
|
-
* Implementations:
|
|
16
|
-
* - GcsReportArtifactUploader (packages/eval) — uploads to GCS
|
|
17
|
-
* - NoOpArtifactUploader (below) — returns null (no-op when GCS is not configured)
|
|
18
|
-
*/
|
|
19
|
-
export interface ArtifactUploader {
|
|
20
|
-
/**
|
|
21
|
-
* Upload a JSON artifact for a report.
|
|
22
|
-
*
|
|
23
|
-
* @param reportId - Report identifier (used as the GCS path prefix)
|
|
24
|
-
* @param fileName - File name within the report prefix (e.g., "test-outputs.json")
|
|
25
|
-
* @param data - Serializable data (will be JSON.stringify'd)
|
|
26
|
-
* @returns ArtifactRef on success, null if upload is skipped or fails
|
|
27
|
-
*/
|
|
28
|
-
upload(reportId: string, fileName: string, data: unknown): Promise<ArtifactRef | null>;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* No-op uploader — always returns null. Used when GCS is not configured.
|
|
32
|
-
*/
|
|
33
|
-
export declare class NoOpArtifactUploader implements ArtifactUploader {
|
|
34
|
-
upload(): Promise<null>;
|
|
35
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Port: ArtifactUploader — uploads report artifacts to external object storage.
|
|
3
|
-
*
|
|
4
|
-
* Separate from ArtifactCollector (which captures forensic archives).
|
|
5
|
-
* This port puts structured files at known paths so Studio can fetch
|
|
6
|
-
* them on demand via signed URLs.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
9
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* No-op uploader — always returns null. Used when GCS is not configured.
|
|
13
|
-
*/
|
|
14
|
-
export class NoOpArtifactUploader {
|
|
15
|
-
async upload() {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ApiGatewayArtifactUploader — uploads report artifacts via the API Gateway.
|
|
3
|
-
*
|
|
4
|
-
* Counterpart to GcsReportArtifactUploader. Used when the CLI runs locally
|
|
5
|
-
* without GCS credentials. Two-step flow:
|
|
6
|
-
*
|
|
7
|
-
* 1. GET {apiBaseUrl}/v1/artifacts/{reportId}/upload-url?type={artifactType}
|
|
8
|
-
* with Authorization: Bearer {apiKey} — returns a signed PUT URL.
|
|
9
|
-
* 2. PUT the JSON to that URL with Content-Type: application/json and
|
|
10
|
-
* x-goog-if-generation-match: 0 (overwrite-protection contract from
|
|
11
|
-
* the gateway's signed URL).
|
|
12
|
-
*
|
|
13
|
-
* The gateway stays out of the data path — Vercel only signs the URL,
|
|
14
|
-
* the artifact bytes go directly to GCS.
|
|
15
|
-
*
|
|
16
|
-
* Design principles:
|
|
17
|
-
* - P5: Non-blocking — any failure returns null and warns, never throws.
|
|
18
|
-
* - Stateless — no client to keep around between calls.
|
|
19
|
-
*
|
|
20
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
21
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
22
|
-
*/
|
|
23
|
-
import type { ArtifactRef, ArtifactUploader } from "../_vendor/ailf-core/index.d.ts";
|
|
24
|
-
export interface ApiGatewayUploaderOptions {
|
|
25
|
-
/** Base URL of the API gateway (e.g., "https://api.ailf.sanity.io"). */
|
|
26
|
-
apiBaseUrl: string;
|
|
27
|
-
/** AILF API key with the `artifact:write` scope. */
|
|
28
|
-
apiKey: string;
|
|
29
|
-
/** GCS bucket name — included in the returned ArtifactRef. */
|
|
30
|
-
bucket: string;
|
|
31
|
-
}
|
|
32
|
-
export declare class ApiGatewayArtifactUploader implements ArtifactUploader {
|
|
33
|
-
private readonly options;
|
|
34
|
-
constructor(options: ApiGatewayUploaderOptions);
|
|
35
|
-
upload(reportId: string, fileName: string, data: unknown): Promise<ArtifactRef | null>;
|
|
36
|
-
/**
|
|
37
|
-
* Fetch a signed upload URL from the gateway. Returns null on any non-2xx
|
|
38
|
-
* response or malformed body so the caller can stay non-blocking.
|
|
39
|
-
*/
|
|
40
|
-
private fetchSignedUrl;
|
|
41
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ApiGatewayArtifactUploader — uploads report artifacts via the API Gateway.
|
|
3
|
-
*
|
|
4
|
-
* Counterpart to GcsReportArtifactUploader. Used when the CLI runs locally
|
|
5
|
-
* without GCS credentials. Two-step flow:
|
|
6
|
-
*
|
|
7
|
-
* 1. GET {apiBaseUrl}/v1/artifacts/{reportId}/upload-url?type={artifactType}
|
|
8
|
-
* with Authorization: Bearer {apiKey} — returns a signed PUT URL.
|
|
9
|
-
* 2. PUT the JSON to that URL with Content-Type: application/json and
|
|
10
|
-
* x-goog-if-generation-match: 0 (overwrite-protection contract from
|
|
11
|
-
* the gateway's signed URL).
|
|
12
|
-
*
|
|
13
|
-
* The gateway stays out of the data path — Vercel only signs the URL,
|
|
14
|
-
* the artifact bytes go directly to GCS.
|
|
15
|
-
*
|
|
16
|
-
* Design principles:
|
|
17
|
-
* - P5: Non-blocking — any failure returns null and warns, never throws.
|
|
18
|
-
* - Stateless — no client to keep around between calls.
|
|
19
|
-
*
|
|
20
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
21
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
22
|
-
*/
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// File-name → artifact-type mapping (mirrors packages/api ARTIFACT_FILES)
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
/**
|
|
27
|
-
* Reverse map of the API gateway's ARTIFACT_FILES. The uploader port speaks
|
|
28
|
-
* file names; the gateway endpoint speaks artifact types. Keep these in sync
|
|
29
|
-
* with packages/api/src/routes/artifacts.ts.
|
|
30
|
-
*/
|
|
31
|
-
const FILE_TO_TYPE = {
|
|
32
|
-
"eval-results.json": "evalResults",
|
|
33
|
-
"grader-prompts.json": "graderPrompts",
|
|
34
|
-
"rendered-prompts.json": "renderedPrompts",
|
|
35
|
-
"task-definitions.json": "taskDefinitions",
|
|
36
|
-
"test-outputs.json": "testOutputs",
|
|
37
|
-
};
|
|
38
|
-
export class ApiGatewayArtifactUploader {
|
|
39
|
-
options;
|
|
40
|
-
constructor(options) {
|
|
41
|
-
this.options = options;
|
|
42
|
-
}
|
|
43
|
-
async upload(reportId, fileName, data) {
|
|
44
|
-
const artifactType = FILE_TO_TYPE[fileName];
|
|
45
|
-
if (!artifactType) {
|
|
46
|
-
console.warn(` ⚠️ Artifact upload skipped (unknown fileName): ${fileName}`);
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
const objectPath = `reports/${reportId}/${fileName}`;
|
|
50
|
-
const json = JSON.stringify(data);
|
|
51
|
-
const bytes = Buffer.byteLength(json, "utf-8");
|
|
52
|
-
try {
|
|
53
|
-
const signed = await this.fetchSignedUrl(reportId, artifactType);
|
|
54
|
-
if (!signed)
|
|
55
|
-
return null;
|
|
56
|
-
const putRes = await fetch(signed.url, {
|
|
57
|
-
body: json,
|
|
58
|
-
headers: signed.requiredHeaders,
|
|
59
|
-
method: "PUT",
|
|
60
|
-
});
|
|
61
|
-
if (!putRes.ok) {
|
|
62
|
-
console.warn(` ⚠️ Artifact upload failed (non-blocking): ${objectPath} — GCS PUT ${putRes.status} ${putRes.statusText}`);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
bucket: signed.bucket,
|
|
67
|
-
bytes,
|
|
68
|
-
entryCount: extractEntryCount(data),
|
|
69
|
-
path: signed.path,
|
|
70
|
-
store: "gcs",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
-
console.warn(` ⚠️ Artifact upload failed (non-blocking): ${objectPath} — ${message}`);
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Fetch a signed upload URL from the gateway. Returns null on any non-2xx
|
|
81
|
-
* response or malformed body so the caller can stay non-blocking.
|
|
82
|
-
*/
|
|
83
|
-
async fetchSignedUrl(reportId, artifactType) {
|
|
84
|
-
const url = `${this.options.apiBaseUrl.replace(/\/$/, "")}/v1/artifacts/${encodeURIComponent(reportId)}/upload-url?type=${encodeURIComponent(artifactType)}`;
|
|
85
|
-
const res = await fetch(url, {
|
|
86
|
-
headers: {
|
|
87
|
-
Authorization: `Bearer ${this.options.apiKey}`,
|
|
88
|
-
},
|
|
89
|
-
method: "GET",
|
|
90
|
-
});
|
|
91
|
-
if (!res.ok) {
|
|
92
|
-
console.warn(` ⚠️ Signed-URL request failed: ${res.status} ${res.statusText}`);
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
const body = (await res.json());
|
|
96
|
-
if (body.object !== "signed_upload_url" ||
|
|
97
|
-
typeof body.url !== "string" ||
|
|
98
|
-
typeof body.path !== "string" ||
|
|
99
|
-
typeof body.bucket !== "string" ||
|
|
100
|
-
!body.requiredHeaders) {
|
|
101
|
-
console.warn(` ⚠️ Signed-URL response was malformed`);
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
bucket: body.bucket,
|
|
106
|
-
method: "PUT",
|
|
107
|
-
object: "signed_upload_url",
|
|
108
|
-
path: body.path,
|
|
109
|
-
requiredHeaders: body.requiredHeaders,
|
|
110
|
-
url: body.url,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function extractEntryCount(data) {
|
|
115
|
-
if (typeof data === "object" &&
|
|
116
|
-
data !== null &&
|
|
117
|
-
"entries" in data &&
|
|
118
|
-
typeof data.entries === "object") {
|
|
119
|
-
return Object.keys(data.entries)
|
|
120
|
-
.length;
|
|
121
|
-
}
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GcsReportArtifactUploader — uploads report artifacts to known GCS paths.
|
|
3
|
-
*
|
|
4
|
-
* Separate from GcsArtifactCollector (which handles forensic capture archives).
|
|
5
|
-
* This uploader puts structured JSON files at predictable paths so the
|
|
6
|
-
* API Gateway can sign URLs and Studio can fetch them on demand.
|
|
7
|
-
*
|
|
8
|
-
* GCS path convention: reports/{reportId}/{fileName}
|
|
9
|
-
* Example: reports/01926abc.../test-outputs.json
|
|
10
|
-
*
|
|
11
|
-
* Design principles:
|
|
12
|
-
* - P5: Non-blocking — GCS upload failure returns null, never throws
|
|
13
|
-
* - Lazy client — Storage created on first upload, not at construction
|
|
14
|
-
* - Same credentials path as GcsArtifactCollector (ADC or key file)
|
|
15
|
-
*
|
|
16
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
17
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
18
|
-
*/
|
|
19
|
-
import type { ArtifactRef, ArtifactUploader } from "../_vendor/ailf-core/index.d.ts";
|
|
20
|
-
export interface GcsUploaderOptions {
|
|
21
|
-
/** GCS bucket name (e.g., "ailf-artifacts") */
|
|
22
|
-
bucket: string;
|
|
23
|
-
}
|
|
24
|
-
export declare class GcsReportArtifactUploader implements ArtifactUploader {
|
|
25
|
-
private client;
|
|
26
|
-
private readonly options;
|
|
27
|
-
constructor(options: GcsUploaderOptions);
|
|
28
|
-
upload(reportId: string, fileName: string, data: unknown): Promise<ArtifactRef | null>;
|
|
29
|
-
/** Lazily create the GCS Storage client (ADC). */
|
|
30
|
-
private getClient;
|
|
31
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GcsReportArtifactUploader — uploads report artifacts to known GCS paths.
|
|
3
|
-
*
|
|
4
|
-
* Separate from GcsArtifactCollector (which handles forensic capture archives).
|
|
5
|
-
* This uploader puts structured JSON files at predictable paths so the
|
|
6
|
-
* API Gateway can sign URLs and Studio can fetch them on demand.
|
|
7
|
-
*
|
|
8
|
-
* GCS path convention: reports/{reportId}/{fileName}
|
|
9
|
-
* Example: reports/01926abc.../test-outputs.json
|
|
10
|
-
*
|
|
11
|
-
* Design principles:
|
|
12
|
-
* - P5: Non-blocking — GCS upload failure returns null, never throws
|
|
13
|
-
* - Lazy client — Storage created on first upload, not at construction
|
|
14
|
-
* - Same credentials path as GcsArtifactCollector (ADC or key file)
|
|
15
|
-
*
|
|
16
|
-
* @see docs/design-docs/external-artifact-store.md
|
|
17
|
-
* @see docs/decisions/D0030-external-artifact-store.md
|
|
18
|
-
*/
|
|
19
|
-
import { Storage } from "@google-cloud/storage";
|
|
20
|
-
export class GcsReportArtifactUploader {
|
|
21
|
-
client = null;
|
|
22
|
-
options;
|
|
23
|
-
constructor(options) {
|
|
24
|
-
this.options = options;
|
|
25
|
-
}
|
|
26
|
-
async upload(reportId, fileName, data) {
|
|
27
|
-
const objectPath = `reports/${reportId}/${fileName}`;
|
|
28
|
-
const json = JSON.stringify(data);
|
|
29
|
-
const bytes = Buffer.byteLength(json, "utf-8");
|
|
30
|
-
try {
|
|
31
|
-
const storage = this.getClient();
|
|
32
|
-
const file = storage.bucket(this.options.bucket).file(objectPath);
|
|
33
|
-
await file.save(json, {
|
|
34
|
-
contentType: "application/json",
|
|
35
|
-
metadata: {
|
|
36
|
-
reportId,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
return {
|
|
40
|
-
store: "gcs",
|
|
41
|
-
bucket: this.options.bucket,
|
|
42
|
-
path: objectPath,
|
|
43
|
-
bytes,
|
|
44
|
-
entryCount: typeof data === "object" &&
|
|
45
|
-
data !== null &&
|
|
46
|
-
"entries" in data &&
|
|
47
|
-
typeof data.entries === "object"
|
|
48
|
-
? Object.keys(data.entries)
|
|
49
|
-
.length
|
|
50
|
-
: undefined,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
-
console.warn(` ⚠️ Artifact upload failed (non-blocking): ${objectPath} — ${message}`);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/** Lazily create the GCS Storage client (ADC). */
|
|
60
|
-
getClient() {
|
|
61
|
-
if (this.client)
|
|
62
|
-
return this.client;
|
|
63
|
-
this.client = new Storage();
|
|
64
|
-
return this.client;
|
|
65
|
-
}
|
|
66
|
-
}
|