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