/*
Facilitates integrity verification of managed files against their manifests and registry records.
- Do not handle file content parsing or transformation.
- Do not manage the lifecycle of registry entities.
- Do not perform network operations for data retrieval.
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
/**
* Core integrity verification logic.
* Validates file hashes against manifests and checks registry consistency.
*/
import path from "node:path";
import { byteHashFile } from "@warpgogol/werkstatt-engine/fingerprint";
import { discoverManagedDirectories, discoverManagedFiles } from "./discover.ts";
import { pathExists } from "./fs.ts";
import { getManifestFileName, loadDirectoryManifest } from "./manifests.ts";
import { loadEntitiesById, loadPathsCurrent } from "./registry.ts";
import { validateIntegrityJsonSchemas } from "./schema-validation.ts";
import type {
DirectoryManifest,
ManifestFileRecord,
RegistryEntity,
VerifyIssue,
VerifyReport,
VerifyStats,
} from "./types.ts";
function isIsoDateTime(value: string): boolean {
if (!value) return false;
return !Number.isNaN(Date.parse(value));
}
function isHexSha256(value: string): boolean {
return /^sha256:[0-9a-f]{64}$/i.test(value);
}
function pushIssue(
issues: VerifyIssue[],
level: "error" | "warning",
code: string,
message: string,
extras: Pick = {},
): void {
issues.push({ level, code, message, ...extras });
}
function validateRecordShape(
issues: VerifyIssue[],
scope: "manifest" | "registry",
record: ManifestFileRecord | RegistryEntity,
filePath: string,
entityId?: string,
): void {
if (!isIsoDateTime(record.createdAt)) {
pushIssue(issues, "error", "INVALID_CREATED_AT", `${scope} has invalid createdAt`, {
path: filePath,
entityId,
});
}
if (!isIsoDateTime(record.updatedAt)) {
pushIssue(issues, "error", "INVALID_UPDATED_AT", `${scope} has invalid updatedAt`, {
path: filePath,
entityId,
});
}
if (
isIsoDateTime(record.createdAt) &&
isIsoDateTime(record.updatedAt) &&
record.createdAt > record.updatedAt
) {
pushIssue(
issues,
"error",
"INVALID_TIMESTAMP_ORDER",
`${scope} createdAt is greater than updatedAt`,
{ path: filePath, entityId },
);
}
if (!Number.isInteger(record.revision) || record.revision < 1) {
pushIssue(issues, "error", "INVALID_REVISION", `${scope} revision must be an integer >= 1`, {
path: filePath,
entityId,
});
}
if (!isHexSha256(record.contentHash)) {
pushIssue(
issues,
"error",
"INVALID_CONTENT_HASH",
`${scope} contentHash must match sha256:<64-hex>`,
{ path: filePath, entityId },
);
}
if (!record.gitSha || record.gitSha.length < 7) {
pushIssue(issues, "warning", "WEAK_GIT_SHA", `${scope} gitSha looks missing or too short`, {
path: filePath,
entityId,
});
}
}
function compareManifestAndRegistry(
issues: VerifyIssue[],
repoPath: string,
manifestRecord: ManifestFileRecord,
registryRecord: RegistryEntity,
): void {
const entityId = manifestRecord.entityId;
if (registryRecord.status !== "active") {
pushIssue(
issues,
"error",
"REGISTRY_ENTITY_NOT_ACTIVE",
"Registry entity for managed file is not active",
{ path: repoPath, entityId },
);
}
if (registryRecord.currentPath !== repoPath) {
pushIssue(
issues,
"error",
"CURRENT_PATH_MISMATCH",
"Registry currentPath does not match managed file path",
{ path: repoPath, entityId },
);
}
if (manifestRecord.status !== "active") {
pushIssue(
issues,
"error",
"MANIFEST_RECORD_NOT_ACTIVE",
"Manifest record for managed file is not active",
{ path: repoPath, entityId },
);
}
if (manifestRecord.createdAt !== registryRecord.createdAt) {
pushIssue(issues, "error", "CREATED_AT_MISMATCH", "Manifest and registry createdAt differ", {
path: repoPath,
entityId,
});
}
if (manifestRecord.updatedAt !== registryRecord.updatedAt) {
pushIssue(issues, "error", "UPDATED_AT_MISMATCH", "Manifest and registry updatedAt differ", {
path: repoPath,
entityId,
});
}
if (manifestRecord.revision !== registryRecord.revision) {
pushIssue(issues, "error", "REVISION_MISMATCH", "Manifest and registry revision differ", {
path: repoPath,
entityId,
});
}
if (manifestRecord.contentHash !== registryRecord.contentHash) {
pushIssue(
issues,
"error",
"CONTENT_HASH_MISMATCH_BETWEEN_INDEXES",
"Manifest and registry contentHash differ",
{ path: repoPath, entityId },
);
}
if (manifestRecord.gitSha !== registryRecord.gitSha) {
pushIssue(
issues,
"warning",
"GIT_SHA_MISMATCH_BETWEEN_INDEXES",
"Manifest and registry gitSha differ",
{ path: repoPath, entityId },
);
}
}
async function loadExpectedManifests(
cwd: string,
managedDirectories: string[],
issues: VerifyIssue[],
): Promise