@sanity/ailf 4.0.7 → 4.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/bin/ailf.js +6 -1
- package/dist/_vendor/ailf-core/schemas/external-providers.d.ts +136 -0
- package/dist/_vendor/ailf-core/schemas/external-providers.js +136 -0
- package/dist/_vendor/ailf-core/schemas/index.d.ts +2 -0
- package/dist/_vendor/ailf-core/schemas/index.js +2 -0
- package/dist/_vendor/ailf-core/schemas/pipeline-request.d.ts +2 -3
- package/dist/_vendor/ailf-core/schemas/report.d.ts +251 -0
- package/dist/_vendor/ailf-core/schemas/report.js +235 -0
- 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/report-to-markdown.d.ts +38 -0
- package/dist/_vendor/ailf-core/services/report-to-markdown.js +696 -0
- package/dist/_vendor/ailf-core/types/api-requests.d.ts +159 -0
- package/dist/_vendor/ailf-core/types/api-requests.js +27 -0
- package/dist/_vendor/ailf-core/types/index.d.ts +3 -0
- package/dist/_vendor/ailf-core/types/pipeline-request.d.ts +112 -0
- package/dist/_vendor/ailf-core/types/pipeline-request.js +18 -0
- package/dist/_vendor/ailf-core/types/repo-config.d.ts +146 -0
- package/dist/_vendor/ailf-core/types/repo-config.js +18 -0
- package/dist/_vendor/ailf-shared/index.d.ts +7 -5
- package/dist/_vendor/ailf-shared/index.js +7 -5
- package/dist/adapters/api-client/types.d.ts +2 -5
- package/dist/adapters/task-sources/content-lake-task-source.d.ts +58 -1
- package/dist/adapters/task-sources/content-lake-task-source.js +1 -1
- package/dist/adapters/task-sources/index.d.ts +1 -1
- package/dist/adapters/task-sources/index.js +1 -1
- package/dist/adapters/task-sources/repo-schemas.d.ts +3 -2
- package/dist/adapters/task-sources/repo-schemas.js +3 -1
- package/dist/adapters/task-sources/repo-validation.d.ts +6 -6
- package/dist/adapters/task-sources/repo-validation.js +1 -1
- package/dist/agent-observer/agentic-provider.d.ts +1 -0
- package/dist/agent-observer/agentic-provider.js +43 -36
- package/dist/agent-observer/config-schemas.d.ts +61 -0
- package/dist/agent-observer/config-schemas.js +65 -0
- package/dist/agent-observer/provider.d.ts +1 -0
- package/dist/agent-observer/provider.js +19 -17
- package/dist/cli.js +4 -4
- package/dist/commands/validate-tasks.js +2 -2
- package/dist/composition-root.js +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/job-store.js +2 -2
- package/dist/lib/dotenv-resolution.d.ts +21 -0
- package/dist/lib/dotenv-resolution.js +30 -0
- package/dist/orchestration/steps/mirror-repo-tasks-step.js +14 -3
- package/dist/orchestration/steps/run-eval-step.js +21 -3
- package/dist/pipeline/agent-behavior-report.d.ts +2 -8
- package/dist/pipeline/cache.d.ts +2 -2
- package/dist/pipeline/checks.d.ts +10 -2
- package/dist/pipeline/checks.js +14 -4
- package/dist/pipeline/compiler/literacy-bridge.js +2 -2
- package/dist/pipeline/compiler/mode-handlers/agent-harness/types.d.ts +2 -2
- package/dist/pipeline/compiler/mode-handlers/index.d.ts +1 -1
- package/dist/pipeline/compiler/mode-handlers/knowledge-probe/types.d.ts +2 -2
- package/dist/pipeline/compiler/mode-handlers/literacy/index.d.ts +1 -1
- package/dist/pipeline/compiler/mode-handlers/literacy/types.d.ts +3 -3
- package/dist/pipeline/compiler/promptfoo-compiler.js +7 -11
- package/dist/pipeline/compiler/provider-assembler.js +33 -3
- package/dist/pipeline/compiler/rubric-resolution.d.ts +2 -2
- package/dist/pipeline/mirror-repo-tasks.d.ts +13 -5
- package/dist/pipeline/mirror-repo-tasks.js +16 -8
- package/dist/pipeline/pr-comment.d.ts +22 -9
- package/dist/pipeline/pr-comment.js +52 -472
- package/dist/pipeline/resolve-mappings.d.ts +8 -3
- package/dist/promptfoo-providers/mock-path.d.ts +12 -0
- package/dist/promptfoo-providers/mock-path.js +15 -0
- package/dist/report-store.d.ts +63 -1
- package/dist/report-store.js +111 -31
- package/dist/sanity/client.d.ts +58 -0
- package/dist/sanity/client.js +106 -0
- package/package.json +8 -7
package/dist/report-store.d.ts
CHANGED
|
@@ -83,6 +83,9 @@ export declare class ReportStore {
|
|
|
83
83
|
findComparableBaseline(query: LineageQuery): Promise<null | Report>;
|
|
84
84
|
/**
|
|
85
85
|
* Read a report by its ID.
|
|
86
|
+
*
|
|
87
|
+
* @throws {ReportSchemaValidationError} if the stored document fails the
|
|
88
|
+
* W0191 runtime schema gate. Sanity API failures still return null.
|
|
86
89
|
*/
|
|
87
90
|
read(id: ReportId): Promise<null | Report>;
|
|
88
91
|
/**
|
|
@@ -91,7 +94,10 @@ export declare class ReportStore {
|
|
|
91
94
|
* Creates an immutable `ailf.report` document. The document _id is
|
|
92
95
|
* prefixed with `report-` for easy GROQ filtering.
|
|
93
96
|
*
|
|
94
|
-
* @returns The report ID on success, null on failure (logged,
|
|
97
|
+
* @returns The report ID on success, null on Sanity API failure (logged,
|
|
98
|
+
* not thrown — P5 local-first).
|
|
99
|
+
* @throws {ReportSchemaValidationError} if the report fails the W0191
|
|
100
|
+
* runtime schema gate. Schema drift is a bug, not an outage.
|
|
95
101
|
*/
|
|
96
102
|
write(report: Report): Promise<null | ReportId>;
|
|
97
103
|
/**
|
|
@@ -121,3 +127,59 @@ export declare class ReportStore {
|
|
|
121
127
|
* Uses crypto.randomUUID() as a base and overwrites the timestamp portion.
|
|
122
128
|
*/
|
|
123
129
|
export declare function generateReportId(): ReportId;
|
|
130
|
+
/**
|
|
131
|
+
* Tagged error thrown by `toReport` and `ReportStore.write` when a Content
|
|
132
|
+
* Lake document fails the W0191 runtime schema gate. Read paths
|
|
133
|
+
* (`read`, `findByFingerprint`, `findComparableBaseline`) re-throw this
|
|
134
|
+
* error rather than swallowing it via the P5 log-and-continue try/catch
|
|
135
|
+
* — schema drift is a bug to be surfaced, not an outage to be tolerated.
|
|
136
|
+
*/
|
|
137
|
+
export declare class ReportSchemaValidationError extends Error {
|
|
138
|
+
constructor(message: string);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Build the Sanity `ailf.report` document shape for a domain `Report`.
|
|
142
|
+
*
|
|
143
|
+
* Inverse of `toReport`. Strips the baseline + experiment ScoreSummary
|
|
144
|
+
* bulk from any nested comparison (they duplicate `report.summary` and the
|
|
145
|
+
* `comparedAgainst` lineage), and lifts `artifactManifest` into the
|
|
146
|
+
* `summary.artifactManifest` slot where the D0032 GROQ projection expects
|
|
147
|
+
* it.
|
|
148
|
+
*
|
|
149
|
+
* Pure: takes a `Report`, returns a Sanity-shaped object literal. No I/O.
|
|
150
|
+
* Used by `ReportStore.write` (production) and round-trip contract tests
|
|
151
|
+
* (W0188) which need a deterministic forward-and-back mapping.
|
|
152
|
+
*/
|
|
153
|
+
export interface SanityReportDoc {
|
|
154
|
+
_id: string;
|
|
155
|
+
_type: string;
|
|
156
|
+
comparison: null | Omit<ComparisonReport, "baseline" | "experiment">;
|
|
157
|
+
completedAt: string;
|
|
158
|
+
durationMs: number;
|
|
159
|
+
provenance: Report["provenance"];
|
|
160
|
+
reportId: ReportId;
|
|
161
|
+
summary: Report["summary"] & {
|
|
162
|
+
artifactManifest?: Report["artifactManifest"];
|
|
163
|
+
};
|
|
164
|
+
tag: null | string;
|
|
165
|
+
title: null | string;
|
|
166
|
+
}
|
|
167
|
+
export declare function toSanityReportDoc(report: Report): SanityReportDoc;
|
|
168
|
+
/**
|
|
169
|
+
* Convert a raw Sanity document to a typed Report.
|
|
170
|
+
*
|
|
171
|
+
* The Sanity document shape mirrors the Report type but includes Sanity
|
|
172
|
+
* metadata (_id, _type, _rev, etc.) that we strip.
|
|
173
|
+
*/
|
|
174
|
+
export declare function toReport(doc: Record<string, unknown>): Report;
|
|
175
|
+
/**
|
|
176
|
+
* Remove the `baseline` and `experiment` ScoreSummary objects from a
|
|
177
|
+
* ComparisonReport, producing a slim copy suitable for persistence.
|
|
178
|
+
*
|
|
179
|
+
* These fields are redundant in the stored document:
|
|
180
|
+
* - `experiment` is byte-for-byte identical to `report.summary`
|
|
181
|
+
* - `baseline` is fetchable via `provenance.lineage.comparedAgainst`
|
|
182
|
+
*
|
|
183
|
+
* Everything else (deltas, areas, classifications) is preserved.
|
|
184
|
+
*/
|
|
185
|
+
export declare function stripComparisonBulk(comparison: ComparisonReport): Omit<ComparisonReport, "baseline" | "experiment">;
|
package/dist/report-store.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* @see docs/design-docs/report-store/architecture.md
|
|
15
15
|
* @see docs/design-docs/report-store/domain-model.md
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import { ReportSchema } from "./_vendor/ailf-core/index.js";
|
|
18
|
+
import { getAilfSanityClient } from "./sanity/client.js";
|
|
18
19
|
import { compare } from "./pipeline/compare.js";
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Constants
|
|
@@ -30,7 +31,7 @@ export class ReportStore {
|
|
|
30
31
|
this.client = options.client;
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
|
-
this.client =
|
|
34
|
+
this.client = getAilfSanityClient({
|
|
34
35
|
...(options.dataset ? { dataset: options.dataset } : {}),
|
|
35
36
|
...(options.projectId ? { projectId: options.projectId } : {}),
|
|
36
37
|
...(options.token ? { token: options.token } : {}),
|
|
@@ -130,6 +131,9 @@ export class ReportStore {
|
|
|
130
131
|
return doc ? toReport(doc) : null;
|
|
131
132
|
}
|
|
132
133
|
catch (error) {
|
|
134
|
+
// W0191: schema-validation errors are bugs, not outages — surface them.
|
|
135
|
+
if (error instanceof ReportSchemaValidationError)
|
|
136
|
+
throw error;
|
|
133
137
|
console.warn(` ⚠️ Failed to query cached report by fingerprint: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
138
|
return null;
|
|
135
139
|
}
|
|
@@ -166,12 +170,18 @@ export class ReportStore {
|
|
|
166
170
|
return doc ? toReport(doc) : null;
|
|
167
171
|
}
|
|
168
172
|
catch (error) {
|
|
173
|
+
// W0191: schema-validation errors are bugs, not outages — surface them.
|
|
174
|
+
if (error instanceof ReportSchemaValidationError)
|
|
175
|
+
throw error;
|
|
169
176
|
console.warn(` ⚠️ Failed to query comparable baseline: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
177
|
return null;
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
/**
|
|
174
181
|
* Read a report by its ID.
|
|
182
|
+
*
|
|
183
|
+
* @throws {ReportSchemaValidationError} if the stored document fails the
|
|
184
|
+
* W0191 runtime schema gate. Sanity API failures still return null.
|
|
175
185
|
*/
|
|
176
186
|
async read(id) {
|
|
177
187
|
try {
|
|
@@ -179,6 +189,9 @@ export class ReportStore {
|
|
|
179
189
|
return doc ? toReport(doc) : null;
|
|
180
190
|
}
|
|
181
191
|
catch (error) {
|
|
192
|
+
// W0191: schema-validation errors are bugs, not outages — surface them.
|
|
193
|
+
if (error instanceof ReportSchemaValidationError)
|
|
194
|
+
throw error;
|
|
182
195
|
console.warn(` ⚠️ Failed to read report from Sanity: ${error instanceof Error ? error.message : String(error)}`);
|
|
183
196
|
return null;
|
|
184
197
|
}
|
|
@@ -189,36 +202,30 @@ export class ReportStore {
|
|
|
189
202
|
* Creates an immutable `ailf.report` document. The document _id is
|
|
190
203
|
* prefixed with `report-` for easy GROQ filtering.
|
|
191
204
|
*
|
|
192
|
-
* @returns The report ID on success, null on failure (logged,
|
|
205
|
+
* @returns The report ID on success, null on Sanity API failure (logged,
|
|
206
|
+
* not thrown — P5 local-first).
|
|
207
|
+
* @throws {ReportSchemaValidationError} if the report fails the W0191
|
|
208
|
+
* runtime schema gate. Schema drift is a bug, not an outage.
|
|
193
209
|
*/
|
|
194
210
|
async write(report) {
|
|
211
|
+
// W0191 runtime gate — parse the wire payload before sending it to the
|
|
212
|
+
// Content Lake so producer-side drift surfaces here rather than as
|
|
213
|
+
// silent undefined-propagation in Studio. Mirrors the W0073 gate in
|
|
214
|
+
// ContentLakeTaskSource. ZodErrors throw; network/auth failures
|
|
215
|
+
// continue to use the P5 log-and-continue path below.
|
|
216
|
+
//
|
|
217
|
+
// The schema validates the wire shape (top-level `id`, not Sanity's
|
|
218
|
+
// `_id`/`reportId`), so we hand it the SanityReportDoc with `id` added.
|
|
219
|
+
// ReportSchema is `.passthrough()` at the top level — the Sanity-only
|
|
220
|
+
// fields (`_id`, `_type`, `reportId`) ride through harmlessly.
|
|
221
|
+
const sanityDoc = toSanityReportDoc(report);
|
|
222
|
+
const parsed = ReportSchema.safeParse({ ...sanityDoc, id: report.id });
|
|
223
|
+
if (!parsed.success) {
|
|
224
|
+
throw new ReportSchemaValidationError(`ReportStore.write: ailf.report "${report.id}" failed schema validation:\n` +
|
|
225
|
+
formatReportZodIssues(parsed.error));
|
|
226
|
+
}
|
|
195
227
|
try {
|
|
196
|
-
|
|
197
|
-
// before persisting — they duplicate report.summary (experiment) and
|
|
198
|
-
// are fetchable by ID via provenance.lineage.comparedAgainst (baseline).
|
|
199
|
-
// This reduces document size by ~50-65% for full-mode reports.
|
|
200
|
-
const comparison = report.comparison
|
|
201
|
-
? stripComparisonBulk(report.comparison)
|
|
202
|
-
: null;
|
|
203
|
-
await this.client.create({
|
|
204
|
-
_id: `report-${report.id}`,
|
|
205
|
-
_type: REPORT_TYPE,
|
|
206
|
-
comparison,
|
|
207
|
-
completedAt: report.completedAt,
|
|
208
|
-
durationMs: report.durationMs,
|
|
209
|
-
provenance: report.provenance,
|
|
210
|
-
reportId: report.id,
|
|
211
|
-
summary: {
|
|
212
|
-
...report.summary,
|
|
213
|
-
// Artifact references live inside summary in Sanity so they're
|
|
214
|
-
// projected automatically by the reportDetailQuery (D0032)
|
|
215
|
-
...(report.artifactManifest
|
|
216
|
-
? { artifactManifest: report.artifactManifest }
|
|
217
|
-
: {}),
|
|
218
|
-
},
|
|
219
|
-
tag: report.tag ?? null,
|
|
220
|
-
title: report.title ?? null,
|
|
221
|
-
});
|
|
228
|
+
await this.client.create(sanityDoc);
|
|
222
229
|
return report.id;
|
|
223
230
|
}
|
|
224
231
|
catch (error) {
|
|
@@ -279,13 +286,71 @@ export function generateReportId() {
|
|
|
279
286
|
// ---------------------------------------------------------------------------
|
|
280
287
|
// Sanity document → Report mapping
|
|
281
288
|
// ---------------------------------------------------------------------------
|
|
289
|
+
/**
|
|
290
|
+
* Tagged error thrown by `toReport` and `ReportStore.write` when a Content
|
|
291
|
+
* Lake document fails the W0191 runtime schema gate. Read paths
|
|
292
|
+
* (`read`, `findByFingerprint`, `findComparableBaseline`) re-throw this
|
|
293
|
+
* error rather than swallowing it via the P5 log-and-continue try/catch
|
|
294
|
+
* — schema drift is a bug to be surfaced, not an outage to be tolerated.
|
|
295
|
+
*/
|
|
296
|
+
export class ReportSchemaValidationError extends Error {
|
|
297
|
+
constructor(message) {
|
|
298
|
+
super(message);
|
|
299
|
+
this.name = "ReportSchemaValidationError";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export function toSanityReportDoc(report) {
|
|
303
|
+
const comparison = report.comparison
|
|
304
|
+
? stripComparisonBulk(report.comparison)
|
|
305
|
+
: null;
|
|
306
|
+
return {
|
|
307
|
+
_id: `report-${report.id}`,
|
|
308
|
+
_type: REPORT_TYPE,
|
|
309
|
+
comparison,
|
|
310
|
+
completedAt: report.completedAt,
|
|
311
|
+
durationMs: report.durationMs,
|
|
312
|
+
provenance: report.provenance,
|
|
313
|
+
reportId: report.id,
|
|
314
|
+
summary: {
|
|
315
|
+
...report.summary,
|
|
316
|
+
// Artifact references live inside summary in Sanity so they're
|
|
317
|
+
// projected automatically by the reportDetailQuery (D0032)
|
|
318
|
+
...(report.artifactManifest
|
|
319
|
+
? { artifactManifest: report.artifactManifest }
|
|
320
|
+
: {}),
|
|
321
|
+
},
|
|
322
|
+
tag: report.tag ?? null,
|
|
323
|
+
title: report.title ?? null,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
282
326
|
/**
|
|
283
327
|
* Convert a raw Sanity document to a typed Report.
|
|
284
328
|
*
|
|
285
329
|
* The Sanity document shape mirrors the Report type but includes Sanity
|
|
286
330
|
* metadata (_id, _type, _rev, etc.) that we strip.
|
|
287
331
|
*/
|
|
288
|
-
function toReport(doc) {
|
|
332
|
+
export function toReport(doc) {
|
|
333
|
+
// W0191 runtime gate — parse the raw Sanity document before constructing
|
|
334
|
+
// the typed Report. The schema has `id` at the top level (matching the
|
|
335
|
+
// wire shape used on write); the stored document carries the same value
|
|
336
|
+
// under `reportId`, so build the parse candidate from `reportId` and
|
|
337
|
+
// surface the document `_id` (or `reportId`) on parse failures so
|
|
338
|
+
// operators can locate the malformed record.
|
|
339
|
+
const reportIdRaw = doc.reportId;
|
|
340
|
+
const candidate = {
|
|
341
|
+
...doc,
|
|
342
|
+
id: reportIdRaw,
|
|
343
|
+
};
|
|
344
|
+
const parsed = ReportSchema.safeParse(candidate);
|
|
345
|
+
if (!parsed.success) {
|
|
346
|
+
const docKey = typeof doc._id === "string" && doc._id.length > 0
|
|
347
|
+
? doc._id
|
|
348
|
+
: typeof reportIdRaw === "string" && reportIdRaw.length > 0
|
|
349
|
+
? reportIdRaw
|
|
350
|
+
: "<unknown>";
|
|
351
|
+
throw new ReportSchemaValidationError(`ReportStore.toReport: ailf.report "${docKey}" failed schema ` +
|
|
352
|
+
`validation:\n${formatReportZodIssues(parsed.error)}`);
|
|
353
|
+
}
|
|
289
354
|
const summary = doc.summary;
|
|
290
355
|
const artifactManifest = summary?.artifactManifest;
|
|
291
356
|
return {
|
|
@@ -300,6 +365,21 @@ function toReport(doc) {
|
|
|
300
365
|
title: doc.title,
|
|
301
366
|
};
|
|
302
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Format the first 5 ZodError issues for human-readable error output.
|
|
370
|
+
* Mirrors the W0073 ContentLakeTaskSource formatter so the two Content
|
|
371
|
+
* Lake gates produce comparable error shapes.
|
|
372
|
+
*/
|
|
373
|
+
function formatReportZodIssues(error) {
|
|
374
|
+
const issues = error.issues
|
|
375
|
+
.slice(0, 5)
|
|
376
|
+
.map((i) => ` [${i.path.join(".")}]: ${i.message}`)
|
|
377
|
+
.join("\n");
|
|
378
|
+
const more = error.issues.length > 5
|
|
379
|
+
? `\n …and ${error.issues.length - 5} more issue(s)`
|
|
380
|
+
: "";
|
|
381
|
+
return `${issues}${more}`;
|
|
382
|
+
}
|
|
303
383
|
/**
|
|
304
384
|
* Remove the `baseline` and `experiment` ScoreSummary objects from a
|
|
305
385
|
* ComparisonReport, producing a slim copy suitable for persistence.
|
|
@@ -310,7 +390,7 @@ function toReport(doc) {
|
|
|
310
390
|
*
|
|
311
391
|
* Everything else (deltas, areas, classifications) is preserved.
|
|
312
392
|
*/
|
|
313
|
-
function stripComparisonBulk(comparison) {
|
|
393
|
+
export function stripComparisonBulk(comparison) {
|
|
314
394
|
const { baseline: _, experiment: __, ...slim } = comparison;
|
|
315
395
|
return slim;
|
|
316
396
|
}
|
package/dist/sanity/client.d.ts
CHANGED
|
@@ -36,3 +36,61 @@ export declare function createPublishedClient(source?: ResolvedSourceConfig): Sa
|
|
|
36
36
|
* passed as a stacked array: [perspectiveId, "published"].
|
|
37
37
|
*/
|
|
38
38
|
export declare function getSanityClient(overrides?: Partial<SanityConfig>, source?: ResolvedSourceConfig): SanityClient;
|
|
39
|
+
/**
|
|
40
|
+
* Get a Sanity client targeting the AILF private dataset.
|
|
41
|
+
*
|
|
42
|
+
* Use this for reads and writes against `ailf.*` document types. The
|
|
43
|
+
* returned client is cached when called without overrides.
|
|
44
|
+
*
|
|
45
|
+
* @param overrides - Per-call config overrides (e.g., explicit token, dataset).
|
|
46
|
+
*/
|
|
47
|
+
export declare function getAilfSanityClient(overrides?: Partial<SanityConfig>): SanityClient;
|
|
48
|
+
/**
|
|
49
|
+
* Reset the cached AILF client. Test-only — call from `beforeEach` when
|
|
50
|
+
* mutating env vars between tests so the cached client doesn't leak.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resetAilfSanityClientCache(): void;
|
|
53
|
+
/**
|
|
54
|
+
* The write shape for a Cross Dataset Reference. Sanity's Mutation API
|
|
55
|
+
* requires `_projectId` even when the target dataset lives in the same
|
|
56
|
+
* project (spike Finding 13). `_weak: true` keeps the AILF source doc
|
|
57
|
+
* publishable when the editorial target is retired (Finding 16).
|
|
58
|
+
*
|
|
59
|
+
* The literal `_type: "crossDatasetReference"` matches the receiving
|
|
60
|
+
* Studio schema field type and the validated migration script
|
|
61
|
+
* (`packages/studio/scripts/poc-migrate-to-private.ts`).
|
|
62
|
+
*
|
|
63
|
+
* @see docs/decisions/D0043-private-dataset-with-cdrs.md
|
|
64
|
+
*/
|
|
65
|
+
export interface EditorialReference {
|
|
66
|
+
_type: "crossDatasetReference";
|
|
67
|
+
_projectId: string;
|
|
68
|
+
_dataset: string;
|
|
69
|
+
_ref: string;
|
|
70
|
+
_weak?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface BuildEditorialReferenceOptions {
|
|
73
|
+
/** Override the editorial-side project ID. Defaults to AILF project. */
|
|
74
|
+
projectId?: string;
|
|
75
|
+
/** Override the editorial dataset. Defaults to `AILF_EDITORIAL_DATASET`. */
|
|
76
|
+
dataset?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Mark the reference as weak. Strongly recommended for any AILF→editorial
|
|
79
|
+
* link — keeps the AILF doc publishable when the target is deleted.
|
|
80
|
+
* Defaults to `true`.
|
|
81
|
+
*/
|
|
82
|
+
weak?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a Cross Dataset Reference from an AILF document into editorial content.
|
|
86
|
+
*
|
|
87
|
+
* Use this for any reference field on an `ailf.*` document that points at an
|
|
88
|
+
* editorial type (`article`, `docPage`, …). The schema-side counterpart is a
|
|
89
|
+
* `crossDatasetReference` field with matching `dataset` and `weak: true`.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const docRef = buildEditorialReference(articleId)
|
|
93
|
+
* // → { _type: "reference", _projectId: "3do82whm",
|
|
94
|
+
* // _dataset: "next", _ref: articleId, _weak: true }
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildEditorialReference(refId: string, options?: BuildEditorialReferenceOptions): EditorialReference;
|
package/dist/sanity/client.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { createClient } from "@sanity/client";
|
|
2
|
+
// Transitional default for AILF operational documents while D0043 rollout
|
|
3
|
+
// is in progress. Code paths route through `getAilfSanityClient()` so the
|
|
4
|
+
// default flip to `ailf-prod-private` is a single-line change after the
|
|
5
|
+
// migration script (D0043 rollout step 5) ships and runs. Until then the
|
|
6
|
+
// default mirrors the editorial dataset to avoid silent dual-write across
|
|
7
|
+
// the cutover boundary.
|
|
8
|
+
const AILF_DATASET_DEFAULT = "next";
|
|
9
|
+
// Default editorial dataset that AILF cross-dataset references point at.
|
|
10
|
+
// Used by `buildEditorialReference()` and by Studio's CDR field config.
|
|
11
|
+
const EDITORIAL_DATASET_DEFAULT = "next";
|
|
2
12
|
/**
|
|
3
13
|
* Build the default Sanity client config by reading process.env at call time.
|
|
4
14
|
*
|
|
@@ -84,3 +94,99 @@ export function getSanityClient(overrides, source) {
|
|
|
84
94
|
_client = createClient(config);
|
|
85
95
|
return _client;
|
|
86
96
|
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// AILF private-dataset client (D0043)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/**
|
|
101
|
+
* Build the default config for clients that read/write AILF documents
|
|
102
|
+
* (ailf.report, ailf.task, ailf.job, ailf.featureArea, ailf.evalRequest).
|
|
103
|
+
*
|
|
104
|
+
* Dataset precedence: explicit `AILF_REPORT_DATASET` overrides; otherwise
|
|
105
|
+
* fall back to `SANITY_DATASET` so existing CI workflows that pin a
|
|
106
|
+
* test/staging dataset (e.g. Tier 2 with `SANITY_DATASET=ailf-test`)
|
|
107
|
+
* continue to work without a new env var. The hard-coded fallback is
|
|
108
|
+
* the editorial dataset name during the D0043 cutover window — the flip
|
|
109
|
+
* to `ailf-prod-private` happens after the migration script runs.
|
|
110
|
+
*
|
|
111
|
+
* Token resolution prefers the AILF-scoped token, falling back to
|
|
112
|
+
* the shared `SANITY_API_TOKEN`.
|
|
113
|
+
*/
|
|
114
|
+
function getAilfDefaultConfig() {
|
|
115
|
+
return {
|
|
116
|
+
apiVersion: new Date().toISOString().split("T")[0],
|
|
117
|
+
dataset:
|
|
118
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
119
|
+
process.env.AILF_REPORT_DATASET ||
|
|
120
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
121
|
+
process.env.SANITY_DATASET ||
|
|
122
|
+
AILF_DATASET_DEFAULT,
|
|
123
|
+
projectId:
|
|
124
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
125
|
+
process.env.AILF_REPORT_PROJECT_ID ||
|
|
126
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
127
|
+
process.env.SANITY_PROJECT_ID ||
|
|
128
|
+
"3do82whm",
|
|
129
|
+
token: process.env.AILF_REPORT_SANITY_API_TOKEN ?? process.env.SANITY_API_TOKEN,
|
|
130
|
+
useCdn: false,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
let _ailfClient = null;
|
|
134
|
+
/**
|
|
135
|
+
* Get a Sanity client targeting the AILF private dataset.
|
|
136
|
+
*
|
|
137
|
+
* Use this for reads and writes against `ailf.*` document types. The
|
|
138
|
+
* returned client is cached when called without overrides.
|
|
139
|
+
*
|
|
140
|
+
* @param overrides - Per-call config overrides (e.g., explicit token, dataset).
|
|
141
|
+
*/
|
|
142
|
+
export function getAilfSanityClient(overrides) {
|
|
143
|
+
if (_ailfClient && !overrides) {
|
|
144
|
+
return _ailfClient;
|
|
145
|
+
}
|
|
146
|
+
const config = { ...getAilfDefaultConfig(), ...overrides };
|
|
147
|
+
const client = createClient(config);
|
|
148
|
+
if (!overrides)
|
|
149
|
+
_ailfClient = client;
|
|
150
|
+
return client;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Reset the cached AILF client. Test-only — call from `beforeEach` when
|
|
154
|
+
* mutating env vars between tests so the cached client doesn't leak.
|
|
155
|
+
*/
|
|
156
|
+
export function resetAilfSanityClientCache() {
|
|
157
|
+
_ailfClient = null;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Build a Cross Dataset Reference from an AILF document into editorial content.
|
|
161
|
+
*
|
|
162
|
+
* Use this for any reference field on an `ailf.*` document that points at an
|
|
163
|
+
* editorial type (`article`, `docPage`, …). The schema-side counterpart is a
|
|
164
|
+
* `crossDatasetReference` field with matching `dataset` and `weak: true`.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* const docRef = buildEditorialReference(articleId)
|
|
168
|
+
* // → { _type: "reference", _projectId: "3do82whm",
|
|
169
|
+
* // _dataset: "next", _ref: articleId, _weak: true }
|
|
170
|
+
*/
|
|
171
|
+
export function buildEditorialReference(refId, options = {}) {
|
|
172
|
+
if (!refId) {
|
|
173
|
+
throw new Error("buildEditorialReference: refId is required");
|
|
174
|
+
}
|
|
175
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
176
|
+
const envProjectId = process.env.AILF_REPORT_PROJECT_ID || process.env.SANITY_PROJECT_ID;
|
|
177
|
+
// Editorial dataset precedence: explicit AILF_EDITORIAL_DATASET overrides;
|
|
178
|
+
// otherwise reuse SANITY_DATASET (which CI workflows already pin to the
|
|
179
|
+
// correct editorial dataset); fall back to "next" only when nothing is set.
|
|
180
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string env var should fall back
|
|
181
|
+
const envDataset = process.env.AILF_EDITORIAL_DATASET || process.env.SANITY_DATASET;
|
|
182
|
+
const projectId = options.projectId ?? envProjectId ?? "3do82whm";
|
|
183
|
+
const dataset = options.dataset ?? envDataset ?? EDITORIAL_DATASET_DEFAULT;
|
|
184
|
+
const weak = options.weak ?? true;
|
|
185
|
+
return {
|
|
186
|
+
_type: "crossDatasetReference",
|
|
187
|
+
_projectId: projectId,
|
|
188
|
+
_dataset: dataset,
|
|
189
|
+
_ref: refId,
|
|
190
|
+
...(weak ? { _weak: true } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ailf",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"nock": "^14.0.13",
|
|
54
54
|
"tsx": "^4.19.2",
|
|
55
55
|
"typescript": "^5.7.3",
|
|
56
|
+
"vitest": "^4.1.5",
|
|
56
57
|
"@sanity/ailf-core": "0.1.0",
|
|
57
58
|
"@sanity/ailf-shared": "0.1.0"
|
|
58
59
|
},
|
|
@@ -74,12 +75,12 @@
|
|
|
74
75
|
"cli": "tsx src/cli.ts",
|
|
75
76
|
"pipeline": "tsx src/cli.ts pipeline",
|
|
76
77
|
"validate": "tsx src/cli.ts validate config",
|
|
77
|
-
"test": "
|
|
78
|
-
"test:e2e": "AILF_E2E=1
|
|
79
|
-
"test:e2e:adapters": "AILF_E2E=1
|
|
80
|
-
"test:e2e:api": "AILF_E2E_API=1
|
|
81
|
-
"test:tier3:roundtrip": "AILF_E2E_API=1 AILF_E2E_GITHUB_DISPATCH=1
|
|
82
|
-
"test:all": "AILF_E2E=1
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:e2e": "AILF_E2E=1 vitest run src/__tests__/e2e",
|
|
80
|
+
"test:e2e:adapters": "AILF_E2E=1 vitest run src/adapters",
|
|
81
|
+
"test:e2e:api": "AILF_E2E_API=1 vitest run src/__tests__/api-tier2-tenant-integration.test.ts src/__tests__/gcs-artifact-writer-roundtrip.test.ts",
|
|
82
|
+
"test:tier3:roundtrip": "AILF_E2E_API=1 AILF_E2E_GITHUB_DISPATCH=1 vitest run src/__tests__/api-tier3-round-trip.test.ts",
|
|
83
|
+
"test:all": "AILF_E2E=1 vitest run",
|
|
83
84
|
"pr-comment": "tsx src/cli.ts pr-comment",
|
|
84
85
|
"coverage-audit": "tsx src/cli.ts report coverage",
|
|
85
86
|
"readiness-report": "tsx src/cli.ts report readiness",
|