#!/usr/bin/env node import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const driftMapPath = path.join(repoRoot, "protocol/schema-drift-map.json"); const requiredKeys = [ "projectId", "operationId", "auditId", "traceId", "evidenceId", "serviceId", "environment", "resourceId", "sessionId" ]; const requiredStatuses = new Set(["aligned", "gap"]); function assertObject(value, label) { assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`); } function loadDriftMap() { const raw = readFile(driftMapPath, "utf8"); return raw.then((content) => JSON.parse(content)); } function assertRequiredCoverage(map) { assertObject(map, "drift map"); assert.equal(map.version, 1, "drift map version must be 1"); assertObject(map.summary, "drift map summary"); assert.ok(Array.isArray(map.summary.aligned), "drift map summary.aligned must be an array"); assert.ok(Array.isArray(map.entities), "drift map entities must be an array"); assert.ok( map.entities.length >= 5, "expected drift map entities for protocol, DB, audit, evidence chain, evidence, and deploy" ); assertObject(map.requiredKeyCoverage, "drift map requiredKeyCoverage"); for (const key of requiredKeys) { assert.ok(Object.hasOwn(map.requiredKeyCoverage, key), `missing required coverage for ${key}`); const coverage = map.requiredKeyCoverage[key]; assertObject(coverage, `coverage for ${key}`); assert.ok(Array.isArray(coverage.protocol) && coverage.protocol.length >= 1, `${key} protocol coverage must exist`); assert.ok( Array.isArray(coverage.dbMigration) && coverage.dbMigration.length >= 1, `${key} dbMigration coverage must exist` ); } } function assertEntityDrift(entity) { assertObject(entity, `entity ${entity?.name ?? ""}`); assert.equal(typeof entity.name, "string", "entity.name must be a string"); assert.equal(typeof entity.protocol, "string", `entity ${entity.name} protocol must be a string`); assert.ok(Array.isArray(entity.drift) && entity.drift.length >= 1, `entity ${entity.name} drift must exist`); for (const [index, entry] of entity.drift.entries()) { assertObject(entry, `${entity.name}.drift[${index}]`); assert.equal(typeof entry.field, "string", `${entity.name}.drift[${index}].field must be a string`); assert.equal(typeof entry.protocolField, "string", `${entity.name}.drift[${index}].protocolField must be a string`); assert.equal(typeof entry.dbColumn, "string", `${entity.name}.drift[${index}].dbColumn must be a string`); assert.ok(requiredStatuses.has(entry.status), `${entity.name}.drift[${index}].status must be aligned or gap`); if (entry.status === "gap") { assert.equal(typeof entry.gap, "string", `${entity.name}.drift[${index}].gap must describe the gap`); assert.ok(entry.gap.length > 0, `${entity.name}.drift[${index}].gap must not be empty`); } } } function assertSummary(map) { const aligned = new Set(map.summary?.aligned ?? []); for (const key of requiredKeys) { assert.ok(aligned.has(key), `summary.aligned must include ${key}`); } assert.equal(typeof map.summary.gapCount, "number", "summary.gapCount must be numeric"); assert.ok(map.summary.gapCount >= 1, "summary.gapCount must report at least one gap"); } async function main() { const map = await loadDriftMap(); assert.equal(map.$schema, "https://json-schema.org/draft/2020-12/schema", "drift map $schema"); assert.equal( map.$id, "https://hwlab.pikastech.local/protocol/schema-drift-map.json", "drift map $id" ); assertSummary(map); assertRequiredCoverage(map); let alignCount = 0; let gapCount = 0; for (const entity of map.entities) { assertEntityDrift(entity); alignCount += entity.drift.filter((entry) => entry.status === "aligned").length; gapCount += entity.drift.filter((entry) => entry.status === "gap").length; } assert.equal(gapCount, map.summary.gapCount, "summary.gapCount must match computed gap count"); assert.ok( Array.isArray(map.missingToFixBeforeRealPersistence) && map.missingToFixBeforeRealPersistence.length >= 1, "expected a non-empty list of persistence blockers" ); assert.ok(alignCount >= 20, "expected broad alignment coverage"); assert.ok(gapCount >= 5, "expected explicit drift gaps"); console.log(`validated schema drift map with ${alignCount} aligned fields and ${gapCount} gaps`); } await main();