/* RFC-0707: nachweis.withdraw command handler — revokes consent (conditional on policy), sets withdrawn status, regenerates manifest. Does not implement data retention policy — deferred to future RFC. RFC-0872: conditionally revoke consent based on publication policy (only attestation-v1). RFC-0888: pass --skip-bordbuch to manifest.generate and append sichtpass Bordbuch entry with withdrawn: true after manifest regeneration. 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-1254: drop unused io-migration leftovers RFC-0707 */ import path from "node:path"; import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-engine/kernel"; import { executeKernelCommand } from "@warpgogol/werkstatt-engine/kernel"; import { parseMarkdownFrontmatter, stringifyMarkdownFrontmatter, } from "@warpgogol/werkstatt-shared/content"; import { appendBatchAndCommitBordbuch, appendAndCommitBordbuch, } from "../bordbuch/bordbuch-commit-helper.ts"; import { acquireLock, releaseLock, generateOperationId } from "../werkstatt/index.ts"; import { isNachweisEntitled, makeSkipResult, resolveNachweisCachePath, resolvePbpEntityDir, resolveDefaultLang, resolveNachweisPublicationPolicy, type NachweisWithdrawResult, } from "./nachweis-io.ts"; function flagString(input: KernelCommandInput, key: string): string | undefined { const v = input.flags[key]; return typeof v === "string" ? v : undefined; } export async function runNachweisWithdraw( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const { workspaceRoot, logger } = context; const systemId = flagString(input, "system") ?? context.site?.name; const slug = flagString(input, "slug"); const reason = flagString(input, "reason") ?? "withdrawn by operator"; if (!systemId) throw new Error("[nachweis.withdraw] --system is required"); if (!slug) throw new Error("[nachweis.withdraw] --slug is required"); const entitled = await isNachweisEntitled(workspaceRoot, systemId); if (!entitled) { return makeSkipResult( "nachweis.withdraw", systemId, ) as unknown as KernelCommandResult; } const cachePath = await resolveNachweisCachePath(workspaceRoot, systemId); const lang = await resolveDefaultLang(cachePath); // Read EvidenceSource entity const evidenceDir = resolvePbpEntityDir(cachePath, lang, "evidence-source"); const evidenceFile = path.join(evidenceDir, `${slug}.md`); if (!(await context.io.exists(evidenceFile))) { throw new Error( `[nachweis.withdraw] NOT_FOUND: evidence-source '${slug}' not found at ${evidenceFile}`, ); } const rawEvidence = await context.io.readFile(evidenceFile); const { data: evidenceData, content: evidenceContent } = parseMarkdownFrontmatter(rawEvidence); // Check if already withdrawn (idempotent) const currentRecordStatus = (evidenceData as Record).recordStatus as string | undefined; if (currentRecordStatus === "withdrawn") { return { data: { recordId: (evidenceData.recordId as string | undefined) ?? `nr_${slug}`, systemId, withdrawn: false, alreadyWithdrawn: true, bordbuchEventIds: [], }, exitCode: 0, summary: `[nachweis.withdraw] ${systemId}: '${slug}' already withdrawn — no-op`, }; } // Update EvidenceSource: set record_status and publication.visibility (evidenceData as Record).recordStatus = "withdrawn"; const publication = ((evidenceData as Record).publication as Record | undefined) ?? {}; publication.visibility = "private"; (evidenceData as Record).publication = publication; const updatedContent = stringifyMarkdownFrontmatter(evidenceContent, evidenceData); await context.io.writeFile(evidenceFile, updatedContent); // RFC-0872: conditionally revoke consent based on publication policy const kind = (evidenceData as Record).kind as string | undefined; const policyId = kind ? resolveNachweisPublicationPolicy(kind) : "attestation-v1"; const shouldRevokeConsent = policyId === "attestation-v1"; // Update Consent entity if it exists and policy requires consent const consentDir = resolvePbpEntityDir(cachePath, lang, "consent"); const consentFile = path.join(consentDir, `${slug}.md`); let consentRevoked = false; if (shouldRevokeConsent && (await context.io.exists(consentFile))) { const rawConsent = await context.io.readFile(consentFile); const { data: consentData, content: consentContent } = parseMarkdownFrontmatter(rawConsent); const existingScope = (consentData.consentScope as | { document?: { status?: string; grantedAt?: string | null; method?: string }; screenshot?: { status?: string; grantedAt?: string | null; method?: string }; websiteLink?: { status?: string; grantedAt?: string | null; method?: string }; } | undefined) ?? {}; const defaultEntry = { status: "not_requested", grantedAt: null, method: "none" }; consentData.consentScope = { document: { status: "denied", grantedAt: null, method: "none" }, screenshot: existingScope.screenshot ?? defaultEntry, websiteLink: existingScope.websiteLink ?? defaultEntry, }; delete consentData.consentStatus; delete consentData.grantedAt; delete consentData.method; const updatedConsent = stringifyMarkdownFrontmatter(consentContent, consentData); await context.io.writeFile(consentFile, updatedConsent); consentRevoked = true; } // Append Bordbuch entries const operationId = generateOperationId(); await acquireLock(workspaceRoot, `system:${systemId}`, operationId, "nachweis.withdraw", "agent"); await acquireLock( workspaceRoot, `bordbuch:${systemId}`, operationId, "nachweis.withdraw", "agent", ); const bordbuchEventIds: string[] = []; try { const { entries } = await appendBatchAndCommitBordbuch( workspaceRoot, systemId, shouldRevokeConsent ? [ { kind: "nachweis-consent", summary: `Consent revoked for '${slug}': ${reason}`, actor: "agent", options: { writerRole: "nachweis", metadata: { slug, reason, action: "withdraw" }, }, }, { kind: "nachweis-record", summary: `Withdrawn '${slug}': ${reason}`, actor: "agent", options: { writerRole: "nachweis", metadata: { slug, reason, action: "withdraw", policyId }, }, }, ] : [ { kind: "nachweis-record", summary: `Withdrawn '${slug}': ${reason}`, actor: "agent", options: { writerRole: "nachweis", metadata: { slug, reason, action: "withdraw", policyId }, }, }, ], `Bordbuch: nachweis-withdraw ${systemId} ${slug}`, ); for (const e of entries) { bordbuchEventIds.push(e.id); } } finally { await releaseLock(workspaceRoot, `bordbuch:${systemId}`); await releaseLock(workspaceRoot, `system:${systemId}`); } // Regenerate manifest — pass --skip-bordbuch to prevent duplicate sichtpass entry (RFC-0888) await executeKernelCommand({ workspaceRoot, commandName: "nachweis.manifest.generate", siteName: systemId, argv: [`--system=${systemId}`, "--skip-bordbuch"], }); logger.info( `[nachweis.withdraw] withdrawn '${slug}' — manifest regenerated${consentRevoked ? " (consent revoked)" : " (no consent revocation — policy: " + policyId + ")"}`, ); // RFC-0888: Append sichtpass Bordbuch entry with withdrawn: true const sichtpassOperationId = generateOperationId(); await acquireLock( workspaceRoot, `system:${systemId}`, sichtpassOperationId, "nachweis.withdraw", "agent", ); await acquireLock( workspaceRoot, `bordbuch:${systemId}`, sichtpassOperationId, "nachweis.withdraw", "agent", ); try { await appendAndCommitBordbuch( workspaceRoot, systemId, "sichtpass", `Sichtpass manifest entry withdrawn for '${slug}'`, "agent", { writerRole: "nachweis", metadata: { slug, manifestVersion: "1.0.0", withdrawn: true, verificationLevel: "N0", }, }, `Bordbuch: sichtpass ${systemId} ${slug} withdrawn`, ); } finally { await releaseLock(workspaceRoot, `bordbuch:${systemId}`); await releaseLock(workspaceRoot, `system:${systemId}`); } return { data: { recordId: (evidenceData.recordId as string | undefined) ?? `nr_${slug}`, systemId, withdrawn: true, alreadyWithdrawn: false, bordbuchEventIds, }, exitCode: 0, summary: `[nachweis.withdraw] ${systemId}: withdrawn '${slug}' (bordbuch: ${bordbuchEventIds.length} entries)`, nextSteps: [ { action: `Validate the nachweis: pnpm exec werkstatt run nachweis.validate --site ${systemId}`, kind: "optional", }, ], }; }