/*
RFC-0947: sichtpass.generate command handler — produces a site-wide visibility snapshot with deduplication.
- Does not define W3C Verifiable Credential issuance.
- Does not build a UI for the Sichtpass.
- Does not change the Bordbuch hash-chain algorithm or NDJSON format.
- Does not retroactively clean up existing duplicate sichtpass events.
- RFC-0947: initial sichtpass.generate command handler.
- 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.
- RFC-1297: lastPropagated.healthy is nullable (rollback/reconcile record unknown health); widened the projection param.
*/
import path from "node:path";
import { parse as parseYaml } from "yaml";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import {
snapshotCanonicalJsonObjectV1,
canonicalJsonHashV1,
} from "@warpgogol/werkstatt-engine/fingerprint";
import type { BordbuchEntry } from "@warpgogol/werkstatt-engine/schemas";
import { readBordbuch } from "../bordbuch/bordbuch-io.ts";
import { appendAndCommitBordbuch } from "../bordbuch/bordbuch-commit-helper.ts";
import { acquireLock, releaseLock, generateOperationId } from "../werkstatt/index.ts";
import {
resolveCacheClonePath,
resolveActiveWorkpieceDir,
readSystemState,
readSystemConfig,
} from "../sternsystem/registry-io.ts";
import { resolveNachweisCachePath } from "../nachweis/nachweis-io.ts";
import type {
SichtpassSiteSnapshot,
SichtpassBordbuchMetadata,
SichtpassChannelState,
SichtpassPseoModule,
} from "./sichtpass-snapshot.ts";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
interface ContentRegressionSnapshotData {
contentHash: string | null;
routeCount: number;
}
interface BehaviorSnapshotData {
routeCount: number;
}
interface NachweisManifestData {
count: number;
slugs: string[];
}
interface SystemStateData {
releaseId: string | null;
channels: {
dev: SichtpassChannelState;
alt: SichtpassChannelState;
main: SichtpassChannelState;
};
}
interface PseoModulesData {
modules: SichtpassPseoModule[];
}
interface CoverageLedgerData {
atoms: number;
pages: string[];
}
async function readContentRegressionSnapshot(
cachePath: string,
_systemId: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const snapshotDir = path.join(cachePath, ".cache", "content-regression");
if (!(await io.exists(snapshotDir))) {
return { contentHash: null, routeCount: 0 };
}
try {
const files = await io.readdir(snapshotDir);
const snapshotFile = files.find((f) => f.name.endsWith(".snapshot.yaml"));
if (!snapshotFile) {
return { contentHash: null, routeCount: 0 };
}
const raw = await io.readFile(path.join(snapshotDir, snapshotFile.name));
const parsed = parseYaml(raw) as {
contentHash?: string;
routes?: unknown[];
};
return {
contentHash: typeof parsed.contentHash === "string" ? parsed.contentHash : null,
routeCount: Array.isArray(parsed.routes) ? parsed.routes.length : 0,
};
} catch {
return { contentHash: null, routeCount: 0 };
}
}
async function readBehaviorSnapshot(
cachePath: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const filePath = path.join(cachePath, "behavior.snapshot.generated.yaml");
if (!(await io.exists(filePath))) {
return { routeCount: 0 };
}
try {
const raw = await io.readFile(filePath);
const parsed = parseYaml(raw) as { routes?: unknown[] };
return {
routeCount: Array.isArray(parsed.routes) ? parsed.routes.length : 0,
};
} catch {
return { routeCount: 0 };
}
}
async function readNachweisManifest(
workspaceRoot: string,
systemId: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
try {
const cachePath = await resolveNachweisCachePath(workspaceRoot, systemId);
const manifestPath = path.join(cachePath, "public", "nachweise", "manifest.json");
if (!(await io.exists(manifestPath))) {
return { count: 0, slugs: [] };
}
const raw = await io.readFile(manifestPath);
const parsed = JSON.parse(raw) as { records?: Array<{ slug?: string }> };
const records = Array.isArray(parsed.records) ? parsed.records : [];
return {
count: records.length,
slugs: records.map((r) => r.slug).filter((s): s is string => typeof s === "string"),
};
} catch {
return { count: 0, slugs: [] };
}
}
async function readSystemStateData(
workspaceRoot: string,
systemId: string,
): Promise {
try {
const state = await readSystemState(workspaceRoot, systemId);
const config = await readSystemConfig(workspaceRoot, systemId);
const channels = config.deployment?.channels;
const lastProp = state.lastPropagated;
const makeChannel = (
ch: { url: string; workerName: string } | undefined,
prop: { releaseId: string; healthy: boolean | null; at: string } | undefined,
): SichtpassChannelState => ({
healthy: prop?.healthy ?? false,
releaseId: prop?.releaseId ?? null,
deployedAt: prop?.at ?? null,
});
return {
releaseId: state.lastRelease,
channels: {
dev: makeChannel(channels?.dev, lastProp.dev),
alt: makeChannel(channels?.alt, lastProp.alt),
main: makeChannel(channels?.main, lastProp.main),
},
};
} catch {
return {
releaseId: null,
channels: {
dev: { healthy: false, releaseId: null, deployedAt: null },
alt: { healthy: false, releaseId: null, deployedAt: null },
main: { healthy: false, releaseId: null, deployedAt: null },
},
};
}
}
async function readPseoModules(
cachePath: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const statusPath = path.join(cachePath, "bordbuch", "status.generated.yaml");
if (!(await io.exists(statusPath))) {
return { modules: [] };
}
try {
const raw = await io.readFile(statusPath);
const parsed = parseYaml(raw) as {
pseo?: {
modules?: Array<{
id: string;
masterLocale: string;
publishedLocales?: string[];
}>;
};
};
const modules = parsed.pseo?.modules ?? [];
return {
modules: modules.map((m) => ({
id: m.id,
masterLocale: m.masterLocale,
publishedLocales: Array.isArray(m.publishedLocales) ? m.publishedLocales : [],
})),
};
} catch {
return { modules: [] };
}
}
async function readCoverageLedger(
cachePath: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const ledgerPath = path.join(cachePath, "provenance", "coverage-ledger.yaml");
if (!(await io.exists(ledgerPath))) {
return { atoms: 0, pages: [] };
}
try {
const raw = await io.readFile(ledgerPath);
const parsed = parseYaml(raw) as {
atoms?: Array<{ pageId?: string; supersededBy?: string }>;
};
const atoms = Array.isArray(parsed.atoms) ? parsed.atoms : [];
const activeAtoms = atoms.filter((a) => !a.supersededBy);
const pageSet = new Set();
for (const a of activeAtoms) {
if (typeof a.pageId === "string" && a.pageId) {
pageSet.add(a.pageId);
}
}
return {
atoms: activeAtoms.length,
pages: [...pageSet].sort(),
};
} catch {
return { atoms: 0, pages: [] };
}
}
function computeCompositeHash(snapshot: Omit): string {
const hashInput = {
schemaVersion: snapshot.schemaVersion,
systemId: snapshot.systemId,
contentHash: snapshot.contentHash,
routeCount: snapshot.routeCount,
behaviorRouteCount: snapshot.behaviorRouteCount,
nachweisCount: snapshot.nachweisCount,
nachweisSlugs: snapshot.nachweisSlugs,
releaseId: snapshot.releaseId,
channels: {
dev: {
healthy: snapshot.channels.dev.healthy,
releaseId: snapshot.channels.dev.releaseId,
},
alt: {
healthy: snapshot.channels.alt.healthy,
releaseId: snapshot.channels.alt.releaseId,
},
main: {
healthy: snapshot.channels.main.healthy,
releaseId: snapshot.channels.main.releaseId,
},
},
pseoModules: snapshot.pseoModules,
coverageAtoms: snapshot.coverageAtoms,
coveragePages: snapshot.coveragePages,
};
const snapshotResult = snapshotCanonicalJsonObjectV1(hashInput);
if (!snapshotResult.ok) {
throw new Error(`[sichtpass.generate] canonical JSON snapshot failed: ${snapshotResult.code}`);
}
return canonicalJsonHashV1(snapshotResult.value);
}
function findLastSichtpassSiteEntry(entries: BordbuchEntry[]): BordbuchEntry | null {
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
if (entry.kind !== "sichtpass") continue;
const meta = entry.metadata as Record | undefined;
if (meta?.slug === "__site__") return entry;
}
return null;
}
export async function runSichtpassGenerate(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
if (!systemId) throw new Error("[sichtpass.generate] --system is required");
// Resolve workpiece-aware cache path for content/behavior/nachweis data
const workpieceDir = await resolveActiveWorkpieceDir(workspaceRoot, systemId);
const contentCachePath = workpieceDir ?? (await resolveCacheClonePath(workspaceRoot, systemId));
const cacheClonePath = await resolveCacheClonePath(workspaceRoot, systemId);
// Read all data sources
const [contentRegression, behavior, nachweis, systemStateData, pseoModules, coverageLedger] =
await Promise.all([
readContentRegressionSnapshot(contentCachePath, systemId),
readBehaviorSnapshot(contentCachePath),
readNachweisManifest(workspaceRoot, systemId),
readSystemStateData(workspaceRoot, systemId),
readPseoModules(cacheClonePath),
readCoverageLedger(cacheClonePath),
]);
// Log warnings for missing data sources
if (contentRegression.contentHash === null) {
logger.warn(
"[sichtpass.generate] content-regression snapshot not found — contentHash will be null",
);
}
if (behavior.routeCount === 0) {
logger.warn(
"[sichtpass.generate] behavior snapshot not found or empty — behaviorRouteCount will be 0",
);
}
if (nachweis.count === 0) {
logger.info("[sichtpass.generate] no published nachweis records found");
}
// Build snapshot without compositeHash first
const snapshotBase: Omit = {
schemaVersion: "1.0.0",
systemId,
generatedAt: null,
contentHash: contentRegression.contentHash,
routeCount: contentRegression.routeCount,
behaviorRouteCount: behavior.routeCount,
nachweisCount: nachweis.count,
nachweisSlugs: nachweis.slugs,
releaseId: systemStateData.releaseId,
channels: systemStateData.channels,
pseoModules: pseoModules.modules,
coverageAtoms: coverageLedger.atoms,
coveragePages: coverageLedger.pages,
};
const compositeHash = computeCompositeHash(snapshotBase);
const snapshot: SichtpassSiteSnapshot = {
...snapshotBase,
compositeHash,
};
// Deduplication: read last sichtpass __site__ entry and compare compositeHash
let deduplicated = false;
let bordbuchWarning: string | null = null;
try {
const entries = await readBordbuch(workspaceRoot, systemId);
const lastSiteEntry = findLastSichtpassSiteEntry(entries);
if (lastSiteEntry) {
const lastMeta = lastSiteEntry.metadata as Record | undefined;
const lastHash = typeof lastMeta?.recordHash === "string" ? lastMeta.recordHash : null;
if (lastHash === compositeHash) {
deduplicated = true;
logger.info(
`[sichtpass.generate] composite hash unchanged (${compositeHash.slice(0, 16)}...) — skipping Bordbuch append`,
);
}
}
} catch (err) {
bordbuchWarning = `Bordbuch read failed: ${(err as Error).message}`;
logger.warn(`[sichtpass.generate] ${bordbuchWarning} — proceeding with append`);
}
// Append Bordbuch entry if not deduplicated
if (!deduplicated) {
const operationId = generateOperationId();
await acquireLock(
workspaceRoot,
`system:${systemId}`,
operationId,
"sichtpass.generate",
"agent",
);
await acquireLock(
workspaceRoot,
`bordbuch:${systemId}`,
operationId,
"sichtpass.generate",
"agent",
);
try {
const metadata: SichtpassBordbuchMetadata = {
slug: "__site__",
manifestVersion: "1.0.0",
recordHash: compositeHash,
signaturePresent: false,
timestampPresent: false,
verificationLevel: "N0",
contentHash: snapshot.contentHash ?? undefined,
routeCount: snapshot.routeCount,
nachweisCount: snapshot.nachweisCount,
releaseId: snapshot.releaseId,
channelHealth: {
dev: snapshot.channels.dev.healthy,
alt: snapshot.channels.alt.healthy,
main: snapshot.channels.main.healthy,
},
};
await appendAndCommitBordbuch(
workspaceRoot,
systemId,
"sichtpass",
`Sichtpass site visibility snapshot for '${systemId}'`,
"agent",
{
writerRole: "nachweis",
metadata: metadata as unknown as Record,
},
`Bordbuch: sichtpass ${systemId} site-visibility-snapshot`,
);
logger.success(`[sichtpass.generate] appended sichtpass __site__ entry for ${systemId}`);
} catch (err) {
bordbuchWarning = `Bordbuch append failed: ${(err as Error).message}`;
logger.warn(`[sichtpass.generate] ${bordbuchWarning} — non-fatal`);
} finally {
await releaseLock(workspaceRoot, `bordbuch:${systemId}`);
await releaseLock(workspaceRoot, `system:${systemId}`);
}
}
const summaryParts = [
`[sichtpass.generate] ${systemId}: compositeHash=${compositeHash.slice(0, 16)}...`,
`routes=${snapshot.routeCount}`,
`nachweis=${snapshot.nachweisCount}`,
`pseo=${snapshot.pseoModules.length}`,
];
if (deduplicated) {
summaryParts.push("deduplicated=true");
}
if (bordbuchWarning) {
summaryParts.push(`warning=${bordbuchWarning}`);
}
return {
data: snapshot,
exitCode: 0,
summary: summaryParts.join(", "),
};
}