import type { DependencyCatalogResponse, DependencyPlanStepViewResponse, DependencyPlanViewResponse, DependencyProbeViewResponse, DependencyState, RunUpdateJobListResponse, RunUpdateJobResponse, RunUpdatePhase } from "../api/types"; const dependencyStates = new Set(["unknown", "present", "missing", "installing", "failed"]); const updatePhases = new Set(["queued", "downloading", "staged", "restart-requested", "activating", "succeeded", "rolled-back", "failed"]); const updateStatuses = new Set(["queued", "running", "succeeded", "failed", "denied"]); const forbiddenProjectionKeys = new Set([ "leasetoken", "leasetokenhash", "leasesessiongeneration", "sessiontoken", "runtoken", "secretref", "hostpath", "executablepath", "stagingpath", "backuppath", "socket", "credential" ]); export function parseSafeDependencyCatalog(value: unknown): DependencyCatalogResponse { const record = requiredRecordValue(value, "dependency catalog"); rejectForbiddenProjection(record); return { serverInstanceId: requiredString(record, "serverInstanceId"), pluginId: requiredString(record, "pluginId"), pluginVersion: requiredString(record, "pluginVersion"), profileKey: requiredString(record, "profileKey"), targetOs: requiredString(record, "targetOs"), targetArch: requiredString(record, "targetArch"), probes: requiredArray(record, "probes").map(parseDependencyProbe), plans: requiredArray(record, "plans").map(parseDependencyPlan), updatedAt: requiredString(record, "updatedAt") }; } export function parseSafeRunUpdateList(value: unknown): RunUpdateJobListResponse { const record = requiredRecordValue(value, "Run update list"); rejectForbiddenProjection(record); const items = requiredArray(record, "items").map((item) => parseRunUpdate(requiredRecordValue(item, "Run update"))); const count = requiredNumber(record, "count"); if (count !== items.length) throw new Error("Run update count does not match items"); return { items, count }; } export function parseSafeRunUpdate(value: unknown): RunUpdateJobResponse { const record = requiredRecordValue(value, "Run update"); rejectForbiddenProjection(record); return parseRunUpdate(record); } function parseDependencyProbe(value: unknown): DependencyProbeViewResponse { const record = requiredRecordValue(value, "dependency probe"); const state = requiredString(record, "state") as DependencyState; if (!dependencyStates.has(state)) throw new Error("dependency state is invalid"); return { key: requiredString(record, "key"), kind: requiredString(record, "kind"), required: requiredBoolean(record, "required"), minimumVersion: optionalString(record, "minimumVersion"), state, evidence: optionalString(record, "evidence"), installPlanKey: optionalString(record, "installPlanKey") }; } function parseDependencyPlan(value: unknown): DependencyPlanViewResponse { const record = requiredRecordValue(value, "dependency plan"); return { key: requiredString(record, "key"), title: requiredString(record, "title"), targetOs: requiredString(record, "targetOs"), targetArch: requiredString(record, "targetArch"), digest: requiredChecksum(record, "digest"), steps: requiredArray(record, "steps").map(parseDependencyStep) }; } function parseDependencyStep(value: unknown): DependencyPlanStepViewResponse { const record = requiredRecordValue(value, "dependency plan step"); const downloadHost = optionalString(record, "downloadHost"); if (downloadHost && (downloadHost.includes("/") || downloadHost.includes("@") || downloadHost.includes(":"))) throw new Error("dependency download host is invalid"); return { type: requiredString(record, "type"), targetKey: requiredString(record, "targetKey"), packageManager: optionalString(record, "packageManager"), packageName: optionalString(record, "packageName"), version: optionalString(record, "version"), downloadHost, sizeBytes: optionalNumber(record, "sizeBytes") }; } function parseRunUpdate(record: Record): RunUpdateJobResponse { const phase = requiredString(record, "phase") as RunUpdatePhase; if (!updatePhases.has(phase)) throw new Error("Run update phase is invalid"); const status = requiredString(record, "status"); if (!updateStatuses.has(status)) throw new Error("Run update status is invalid"); return { id: requiredString(record, "id"), serverInstanceId: requiredString(record, "serverInstanceId"), runEndpointId: requiredString(record, "runEndpointId"), artifactId: requiredString(record, "artifactId"), checksum: requiredChecksum(record, "checksum"), targetOs: requiredString(record, "targetOs"), targetArch: requiredString(record, "targetArch"), targetRelease: optionalString(record, "targetRelease"), previousVersion: optionalString(record, "previousVersion"), jobId: optionalString(record, "jobId"), idempotencyKey: optionalString(record, "idempotencyKey"), status, phase, message: optionalString(record, "message"), rollback: requiredBoolean(record, "rollback"), createdAt: requiredString(record, "createdAt"), updatedAt: requiredString(record, "updatedAt") }; } function rejectForbiddenProjection(value: unknown): void { if (!isRecord(value)) return; for (const key of Object.keys(value)) { if (forbiddenProjectionKeys.has(key.toLowerCase())) throw new Error(`runtime projection contains forbidden field ${key}`); } } function requiredRecordValue(value: unknown, label: string): Record { if (!isRecord(value)) throw new Error(`${label} must be an object`); return value; } function requiredArray(value: Record, key: string): unknown[] { const field = value[key]; if (!Array.isArray(field)) throw new Error(`${key} must be an array`); return field; } function requiredString(value: Record, key: string): string { const field = value[key]; if (typeof field !== "string" || field.trim() === "") throw new Error(`${key} must be a non-empty string`); return field; } function optionalString(value: Record, key: string): string | undefined { const field = value[key]; if (field === undefined) return undefined; if (typeof field !== "string") throw new Error(`${key} must be a string`); return field; } function requiredChecksum(value: Record, key: string): string { const field = requiredString(value, key); if (!/^sha256:[a-f0-9]{64}$/.test(field)) throw new Error(`${key} must be a SHA-256 checksum`); return field; } function requiredNumber(value: Record, key: string): number { const field = value[key]; if (typeof field !== "number" || !Number.isFinite(field) || field < 0) throw new Error(`${key} must be a non-negative number`); return field; } function optionalNumber(value: Record, key: string): number | undefined { if (value[key] === undefined) return undefined; return requiredNumber(value, key); } function requiredBoolean(value: Record, key: string): boolean { const field = value[key]; if (typeof field !== "boolean") throw new Error(`${key} must be a boolean`); return field; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); }