/*
RFC-0714: nachweis.approve command handler — records human approval, verification level, and legal content check in a Bordbuch entry.
- Does not read or modify the evidence-source entity — approval is recorded only in the Bordbuch audit trail.
- Does not validate the verification level — the operator is responsible for passing the correct level.
- RFC-0715: add N3 gate — verify nachweis-signed and nachweis-timestamped Bordbuch entries exist before approving at N3.
- 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-1237: resolve dry-run via context.dryRun fallback — consumeCommonFlags strips --dry-run into context.dryRun before dispatch, so a flags-only read was permanently false under werkstatt run (enforced by KERNEL-FLAG-10).
- RFC-1254: drop unused io-migration leftovers
RFC-0714
*/
import path from "node:path";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { appendAndCommitBordbuch } from "../bordbuch/bordbuch-commit-helper.ts";
import { readBordbuch } from "../bordbuch/bordbuch-io.ts";
import { acquireLock, releaseLock, generateOperationId } from "../werkstatt/index.ts";
import {
isNachweisEntitled,
makeSkipResult,
resolveNachweisCachePath,
resolvePbpEntityDir,
resolveDefaultLang,
type NachweisApproveResult,
} from "./nachweis-io.ts";
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
function flagBool(input: KernelCommandInput, key: string): boolean {
const v = input.flags[key];
return v === true || v === "true";
}
export async function runNachweisApprove(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
const slug = flagString(input, "slug");
const verificationLevel = flagString(input, "verification-level");
const legalContentCheckRaw = flagString(input, "legal-content-check");
const dryRun = context.dryRun === true || flagBool(input, "dry-run");
if (!systemId) throw new Error("[nachweis.approve] --system is required");
if (!slug) throw new Error("[nachweis.approve] --slug is required");
if (!verificationLevel) throw new Error("[nachweis.approve] --verification-level is required");
if (!legalContentCheckRaw)
throw new Error("[nachweis.approve] --legal-content-check is required");
const ALLOWED_VERIFICATION_LEVELS = ["N0", "N1", "N2", "N3"];
if (!ALLOWED_VERIFICATION_LEVELS.includes(verificationLevel)) {
throw new Error(
`[nachweis.approve] INVALID_VERIFICATION_LEVEL: '${verificationLevel}' — must be one of ${ALLOWED_VERIFICATION_LEVELS.join(", ")}`,
);
}
const ALLOWED_LEGAL_CHECK_VALUES = ["passed", "failed"];
if (!ALLOWED_LEGAL_CHECK_VALUES.includes(legalContentCheckRaw)) {
throw new Error(
`[nachweis.approve] INVALID_LEGAL_CONTENT_CHECK: '${legalContentCheckRaw}' — must be 'passed' or 'failed'`,
);
}
const legalContentCheckPassed = legalContentCheckRaw === "passed";
const entitled = await isNachweisEntitled(workspaceRoot, systemId);
if (!entitled) {
return makeSkipResult(
"nachweis.approve",
systemId,
) as unknown as KernelCommandResult;
}
const cachePath = await resolveNachweisCachePath(workspaceRoot, systemId);
const lang = await resolveDefaultLang(cachePath);
const evidenceDir = resolvePbpEntityDir(cachePath, lang, "evidence-source");
const evidenceFile = path.join(evidenceDir, `${slug}.md`);
if (!(await context.io.exists(evidenceFile))) {
logger.warn(
`[nachweis.approve] no evidence-source file found for slug '${slug}' — Bordbuch entry will still be written`,
);
}
if (dryRun) {
return {
data: {
slug,
systemId,
verificationLevel,
legalContentCheckPassed,
bordbuchEventId: null,
},
exitCode: 0,
summary: `[nachweis.approve] ${systemId}: DRY RUN — would approve '${slug}' (verification: ${verificationLevel}, legal: ${legalContentCheckRaw})`,
nextSteps: [
{
action: `Approve for real: pnpm exec werkstatt run nachweis.approve --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}
// RFC-0715: N3 gate — verify signature and timestamp artifacts exist in Bordbuch
if (verificationLevel === "N3") {
const bordbuchEntries = await readBordbuch(workspaceRoot, systemId);
const hasSigned = bordbuchEntries.some(
(e) => e.kind === "nachweis-signed" && e.metadata?.slug === slug,
);
const hasTimestamped = bordbuchEntries.some(
(e) => e.kind === "nachweis-timestamped" && e.metadata?.slug === slug,
);
if (!hasSigned || !hasTimestamped) {
const missing: string[] = [];
if (!hasSigned) missing.push("nachweis-signed");
if (!hasTimestamped) missing.push("nachweis-timestamped");
return {
data: {
slug,
systemId,
verificationLevel,
legalContentCheckPassed,
bordbuchEventId: null,
},
exitCode: 1,
summary: `[nachweis.approve] N3_GATE_FAILED: missing ${missing.join(", ")} Bordbuch entries for '${slug}'. Run nachweis.sign and nachweis.timestamp first.`,
nextSteps: [
{
action: `Sign the nachweis: pnpm exec werkstatt run nachweis.sign --site ${systemId} --slug ${slug}`,
kind: "required",
},
{
action: `Timestamp the nachweis: pnpm exec werkstatt run nachweis.timestamp --site ${systemId} --slug ${slug}`,
kind: "required",
},
],
};
}
}
const operationId = generateOperationId();
await acquireLock(workspaceRoot, `system:${systemId}`, operationId, "nachweis.approve", "agent");
await acquireLock(
workspaceRoot,
`bordbuch:${systemId}`,
operationId,
"nachweis.approve",
"agent",
);
let bordbuchEventId: string;
try {
const { entry } = await appendAndCommitBordbuch(
workspaceRoot,
systemId,
"nachweis-record",
`Record '${slug}' approved (verification: ${verificationLevel}, legal: ${legalContentCheckRaw})`,
"agent",
{
writerRole: "nachweis",
metadata: {
slug,
verificationLevel,
legalContentCheckPassed,
approved: true,
},
},
`Bordbuch: nachweis-record ${systemId} ${slug}`,
);
bordbuchEventId = entry.id;
} finally {
await releaseLock(workspaceRoot, `bordbuch:${systemId}`);
await releaseLock(workspaceRoot, `system:${systemId}`);
}
return {
data: {
slug,
systemId,
verificationLevel,
legalContentCheckPassed,
bordbuchEventId,
},
exitCode: 0,
summary: `[nachweis.approve] ${systemId}: approved '${slug}' (verification: ${verificationLevel}, legal: ${legalContentCheckRaw}, bordbuch: ${bordbuchEventId})`,
nextSteps: [
{
action: `Publish the nachweis: pnpm exec werkstatt run nachweis.publish --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}