@sanity/ailf 2.7.0 → 2.8.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 (51) hide show
  1. package/dist/_vendor/ailf-core/artifact-registry.d.ts +72 -0
  2. package/dist/_vendor/ailf-core/artifact-registry.js +150 -0
  3. package/dist/_vendor/ailf-core/examples/index.d.ts +1 -1
  4. package/dist/_vendor/ailf-core/examples/index.js +1 -1
  5. package/dist/_vendor/ailf-core/index.d.ts +2 -1
  6. package/dist/_vendor/ailf-core/index.js +2 -1
  7. package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +3 -3
  8. package/dist/_vendor/ailf-core/ports/artifact-writer.d.ts +56 -0
  9. package/dist/_vendor/ailf-core/ports/artifact-writer.js +28 -0
  10. package/dist/_vendor/ailf-core/ports/context.d.ts +13 -3
  11. package/dist/_vendor/ailf-core/ports/index.d.ts +3 -3
  12. package/dist/_vendor/ailf-core/ports/index.js +1 -1
  13. package/dist/_vendor/ailf-core/types/branded-ids.d.ts +9 -0
  14. package/dist/_vendor/ailf-core/types/branded-ids.js +21 -0
  15. package/dist/_vendor/ailf-core/types/index.d.ts +117 -70
  16. package/dist/_vendor/ailf-core/types/index.js +1 -1
  17. package/dist/_vendor/ailf-shared/index.d.ts +2 -0
  18. package/dist/_vendor/ailf-shared/index.js +2 -0
  19. package/dist/_vendor/ailf-shared/run-context.d.ts +55 -0
  20. package/dist/_vendor/ailf-shared/run-context.js +17 -0
  21. package/dist/_vendor/ailf-shared/run-trigger.d.ts +30 -0
  22. package/dist/_vendor/ailf-shared/run-trigger.js +13 -0
  23. package/dist/artifact-capture/api-gateway-artifact-writer.d.ts +39 -0
  24. package/dist/artifact-capture/api-gateway-artifact-writer.js +148 -0
  25. package/dist/artifact-capture/gcs-artifact-writer.d.ts +30 -0
  26. package/dist/artifact-capture/gcs-artifact-writer.js +119 -0
  27. package/dist/commands/init.js +2 -6
  28. package/dist/commands/publish.js +3 -2
  29. package/dist/composition-root.d.ts +3 -3
  30. package/dist/composition-root.js +20 -15
  31. package/dist/orchestration/build-step-sequence.js +6 -1
  32. package/dist/orchestration/steps/calculate-scores-step.js +42 -2
  33. package/dist/orchestration/steps/finalize-run-step.d.ts +29 -0
  34. package/dist/orchestration/steps/finalize-run-step.js +103 -0
  35. package/dist/orchestration/steps/publish-report-step.js +25 -27
  36. package/dist/pipeline/calculate-scores.js +13 -2
  37. package/dist/pipeline/provenance.d.ts +24 -44
  38. package/dist/pipeline/provenance.js +17 -165
  39. package/dist/pipeline/report-title.d.ts +2 -2
  40. package/dist/pipeline/run-context.d.ts +57 -0
  41. package/dist/pipeline/run-context.js +156 -0
  42. package/dist/pipeline/upload-test-outputs.d.ts +26 -0
  43. package/dist/pipeline/upload-test-outputs.js +34 -0
  44. package/dist/report-store.js +4 -2
  45. package/package.json +1 -1
  46. package/dist/_vendor/ailf-core/ports/artifact-uploader.d.ts +0 -35
  47. package/dist/_vendor/ailf-core/ports/artifact-uploader.js +0 -18
  48. package/dist/artifact-capture/api-gateway-artifact-uploader.d.ts +0 -41
  49. package/dist/artifact-capture/api-gateway-artifact-uploader.js +0 -123
  50. package/dist/artifact-capture/gcs-report-artifact-uploader.d.ts +0 -31
  51. package/dist/artifact-capture/gcs-report-artifact-uploader.js +0 -66
@@ -9,9 +9,10 @@
9
9
  * Ports & Adapters migration (Phase 0c). The original file is now a
10
10
  * re-export barrel that preserves backward compatibility.
