import type { ErrorCode } from "./error"; export const LIBRARY_LINKED_RELOAD_SCHEMA = 1 as const; export const LINKED_RELOAD_OPERATION = "RELOAD" as const; export interface LinkedSnapshotDataBlockIR { dataBlockId: string; owner: "SOURCE_LIBRARY"; readOnly: true; } export interface LinkedSnapshotIR { sourceLibraryId: string; sourceGeneration: number; sourceRevision: number; dependencyClosureSha256: string; graphSha256: string; dataBlocks: LinkedSnapshotDataBlockIR[]; } export interface LinkedReloadStateIR { schemaVersion: typeof LIBRARY_LINKED_RELOAD_SCHEMA; snapshots: LinkedSnapshotIR[]; } export interface LinkedReloadRequestIR { schemaVersion: typeof LIBRARY_LINKED_RELOAD_SCHEMA; operation: typeof LINKED_RELOAD_OPERATION; sourceLibraryId: string; expectedGeneration: number; expectedRevision: number; replacement: LinkedSnapshotIR; } export interface LinkedReloadDecisionIR { status: "REPLACED" | "STALE"; code: ErrorCode | null; sourceLibraryId: string; replacedGeneration: number; replacementGeneration: number; state: LinkedReloadStateIR; } export class LinkedReloadValidationError extends Error { readonly code: ErrorCode; readonly path?: string; constructor(code: ErrorCode, message: string, path?: string) { super(`${code}: ${message}`); this.name = "LinkedReloadValidationError"; this.code = code; this.path = path; } } const LIBRARY_ID = /^library:[a-f0-9]{64}$/; const SHA256 = /^[a-f0-9]{64}$/; const DATA_BLOCK_ID = /^[A-Za-z0-9][A-Za-z0-9:._/ -]{0,255}$/; const MAX_SNAPSHOTS = 10_000; const MAX_DATA_BLOCKS = 256; function record(value: unknown, path: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path} must be an object`, path); } return value as Record; } function exactKeys(value: Record, expected: readonly string[], path: string): void { const keys = Object.keys(value).sort(); const allowed = [...expected].sort(); if (keys.length !== allowed.length || keys.some((key, index) => key !== allowed[index])) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path} contains undeclared fields`, path); } } function integer(value: unknown, path: string): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path} must be a safe integer >= 0`, path); } return value; } function libraryId(value: unknown, path: string): string { if (typeof value !== "string" || !LIBRARY_ID.test(value)) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path} must be a library identity`, path); } return value; } function digest(value: unknown, path: string): string { if (typeof value !== "string" || !SHA256.test(value)) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path} must be a lowercase SHA-256 digest`, path); } return value; } function dataBlock(value: unknown, path: string): LinkedSnapshotDataBlockIR { const item = record(value, path); exactKeys(item, ["dataBlockId", "owner", "readOnly"], path); if (typeof item.dataBlockId !== "string" || !DATA_BLOCK_ID.test(item.dataBlockId)) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path}.dataBlockId is invalid`, `${path}.dataBlockId`); } if (item.owner !== "SOURCE_LIBRARY" || item.readOnly !== true) { throw new LinkedReloadValidationError("LINKED_DATA_MUTATION_BLOCKED", `${path} must remain source-library/read-only`, path); } return { dataBlockId: item.dataBlockId, owner: "SOURCE_LIBRARY", readOnly: true }; } export function parseLinkedSnapshot(value: unknown, path = "snapshot"): LinkedSnapshotIR { const snapshot = record(value, path); exactKeys(snapshot, ["sourceLibraryId", "sourceGeneration", "sourceRevision", "dependencyClosureSha256", "graphSha256", "dataBlocks"], path); if (!Array.isArray(snapshot.dataBlocks) || snapshot.dataBlocks.length === 0 || snapshot.dataBlocks.length > MAX_DATA_BLOCKS) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path}.dataBlocks is outside its bounded range`, `${path}.dataBlocks`); } const dataBlocks = snapshot.dataBlocks.map((item, index) => dataBlock(item, `${path}.dataBlocks[${index}]`)); if (new Set(dataBlocks.map((item) => item.dataBlockId)).size !== dataBlocks.length) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", `${path}.dataBlocks contains duplicate IDs`, `${path}.dataBlocks`); } return { sourceLibraryId: libraryId(snapshot.sourceLibraryId, `${path}.sourceLibraryId`), sourceGeneration: integer(snapshot.sourceGeneration, `${path}.sourceGeneration`), sourceRevision: integer(snapshot.sourceRevision, `${path}.sourceRevision`), dependencyClosureSha256: digest(snapshot.dependencyClosureSha256, `${path}.dependencyClosureSha256`), graphSha256: digest(snapshot.graphSha256, `${path}.graphSha256`), dataBlocks, }; } export function parseLinkedReloadState(value: unknown): LinkedReloadStateIR { const state = record(value, "state"); exactKeys(state, ["schemaVersion", "snapshots"], "state"); if (state.schemaVersion !== LIBRARY_LINKED_RELOAD_SCHEMA) { throw new LinkedReloadValidationError("PROTOCOL_MISMATCH", "Unsupported linked reload state schema", "schemaVersion"); } if (!Array.isArray(state.snapshots) || state.snapshots.length > MAX_SNAPSHOTS) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", "state.snapshots exceeds its bounded range", "snapshots"); } const snapshots = state.snapshots.map((item, index) => parseLinkedSnapshot(item, `state.snapshots[${index}]`)); const identities = snapshots.map((item) => `${item.sourceLibraryId}:${item.sourceGeneration}`); if (new Set(identities).size !== identities.length) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", "state contains duplicate library generations", "snapshots"); } return { schemaVersion: LIBRARY_LINKED_RELOAD_SCHEMA, snapshots }; } export function parseLinkedReloadRequest(value: unknown): LinkedReloadRequestIR { const request = record(value, "request"); exactKeys(request, ["schemaVersion", "operation", "sourceLibraryId", "expectedGeneration", "expectedRevision", "replacement"], "request"); if (request.schemaVersion !== LIBRARY_LINKED_RELOAD_SCHEMA) { throw new LinkedReloadValidationError("PROTOCOL_MISMATCH", "Unsupported linked reload request schema", "schemaVersion"); } if (request.operation !== LINKED_RELOAD_OPERATION) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", "linked reload operation is invalid", "operation"); } const replacement = parseLinkedSnapshot(request.replacement, "request.replacement"); const sourceLibraryId = libraryId(request.sourceLibraryId, "request.sourceLibraryId"); if (replacement.sourceLibraryId !== sourceLibraryId) { throw new LinkedReloadValidationError("TASK_VALIDATION_FAILED", "replacement must retain the requested source library", "replacement.sourceLibraryId"); } return { schemaVersion: LIBRARY_LINKED_RELOAD_SCHEMA, operation: LINKED_RELOAD_OPERATION, sourceLibraryId, expectedGeneration: integer(request.expectedGeneration, "request.expectedGeneration"), expectedRevision: integer(request.expectedRevision, "request.expectedRevision"), replacement, }; } function cloneState(state: LinkedReloadStateIR): LinkedReloadStateIR { return { schemaVersion: LIBRARY_LINKED_RELOAD_SCHEMA, snapshots: state.snapshots.map((snapshot) => ({ ...snapshot, dataBlocks: snapshot.dataBlocks.map((dataBlock) => ({ ...dataBlock })), })), }; } export function reloadMatchingLinkedSnapshot(stateValue: unknown, requestValue: unknown): LinkedReloadDecisionIR { const state = parseLinkedReloadState(stateValue); const request = parseLinkedReloadRequest(requestValue); const matchingIndex = state.snapshots.findIndex((snapshot) => snapshot.sourceLibraryId === request.sourceLibraryId && snapshot.sourceGeneration === request.expectedGeneration, ); if (matchingIndex === -1 || state.snapshots[matchingIndex].sourceRevision !== request.expectedRevision) { return { status: "STALE", code: "REVISION_CONFLICT", sourceLibraryId: request.sourceLibraryId, replacedGeneration: request.expectedGeneration, replacementGeneration: request.replacement.sourceGeneration, state: cloneState(state), }; } const current = state.snapshots[matchingIndex]; if (request.replacement.sourceLibraryId !== request.sourceLibraryId || request.replacement.sourceGeneration !== request.expectedGeneration + 1 || request.replacement.sourceRevision <= current.sourceRevision) { return { status: "STALE", code: "REVISION_CONFLICT", sourceLibraryId: request.sourceLibraryId, replacedGeneration: request.expectedGeneration, replacementGeneration: request.replacement.sourceGeneration, state: cloneState(state), }; } const snapshots = state.snapshots.map((snapshot, index) => index === matchingIndex ? request.replacement : snapshot); return { status: "REPLACED", code: null, sourceLibraryId: request.sourceLibraryId, replacedGeneration: current.sourceGeneration, replacementGeneration: request.replacement.sourceGeneration, state: { schemaVersion: LIBRARY_LINKED_RELOAD_SCHEMA, snapshots: snapshots.map((snapshot) => ({ ...snapshot, dataBlocks: snapshot.dataBlocks.map((dataBlock) => ({ ...dataBlock })), })) }, }; }