66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
/**
|
|
* Pipeline budgets — hard ceilings enforced before publish (SPIKE-04 gate 4).
|
|
* Trace: ARCH 10.6, IMPLEMENTATION-CONTRACT.md §33, SPIKE-04 §3 gate 4
|
|
*/
|
|
|
|
export const BUDGETS = {
|
|
MAX_FILE_BYTES: 6 * 1024 * 1024, // 6 MB single file cap
|
|
MAX_SECTIONS_JSON: 3 * 1024 * 1024, // emergency+schedule+map+info+assets.json
|
|
MAX_MAP_ASSETS: 28 * 1024 * 1024,
|
|
MAX_OTHER_ASSETS: 6 * 1024 * 1024,
|
|
TARGET_TOTAL: 40 * 1024 * 1024,
|
|
HARD_TOTAL: 50 * 1024 * 1024,
|
|
FLOOR_MAX: 16 * 1024, // emergency baseline
|
|
MAP_OVERVIEW_MAX_DIM: 1600,
|
|
MAP_DETAIL_MAX_DIM: 3072,
|
|
} as const;
|
|
|
|
export interface BudgetCheckResult {
|
|
readonly ok: boolean;
|
|
readonly reason?: string;
|
|
readonly totals?: {
|
|
sectionsBytes: number;
|
|
mapBytes: number;
|
|
otherBytes: number;
|
|
totalBytes: number;
|
|
};
|
|
}
|
|
|
|
export function checkBudgets(
|
|
files: ReadonlyMap<string, { bytes: number; kind?: string }>,
|
|
): BudgetCheckResult {
|
|
let sectionsBytes = 0;
|
|
let mapBytes = 0;
|
|
let otherBytes = 0;
|
|
for (const [name, meta] of files) {
|
|
if (meta.bytes > BUDGETS.MAX_FILE_BYTES) {
|
|
return { ok: false, reason: `file ${name} exceeds 6MB cap: ${meta.bytes}` };
|
|
}
|
|
if (name.endsWith(".json")) sectionsBytes += meta.bytes;
|
|
else if (meta.kind === "map-base") mapBytes += meta.bytes;
|
|
else otherBytes += meta.bytes;
|
|
}
|
|
if (sectionsBytes > BUDGETS.MAX_SECTIONS_JSON) {
|
|
return { ok: false, reason: `sections JSON ${sectionsBytes} exceeds 3MB` };
|
|
}
|
|
if (mapBytes > BUDGETS.MAX_MAP_ASSETS) {
|
|
return { ok: false, reason: `map assets ${mapBytes} exceeds 28MB` };
|
|
}
|
|
if (otherBytes > BUDGETS.MAX_OTHER_ASSETS) {
|
|
return { ok: false, reason: `other assets ${otherBytes} exceeds 6MB` };
|
|
}
|
|
const totalBytes = sectionsBytes + mapBytes + otherBytes;
|
|
if (totalBytes > BUDGETS.HARD_TOTAL) {
|
|
return { ok: false, reason: `total ${totalBytes} exceeds 50MB hard ceiling` };
|
|
}
|
|
// Target 40MB is pipeline gate — warn but pass? Spec says pipeline reject above 40MB target? We treat >40MB as fail per gate 4 target.
|
|
if (totalBytes > BUDGETS.TARGET_TOTAL) {
|
|
return { ok: false, reason: `total ${totalBytes} exceeds 40MB target` };
|
|
}
|
|
return { ok: true, totals: { sectionsBytes, mapBytes, otherBytes, totalBytes } };
|
|
}
|
|
|
|
export function checkFloorSize(bytes: number): BudgetCheckResult {
|
|
if (bytes > BUDGETS.FLOOR_MAX) return { ok: false, reason: `floor ${bytes} exceeds 16KB` };
|
|
return { ok: true };
|
|
}
|