import { mkdir, readFile, writeFile } from "node:fs/promises"; import { evaluateFieldBudget, percentile75, } from "../src/application/policies/performance-budgets.js"; import { validateFieldEvidenceInput } from "./lib/field-vitals-evidence.mjs"; const inputPath = process.env.FIELD_WEB_VITALS_INPUT || "config/performance/field-input.example.json"; const rawInput = JSON.parse(await readFile(inputPath, "utf8")); const now = new Date(); const validation = validateFieldEvidenceInput( rawInput, process.env.MIN_ELIGIBLE_SAMPLES, now, ); const input = validation.data; const configured = /** @type {{ * p75LcpMs: number, * p75Cls: number, * p75InpMs: number, * minimumEligibleSamples: number | null * }} */ ( JSON.parse(await readFile("config/performance/budgets.json", "utf8")).field ); const minimumEligibleSamples = validation.minimumEligibleSamples; const fallbackEnd = now; const fallbackStart = new Date(fallbackEnd); fallbackStart.setUTCDate(fallbackStart.getUTCDate() - 28); const start = input ? new Date(input.window.start) : fallbackStart; const end = input ? new Date(input.window.end) : fallbackEnd; const eligible = (input?.samples ?? []).filter((sample) => { const timestamp = new Date(sample.timestamp); return ( sample.consent === true && sample.releaseId === input?.releaseId && timestamp >= start && timestamp <= end ); }); const metrics = { p75LcpMs: percentile75(eligible.map((sample) => sample.lcpMs)), p75Cls: percentile75(eligible.map((sample) => sample.cls)), p75InpMs: percentile75(eligible.map((sample) => sample.inpMs)), }; const thresholds = { ...configured, minimumEligibleSamples }; const result = evaluateFieldBudget( { metrics, eligibleSamples: eligible.length }, thresholds, ); const passed = validation.passed && result.passed; const status = validation.passed ? result.status : "FAIL_UNVERIFIED"; const routeSamples = Object.fromEntries( Object.entries( eligible.reduce( (counts, sample) => { counts[sample.routeId] = (counts[sample.routeId] ?? 0) + 1; return counts; }, /** @type {Record} */ ({}), ), ).sort(([left], [right]) => left.localeCompare(right)), ); const report = { schemaVersion: 1, generatedAt: now.toISOString(), window: { days: 28, start: start.toISOString(), end: end.toISOString() }, context: { source: inputPath, sourceSystem: input?.source.system ?? null, exportId: input?.source.exportId ?? null, network: "production-real-user", routeAggregation: "route-id-only", releaseId: input?.releaseId ?? null, privacyApprovalRef: input?.privacy.approvalRef ?? null, thresholdDecisionRef: input?.thresholdDecision.evidenceRef ?? null, validationFailures: validation.failures, }, metrics, thresholds, eligibility: { consentRequired: true, totalSamples: input?.samples.length ?? 0, eligibleSamples: eligible.length, minimumEligibleSamples, routeSamples, }, status, passed, }; await mkdir("artifacts/performance", { recursive: true }); await writeFile( "artifacts/performance/field-web-vitals.json", `${JSON.stringify(report, null, 2)}\n`, ); if (!passed) { process.stderr.write( `Field Web Vitals: ${status} (approved threshold decision and valid 28-day production evidence are required)\n`, ); process.exit(1); } process.stdout.write("Field Web Vitals: PASS\n");