fix: close performance evidence and budget gates
This commit is contained in:
@@ -3,3 +3,13 @@ export function classifyArtifact(path: string, source?: string): string;
|
||||
export function evaluateBudgetTotals(totals: Readonly<Record<string, number>>): {
|
||||
violations: Array<{ budget: string; actual: number; threshold: number }>;
|
||||
};
|
||||
export function evaluateMediaAssetBudget(
|
||||
asset: Readonly<Record<string, unknown>>,
|
||||
probe: Readonly<Record<string, unknown>> | null,
|
||||
transferBytes: number,
|
||||
assetClass?: 'chart' | 'project-still',
|
||||
): { violations: Array<{ budget: string; actual: number; threshold: number; id?: string }> };
|
||||
export function evaluateRuntimeBudgetEvidence(evidence: Readonly<Record<string, unknown>>): {
|
||||
tiers: Array<{ tier: string; textureBytes: number; geometryBytes: number; renderTargetBytes: number; runtimeAllowanceBytes: number; totalBytes: number }>;
|
||||
violations: Array<{ budget: string; actual: number; threshold: number; id?: string }>;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export const classifyArtifact = (path, source = '') => {
|
||||
if (path === 'index.html') return 'initial-html';
|
||||
if (/^assets\/index-[^/]+\.css$/u.test(path)) return 'critical-css';
|
||||
if (/^assets\/index-[^/]+\.js$/u.test(path) && /import\(/u.test(source)) return 'initial-application-js';
|
||||
if (/^assets\/(?:environment-high|high-tier)-[^/]+\.(?:js|avif|webp|png|jpe?g)$/u.test(path)) return 'high-tier-extras';
|
||||
if (/^assets\/environment-[^/]+\.js$/u.test(path)) return 'first-live-scene';
|
||||
if (/\.(?:woff2?|ttf|otf)$/u.test(path)) return 'initial-fonts';
|
||||
if (/^assets\/hero-[^/]+\.(?:avif|webp|png|jpe?g)$/u.test(path)) return 'hero-poster';
|
||||
@@ -42,6 +43,7 @@ const thresholdByTotal = Object.freeze({
|
||||
firstLiveScene: PERFORMANCE_BUDGETS.firstLiveScene,
|
||||
deferredProjectVisuals: PERFORMANCE_BUDGETS.deferredProjectVisuals,
|
||||
deferredNonVideo: PERFORMANCE_BUDGETS.deferredNonVideo,
|
||||
highTierExtras: PERFORMANCE_BUDGETS.highTierExtras,
|
||||
});
|
||||
|
||||
export const evaluateBudgetTotals = (totals) => ({
|
||||
@@ -49,3 +51,85 @@ export const evaluateBudgetTotals = (totals) => ({
|
||||
.filter(([budget, threshold]) => (totals[budget] ?? 0) > threshold)
|
||||
.map(([budget, threshold]) => ({ budget, actual: totals[budget], threshold })),
|
||||
});
|
||||
|
||||
const violation = (budget, actual, threshold, id) => ({ budget, actual, threshold, ...(id === undefined ? {} : { id }) });
|
||||
const finitePositiveInteger = (value) => Number.isInteger(value) && value > 0;
|
||||
|
||||
export const evaluateMediaAssetBudget = (asset, probe, transferBytes, assetClass = undefined) => {
|
||||
if (probe === null || typeof probe !== 'object') throw new Error(`Media probe evidence is required for ${asset?.id ?? 'unknown asset'}.`);
|
||||
if (!finitePositiveInteger(asset?.width) || !finitePositiveInteger(asset?.height)
|
||||
|| !finitePositiveInteger(probe.width) || !finitePositiveInteger(probe.height)) {
|
||||
throw new Error(`Verified dimensions are required for ${asset?.id ?? 'unknown asset'}.`);
|
||||
}
|
||||
if (asset.width !== probe.width || asset.height !== probe.height || asset.mediaType !== probe.mediaType) {
|
||||
throw new Error(`Manifest and probe evidence disagree for ${asset.id}.`);
|
||||
}
|
||||
const violations = [];
|
||||
if (asset.mediaType.startsWith('video/')) {
|
||||
if (!finitePositiveInteger(asset.durationMs) || !finitePositiveInteger(probe.durationMs)
|
||||
|| Math.abs(asset.durationMs - probe.durationMs) > 50) {
|
||||
throw new Error(`Verified duration is required for ${asset.id}.`);
|
||||
}
|
||||
if (transferBytes > PERFORMANCE_BUDGETS.projectVideo) violations.push(violation('projectVideo', transferBytes, PERFORMANCE_BUDGETS.projectVideo, asset.id));
|
||||
if (probe.durationMs > PERFORMANCE_BUDGETS.projectVideoDurationSeconds * 1_000) violations.push(violation('projectVideoDurationSeconds', probe.durationMs / 1_000, PERFORMANCE_BUDGETS.projectVideoDurationSeconds, asset.id));
|
||||
if (probe.width > PERFORMANCE_BUDGETS.projectVideoWidth) violations.push(violation('projectVideoWidth', probe.width, PERFORMANCE_BUDGETS.projectVideoWidth, asset.id));
|
||||
if (probe.height > PERFORMANCE_BUDGETS.projectVideoHeight) violations.push(violation('projectVideoHeight', probe.height, PERFORMANCE_BUDGETS.projectVideoHeight, asset.id));
|
||||
} else {
|
||||
if (asset.durationMs !== null || probe.durationMs !== null) throw new Error(`Still media cannot declare duration for ${asset.id}.`);
|
||||
const budget = assetClass === 'chart' ? 'chart' : 'projectStill';
|
||||
const threshold = PERFORMANCE_BUDGETS[budget];
|
||||
if (transferBytes > threshold) violations.push(violation(budget, transferBytes, threshold, asset.id));
|
||||
}
|
||||
return { violations };
|
||||
};
|
||||
|
||||
const allocationBytes = (allocation, fields, label) => {
|
||||
for (const field of fields) {
|
||||
if (!finitePositiveInteger(allocation?.[field])) throw new Error(`${label} ${allocation?.id ?? 'unknown'} has invalid ${field}.`);
|
||||
}
|
||||
return fields.reduce((total, field) => total * allocation[field], 1);
|
||||
};
|
||||
|
||||
export const evaluateRuntimeBudgetEvidence = (evidence) => {
|
||||
if (evidence?.schema !== 'portfolio-runtime-budget-evidence/v1' || !Array.isArray(evidence.tiers)) {
|
||||
throw new Error('Runtime budget evidence must use portfolio-runtime-budget-evidence/v1.');
|
||||
}
|
||||
const tierNames = evidence.tiers.map((tier) => tier?.tier).sort();
|
||||
if (JSON.stringify(tierNames) !== JSON.stringify(['high', 'low'])) {
|
||||
throw new Error('Runtime evidence must contain exactly one Low and High tier allocation ledger.');
|
||||
}
|
||||
const imageScene = evidence.peakRequests?.imageScene;
|
||||
const video = evidence.peakRequests?.video;
|
||||
if (!Number.isInteger(imageScene) || imageScene < 0 || !Number.isInteger(video) || video < 0) {
|
||||
throw new Error('Runtime request concurrency evidence is incomplete.');
|
||||
}
|
||||
const violations = [];
|
||||
if (imageScene > PERFORMANCE_BUDGETS.imageSceneConcurrency) violations.push(violation('imageSceneConcurrency', imageScene, PERFORMANCE_BUDGETS.imageSceneConcurrency));
|
||||
if (video > PERFORMANCE_BUDGETS.videoConcurrency) violations.push(violation('videoConcurrency', video, PERFORMANCE_BUDGETS.videoConcurrency));
|
||||
const tiers = evidence.tiers.map((tier) => {
|
||||
if (!['low', 'high'].includes(tier?.tier) || !Array.isArray(tier.textures) || !Array.isArray(tier.renderTargets)
|
||||
|| !Number.isInteger(tier.geometryBytes) || tier.geometryBytes < 0) {
|
||||
throw new Error('Runtime tier allocation evidence is incomplete.');
|
||||
}
|
||||
let textureBytes = 0;
|
||||
for (const texture of tier.textures) {
|
||||
const base = allocationBytes(texture, ['width', 'height', 'bytesPerPixel', 'layers'], 'Texture');
|
||||
if (typeof texture.mipmapped !== 'boolean') throw new Error(`Texture ${texture?.id ?? 'unknown'} is missing mipmap evidence.`);
|
||||
textureBytes += Math.ceil(base * (texture.mipmapped ? 4 / 3 : 1));
|
||||
if (tier.tier === 'low' && (texture.width > PERFORMANCE_BUDGETS.mobileTextureDimension || texture.height > PERFORMANCE_BUDGETS.mobileTextureDimension)) {
|
||||
violations.push(violation('mobileTextureDimension', Math.max(texture.width, texture.height), PERFORMANCE_BUDGETS.mobileTextureDimension, texture.id));
|
||||
}
|
||||
}
|
||||
let renderTargetBytes = 0;
|
||||
for (const target of tier.renderTargets) {
|
||||
renderTargetBytes += allocationBytes(target, ['width', 'height', 'bytesPerPixel', 'attachments', 'samples'], 'Render target');
|
||||
}
|
||||
const runtimeAllowanceBytes = 32 * 1024 * 1024;
|
||||
const totalBytes = textureBytes + tier.geometryBytes + renderTargetBytes + runtimeAllowanceBytes;
|
||||
const budget = tier.tier === 'low' ? 'lowTierGpuEstimate' : 'highTierGpuEstimate';
|
||||
const threshold = PERFORMANCE_BUDGETS[budget];
|
||||
if (totalBytes > threshold) violations.push(violation(budget, totalBytes, threshold));
|
||||
return { tier: tier.tier, textureBytes, geometryBytes: tier.geometryBytes, renderTargetBytes, runtimeAllowanceBytes, totalBytes };
|
||||
});
|
||||
return { tiers, violations };
|
||||
};
|
||||
|
||||
@@ -10,6 +10,16 @@ export const aggregateFiveDriveTraces = (runs) => {
|
||||
if (typeof profile !== 'string' || runs.some((run) => run?.schema !== 'drive-trace.v1' || run.profile !== profile)) {
|
||||
throw new Error('All runs must use one drive-trace.v1 profile.');
|
||||
}
|
||||
const identities = runs.map((run) => run.referenceRun);
|
||||
const indexes = identities.map((identity) => identity?.runIndex).sort((left, right) => left - right);
|
||||
if (JSON.stringify(indexes) !== JSON.stringify([1, 2, 3, 4, 5])) {
|
||||
throw new Error('Reference evidence must contain unique run indexes 1 through 5.');
|
||||
}
|
||||
const runIds = new Set(identities.map((identity) => identity?.runId));
|
||||
const digests = new Set(identities.map((identity) => identity?.attestationDigest));
|
||||
if (runIds.size !== 5 || digests.size !== 5 || identities.some((identity) => !/^[a-f0-9]{64}$/u.test(identity?.attestationDigest ?? ''))) {
|
||||
throw new Error('Reference evidence must contain five unique authenticated identities.');
|
||||
}
|
||||
const lcp = runs.map((run) => run.metrics.lcp);
|
||||
const tbt = runs.map((run) => run.metrics.tbt);
|
||||
const everyPerRunBudgetPass = runs.every((run) => Object.values(run.budgetResults).every(Boolean));
|
||||
|
||||
Reference in New Issue
Block a user