11
11
  */
12
- import type { DocumentRef as _DocumentRef, EvalMode } from "../../ailf-shared/index.d.ts";
12
+ import type { DocumentRef as _DocumentRef, EvalMode, RunContext } from "../../ailf-shared/index.d.ts";
13
+ import type { RunId } from "./branded-ids.js";
13
14
  export type { ActualScoreEntry, ComponentResult, TestResult, UrlMetadata, } from "./scoring-input.js";
14
- export type { DocumentRef } from "../../ailf-shared/index.d.ts";
15
+ export type { DocumentRef, RunContext, RunTrigger } from "../../ailf-shared/index.d.ts";
15
16
  export type { StoredBaseline, StoredReport, StoredRun, StoredTaskResult, StoredTrace, SchemaVersioned, } from "./storage-schema.js";
16
17
  export { CURRENT_SCHEMA_VERSION, isSchemaVersioned, migrateDocument, } from "./storage-schema.js";
17
18
  export type { AssertionRegistration, FixtureResolverRegistration, ModeBase, ModeRegistration, PluginManifest, PluginRegistry, PresetDefinition, ReportSinkRegistration, RubricTemplateRegistration, } from "./plugin-registry.js";
@@ -22,7 +23,7 @@ export type { DependencyEdge, ResolvedFixture, TaskGraph, TaskNode, } from "./ta
22
23
  export type { VariableDeclaration, VariableEnvelope, VariableProvenance, VariableSource, } from "./variable-envelope.js";
23
24
  export type { EvalTrace, ToolCallCategory, ToolCallRecord, TraceEvent, TraceSpan, TraceTokenUsage, } from "./trace.js";
24
25
  export type { ArtifactId, Brand, Err, FixtureId, IdValidationError, NewReportId, Ok, ProviderId, PromptId, Result, ResultId, RubricId, RunFingerprint, RunId, SuiteId, TaskId, TaskSlug, TraceId, } from "./branded-ids.js";
25
- export { err, fixtureId, ok, providerId, resultId, runId, suiteId, taskId, traceId, } from "./branded-ids.js";
26
+ export { err, fixtureId, generateRunId, ok, providerId, resultId, runId, suiteId, taskId, traceId, } from "./branded-ids.js";
26
27
  export type { AgentHarnessTaskDefinition, CustomTaskDefinition, GeneralizedAssertionDefinition, GeneralizedDocRef, GeneralizedTaskDefinition, GeneralizedTemplatedAssertion, GeneralizedValueAssertion, IdDocRef, KnowledgeProbeTaskDefinition, LiteracyTaskDefinition, MCPServerTaskDefinition, PathDocRef, PerspectiveDocRef, RubricRef, SlugDocRef, TaskCommonFields, TaskDifficulty, TaskOptions, TaskProviderConfig, TaskStatus, } from "./generalized-task.js";
27
28
  type DocumentRef = _DocumentRef;
28
29
  /** Aggregated retrieval metrics for a feature area */
@@ -309,8 +310,13 @@ export interface StoredTestResult {
309
310
  * API error, or refusal). Same semantics as GraderJudgment.outputFailure.
310
311
  */
311
312
  outputFailure?: boolean;
312
- /** The model's generated code/response (truncated to 8000 chars) */
313
- responseOutput: string;
313
+ /**
314
+ * The model's generated code/response (truncated to 8000 chars).
315
+ * Populated by the scoring step and used by uploadTestOutputs. Stripped
316
+ * from the inline shape after upload (D0030) — the full value lives in
317
+ * the GCS artifact, keyed by `{taskId}::{modelId}`.
318
+ */
319
+ responseOutput?: string;
314
320
  /** True when responseOutput was truncated from a longer response */
315
321
  responseOutputTruncated?: boolean;
316
322
  /** Task description (e.g. "Functions - Webhook handler (gold)") */
