docs: add schema drift map
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/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 ?? "<unknown>"}`);
|
||||
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();
|
||||
Reference in New Issue
Block a user