@policyengine/calibration-tree 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +58 -2
- package/package.json +71 -4
- package/src/calibration-build-comparison.ts +77 -0
- package/src/calibration-comparison-blob.ts +367 -0
- package/src/calibration-comparison-bundle.ts +149 -0
- package/src/calibration-explorer.ts +202 -0
- package/src/calibration-release-locator.ts +353 -0
- package/src/calibration-tree-artifact.ts +1974 -0
- package/src/calibration-tree-blob.ts +403 -0
- package/src/calibration-tree-bundle.ts +569 -0
- package/src/calibration-tree-manifest.ts +349 -0
- package/src/calibration-tree-worker.ts +445 -0
- package/src/calibration-tree.ts +1284 -0
- package/src/chronicle-metadata.ts +42 -0
- package/src/countries.ts +355 -0
- package/src/geography-labels.ts +59 -0
- package/src/hierarchy-catalog-reader.ts +297 -0
- package/src/hierarchy-target-reader.ts +243 -0
- package/src/historical-weight-summary.ts +53 -0
- package/src/latest-artifact.ts +3767 -0
- package/src/legacy-target-reader.ts +337 -0
- package/src/program-label.ts +64 -0
- package/src/publish-calibration-tree.ts +1984 -0
- package/src/reforms.ts +224 -0
- package/src/release-cut.ts +32 -0
- package/src/release-record.ts +277 -0
- package/src/source-label.ts +5 -0
- package/src/source-labels.ts +71 -0
- package/src/staging-artifact.ts +1411 -0
- package/src/staging-contract.ts +916 -0
- package/src/structured-dimension-reader.ts +137 -0
- package/src/structured-target-reader.ts +76 -0
- package/src/target-change.ts +421 -0
- package/src/target-loss-attribution-manifest.ts +436 -0
- package/src/target-loss-attribution.ts +1012 -0
- package/src/target-representation.ts +69 -0
- package/src/target-surface-matcher.ts +266 -0
- package/types/next-fetch-cache.d.ts +8 -0
|
@@ -0,0 +1,1411 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type Calibration,
|
|
5
|
+
type MicrocosmCountry,
|
|
6
|
+
asObject,
|
|
7
|
+
assertSafeReleaseId,
|
|
8
|
+
assertSafeReleaseOrBuildFolderId,
|
|
9
|
+
buildCalibration,
|
|
10
|
+
buildComparison,
|
|
11
|
+
latestMicrocosmCalibrationSummary,
|
|
12
|
+
latestMicrocosmTargetDiagnosticsPage,
|
|
13
|
+
loadCountryRepositoryHashedJson,
|
|
14
|
+
loadPointerReleaseId,
|
|
15
|
+
loadPublishedCutReleases,
|
|
16
|
+
loadRecordedReleases,
|
|
17
|
+
loadRelease,
|
|
18
|
+
loadReleases,
|
|
19
|
+
microcosmCountryGeography,
|
|
20
|
+
microcosmRepo,
|
|
21
|
+
type ReleaseEntry,
|
|
22
|
+
} from "./latest-artifact";
|
|
23
|
+
import { readCalibrationTreeManifest } from "./calibration-tree-blob";
|
|
24
|
+
import {
|
|
25
|
+
calibrationBuildsNewestFirst,
|
|
26
|
+
type CalibrationTreeManifest,
|
|
27
|
+
type CalibrationTreeManifestEntry,
|
|
28
|
+
} from "./calibration-tree-manifest";
|
|
29
|
+
import type { CalibrationTreeSourceArtifact } from "./calibration-tree-artifact";
|
|
30
|
+
import {
|
|
31
|
+
countryRegistration,
|
|
32
|
+
resolveRegisteredStagingRepository,
|
|
33
|
+
stagingLocalAreaPipelines,
|
|
34
|
+
type RepositoryEnvironment,
|
|
35
|
+
type ResolvedRepository,
|
|
36
|
+
} from "./countries";
|
|
37
|
+
import {
|
|
38
|
+
type ReformValidation,
|
|
39
|
+
buildReformValidation,
|
|
40
|
+
} from "./reforms";
|
|
41
|
+
import {
|
|
42
|
+
buildTargetChangeDataset,
|
|
43
|
+
type TargetChangeDataset,
|
|
44
|
+
} from "./target-change";
|
|
45
|
+
import { buildTargetChangeDatasetFromSummaryAndCalibration } from "./calibration-build-comparison";
|
|
46
|
+
import { loadCalibrationComparisonSource } from "./calibration-comparison-blob";
|
|
47
|
+
import {
|
|
48
|
+
IncompatibleStagingDataError,
|
|
49
|
+
isStructuredStagingDocument,
|
|
50
|
+
parseStagingCalibrationProgress,
|
|
51
|
+
parseStagingEvents,
|
|
52
|
+
parseStagingManifest,
|
|
53
|
+
parseStagingProgress,
|
|
54
|
+
parseStagingRunIndex,
|
|
55
|
+
validateStagingRunConsistency,
|
|
56
|
+
type StagingSchemaVersion,
|
|
57
|
+
} from "./staging-contract";
|
|
58
|
+
|
|
59
|
+
type JsonObject = Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
interface TargetChangeCacheEntry {
|
|
62
|
+
expiresAt: number;
|
|
63
|
+
promise: Promise<TargetChangeDataset | null>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface StagingCalibrationCacheEntry {
|
|
67
|
+
expiresAt: number;
|
|
68
|
+
promise: Promise<Calibration | null>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const TARGET_CHANGE_FINAL_CACHE_SECONDS = 21_600;
|
|
72
|
+
const TARGET_CHANGE_MUTABLE_CACHE_SECONDS = 30;
|
|
73
|
+
const TARGET_CHANGE_CACHE_LIMIT = 8;
|
|
74
|
+
const STAGING_CALIBRATION_CACHE_LIMIT = 8;
|
|
75
|
+
const targetChangeCache = new Map<string, TargetChangeCacheEntry>();
|
|
76
|
+
const stagingCalibrationCache = new Map<string, StagingCalibrationCacheEntry>();
|
|
77
|
+
|
|
78
|
+
export const MICROCOSM_STAGING_HF_REPO_ENV = "POPULACE_STAGING_HF_REPO";
|
|
79
|
+
export const MICROCOSM_STAGING_HF_REVISION_ENV = "POPULACE_STAGING_HF_REVISION";
|
|
80
|
+
export type StagingRepository = ResolvedRepository;
|
|
81
|
+
|
|
82
|
+
// Resolve staging telemetry from the country registry. The exported US values
|
|
83
|
+
// retain the legacy API and deployment-variable behavior for existing callers.
|
|
84
|
+
export function stagingRepository(
|
|
85
|
+
country: MicrocosmCountry,
|
|
86
|
+
environment: RepositoryEnvironment = process.env,
|
|
87
|
+
): StagingRepository | null {
|
|
88
|
+
return resolveRegisteredStagingRepository(country, environment);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const MICROCOSM_STAGING_HF_REPO = stagingRepository("us")!.repo;
|
|
92
|
+
export const MICROCOSM_STAGING_HF_REVISION = stagingRepository("us")!.revision;
|
|
93
|
+
|
|
94
|
+
export async function resolveStagingRevisionSha(
|
|
95
|
+
country: MicrocosmCountry,
|
|
96
|
+
revisionOverride?: string,
|
|
97
|
+
): Promise<string> {
|
|
98
|
+
const repository = stagingSource(country, revisionOverride);
|
|
99
|
+
const response = await fetch(
|
|
100
|
+
`https://huggingface.co/api/datasets/${repository.repo}/revision/${encodeURIComponent(repository.revision)}`,
|
|
101
|
+
{
|
|
102
|
+
...stagingFetchOptions(0, country),
|
|
103
|
+
signal: AbortSignal.timeout(30_000),
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new StagingFetchError(
|
|
108
|
+
response.status,
|
|
109
|
+
`revision ${repository.revision}`,
|
|
110
|
+
repository,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const payload = asObject(await response.json());
|
|
114
|
+
const sha = typeof payload.sha === "string" ? payload.sha.toLowerCase() : "";
|
|
115
|
+
if (!/^[0-9a-f]{40,64}$/.test(sha)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Hugging Face returned an invalid commit SHA for ${repository.repo}@${repository.revision}.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return sha;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Staging telemetry is served for countries registered with the `staging`
|
|
124
|
+
// capability. Repository and credential selection happens in this server data
|
|
125
|
+
// module and never enters the API response model.
|
|
126
|
+
export function stagingUnavailableReason(country: MicrocosmCountry): string | null {
|
|
127
|
+
return stagingRepository(country)
|
|
128
|
+
? null
|
|
129
|
+
: `${microcosmCountryGeography(country)} has no staging repository.`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requiredStagingRepository(country: MicrocosmCountry): StagingRepository {
|
|
133
|
+
const repository = stagingRepository(country);
|
|
134
|
+
if (repository) return repository;
|
|
135
|
+
throw new Error(
|
|
136
|
+
stagingUnavailableReason(country) ?? "Staging repository is not configured.",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function stagingResolveUrlFor(repository: StagingRepository, path: string): string {
|
|
141
|
+
return `https://huggingface.co/datasets/${repository.repo}/resolve/${repository.revision}/${path}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function stagingResolveUrl(path: string, country: MicrocosmCountry = "us"): string {
|
|
145
|
+
return stagingResolveUrlFor(requiredStagingRepository(country), path);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function stagingTreeUrl(repository: StagingRepository): string {
|
|
149
|
+
return `https://huggingface.co/api/datasets/${repository.repo}/tree/${repository.revision}/runs?recursive=true`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function stagingRepoUrl(repository: StagingRepository): string {
|
|
153
|
+
return `https://huggingface.co/api/datasets/${repository.repo}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function stagingSource(
|
|
157
|
+
country: MicrocosmCountry,
|
|
158
|
+
revisionOverride?: string,
|
|
159
|
+
): StagingRepository {
|
|
160
|
+
const repository = requiredStagingRepository(country);
|
|
161
|
+
return revisionOverride
|
|
162
|
+
? { ...repository, revision: revisionOverride }
|
|
163
|
+
: repository;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function unavailableStaging(country: MicrocosmCountry) {
|
|
167
|
+
const detail = stagingUnavailableReason(country);
|
|
168
|
+
if (!detail) return null;
|
|
169
|
+
return {
|
|
170
|
+
available: false as const,
|
|
171
|
+
source_repo: null,
|
|
172
|
+
revision: null,
|
|
173
|
+
detail,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class StagingFetchError extends Error {
|
|
178
|
+
constructor(
|
|
179
|
+
public readonly status: number,
|
|
180
|
+
path: string,
|
|
181
|
+
repository: StagingRepository,
|
|
182
|
+
) {
|
|
183
|
+
super(stagingFetchMessage(status, path, repository));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function stagingFetchMessage(
|
|
188
|
+
status: number,
|
|
189
|
+
path: string,
|
|
190
|
+
repository: StagingRepository,
|
|
191
|
+
): string {
|
|
192
|
+
if (status === 401 || status === 403) {
|
|
193
|
+
return (
|
|
194
|
+
`Staging repository ${repository.repo} is not readable by this deployment ` +
|
|
195
|
+
`(${status} fetching ${path}). Configure its server-side read credential.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (status === 404) {
|
|
199
|
+
return `Staging artifact not found (${path}).`;
|
|
200
|
+
}
|
|
201
|
+
return `Staging fetch failed ${status}: ${path}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function stagingToken(country: MicrocosmCountry): string | undefined {
|
|
205
|
+
const tokenEnv = countryRegistration(country).staging?.token_env;
|
|
206
|
+
if (tokenEnv) return process.env[tokenEnv];
|
|
207
|
+
return process.env.HF_TOKEN ?? process.env.HUGGINGFACE_TOKEN;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function hfHeaders(country: MicrocosmCountry): HeadersInit | undefined {
|
|
211
|
+
const token = stagingToken(country);
|
|
212
|
+
return token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function stagingFetchOptions(
|
|
216
|
+
revalidate: number,
|
|
217
|
+
country: MicrocosmCountry,
|
|
218
|
+
): RequestInit {
|
|
219
|
+
return {
|
|
220
|
+
headers: hfHeaders(country),
|
|
221
|
+
...(revalidate > 0 ? { next: { revalidate } } : { cache: "no-store" as const }),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function stagingJson(
|
|
226
|
+
path: string,
|
|
227
|
+
revalidate: number,
|
|
228
|
+
country: MicrocosmCountry,
|
|
229
|
+
revisionOverride?: string,
|
|
230
|
+
): Promise<JsonObject> {
|
|
231
|
+
const repository = stagingSource(country, revisionOverride);
|
|
232
|
+
const res = await fetch(
|
|
233
|
+
stagingResolveUrlFor(repository, path),
|
|
234
|
+
stagingFetchOptions(revalidate, country),
|
|
235
|
+
);
|
|
236
|
+
if (!res.ok) throw new StagingFetchError(res.status, path, repository);
|
|
237
|
+
return asObject(await res.json());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function stagingJsonOrNull(
|
|
241
|
+
path: string,
|
|
242
|
+
revalidate: number,
|
|
243
|
+
country: MicrocosmCountry,
|
|
244
|
+
revisionOverride?: string,
|
|
245
|
+
): Promise<JsonObject | null> {
|
|
246
|
+
try {
|
|
247
|
+
return await stagingJson(path, revalidate, country, revisionOverride);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error instanceof StagingFetchError && error.status !== 404) throw error;
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface HashedStagingJson {
|
|
255
|
+
payload: JsonObject;
|
|
256
|
+
sha256: string;
|
|
257
|
+
source: CalibrationTreeSourceArtifact;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function stagingHashedJsonOrNull(
|
|
261
|
+
path: string,
|
|
262
|
+
revalidate: number,
|
|
263
|
+
country: MicrocosmCountry,
|
|
264
|
+
revisionOverride?: string,
|
|
265
|
+
): Promise<HashedStagingJson | null> {
|
|
266
|
+
const repository = stagingSource(country, revisionOverride);
|
|
267
|
+
const res = await fetch(
|
|
268
|
+
stagingResolveUrlFor(repository, path),
|
|
269
|
+
stagingFetchOptions(revalidate, country),
|
|
270
|
+
);
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
if (res.status === 404) return null;
|
|
273
|
+
throw new StagingFetchError(res.status, path, repository);
|
|
274
|
+
}
|
|
275
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
276
|
+
let parsed: unknown;
|
|
277
|
+
try {
|
|
278
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
279
|
+
} catch {
|
|
280
|
+
throw new IncompatibleStagingDataError(`${path} is not valid JSON.`);
|
|
281
|
+
}
|
|
282
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
283
|
+
throw new IncompatibleStagingDataError(`${path} must contain a JSON object.`);
|
|
284
|
+
}
|
|
285
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
286
|
+
return {
|
|
287
|
+
payload: parsed as JsonObject,
|
|
288
|
+
sha256,
|
|
289
|
+
source: {
|
|
290
|
+
path,
|
|
291
|
+
sha256,
|
|
292
|
+
hfRepo: repository.repo,
|
|
293
|
+
hfCommitSha: repository.revision,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function stagingTextOrNull(
|
|
299
|
+
path: string,
|
|
300
|
+
revalidate: number,
|
|
301
|
+
country: MicrocosmCountry,
|
|
302
|
+
revisionOverride?: string,
|
|
303
|
+
): Promise<string | null> {
|
|
304
|
+
const repository = stagingSource(country, revisionOverride);
|
|
305
|
+
const res = await fetch(
|
|
306
|
+
stagingResolveUrlFor(repository, path),
|
|
307
|
+
stagingFetchOptions(revalidate, country),
|
|
308
|
+
);
|
|
309
|
+
if (!res.ok) {
|
|
310
|
+
if (res.status === 404) return null;
|
|
311
|
+
throw new StagingFetchError(res.status, path, repository);
|
|
312
|
+
}
|
|
313
|
+
return res.text();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function stagingTree(
|
|
317
|
+
revalidate: number,
|
|
318
|
+
country: MicrocosmCountry,
|
|
319
|
+
revisionOverride?: string,
|
|
320
|
+
): Promise<JsonObject[]> {
|
|
321
|
+
const repository = stagingSource(country, revisionOverride);
|
|
322
|
+
const entries: JsonObject[] = [];
|
|
323
|
+
const visited = new Set<string>();
|
|
324
|
+
let url: string | null = stagingTreeUrl(repository);
|
|
325
|
+
while (url) {
|
|
326
|
+
if (visited.has(url)) {
|
|
327
|
+
throw new Error("Staging repository tree pagination repeated a page URL.");
|
|
328
|
+
}
|
|
329
|
+
visited.add(url);
|
|
330
|
+
const res: Response = await fetch(
|
|
331
|
+
url,
|
|
332
|
+
stagingFetchOptions(revalidate, country),
|
|
333
|
+
);
|
|
334
|
+
if (!res.ok) throw new StagingFetchError(res.status, "runs tree", repository);
|
|
335
|
+
const tree: unknown = await res.json();
|
|
336
|
+
if (!Array.isArray(tree)) {
|
|
337
|
+
throw new IncompatibleStagingDataError(
|
|
338
|
+
"runs tree response must be an array.",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
entries.push(...tree.map(asObject));
|
|
342
|
+
const link = res.headers.get("link") ?? "";
|
|
343
|
+
const next = /<([^>]+)>;\s*rel="next"/.exec(link);
|
|
344
|
+
url = next ? next[1] : null;
|
|
345
|
+
}
|
|
346
|
+
return entries;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface StagingRunSummary {
|
|
350
|
+
run_id: string;
|
|
351
|
+
candidate_release_id: string | null;
|
|
352
|
+
release_id: string | null;
|
|
353
|
+
country_code: string | null;
|
|
354
|
+
run_kind: string | null;
|
|
355
|
+
non_release: boolean | null;
|
|
356
|
+
schema_version: StagingSchemaVersion | null;
|
|
357
|
+
status: string | null;
|
|
358
|
+
stage: string | null;
|
|
359
|
+
started_at: string | null;
|
|
360
|
+
updated_at: string | null;
|
|
361
|
+
progress_path: string;
|
|
362
|
+
run_manifest_path: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface IncompatibleStagingRun {
|
|
366
|
+
run_id: string;
|
|
367
|
+
run_manifest_path: string;
|
|
368
|
+
detail: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface StagingRunDetail {
|
|
372
|
+
available: boolean;
|
|
373
|
+
source_repo: string | null;
|
|
374
|
+
revision: string | null;
|
|
375
|
+
detail?: string | null;
|
|
376
|
+
run_id: string;
|
|
377
|
+
candidate_release_id: string | null;
|
|
378
|
+
release_id: string | null;
|
|
379
|
+
country_code: string | null;
|
|
380
|
+
run_kind: string | null;
|
|
381
|
+
non_release: boolean | null;
|
|
382
|
+
schema_version: StagingSchemaVersion | null;
|
|
383
|
+
delivery: JsonObject | null;
|
|
384
|
+
progress: JsonObject | null;
|
|
385
|
+
run_manifest: JsonObject | null;
|
|
386
|
+
calibration_progress: JsonObject | null;
|
|
387
|
+
events: JsonObject[];
|
|
388
|
+
has_calibration: boolean;
|
|
389
|
+
calibration: ReturnType<typeof latestMicrocosmCalibrationSummary> | null;
|
|
390
|
+
reform_validation: ReformValidation | null;
|
|
391
|
+
build_manifest: JsonObject | null;
|
|
392
|
+
release_manifest: JsonObject | null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function stringValue(value: unknown): string | null {
|
|
396
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function booleanValue(value: unknown): boolean | null {
|
|
400
|
+
return typeof value === "boolean" ? value : null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function schemaVersionValue(value: unknown): StagingSchemaVersion | null {
|
|
404
|
+
return value === 1 || value === 2 || value === 3 ? value : null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function objectOrNull(value: unknown): JsonObject | null {
|
|
408
|
+
return value != null && typeof value === "object" && !Array.isArray(value)
|
|
409
|
+
? (value as JsonObject)
|
|
410
|
+
: null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function stagingTargetChangeCacheTtlSeconds(status: unknown): number {
|
|
414
|
+
return ["passed", "published", "completed", "blocked", "failed"].includes(
|
|
415
|
+
String(status ?? "").trim(),
|
|
416
|
+
)
|
|
417
|
+
? TARGET_CHANGE_FINAL_CACHE_SECONDS
|
|
418
|
+
: TARGET_CHANGE_MUTABLE_CACHE_SECONDS;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function summaryFromProgress(runId: string, progress: JsonObject | null): StagingRunSummary {
|
|
422
|
+
const candidate = stringValue(progress?.candidate_release_id);
|
|
423
|
+
return {
|
|
424
|
+
run_id: runId,
|
|
425
|
+
candidate_release_id: candidate,
|
|
426
|
+
release_id: stringValue(progress?.release_id),
|
|
427
|
+
country_code: stringValue(progress?.country_code),
|
|
428
|
+
run_kind: stringValue(progress?.run_kind),
|
|
429
|
+
non_release: booleanValue(progress?.non_release),
|
|
430
|
+
schema_version: schemaVersionValue(progress?.schema_version),
|
|
431
|
+
status: stringValue(progress?.status),
|
|
432
|
+
stage: stringValue(progress?.stage),
|
|
433
|
+
started_at: stringValue(progress?.started_at),
|
|
434
|
+
updated_at: stringValue(progress?.updated_at),
|
|
435
|
+
progress_path: `runs/${runId}/progress.json`,
|
|
436
|
+
run_manifest_path: `runs/${runId}/run_manifest.json`,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function summaryFromManifest(
|
|
441
|
+
runId: string,
|
|
442
|
+
manifest: JsonObject,
|
|
443
|
+
): StagingRunSummary {
|
|
444
|
+
return {
|
|
445
|
+
run_id: runId,
|
|
446
|
+
candidate_release_id: stringValue(manifest.candidate_release_id),
|
|
447
|
+
release_id: stringValue(manifest.release_id),
|
|
448
|
+
country_code: stringValue(manifest.country_code),
|
|
449
|
+
run_kind: stringValue(manifest.run_kind),
|
|
450
|
+
non_release: booleanValue(manifest.non_release),
|
|
451
|
+
schema_version: schemaVersionValue(manifest.schema_version),
|
|
452
|
+
status: stringValue(manifest.status),
|
|
453
|
+
stage: stringValue(manifest.stage),
|
|
454
|
+
started_at: stringValue(manifest.started_at),
|
|
455
|
+
updated_at: stringValue(manifest.updated_at),
|
|
456
|
+
progress_path: `runs/${runId}/progress.json`,
|
|
457
|
+
run_manifest_path: `runs/${runId}/run_manifest.json`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function sortRuns(a: StagingRunSummary, b: StagingRunSummary): number {
|
|
462
|
+
return (
|
|
463
|
+
compareTimestampsDescending(a.updated_at, b.updated_at) ||
|
|
464
|
+
compareTimestampsDescending(a.started_at, b.started_at) ||
|
|
465
|
+
b.run_id.localeCompare(a.run_id)
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function compareTimestampsDescending(
|
|
470
|
+
a: string | null,
|
|
471
|
+
b: string | null,
|
|
472
|
+
): number {
|
|
473
|
+
const aTime = a == null ? Number.NEGATIVE_INFINITY : Date.parse(a);
|
|
474
|
+
const bTime = b == null ? Number.NEGATIVE_INFINITY : Date.parse(b);
|
|
475
|
+
const normalizedA = Number.isNaN(aTime) ? Number.NEGATIVE_INFINITY : aTime;
|
|
476
|
+
const normalizedB = Number.isNaN(bTime) ? Number.NEGATIVE_INFINITY : bTime;
|
|
477
|
+
if (normalizedA === normalizedB) return 0;
|
|
478
|
+
return normalizedB > normalizedA ? 1 : -1;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const RUN_PATH = /^runs\/([A-Za-z0-9][A-Za-z0-9._-]*)\//;
|
|
482
|
+
const RUN_MANIFEST_PATH =
|
|
483
|
+
/^runs\/([A-Za-z0-9][A-Za-z0-9._-]*)\/run_manifest\.json$/;
|
|
484
|
+
|
|
485
|
+
export async function loadStagingRuns(
|
|
486
|
+
revalidate: number,
|
|
487
|
+
country: MicrocosmCountry = "us",
|
|
488
|
+
revisionOverride?: string,
|
|
489
|
+
) {
|
|
490
|
+
const unavailable = unavailableStaging(country);
|
|
491
|
+
if (unavailable) {
|
|
492
|
+
return {
|
|
493
|
+
...unavailable,
|
|
494
|
+
truncated: false,
|
|
495
|
+
runs: [] as StagingRunSummary[],
|
|
496
|
+
incompatible_runs: [] as IncompatibleStagingRun[],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const repository = stagingSource(country, revisionOverride);
|
|
500
|
+
let treeMissing = false;
|
|
501
|
+
let tree: JsonObject[] = [];
|
|
502
|
+
try {
|
|
503
|
+
tree = await stagingTree(revalidate, country, revisionOverride);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!(error instanceof StagingFetchError) || error.status !== 404) throw error;
|
|
506
|
+
// A staging repo may exist before any tree listing is public.
|
|
507
|
+
treeMissing = true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const manifestPaths = new Map<string, string>();
|
|
511
|
+
const listedRunIds = new Set<string>();
|
|
512
|
+
for (const entry of tree) {
|
|
513
|
+
if (entry.type !== "file" || typeof entry.path !== "string") continue;
|
|
514
|
+
const runMatch = RUN_PATH.exec(entry.path);
|
|
515
|
+
if (runMatch) listedRunIds.add(runMatch[1]);
|
|
516
|
+
const manifestMatch = RUN_MANIFEST_PATH.exec(entry.path);
|
|
517
|
+
if (manifestMatch) manifestPaths.set(manifestMatch[1], entry.path);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const manifestResults = await Promise.all(
|
|
521
|
+
[...manifestPaths].map(async ([runId, path]) => {
|
|
522
|
+
try {
|
|
523
|
+
const manifest = parseStagingManifest(
|
|
524
|
+
await stagingJson(path, revalidate, country, revisionOverride),
|
|
525
|
+
);
|
|
526
|
+
if (manifest.run_id !== runId) {
|
|
527
|
+
throw new IncompatibleStagingDataError(
|
|
528
|
+
`run manifest id ${String(manifest.run_id)} does not match directory ${runId}.`,
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
validateStagingRunConsistency(runId, { runManifest: manifest });
|
|
532
|
+
return { manifest, problem: null };
|
|
533
|
+
} catch (error) {
|
|
534
|
+
return {
|
|
535
|
+
manifest: null,
|
|
536
|
+
problem: {
|
|
537
|
+
run_id: runId,
|
|
538
|
+
run_manifest_path: path,
|
|
539
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
540
|
+
} satisfies IncompatibleStagingRun,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
const listedManifests = manifestResults.flatMap(({ manifest }) =>
|
|
546
|
+
manifest == null ? [] : [manifest],
|
|
547
|
+
);
|
|
548
|
+
const incompatibleRuns = manifestResults.flatMap(({ problem }) =>
|
|
549
|
+
problem == null ? [] : [problem],
|
|
550
|
+
);
|
|
551
|
+
const structuredManifests = listedManifests.filter(isStructuredStagingDocument);
|
|
552
|
+
const structuredManifestIds = new Set(
|
|
553
|
+
structuredManifests.map((manifest) => String(manifest.run_id)),
|
|
554
|
+
);
|
|
555
|
+
const v1ManifestIds = new Set(
|
|
556
|
+
listedManifests
|
|
557
|
+
.filter((manifest) => manifest.schema_version === 1)
|
|
558
|
+
.map((manifest) => String(manifest.run_id)),
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
let index: JsonObject | null = null;
|
|
562
|
+
let indexedRuns: StagingRunSummary[] = [];
|
|
563
|
+
if (treeMissing || v1ManifestIds.size > 0) {
|
|
564
|
+
index = await stagingJsonOrNull(
|
|
565
|
+
"runs.json",
|
|
566
|
+
revalidate,
|
|
567
|
+
country,
|
|
568
|
+
revisionOverride,
|
|
569
|
+
);
|
|
570
|
+
if (index?.schema_version === 1) indexedRuns = parseStagingRunIndex(index);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// HF answers 404 (not 401/403) for private repos when auth is missing or
|
|
574
|
+
// expired, so "everything 404'd" is ambiguous between "no runs yet" and "we
|
|
575
|
+
// can't see the repo". Disambiguate via the repo API before reporting an
|
|
576
|
+
// empty list — a silent empty state hides a broken token.
|
|
577
|
+
if (index == null && treeMissing && listedManifests.length === 0) {
|
|
578
|
+
const repoRes = await fetch(
|
|
579
|
+
stagingRepoUrl(repository),
|
|
580
|
+
stagingFetchOptions(revalidate, country),
|
|
581
|
+
);
|
|
582
|
+
if (!repoRes.ok) {
|
|
583
|
+
return {
|
|
584
|
+
available: false,
|
|
585
|
+
source_repo: repository.repo,
|
|
586
|
+
revision: repository.revision,
|
|
587
|
+
detail:
|
|
588
|
+
`Staging repository ${repository.repo} is not visible (HTTP ${repoRes.status}). ` +
|
|
589
|
+
"It is private — a missing or expired HF token reads as 404, not 401.",
|
|
590
|
+
runs: [],
|
|
591
|
+
incompatible_runs: incompatibleRuns,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const byId = new Map<string, StagingRunSummary>(
|
|
597
|
+
indexedRuns.map((run) => [run.run_id, run]),
|
|
598
|
+
);
|
|
599
|
+
const v1RunIds = new Set(v1ManifestIds);
|
|
600
|
+
if (index?.schema_version === 1) {
|
|
601
|
+
for (const runId of listedRunIds) {
|
|
602
|
+
if (!structuredManifestIds.has(runId)) v1RunIds.add(runId);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const missing = [...v1RunIds]
|
|
606
|
+
.filter((runId) => !byId.has(runId))
|
|
607
|
+
.sort((a, b) => (a < b ? 1 : a > b ? -1 : 0));
|
|
608
|
+
const MAX_UNINDEXED_FETCH = 50;
|
|
609
|
+
const fetched = await Promise.all(
|
|
610
|
+
missing.slice(0, MAX_UNINDEXED_FETCH).map(async (runId) => {
|
|
611
|
+
const raw = await stagingJsonOrNull(
|
|
612
|
+
`runs/${runId}/progress.json`,
|
|
613
|
+
revalidate,
|
|
614
|
+
country,
|
|
615
|
+
revisionOverride,
|
|
616
|
+
);
|
|
617
|
+
const progress = raw == null ? null : parseStagingProgress(raw);
|
|
618
|
+
validateStagingRunConsistency(runId, { progress });
|
|
619
|
+
return summaryFromProgress(runId, progress);
|
|
620
|
+
}),
|
|
621
|
+
);
|
|
622
|
+
for (const run of fetched) byId.set(run.run_id, run);
|
|
623
|
+
for (const manifest of structuredManifests) {
|
|
624
|
+
const runId = String(manifest.run_id);
|
|
625
|
+
byId.set(runId, summaryFromManifest(runId, manifest));
|
|
626
|
+
}
|
|
627
|
+
const truncated = missing.length > MAX_UNINDEXED_FETCH;
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
available: true,
|
|
631
|
+
source_repo: repository.repo,
|
|
632
|
+
revision: repository.revision,
|
|
633
|
+
truncated,
|
|
634
|
+
runs: [...byId.values()].sort(sortRuns),
|
|
635
|
+
incompatible_runs: incompatibleRuns,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// A build that staged its finished dataset bundle records where it went in a
|
|
640
|
+
// reviewed `staged_dataset` telemetry artifact: the release repository, the
|
|
641
|
+
// `staged/<run_id>/` prefix, the commit and every file's digest. The bundle's
|
|
642
|
+
// `calibration_diagnostics.json` is the same per-target artifact a release
|
|
643
|
+
// carries, so an unreleased candidate can be inspected through the same views.
|
|
644
|
+
const STAGED_BUNDLE_DIAGNOSTICS_FILE = "calibration_diagnostics.json";
|
|
645
|
+
// Microcosm's UK local lane stages its outputs under the dataset's own stem,
|
|
646
|
+
// so its per-target diagnostics arrive as `<dataset>.diagnostics.json`.
|
|
647
|
+
const STAGED_BUNDLE_STEM_DIAGNOSTICS_SUFFIX = ".diagnostics.json";
|
|
648
|
+
// A recorded file name is joined to the staged prefix in an authenticated
|
|
649
|
+
// release-repository URL, so it must be one path segment.
|
|
650
|
+
const STAGED_BUNDLE_FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,200}$/;
|
|
651
|
+
const STAGED_BUNDLE_LANDED = new Set(["uploaded", "already_staged"]);
|
|
652
|
+
const STAGED_BUNDLE_PREFIX = /^staged\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
653
|
+
const GIT_COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
654
|
+
const SHA_256_HEX = /^[0-9a-f]{64}$/;
|
|
655
|
+
|
|
656
|
+
export interface ResolvedStagingCalibrationSource {
|
|
657
|
+
calibration: Calibration;
|
|
658
|
+
candidateReleaseId: string;
|
|
659
|
+
updatedAt: string | null;
|
|
660
|
+
stagingRepo: string;
|
|
661
|
+
stagingRevision: string;
|
|
662
|
+
sourceArtifacts: {
|
|
663
|
+
calibrationDiagnostics: CalibrationTreeSourceArtifact;
|
|
664
|
+
buildManifest: CalibrationTreeSourceArtifact | null;
|
|
665
|
+
releaseManifest: CalibrationTreeSourceArtifact | null;
|
|
666
|
+
stagingRunManifest: CalibrationTreeSourceArtifact | null;
|
|
667
|
+
stagingProgress: CalibrationTreeSourceArtifact | null;
|
|
668
|
+
stagedDatasetReceipt: CalibrationTreeSourceArtifact | null;
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// The bundle's per-target diagnostics file among the files its receipt
|
|
673
|
+
// recorded: `calibration_diagnostics.json`, or else the one
|
|
674
|
+
// `<dataset>.diagnostics.json`. A bundle recording none, several stem
|
|
675
|
+
// files, or a stem name that is not one path segment has no target view
|
|
676
|
+
// rather than a guessed one. The count includes invalid names, so an
|
|
677
|
+
// invalid name beside a valid one still makes the receipt ambiguous.
|
|
678
|
+
export function stagedBundleDiagnosticsFile(files: JsonObject | null): string | null {
|
|
679
|
+
if (!files) return null;
|
|
680
|
+
if (Object.hasOwn(files, STAGED_BUNDLE_DIAGNOSTICS_FILE)) {
|
|
681
|
+
return STAGED_BUNDLE_DIAGNOSTICS_FILE;
|
|
682
|
+
}
|
|
683
|
+
const stemFiles = Object.keys(files).filter((name) =>
|
|
684
|
+
name.endsWith(STAGED_BUNDLE_STEM_DIAGNOSTICS_SUFFIX)
|
|
685
|
+
);
|
|
686
|
+
return stemFiles.length === 1 && STAGED_BUNDLE_FILE_NAME.test(stemFiles[0])
|
|
687
|
+
? stemFiles[0]
|
|
688
|
+
: null;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async function loadStagedBundleCalibrationSource(
|
|
692
|
+
runId: string,
|
|
693
|
+
runManifest: JsonObject | null,
|
|
694
|
+
progress: JsonObject | null,
|
|
695
|
+
candidateReleaseId: string,
|
|
696
|
+
revalidate: number,
|
|
697
|
+
country: MicrocosmCountry,
|
|
698
|
+
stagingRevision: string,
|
|
699
|
+
runManifestSource: CalibrationTreeSourceArtifact | null,
|
|
700
|
+
progressSource: CalibrationTreeSourceArtifact | null,
|
|
701
|
+
): Promise<ResolvedStagingCalibrationSource | null> {
|
|
702
|
+
const staging = stagingSource(country, stagingRevision);
|
|
703
|
+
const artifacts = objectOrNull(runManifest?.artifacts);
|
|
704
|
+
const stagedArtifact = objectOrNull(artifacts?.staged_dataset);
|
|
705
|
+
const receiptPath = stringValue(stagedArtifact?.staging_path);
|
|
706
|
+
if (!receiptPath) return null;
|
|
707
|
+
const receipt = await stagingHashedJsonOrNull(
|
|
708
|
+
receiptPath,
|
|
709
|
+
revalidate,
|
|
710
|
+
country,
|
|
711
|
+
stagingRevision,
|
|
712
|
+
);
|
|
713
|
+
if (!receipt) {
|
|
714
|
+
throw new IncompatibleStagingDataError(
|
|
715
|
+
"artifact staged_dataset is declared but its receipt is absent.",
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
if (receipt.sha256 !== stringValue(stagedArtifact?.sha256)) {
|
|
719
|
+
throw new IncompatibleStagingDataError(
|
|
720
|
+
"artifact staged_dataset digest does not match its run manifest declaration.",
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const delivery = receipt.payload;
|
|
724
|
+
// A skipped, failed or disabled staging leaves nothing to read.
|
|
725
|
+
if (!STAGED_BUNDLE_LANDED.has(String(delivery.status))) return null;
|
|
726
|
+
if (stringValue(delivery.run_id) !== runId) {
|
|
727
|
+
throw new IncompatibleStagingDataError("staged dataset receipt names another run.");
|
|
728
|
+
}
|
|
729
|
+
const repository = stringValue(delivery.repository);
|
|
730
|
+
const prefix = stringValue(delivery.prefix);
|
|
731
|
+
const revision = stringValue(delivery.revision);
|
|
732
|
+
if (!repository || !prefix || !revision) {
|
|
733
|
+
throw new IncompatibleStagingDataError(
|
|
734
|
+
"staged dataset receipt is missing its repository, prefix or revision.",
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
// The release credential is only ever sent to the country's registered
|
|
738
|
+
// release repository, never to a repository a telemetry file names.
|
|
739
|
+
if (repository !== microcosmRepo(country)) {
|
|
740
|
+
throw new IncompatibleStagingDataError(
|
|
741
|
+
`staged dataset repository ${repository} is not ${microcosmCountryGeography(country)}'s ` +
|
|
742
|
+
"registered release repository; staged bundles are read only from there.",
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
if (!STAGED_BUNDLE_PREFIX.test(prefix) || prefix !== `staged/${runId}`) {
|
|
746
|
+
throw new IncompatibleStagingDataError("staged dataset prefix is not staged/<run_id>.");
|
|
747
|
+
}
|
|
748
|
+
if (!GIT_COMMIT_SHA.test(revision)) {
|
|
749
|
+
throw new IncompatibleStagingDataError("staged dataset revision is not a commit sha.");
|
|
750
|
+
}
|
|
751
|
+
const files = objectOrNull(delivery.files);
|
|
752
|
+
const diagnosticsFile = stagedBundleDiagnosticsFile(files);
|
|
753
|
+
// A bundle without calibration diagnostics (a spine, say) has no target view.
|
|
754
|
+
if (!diagnosticsFile) return null;
|
|
755
|
+
const declared = objectOrNull(files?.[diagnosticsFile]);
|
|
756
|
+
const expectedDigest = stringValue(declared?.sha256);
|
|
757
|
+
if (!expectedDigest || !SHA_256_HEX.test(expectedDigest)) return null;
|
|
758
|
+
const diagnostics = await loadCountryRepositoryHashedJson(
|
|
759
|
+
`${prefix}/${diagnosticsFile}`,
|
|
760
|
+
revalidate,
|
|
761
|
+
country,
|
|
762
|
+
revision,
|
|
763
|
+
);
|
|
764
|
+
if (!diagnostics) {
|
|
765
|
+
throw new IncompatibleStagingDataError(
|
|
766
|
+
`staged bundle ${prefix} records ${diagnosticsFile} but the file is absent at revision ${revision}.`,
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (diagnostics.sha256 !== expectedDigest) {
|
|
770
|
+
throw new IncompatibleStagingDataError(
|
|
771
|
+
`staged bundle ${diagnosticsFile} does not match the digest the run recorded.`,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const updatedAt = stringValue(progress?.updated_at) ??
|
|
775
|
+
stringValue(runManifest?.updated_at);
|
|
776
|
+
const calibration = buildCalibration(
|
|
777
|
+
diagnostics.payload,
|
|
778
|
+
candidateReleaseId,
|
|
779
|
+
updatedAt,
|
|
780
|
+
{},
|
|
781
|
+
{},
|
|
782
|
+
{},
|
|
783
|
+
country,
|
|
784
|
+
"huggingface_staged_bundle",
|
|
785
|
+
diagnostics.sha256,
|
|
786
|
+
revision,
|
|
787
|
+
);
|
|
788
|
+
return {
|
|
789
|
+
calibration,
|
|
790
|
+
candidateReleaseId,
|
|
791
|
+
updatedAt,
|
|
792
|
+
stagingRepo: staging.repo,
|
|
793
|
+
stagingRevision,
|
|
794
|
+
sourceArtifacts: {
|
|
795
|
+
calibrationDiagnostics: {
|
|
796
|
+
path: `${prefix}/${diagnosticsFile}`,
|
|
797
|
+
sha256: diagnostics.sha256,
|
|
798
|
+
hfRepo: repository,
|
|
799
|
+
hfCommitSha: revision,
|
|
800
|
+
},
|
|
801
|
+
buildManifest: null,
|
|
802
|
+
releaseManifest: null,
|
|
803
|
+
stagingRunManifest: runManifestSource,
|
|
804
|
+
stagingProgress: progressSource,
|
|
805
|
+
stagedDatasetReceipt: receipt.source,
|
|
806
|
+
},
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
export async function loadStagingCalibration(
|
|
811
|
+
runId: string,
|
|
812
|
+
revalidate: number,
|
|
813
|
+
country: MicrocosmCountry = "us",
|
|
814
|
+
): Promise<Calibration | null> {
|
|
815
|
+
if (revalidate <= 0) {
|
|
816
|
+
return loadStagingCalibrationUncached(runId, revalidate, country);
|
|
817
|
+
}
|
|
818
|
+
const cacheKey = `${country}:${runId}:${revalidate}`;
|
|
819
|
+
const now = Date.now();
|
|
820
|
+
for (const [key, entry] of stagingCalibrationCache) {
|
|
821
|
+
if (entry.expiresAt <= now) stagingCalibrationCache.delete(key);
|
|
822
|
+
}
|
|
823
|
+
const cached = stagingCalibrationCache.get(cacheKey);
|
|
824
|
+
if (cached && cached.expiresAt > now) return cached.promise;
|
|
825
|
+
|
|
826
|
+
const promise = loadStagingCalibrationUncached(runId, revalidate, country);
|
|
827
|
+
stagingCalibrationCache.set(cacheKey, {
|
|
828
|
+
promise,
|
|
829
|
+
expiresAt: now + revalidate * 1000,
|
|
830
|
+
});
|
|
831
|
+
while (stagingCalibrationCache.size > STAGING_CALIBRATION_CACHE_LIMIT) {
|
|
832
|
+
const oldest = stagingCalibrationCache.keys().next().value;
|
|
833
|
+
if (typeof oldest !== "string") break;
|
|
834
|
+
stagingCalibrationCache.delete(oldest);
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
const result = await promise;
|
|
838
|
+
if (!result) stagingCalibrationCache.delete(cacheKey);
|
|
839
|
+
return result;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
stagingCalibrationCache.delete(cacheKey);
|
|
842
|
+
throw error;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function loadStagingCalibrationUncached(
|
|
847
|
+
runId: string,
|
|
848
|
+
revalidate: number,
|
|
849
|
+
country: MicrocosmCountry,
|
|
850
|
+
): Promise<Calibration | null> {
|
|
851
|
+
const source = await resolveStagingCalibrationSource(
|
|
852
|
+
runId,
|
|
853
|
+
revalidate,
|
|
854
|
+
country,
|
|
855
|
+
);
|
|
856
|
+
return source?.calibration ?? null;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export async function resolveStagingCalibrationSource(
|
|
860
|
+
runId: string,
|
|
861
|
+
revalidate: number,
|
|
862
|
+
country: MicrocosmCountry = "us",
|
|
863
|
+
revisionOverride?: string,
|
|
864
|
+
): Promise<ResolvedStagingCalibrationSource | null> {
|
|
865
|
+
if (stagingUnavailableReason(country)) return null;
|
|
866
|
+
assertSafeReleaseId(runId, "run");
|
|
867
|
+
const staging = stagingSource(country, revisionOverride);
|
|
868
|
+
const [progressArtifact, runManifestArtifact] = await Promise.all([
|
|
869
|
+
stagingHashedJsonOrNull(
|
|
870
|
+
`runs/${runId}/progress.json`,
|
|
871
|
+
revalidate,
|
|
872
|
+
country,
|
|
873
|
+
staging.revision,
|
|
874
|
+
),
|
|
875
|
+
stagingHashedJsonOrNull(
|
|
876
|
+
`runs/${runId}/run_manifest.json`,
|
|
877
|
+
revalidate,
|
|
878
|
+
country,
|
|
879
|
+
staging.revision,
|
|
880
|
+
),
|
|
881
|
+
]);
|
|
882
|
+
const progress = progressArtifact == null
|
|
883
|
+
? null
|
|
884
|
+
: parseStagingProgress(progressArtifact.payload);
|
|
885
|
+
const runManifest =
|
|
886
|
+
runManifestArtifact == null
|
|
887
|
+
? null
|
|
888
|
+
: parseStagingManifest(runManifestArtifact.payload);
|
|
889
|
+
validateStagingRunConsistency(runId, { progress, runManifest });
|
|
890
|
+
const candidateReleaseId =
|
|
891
|
+
stringValue(progress?.candidate_release_id) ??
|
|
892
|
+
stringValue(runManifest?.candidate_release_id) ??
|
|
893
|
+
runId;
|
|
894
|
+
const artifacts = objectOrNull(runManifest?.artifacts);
|
|
895
|
+
const diagnosticsArtifact = objectOrNull(artifacts?.calibration_diagnostics);
|
|
896
|
+
const structured = isStructuredStagingDocument(runManifest);
|
|
897
|
+
const diagnosticsPath = structured
|
|
898
|
+
? stringValue(diagnosticsArtifact?.staging_path)
|
|
899
|
+
: `runs/${runId}/calibration_diagnostics.json`;
|
|
900
|
+
if (!diagnosticsPath) {
|
|
901
|
+
// No telemetry diagnostics artifact: a run that staged its dataset bundle
|
|
902
|
+
// carries the same diagnostics there.
|
|
903
|
+
return loadStagedBundleCalibrationSource(
|
|
904
|
+
runId,
|
|
905
|
+
runManifest,
|
|
906
|
+
progress,
|
|
907
|
+
candidateReleaseId,
|
|
908
|
+
revalidate,
|
|
909
|
+
country,
|
|
910
|
+
staging.revision,
|
|
911
|
+
runManifestArtifact?.source ?? null,
|
|
912
|
+
progressArtifact?.source ?? null,
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
const diagnostics = await stagingHashedJsonOrNull(
|
|
916
|
+
diagnosticsPath,
|
|
917
|
+
revalidate,
|
|
918
|
+
country,
|
|
919
|
+
staging.revision,
|
|
920
|
+
);
|
|
921
|
+
if (!diagnostics) {
|
|
922
|
+
if (structured && diagnosticsArtifact) {
|
|
923
|
+
throw new IncompatibleStagingDataError(
|
|
924
|
+
"artifact calibration_diagnostics is declared but absent.",
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const expectedDigest = stringValue(diagnosticsArtifact?.sha256);
|
|
930
|
+
if (structured && diagnostics.sha256 !== expectedDigest) {
|
|
931
|
+
throw new IncompatibleStagingDataError(
|
|
932
|
+
`artifact calibration_diagnostics digest does not match its run manifest declaration.`,
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const [buildManifest, releaseManifest] = await Promise.all([
|
|
936
|
+
stagingHashedJsonOrNull(
|
|
937
|
+
`runs/${runId}/build_manifest.json`,
|
|
938
|
+
revalidate,
|
|
939
|
+
country,
|
|
940
|
+
staging.revision,
|
|
941
|
+
),
|
|
942
|
+
stagingHashedJsonOrNull(
|
|
943
|
+
`runs/${runId}/release_manifest.json`,
|
|
944
|
+
revalidate,
|
|
945
|
+
country,
|
|
946
|
+
staging.revision,
|
|
947
|
+
),
|
|
948
|
+
]);
|
|
949
|
+
const updatedAt = stringValue(progress?.updated_at) ??
|
|
950
|
+
stringValue(runManifest?.updated_at);
|
|
951
|
+
const calibration = buildCalibration(
|
|
952
|
+
diagnostics.payload,
|
|
953
|
+
candidateReleaseId,
|
|
954
|
+
updatedAt,
|
|
955
|
+
buildManifest?.payload ?? {},
|
|
956
|
+
releaseManifest?.payload ?? {},
|
|
957
|
+
{},
|
|
958
|
+
country,
|
|
959
|
+
"huggingface_live",
|
|
960
|
+
diagnostics.sha256,
|
|
961
|
+
staging.revision,
|
|
962
|
+
);
|
|
963
|
+
return {
|
|
964
|
+
calibration,
|
|
965
|
+
candidateReleaseId,
|
|
966
|
+
updatedAt,
|
|
967
|
+
stagingRepo: staging.repo,
|
|
968
|
+
stagingRevision: staging.revision,
|
|
969
|
+
sourceArtifacts: {
|
|
970
|
+
calibrationDiagnostics: diagnostics.source,
|
|
971
|
+
buildManifest: buildManifest?.source ?? null,
|
|
972
|
+
releaseManifest: releaseManifest?.source ?? null,
|
|
973
|
+
stagingRunManifest: runManifestArtifact?.source ?? null,
|
|
974
|
+
stagingProgress: progressArtifact?.source ?? null,
|
|
975
|
+
stagedDatasetReceipt: null,
|
|
976
|
+
},
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export async function loadStagingTargetChangeDataset(
|
|
981
|
+
runId: string,
|
|
982
|
+
releaseId: string,
|
|
983
|
+
country: MicrocosmCountry = "us",
|
|
984
|
+
): Promise<TargetChangeDataset | null> {
|
|
985
|
+
if (stagingUnavailableReason(country)) return null;
|
|
986
|
+
assertSafeReleaseId(runId, "run");
|
|
987
|
+
assertSafeReleaseOrBuildFolderId(releaseId, "release");
|
|
988
|
+
const progress = await stagingJsonOrNull(
|
|
989
|
+
`runs/${runId}/progress.json`,
|
|
990
|
+
TARGET_CHANGE_MUTABLE_CACHE_SECONDS,
|
|
991
|
+
country,
|
|
992
|
+
);
|
|
993
|
+
const parsedProgress = progress == null ? null : parseStagingProgress(progress);
|
|
994
|
+
validateStagingRunConsistency(runId, { progress: parsedProgress });
|
|
995
|
+
const ttlSeconds = stagingTargetChangeCacheTtlSeconds(parsedProgress?.status);
|
|
996
|
+
const cacheKey = `${country}:${runId}:${releaseId}`;
|
|
997
|
+
const now = Date.now();
|
|
998
|
+
for (const [key, entry] of targetChangeCache) {
|
|
999
|
+
if (entry.expiresAt <= now) targetChangeCache.delete(key);
|
|
1000
|
+
}
|
|
1001
|
+
const cached = targetChangeCache.get(cacheKey);
|
|
1002
|
+
if (cached && cached.expiresAt > now) return cached.promise;
|
|
1003
|
+
|
|
1004
|
+
const promise = Promise.all([
|
|
1005
|
+
loadRelease(releaseId, TARGET_CHANGE_FINAL_CACHE_SECONDS, country),
|
|
1006
|
+
loadStagingCalibration(runId, ttlSeconds, country),
|
|
1007
|
+
]).then(([release, candidate]) =>
|
|
1008
|
+
candidate ? buildTargetChangeDataset(release, candidate) : null,
|
|
1009
|
+
);
|
|
1010
|
+
targetChangeCache.set(cacheKey, {
|
|
1011
|
+
expiresAt: now + ttlSeconds * 1000,
|
|
1012
|
+
promise,
|
|
1013
|
+
});
|
|
1014
|
+
while (targetChangeCache.size > TARGET_CHANGE_CACHE_LIMIT) {
|
|
1015
|
+
const oldest = targetChangeCache.keys().next().value;
|
|
1016
|
+
if (typeof oldest !== "string") break;
|
|
1017
|
+
targetChangeCache.delete(oldest);
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const result = await promise;
|
|
1021
|
+
if (!result) targetChangeCache.delete(cacheKey);
|
|
1022
|
+
return result;
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
targetChangeCache.delete(cacheKey);
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
export async function loadStagingTargetChangeDatasetFromBuild(
|
|
1030
|
+
runId: string,
|
|
1031
|
+
currentBuildArtifactId: string,
|
|
1032
|
+
country: MicrocosmCountry = "us",
|
|
1033
|
+
): Promise<TargetChangeDataset | null> {
|
|
1034
|
+
if (stagingUnavailableReason(country)) return null;
|
|
1035
|
+
assertSafeReleaseId(runId, "run");
|
|
1036
|
+
const progress = await stagingJsonOrNull(
|
|
1037
|
+
`runs/${runId}/progress.json`,
|
|
1038
|
+
TARGET_CHANGE_MUTABLE_CACHE_SECONDS,
|
|
1039
|
+
country,
|
|
1040
|
+
);
|
|
1041
|
+
const parsedProgress = progress == null ? null : parseStagingProgress(progress);
|
|
1042
|
+
validateStagingRunConsistency(runId, { progress: parsedProgress });
|
|
1043
|
+
const ttlSeconds = stagingTargetChangeCacheTtlSeconds(parsedProgress?.status);
|
|
1044
|
+
const cacheKey = `${country}:${runId}:build:${currentBuildArtifactId}`;
|
|
1045
|
+
const now = Date.now();
|
|
1046
|
+
for (const [key, entry] of targetChangeCache) {
|
|
1047
|
+
if (entry.expiresAt <= now) targetChangeCache.delete(key);
|
|
1048
|
+
}
|
|
1049
|
+
const cached = targetChangeCache.get(cacheKey);
|
|
1050
|
+
if (cached && cached.expiresAt > now) return cached.promise;
|
|
1051
|
+
|
|
1052
|
+
const promise = Promise.all([
|
|
1053
|
+
loadCalibrationComparisonSource(country, currentBuildArtifactId),
|
|
1054
|
+
loadStagingCalibration(runId, ttlSeconds, country),
|
|
1055
|
+
]).then(([current, candidate]) =>
|
|
1056
|
+
candidate
|
|
1057
|
+
? buildTargetChangeDatasetFromSummaryAndCalibration(
|
|
1058
|
+
{
|
|
1059
|
+
country,
|
|
1060
|
+
comparison: current.index.targetComparison,
|
|
1061
|
+
targets: current.targetSummaries,
|
|
1062
|
+
},
|
|
1063
|
+
candidate,
|
|
1064
|
+
)
|
|
1065
|
+
: null,
|
|
1066
|
+
);
|
|
1067
|
+
targetChangeCache.set(cacheKey, {
|
|
1068
|
+
promise,
|
|
1069
|
+
expiresAt: now + ttlSeconds * 1000,
|
|
1070
|
+
});
|
|
1071
|
+
while (targetChangeCache.size > TARGET_CHANGE_CACHE_LIMIT) {
|
|
1072
|
+
const oldest = targetChangeCache.keys().next().value;
|
|
1073
|
+
if (typeof oldest !== "string") break;
|
|
1074
|
+
targetChangeCache.delete(oldest);
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
const result = await promise;
|
|
1078
|
+
if (!result) targetChangeCache.delete(cacheKey);
|
|
1079
|
+
return result;
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
targetChangeCache.delete(cacheKey);
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export async function loadStagingRun(
|
|
1087
|
+
runId: string,
|
|
1088
|
+
revalidate: number,
|
|
1089
|
+
country: MicrocosmCountry = "us",
|
|
1090
|
+
): Promise<StagingRunDetail> {
|
|
1091
|
+
const unavailable = unavailableStaging(country);
|
|
1092
|
+
if (unavailable) {
|
|
1093
|
+
return {
|
|
1094
|
+
...unavailable,
|
|
1095
|
+
run_id: runId,
|
|
1096
|
+
candidate_release_id: null,
|
|
1097
|
+
release_id: null,
|
|
1098
|
+
country_code: null,
|
|
1099
|
+
run_kind: null,
|
|
1100
|
+
non_release: null,
|
|
1101
|
+
schema_version: null,
|
|
1102
|
+
delivery: null,
|
|
1103
|
+
progress: null,
|
|
1104
|
+
run_manifest: null,
|
|
1105
|
+
calibration_progress: null,
|
|
1106
|
+
events: [],
|
|
1107
|
+
has_calibration: false,
|
|
1108
|
+
calibration: null,
|
|
1109
|
+
reform_validation: null,
|
|
1110
|
+
build_manifest: null,
|
|
1111
|
+
release_manifest: null,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
assertSafeReleaseId(runId, "run");
|
|
1115
|
+
const repository = stagingSource(country);
|
|
1116
|
+
const [
|
|
1117
|
+
progressRaw,
|
|
1118
|
+
runManifestRaw,
|
|
1119
|
+
calibrationProgressRaw,
|
|
1120
|
+
eventsText,
|
|
1121
|
+
cal,
|
|
1122
|
+
reformValidationRaw,
|
|
1123
|
+
] = await Promise.all([
|
|
1124
|
+
stagingJsonOrNull(`runs/${runId}/progress.json`, revalidate, country),
|
|
1125
|
+
stagingJsonOrNull(`runs/${runId}/run_manifest.json`, revalidate, country),
|
|
1126
|
+
stagingJsonOrNull(
|
|
1127
|
+
`runs/${runId}/calibration_progress.json`,
|
|
1128
|
+
revalidate,
|
|
1129
|
+
country,
|
|
1130
|
+
),
|
|
1131
|
+
stagingTextOrNull(`runs/${runId}/events.ndjson`, revalidate, country),
|
|
1132
|
+
loadStagingCalibration(runId, revalidate, country),
|
|
1133
|
+
stagingJsonOrNull(`runs/${runId}/reform_validation.json`, revalidate, country),
|
|
1134
|
+
]);
|
|
1135
|
+
const progress = progressRaw == null ? null : parseStagingProgress(progressRaw);
|
|
1136
|
+
const runManifest =
|
|
1137
|
+
runManifestRaw == null ? null : parseStagingManifest(runManifestRaw);
|
|
1138
|
+
const calibrationProgress =
|
|
1139
|
+
calibrationProgressRaw == null
|
|
1140
|
+
? null
|
|
1141
|
+
: parseStagingCalibrationProgress(calibrationProgressRaw);
|
|
1142
|
+
const events = parseStagingEvents(eventsText);
|
|
1143
|
+
validateStagingRunConsistency(runId, {
|
|
1144
|
+
progress,
|
|
1145
|
+
runManifest,
|
|
1146
|
+
calibrationProgress,
|
|
1147
|
+
events,
|
|
1148
|
+
});
|
|
1149
|
+
const candidateReleaseId =
|
|
1150
|
+
stringValue(progress?.candidate_release_id) ??
|
|
1151
|
+
stringValue(runManifest?.candidate_release_id) ??
|
|
1152
|
+
runId;
|
|
1153
|
+
const identity = progress ?? runManifest;
|
|
1154
|
+
return {
|
|
1155
|
+
available: true,
|
|
1156
|
+
source_repo: repository.repo,
|
|
1157
|
+
revision: repository.revision,
|
|
1158
|
+
run_id: runId,
|
|
1159
|
+
candidate_release_id: candidateReleaseId,
|
|
1160
|
+
release_id: stringValue(identity?.release_id),
|
|
1161
|
+
country_code: stringValue(identity?.country_code),
|
|
1162
|
+
run_kind: stringValue(identity?.run_kind),
|
|
1163
|
+
non_release: booleanValue(identity?.non_release),
|
|
1164
|
+
schema_version: schemaVersionValue(identity?.schema_version),
|
|
1165
|
+
delivery: objectOrNull(identity?.delivery),
|
|
1166
|
+
progress,
|
|
1167
|
+
run_manifest: runManifest,
|
|
1168
|
+
calibration_progress: calibrationProgress,
|
|
1169
|
+
events,
|
|
1170
|
+
has_calibration: cal != null,
|
|
1171
|
+
calibration: cal ? latestMicrocosmCalibrationSummary(cal) : null,
|
|
1172
|
+
reform_validation: reformValidationRaw
|
|
1173
|
+
? buildReformValidation(
|
|
1174
|
+
reformValidationRaw,
|
|
1175
|
+
candidateReleaseId,
|
|
1176
|
+
stringValue(progress?.updated_at),
|
|
1177
|
+
)
|
|
1178
|
+
: null,
|
|
1179
|
+
build_manifest: cal?.build_manifest ?? null,
|
|
1180
|
+
release_manifest: cal?.release_manifest ?? null,
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Telemetry only (progress, manifest, calibration progress, events), without
|
|
1185
|
+
// the candidate diagnostics loadStagingRun also resolves. The build monitor
|
|
1186
|
+
// reads every run this way to time stages across runs.
|
|
1187
|
+
export async function loadStagingRunTelemetry(
|
|
1188
|
+
runId: string,
|
|
1189
|
+
revalidate: number,
|
|
1190
|
+
country: MicrocosmCountry = "us",
|
|
1191
|
+
) {
|
|
1192
|
+
assertSafeReleaseId(runId, "run");
|
|
1193
|
+
const [progressRaw, runManifestRaw, calibrationProgressRaw, eventsText] =
|
|
1194
|
+
await Promise.all([
|
|
1195
|
+
stagingJsonOrNull(`runs/${runId}/progress.json`, revalidate, country),
|
|
1196
|
+
stagingJsonOrNull(`runs/${runId}/run_manifest.json`, revalidate, country),
|
|
1197
|
+
stagingJsonOrNull(`runs/${runId}/calibration_progress.json`, revalidate, country),
|
|
1198
|
+
stagingTextOrNull(`runs/${runId}/events.ndjson`, revalidate, country),
|
|
1199
|
+
]);
|
|
1200
|
+
const progress = progressRaw == null ? null : parseStagingProgress(progressRaw);
|
|
1201
|
+
const runManifest =
|
|
1202
|
+
runManifestRaw == null ? null : parseStagingManifest(runManifestRaw);
|
|
1203
|
+
const calibrationProgress =
|
|
1204
|
+
calibrationProgressRaw == null
|
|
1205
|
+
? null
|
|
1206
|
+
: parseStagingCalibrationProgress(calibrationProgressRaw);
|
|
1207
|
+
const events = parseStagingEvents(eventsText);
|
|
1208
|
+
validateStagingRunConsistency(runId, {
|
|
1209
|
+
progress,
|
|
1210
|
+
runManifest,
|
|
1211
|
+
calibrationProgress,
|
|
1212
|
+
events,
|
|
1213
|
+
});
|
|
1214
|
+
return {
|
|
1215
|
+
run_id: runId,
|
|
1216
|
+
progress,
|
|
1217
|
+
run_manifest: runManifest,
|
|
1218
|
+
calibration_progress: calibrationProgress,
|
|
1219
|
+
events,
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
export async function loadStagingReformValidationRaw(
|
|
1224
|
+
runId: string,
|
|
1225
|
+
revalidate: number,
|
|
1226
|
+
country: MicrocosmCountry = "us",
|
|
1227
|
+
): Promise<JsonObject | null> {
|
|
1228
|
+
if (stagingUnavailableReason(country)) return null;
|
|
1229
|
+
assertSafeReleaseId(runId, "run");
|
|
1230
|
+
return stagingJsonOrNull(
|
|
1231
|
+
`runs/${runId}/reform_validation.json`,
|
|
1232
|
+
revalidate,
|
|
1233
|
+
country,
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
export async function loadStagingTargetDiagnostics(
|
|
1238
|
+
requestUrl: string,
|
|
1239
|
+
runId: string,
|
|
1240
|
+
revalidate: number,
|
|
1241
|
+
country: MicrocosmCountry = "us",
|
|
1242
|
+
) {
|
|
1243
|
+
const unavailable = unavailableStaging(country);
|
|
1244
|
+
if (unavailable) return { ...unavailable, run_id: runId };
|
|
1245
|
+
const cal = await loadStagingCalibration(runId, revalidate, country);
|
|
1246
|
+
if (!cal) {
|
|
1247
|
+
return {
|
|
1248
|
+
available: false,
|
|
1249
|
+
run_id: runId,
|
|
1250
|
+
detail: "This staging run has not uploaded calibration_diagnostics.json yet.",
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return latestMicrocosmTargetDiagnosticsPage(requestUrl, cal);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// --- which release a staging candidate is compared with -----------------
|
|
1257
|
+
// A national candidate is compared with the country's current release, the
|
|
1258
|
+
// build the dashboard manifest marks latest (for the UK, the one
|
|
1259
|
+
// latest-national.json names). A local-area candidate calibrates local
|
|
1260
|
+
// geographies the national release does not, so it is compared with the
|
|
1261
|
+
// local-area release instead. That release has no pointer file and is never
|
|
1262
|
+
// promoted, so its most recently published build, as the dashboard manifest
|
|
1263
|
+
// records it, is used. A run is a local-area candidate when its run
|
|
1264
|
+
// manifest's pipeline id is one the country registers as local-area: no
|
|
1265
|
+
// staged receipt or manifest records a dataset role, and the dataset file
|
|
1266
|
+
// names change between builds.
|
|
1267
|
+
|
|
1268
|
+
export type StagingComparisonBasis = "current_release" | "newest_local_area_release";
|
|
1269
|
+
|
|
1270
|
+
export interface StagingComparisonRelease {
|
|
1271
|
+
release_id: string;
|
|
1272
|
+
basis: StagingComparisonBasis;
|
|
1273
|
+
/** The run's pipeline builds local-area candidates. */
|
|
1274
|
+
local_area_candidate: boolean;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export interface StagingComparisonReleaseDependencies {
|
|
1278
|
+
runPipelineId(runId: string, country: MicrocosmCountry): Promise<string | null>;
|
|
1279
|
+
currentReleaseId(country: MicrocosmCountry): Promise<string>;
|
|
1280
|
+
newestLocalAreaReleaseId(country: MicrocosmCountry): Promise<string | null>;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** The `pipeline.id` a staging run's manifest records, or null without one. */
|
|
1284
|
+
export async function stagingRunPipelineId(
|
|
1285
|
+
runId: string,
|
|
1286
|
+
revalidate: number,
|
|
1287
|
+
country: MicrocosmCountry,
|
|
1288
|
+
): Promise<string | null> {
|
|
1289
|
+
assertSafeReleaseId(runId, "run");
|
|
1290
|
+
const manifest = await stagingJsonOrNull(`runs/${runId}/run_manifest.json`, revalidate, country);
|
|
1291
|
+
const pipelineId = asObject(manifest?.pipeline).id;
|
|
1292
|
+
return typeof pipelineId === "string" && pipelineId.trim() ? pipelineId.trim() : null;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* The most recently published release build whose release is local-area, by
|
|
1297
|
+
* the dataset roles `releases` carries (read from releases.json, or from each
|
|
1298
|
+
* release manifest, as the release picker reads them) and the publication
|
|
1299
|
+
* time the dashboard manifest records. A build releases.json marks withdrawn
|
|
1300
|
+
* is never chosen.
|
|
1301
|
+
*/
|
|
1302
|
+
export function newestPublishedLocalAreaRelease(
|
|
1303
|
+
manifest: CalibrationTreeManifest,
|
|
1304
|
+
country: MicrocosmCountry,
|
|
1305
|
+
releases: readonly ReleaseEntry[],
|
|
1306
|
+
): CalibrationTreeManifestEntry | null {
|
|
1307
|
+
const localAreaReleases = new Set(
|
|
1308
|
+
releases
|
|
1309
|
+
.filter((release) => release.is_local_area && release.withdrawn !== true)
|
|
1310
|
+
.map((release) => release.release_id),
|
|
1311
|
+
);
|
|
1312
|
+
const builds = (manifest.countries[country]?.builds ?? []).filter(
|
|
1313
|
+
(build) =>
|
|
1314
|
+
build.kind === "release" &&
|
|
1315
|
+
build.releaseId != null &&
|
|
1316
|
+
localAreaReleases.has(build.releaseId),
|
|
1317
|
+
);
|
|
1318
|
+
return calibrationBuildsNewestFirst(builds)[0] ?? null;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* The releases a local-area Staging candidate may be compared with, with
|
|
1323
|
+
* their roles. A repository with releases.json lists the builds it names and
|
|
1324
|
+
* nothing else; one without lists the folders on main and its published
|
|
1325
|
+
* tagged builds. A releases.json that cannot be read or is refused throws,
|
|
1326
|
+
* so the candidate gets no baseline.
|
|
1327
|
+
*/
|
|
1328
|
+
export async function stagingBaselineReleases(
|
|
1329
|
+
revalidate: number,
|
|
1330
|
+
country: MicrocosmCountry,
|
|
1331
|
+
manifest: CalibrationTreeManifest,
|
|
1332
|
+
): Promise<ReleaseEntry[]> {
|
|
1333
|
+
const recorded = await loadRecordedReleases(revalidate, country, manifest);
|
|
1334
|
+
if (recorded) return recorded;
|
|
1335
|
+
// The published tagged builds are the ones the folder listing cannot name.
|
|
1336
|
+
const folders = await loadReleases(revalidate, country);
|
|
1337
|
+
const tagged = await loadPublishedCutReleases(revalidate, country, manifest, folders);
|
|
1338
|
+
return [...folders, ...tagged];
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function defaultStagingComparisonReleaseDependencies(
|
|
1342
|
+
revalidate: number,
|
|
1343
|
+
): StagingComparisonReleaseDependencies {
|
|
1344
|
+
return {
|
|
1345
|
+
runPipelineId: (runId, country) => stagingRunPipelineId(runId, revalidate, country),
|
|
1346
|
+
currentReleaseId: async (country) =>
|
|
1347
|
+
(await loadPointerReleaseId(revalidate, country)).release_id,
|
|
1348
|
+
newestLocalAreaReleaseId: async (country) => {
|
|
1349
|
+
const { manifest } = await readCalibrationTreeManifest();
|
|
1350
|
+
const releases = await stagingBaselineReleases(revalidate, country, manifest);
|
|
1351
|
+
return newestPublishedLocalAreaRelease(manifest, country, releases)?.releaseId ?? null;
|
|
1352
|
+
},
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
export async function resolveStagingComparisonRelease(
|
|
1357
|
+
runId: string,
|
|
1358
|
+
country: MicrocosmCountry,
|
|
1359
|
+
dependencies: StagingComparisonReleaseDependencies =
|
|
1360
|
+
defaultStagingComparisonReleaseDependencies(300),
|
|
1361
|
+
): Promise<StagingComparisonRelease> {
|
|
1362
|
+
const localAreaPipelines = stagingLocalAreaPipelines(country);
|
|
1363
|
+
const pipelineId = localAreaPipelines.length
|
|
1364
|
+
? await dependencies.runPipelineId(runId, country)
|
|
1365
|
+
: null;
|
|
1366
|
+
const localAreaCandidate = pipelineId != null && localAreaPipelines.includes(pipelineId);
|
|
1367
|
+
if (localAreaCandidate) {
|
|
1368
|
+
const localAreaRelease = await dependencies.newestLocalAreaReleaseId(country);
|
|
1369
|
+
if (localAreaRelease) {
|
|
1370
|
+
return {
|
|
1371
|
+
release_id: localAreaRelease,
|
|
1372
|
+
basis: "newest_local_area_release",
|
|
1373
|
+
local_area_candidate: true,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
release_id: await dependencies.currentReleaseId(country),
|
|
1379
|
+
basis: "current_release",
|
|
1380
|
+
local_area_candidate: localAreaCandidate,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/** A staging run compared with a release, as `/microcosm/staging/compare` returns it. */
|
|
1385
|
+
export type StagingComparison = Awaited<ReturnType<typeof loadStagingComparison>>;
|
|
1386
|
+
|
|
1387
|
+
export async function loadStagingComparison(
|
|
1388
|
+
runId: string,
|
|
1389
|
+
releaseId: string,
|
|
1390
|
+
revalidate: number,
|
|
1391
|
+
country: MicrocosmCountry = "us",
|
|
1392
|
+
) {
|
|
1393
|
+
const unavailable = unavailableStaging(country);
|
|
1394
|
+
if (unavailable) return { ...unavailable, run_id: runId };
|
|
1395
|
+
const [release, staging] = await Promise.all([
|
|
1396
|
+
loadRelease(releaseId || "latest", revalidate, country),
|
|
1397
|
+
loadStagingCalibration(runId, revalidate, country),
|
|
1398
|
+
]);
|
|
1399
|
+
if (!staging) {
|
|
1400
|
+
return {
|
|
1401
|
+
available: false,
|
|
1402
|
+
run_id: runId,
|
|
1403
|
+
detail: "This staging run has not uploaded calibration_diagnostics.json yet.",
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
return {
|
|
1407
|
+
available: true,
|
|
1408
|
+
run_id: runId,
|
|
1409
|
+
...buildComparison(release, staging),
|
|
1410
|
+
};
|
|
1411
|
+
}
|