@@ -531,6 +537,19 @@ export interface PromptfooUrlEntry {
531
537
  * only matter during a single pipeline execution.
532
538
  */
533
539
  export interface PipelineState {
540
+ /**
541
+ * Artifact refs produced by upstream steps during the run.
542
+ * Populated incrementally (CalculateScoresStep writes testOutputs; future
543
+ * steps will write renderedPrompts, traces, etc.). Read by FinalizeRunStep
544
+ * to build the `RunManifest.artifacts` catalog.
545
+ */
546
+ artifactRefs?: Partial<ArtifactManifest>;
547
+ /**
548
+ * The run manifest, finalized and written to `runs/{runId}/manifest.json`.
549
+ * Populated by FinalizeRunStep. Consumed by PublishReportStep to snapshot
550
+ * the `artifacts` slice into `Report.artifactManifest` (D0032).
551
+ */
552
+ runManifest?: RunManifest;
534
553
  /** Report ID generated by PublishReportStep, consumed by CallbackStep + orchestrator job update */
535
554
  reportId?: string;
536
555
  /** Eval fingerprint computed by RunEvalStep, consumed by PublishReportStep */
@@ -1157,23 +1176,52 @@ export interface PublishResult {
1157
1176
  result: SinkResult;
1158
1177
  }[];
1159
1178
  }
1160
- /** Reference to an artifact in external object storage (GCS). See D0030. */
1179
+ /**
1180
+ * Reference to an artifact in external object storage (GCS). See D0032.
1181
+ *
1182
+ * `layout` determines the on-disk shape:
1183
+ * - `"bulk"` — a single object at `path`. `entries` is absent.
1184
+ * - `"per-entry"` — `path` is a directory prefix. Each entry is a
1185
+ * separate object at `{path}/{sanitizedKey}.json`. `entries` inlines
1186
+ * the catalog so consumers can render drill-down states without a
1187
+ * second list call.
1188
+ */
1161
1189
  export interface ArtifactRef {
1162
1190
  store: "gcs";
1163
1191
  bucket: string;
1164
1192
  path: string;
1165
1193
  bytes?: number;
1166
1194
  entryCount?: number;
1195
+ layout: "bulk" | "per-entry";
1196
+ entries?: {
1197
+ key: string;
1198
+ bytes: number;
1199
+ }[];
1200
+ }
1201
+ /**
1202
+ * Catalog of artifact refs produced by a single pipeline run.
1203
+ *
1204
+ * Lives on `RunManifest.artifacts` (source of truth in GCS) and is
1205
+ * snapshotted onto `Report.artifactManifest` at publish time.
1206
+ */
1207
+ export interface ArtifactManifest {
1208
+ testOutputs?: ArtifactRef;
1209
+ renderedPrompts?: ArtifactRef;
1210
+ rawResults?: ArtifactRef;
1211
+ graderPrompts?: ArtifactRef;
1212
+ taskDefinitions?: ArtifactRef;
1213
+ evalResults?: ArtifactRef;
1214
+ traces?: ArtifactRef;
1167
1215
  }
1168
1216
  /** A published evaluation report — the atomic unit of the report store */
1169
1217
  export interface Report {
1170
- /** External artifact references — set by publish step when uploader is available (D0030) */
1171
- artifacts?: {
1172
- testOutputs?: ArtifactRef;
1173
- renderedPrompts?: ArtifactRef;
1174
- rawResults?: ArtifactRef;
1175
- traces?: ArtifactRef;
1176
- };
1218
+ /**
1219
+ * Snapshot of the run manifest's `artifacts` slice at publish time (D0032).
1220
+ * The source of truth lives in `gs://…/runs/{runId}/manifest.json`; this
1221
+ * field denormalizes enough information for Studio to render drill-down
1222
+ * states without an extra manifest fetch.
1223
+ */
1224
+ artifactManifest?: ArtifactManifest;
1177
1225
  /** Optional auto-comparison against the most recent comparable report */
1178
1226
  comparison?: ComparisonReport;
1179
1227
  /** When the evaluation completed */
@@ -1234,76 +1282,75 @@ export interface ReportLineage {
1234
1282
  */
1235
1283
  rerunOf?: ReportId;
1236
1284
  }
1237
- /** Full provenance metadata for an evaluation report */
1238
- export interface ReportProvenance {
1239
- /** Which feature areas were evaluated */
1240
- areas: string[];
1285
+ /**
1286
+ * Full provenance metadata for an evaluation report.
1287
+ *
1288
+ * Inherits the 9 run-description fields (mode, areas, taskIds, models,
1289
+ * graderModel, source, evalFingerprint, trigger, git) from `RunContext`.
1290
+ * Adding a field to `RunContext` makes it available here automatically —
1291
+ * the structural extension is the drift-prevention mechanism described in
1292
+ * D0032 § "Drift Prevention".
1293
+ */
1294
+ export interface ReportProvenance extends RunContext {
1241
1295
  /** Release auto-scope metadata (when perspective evaluation was scoped to affected tasks) */
1242
1296
  autoScope?: ReportAutoScope;
1243
1297
  /** Content hash of the documentation context at eval time */
1244
1298
  contextHash?: string;
1245
- /**
1246
- * Evaluation fingerprint — SHA-256 of all inputs that affect eval output.
1247
- * Used for cross-environment cache lookup (CI → Content Lake).
1248
- * @see docs/design-docs/content-lake-eval-caching.md
1249
- */
1250
- evalFingerprint?: string;
1251
- /** Git metadata (when run from CI) */
1252
- git?: {
1253
- branch: string;
1254
- prNumber?: number;
1255
- repo: string;
1256
- sha: string;
1257
- };
1258
- /** Grader model used for scoring */
1259
- graderModel: string;
1260
1299
  /** Typed relationships with other reports (re-run, comparison) */
1261
1300
  lineage?: ReportLineage;
1262
- /** Evaluation mode */
1263
- mode: EvalMode;
1264
- /** Models under evaluation */
1265
- models: {
1266
- id: string;
1267
- label: string;
1268
- }[];
1269
1301
  /** @deprecated Use `promptfooUrls` — kept for backward compatibility */
1270
1302
  promptfooUrl?: string;
1271
1303
  /** Per-mode Promptfoo share URLs (one per sub-eval that produced a shareable link) */
1272
1304
  promptfooUrls?: PromptfooUrlEntry[];
1273
- /** Documentation source configuration */
1274
- source: {
1275
- baseUrl: string;
1276
- dataset?: string;
1277
- name: string;
1278
- perspective?: string;
1279
- projectId?: string;
1280
- };
1305
+ /**
1306
+ * Identity of the pipeline run that produced this report. Links the
1307
+ * Content Lake document back to the GCS run manifest and its artifacts.
1308
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
1309
+ */
1310
+ runId: RunId;
1281
1311
  /** Sanity document IDs that were targeted (if using --sanity-document) */
1282
1312
  targetDocuments?: string[];
1283
- /** Which specific task IDs were evaluated (if scoped) */
1284
- taskIds?: string[];
1285
- /** What initiated this evaluation */
1286
- trigger: ReportTrigger;
1287
1313
  }
1288
- /** What triggered this evaluation */
1289
- export type ReportTrigger = {
1290
- type: "ci";
1291
- runId: string;
1292
- workflow: string;
1293
- } | {
1294
- type: "cross-repo";
1295
- callerRef?: string;
1296
- callerRepo: string;
1297
- } | {
1298
- type: "manual";
1299
- } | {
1300
- type: "scheduled";
1301
- schedule: string;
1302
- } | {
1303
- type: "webhook";
1304
- documentId?: string;
1305
- source: string;
1306
- };
1314
+ /**
1315
+ * A run's manifest in GCS (`runs/{runId}/manifest.json`). Source of truth
1316
+ * for artifact locations, run identity, and outcome. Reports snapshot the
1317
+ * `artifacts` slice into `Report.artifactManifest` at publish time.
1318
+ *
1319
+ * Written once by `FinalizeRunStep`; `reportIds` may be appended by
1320
+ * `PublishReportStep` via strongly-consistent GCS overwrite.
1321
+ */
1322
+ export interface RunManifest {
1323
+ /** Schema version — bumped when the manifest shape changes */
1324
+ version: 1;
1325
+ /** Identity for this pipeline run */
1326
+ runId: RunId;
1327
+ /** When the manifest was written (pipeline finalization time) */
1328
+ createdAt: ISOTimestamp;
1329
+ /** Total pipeline duration */
1330
+ durationMs: number;
1331
+ /** Outcome of the run */
1332
+ status: "completed" | "failed" | "partial";
1333
+ /** Failure classification when status is not "completed" */
1334
+ failureReason?: PipelineFailureReason;
1335
+ /** What ran — shared shape with ReportProvenance */
1336
+ context: RunContext;
1337
+ /** Run-level aggregates (self-describing without a report) */
1338
+ outcomes?: {
1339
+ testSummary?: TestSummary;
1340
+ usage?: PipelineUsage;
1341
+ cache?: {
1342
+ hits: number;
1343
+ misses: number;
1344
+ skipped: number;
1345
+ };
1346
+ };
1347
+ /** Reports published from this run (0..N). Authoritative link is Report.provenance.runId. */
1348
+ reportIds?: ReportId[];
1349
+ /** Promptfoo share URLs collected during the run */
1350
+ promptfooUrls?: PromptfooUrlEntry[];
1351
+ /** Artifact catalog — per-type refs with inline per-entry indexes */
1352
+ artifacts: ArtifactManifest;
1353
+ }
1307
1354
  /** Health check result for a sink */
1308
1355
  export type SinkHealthStatus = {
1309
1356
  healthy: false;
@@ -16,7 +16,7 @@ export { InMemoryPluginRegistry } from "./plugin-registry.js";
16
16
  // version is used internally by LiteracyModeConfig. If consumers need
17
17
  // the mode-specific version, they import from "./eval-mode-config.js".
18
18
  export { evalModeType } from "./eval-mode-config.js";
19
- export { err, fixtureId, ok, providerId, resultId, runId, suiteId, taskId, traceId, } from "./branded-ids.js";
19
+ export { err, fixtureId, generateRunId, ok, providerId, resultId, runId, suiteId, taskId, traceId, } from "./branded-ids.js";
20
20
  // ---------------------------------------------------------------------------
21
21
  // Comparison (Approach 2: structured comparison output)
22
22
  // ---------------------------------------------------------------------------
@@ -13,3 +13,5 @@ export * from "./document-ref.js";
13
13
  export * from "./score-grades.js";
14
14
  export * from "./noise-threshold.js";
15
15
  export * from "./eval-modes.js";
16
+ export * from "./run-trigger.js";
17
+ export * from "./run-context.js";
@@ -13,3 +13,5 @@ export * from "./document-ref.js";
13
13
  export * from "./score-grades.js";
14
14
  export * from "./noise-threshold.js";
15
15
  export * from "./eval-modes.js";
16
+ export * from "./run-trigger.js";
17
+ export * from "./run-context.js";
@@ -0,0 +1,55 @@
1
+ /**
2
+ * RunContext — the set of fields that describe an AILF pipeline run.
3
+ *
4
+ * This is the single source of truth for run-description data. Both
5
+ * `RunManifest.context` (in GCS, written by FinalizeRunStep) and
6
+ * `ReportProvenance` (in Content Lake, built by PublishReportStep)
7
+ * carry this shape. `ReportProvenance extends RunContext` to
8
+ * structurally enforce parity — adding a field here becomes a
9
+ * compile-time failure until every consumer threads it through.
10
+ *
11
+ * Fields are alphabetized to match the surrounding codebase convention
12
+ * (see `ReportProvenance` in `@sanity/ailf-core`).
13
+ *
14
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
15
+ * @see docs/design-docs/run-artifact-store.md (§ Drift Prevention)
16
+ */
17
+ import type { EvalMode } from "./eval-modes.js";
18
+ import type { RunTrigger } from "./run-trigger.js";
19
+ export interface RunContext {
20
+ /** Which feature areas were evaluated */
21
+ areas: string[];
22
+ /**
23
+ * Evaluation fingerprint — SHA-256 of all inputs that affect eval output.
24
+ * Used for cross-environment cache lookup (CI → Content Lake).
25
+ */
26
+ evalFingerprint?: string;
27
+ /** Git metadata (when run from CI) */
28
+ git?: {
29
+ branch: string;
30
+ prNumber?: number;
31
+ repo: string;
32
+ sha: string;
33
+ };
34
+ /** Grader model used for scoring */
35
+ graderModel: string;
36
+ /** Evaluation mode */
37
+ mode: EvalMode;
38
+ /** Models under evaluation */
39
+ models: {
40
+ id: string;
41
+ label: string;
42
+ }[];
43
+ /** Documentation source configuration */
44
+ source: {
45
+ baseUrl: string;
46
+ dataset?: string;
47
+ name: string;
48
+ perspective?: string;
49
+ projectId?: string;
50
+ };
51
+ /** Specific task IDs evaluated when scoped to a subset */
52
+ taskIds?: string[];
53
+ /** What initiated this run */
54
+ trigger: RunTrigger;
55
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * RunContext — the set of fields that describe an AILF pipeline run.
3
+ *
4
+ * This is the single source of truth for run-description data. Both
5
+ * `RunManifest.context` (in GCS, written by FinalizeRunStep) and
6
+ * `ReportProvenance` (in Content Lake, built by PublishReportStep)
7
+ * carry this shape. `ReportProvenance extends RunContext` to
8
+ * structurally enforce parity — adding a field here becomes a
9
+ * compile-time failure until every consumer threads it through.
10
+ *
11
+ * Fields are alphabetized to match the surrounding codebase convention
12
+ * (see `ReportProvenance` in `@sanity/ailf-core`).
13
+ *
14
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
15
+ * @see docs/design-docs/run-artifact-store.md (§ Drift Prevention)
16
+ */
17
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * RunTrigger — what initiated a pipeline run.
3
+ *
4
+ * Lives in shared so it can be referenced from RunContext (the single
5
+ * source of truth for run-description fields) and consumed by both
6
+ * RunManifest (GCS) and ReportProvenance (Content Lake) without creating
7
+ * a core → core cycle.
8
+ *
9
+ * The `ci.runId` field is the external workflow run identifier (e.g.
10
+ * GitHub Actions run ID). It is unrelated to the pipeline-level `RunId`
11
+ * brand in `@sanity/ailf-core`, which identifies an AILF pipeline run.
12
+ */
13
+ export type RunTrigger = {
14
+ type: "ci";
15
+ runId: string;
16
+ workflow: string;
17
+ } | {
18
+ type: "cross-repo";
19
+ callerRef?: string;
20
+ callerRepo: string;
21
+ } | {
22
+ type: "manual";
23
+ } | {
24
+ type: "scheduled";
25
+ schedule: string;
26
+ } | {
27
+ type: "webhook";
28
+ documentId?: string;
29
+ source: string;
30
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * RunTrigger — what initiated a pipeline run.
3
+ *
4
+ * Lives in shared so it can be referenced from RunContext (the single
5
+ * source of truth for run-description fields) and consumed by both
6
+ * RunManifest (GCS) and ReportProvenance (Content Lake) without creating
7
+ * a core → core cycle.
8
+ *
9
+ * The `ci.runId` field is the external workflow run identifier (e.g.
10
+ * GitHub Actions run ID). It is unrelated to the pipeline-level `RunId`
11
+ * brand in `@sanity/ailf-core`, which identifies an AILF pipeline run.
12
+ */
13
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * ApiGatewayArtifactWriter — uploads AILF run artifacts via the API Gateway.
3
+ *
4
+ * Used when the CLI runs locally without GCS credentials. The Gateway signs a
5
+ * PUT URL (scoped to a single GCS object) and the writer PUTs JSON directly
6
+ * to GCS so Vercel stays out of the data path.
7
+ *
8
+ * Endpoints:
9
+ * - Bulk: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/upload-url
10
+ * - Per-entry: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/{entryKey}/upload-url
11
+ * - Manifest: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/manifest/upload-url
12
+ *
13
+ * The Gateway routes are wired in Phase 10 (W0047).
14
+ *
15
+ * Design principles:
16
+ * - P5: Non-blocking — any failure returns null and warns, never throws.
17
+ * - Stateless — no client state between calls.
18
+ *
19
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
20
+ */
21
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
22
+ export interface ApiGatewayArtifactWriterOptions {
23
+ /** Base URL of the API gateway (e.g., "https://ailf-api.sanity.build"). */
24
+ apiBaseUrl: string;
25
+ /** AILF API key with the `artifact:write` scope. */
26
+ apiKey: string;
27
+ /** GCS bucket name — included in the returned ArtifactRef. */
28
+ bucket: string;
29
+ }
30
+ export declare class ApiGatewayArtifactWriter implements ArtifactWriter {
31
+ private readonly options;
32
+ constructor(options: ApiGatewayArtifactWriterOptions);
33
+ writeBulk(type: ArtifactType, runId: RunId, data: unknown): Promise<ArtifactRef | null>;
34
+ writePerEntry(type: ArtifactType, runId: RunId, entries: readonly ArtifactEntry[]): Promise<ArtifactRef | null>;
35
+ writeManifest(runId: RunId, manifest: RunManifest): Promise<ArtifactRef | null>;
36
+ private putJson;
37
+ private putJsonRaw;
38
+ private fetchSignedUrl;
39
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ApiGatewayArtifactWriter — uploads AILF run artifacts via the API Gateway.
3
+ *
4
+ * Used when the CLI runs locally without GCS credentials. The Gateway signs a
5
+ * PUT URL (scoped to a single GCS object) and the writer PUTs JSON directly
6
+ * to GCS so Vercel stays out of the data path.
7
+ *
8
+ * Endpoints:
9
+ * - Bulk: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/upload-url
10
+ * - Per-entry: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/{type}/{entryKey}/upload-url
11
+ * - Manifest: GET {apiBaseUrl}/v1/runs/{runId}/artifacts/manifest/upload-url
12
+ *
13
+ * The Gateway routes are wired in Phase 10 (W0047).
14
+ *
15
+ * Design principles:
16
+ * - P5: Non-blocking — any failure returns null and warns, never throws.
17
+ * - Stateless — no client state between calls.
18
+ *
19
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
20
+ */
21
+ import { ARTIFACT_REGISTRY, } from "../_vendor/ailf-core/index.js";
22
+ export class ApiGatewayArtifactWriter {
23
+ options;
24
+ constructor(options) {
25
+ this.options = options;
26
+ }
27
+ async writeBulk(type, runId, data) {
28
+ const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(type)}/upload-url`;
29
+ return this.putJson(uploadUrlPath, data, {
30
+ layout: "bulk",
31
+ entryCount: entryCountOf(data),
32
+ });
33
+ }
34
+ async writePerEntry(type, runId, entries) {
35
+ const descriptor = ARTIFACT_REGISTRY[type];
36
+ if (!descriptor.parseEntryKey) {
37
+ console.warn(` ⚠️ writePerEntry called for "${type}" but the registry has no parseEntryKey`);
38
+ return null;
39
+ }
40
+ const uploaded = [];
41
+ let totalBytes = 0;
42
+ let bucket = this.options.bucket;
43
+ for (const entry of entries) {
44
+ const parsed = descriptor.parseEntryKey(entry.key);
45
+ if (!parsed.ok) {
46
+ console.warn(` ⚠️ Skipping entry with invalid key "${entry.key}": ${parsed.reason}`);
47
+ continue;
48
+ }
49
+ const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(type)}/${encodeURIComponent(entry.key)}/upload-url`;
50
+ const result = await this.putJsonRaw(uploadUrlPath, entry.data);
51
+ if (!result)
52
+ continue;
53
+ bucket = result.bucket;
54
+ uploaded.push({ key: entry.key, bytes: result.bytes });
55
+ totalBytes += result.bytes;
56
+ }
57
+ if (uploaded.length === 0)
58
+ return null;
59
+ return {
60
+ store: "gcs",
61
+ bucket,
62
+ path: `runs/${runId}/${descriptor.slug}`,
63
+ bytes: totalBytes,
64
+ entryCount: uploaded.length,
65
+ layout: "per-entry",
66
+ entries: uploaded,
67
+ };
68
+ }
69
+ async writeManifest(runId, manifest) {
70
+ const uploadUrlPath = `/v1/runs/${encodeURIComponent(runId)}/artifacts/manifest/upload-url`;
71
+ return this.putJson(uploadUrlPath, manifest, { layout: "bulk" });
72
+ }
73
+ async putJson(uploadUrlPath, data, meta) {
74
+ const result = await this.putJsonRaw(uploadUrlPath, data);
75
+ if (!result)
76
+ return null;
77
+ return {
78
+ store: "gcs",
79
+ bucket: result.bucket,
80
+ path: result.path,
81
+ bytes: result.bytes,
82
+ entryCount: meta.entryCount,
83
+ layout: meta.layout,
84
+ };
85
+ }
86
+ async putJsonRaw(uploadUrlPath, data) {
87
+ const json = JSON.stringify(data);
88
+ const bytes = Buffer.byteLength(json, "utf-8");
89
+ try {
90
+ const signed = await this.fetchSignedUrl(uploadUrlPath);
91
+ if (!signed)
92
+ return null;
93
+ const putRes = await fetch(signed.url, {
94
+ body: json,
95
+ headers: signed.requiredHeaders,
96
+ method: "PUT",
97
+ });
98
+ if (!putRes.ok) {
99
+ console.warn(` ⚠️ Artifact upload failed (non-blocking): ${signed.path} — GCS PUT ${putRes.status} ${putRes.statusText}`);
100
+ return null;
101
+ }
102
+ return { bucket: signed.bucket, path: signed.path, bytes };
103
+ }
104
+ catch (err) {
105
+ const message = err instanceof Error ? err.message : String(err);
106
+ console.warn(` ⚠️ Artifact upload failed (non-blocking): ${uploadUrlPath} — ${message}`);
107
+ return null;
108
+ }
109
+ }
110
+ async fetchSignedUrl(uploadUrlPath) {
111
+ const url = `${this.options.apiBaseUrl.replace(/\/$/, "")}${uploadUrlPath}`;
112
+ const res = await fetch(url, {
113
+ headers: { Authorization: `Bearer ${this.options.apiKey}` },
114
+ method: "GET",
115
+ });
116
+ if (!res.ok) {
117
+ console.warn(` ⚠️ Signed-URL request failed: ${res.status} ${res.statusText}`);
118
+ return null;
119
+ }
120
+ const body = (await res.json());
121
+ if (body.object !== "signed_upload_url" ||
122
+ typeof body.url !== "string" ||
123
+ typeof body.path !== "string" ||
124
+ typeof body.bucket !== "string" ||
125
+ !body.requiredHeaders) {
126
+ console.warn(` ⚠️ Signed-URL response was malformed`);
127
+ return null;
128
+ }
129
+ return {
130
+ bucket: body.bucket,
131
+ method: "PUT",
132
+ object: "signed_upload_url",
133
+ path: body.path,
134
+ requiredHeaders: body.requiredHeaders,
135
+ url: body.url,
136
+ };
137
+ }
138
+ }
139
+ function entryCountOf(data) {
140
+ if (typeof data === "object" &&
141
+ data !== null &&
142
+ "entries" in data &&
143
+ typeof data.entries === "object") {
144
+ return Object.keys(data.entries)
145
+ .length;
146
+ }
147
+ return undefined;
148
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * GcsArtifactWriter — writes AILF run artifacts + manifest directly to GCS.
3
+ *
4
+ * Uses Application Default Credentials (ADC). Used when the CLI runs in CI or
5
+ * anywhere ADC is configured — the client talks to GCS without the API Gateway
6
+ * acting as a middleman.
7
+ *
8
+ * Paths come from `ARTIFACT_REGISTRY` so writers, signers, and readers agree.
9
+ *
10
+ * Design principles:
11
+ * - P5: Non-blocking — upload failure returns null, never throws.
12
+ * - Lazy client — Storage created on first write.
13
+ *
14
+ * @see docs/decisions/D0032-run-anchored-artifact-store.md
15
+ */
16
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
17
+ export interface GcsArtifactWriterOptions {
18
+ /** GCS bucket name (e.g., "ailf-artifacts") */
19
+ bucket: string;
20
+ }
21
+ export declare class GcsArtifactWriter implements ArtifactWriter {
22
+ private client;
23
+ private readonly options;
24
+ constructor(options: GcsArtifactWriterOptions);
25
+ writeBulk(type: ArtifactType, runId: RunId, data: unknown): Promise<ArtifactRef | null>;
26
+ writePerEntry(type: ArtifactType, runId: RunId, entries: readonly ArtifactEntry[]): Promise<ArtifactRef | null>;
27
+ writeManifest(runId: RunId, manifest: RunManifest): Promise<ArtifactRef | null>;
28
+ private putJson;
29
+ private getClient;
30
+ }