/* 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(", "), }; }