/*
RFC-0715/RFC-0921: nachweis.sign command handler — signs the core evidence fields of a Nachweis record with an Ed25519 operator key via shared signing core.
- Does not timestamp — that is nachweis.timestamp.
- Does not verify — that is nachweis.verify-signature.
- RFC-0921: delegate signing to shared signing core (sign, canonicalBytes, fromHex). Remove @noble/ed25519 and stableStringify imports. Remove canonicalRecordPayload function.
- 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-0715
*/
import path from "node:path";
import { byteHash } from "@warpgogol/werkstatt-engine/fingerprint";
import {
sign as signingSign,
canonicalBytes as signingCanonicalBytes,
fromHex,
toHex,
getPublicKey,
} from "@warpgogol/werkstatt-engine/signing";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { parseMarkdownFrontmatter } from "@warpgogol/werkstatt-shared/content";
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,
resolveDefaultLang,
resolvePbpEntityDir,
flagString,
flagBool,
type NachweisSignResult,
} from "./nachweis-n3-types.ts";
export interface NachweisRecordPayload {
recordId: string;
slug: string;
kind: string;
name: string;
items: Record;
}
export async function runNachweisSign(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
const slug = flagString(input, "slug");
const keyFilePath = flagString(input, "key-file");
const dryRun = context.dryRun === true || flagBool(input, "dry-run");
if (!systemId) throw new Error("[nachweis.sign] --system is required");
if (!slug) throw new Error("[nachweis.sign] --slug is required");
if (!keyFilePath) throw new Error("[nachweis.sign] --key-file is required");
const entitled = await isNachweisEntitled(workspaceRoot, systemId);
if (!entitled) {
return makeSkipResult(
"nachweis.sign",
systemId,
) as unknown as KernelCommandResult;
}
if (!(await context.io.exists(keyFilePath))) {
throw new Error(
`[nachweis.sign] KEY_NOT_FOUND: private key file '${keyFilePath}' does not exist. Run nachweis.key.ensure first.`,
);
}
const privateKeyHex = (await context.io.readFile(keyFilePath)).trim();
const privateKeyBytes = fromHex(privateKeyHex);
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))) {
throw new Error(
`[nachweis.sign] NOT_FOUND: evidence-source '${slug}' not found at ${evidenceFile}`,
);
}
const rawEvidence = await context.io.readFile(evidenceFile);
const { data: evidenceData } = parseMarkdownFrontmatter(rawEvidence);
const payload: NachweisRecordPayload = {
recordId: (evidenceData.recordId as string | undefined) ?? `nr_${slug}`,
slug,
kind: (evidenceData.kind as string | undefined) ?? "evidence",
name: (evidenceData.name as string | undefined) ?? slug,
items: (evidenceData.items as Record | undefined) ?? {},
};
const canonicalBytes = signingCanonicalBytes(payload as unknown as Record);
const signatureBytes = await signingSign(
privateKeyBytes,
payload as unknown as Record,
);
const signatureHex = toHex(signatureBytes);
const publicKeyBytes = await getPublicKey(privateKeyBytes);
const publicKeyHex = toHex(publicKeyBytes);
// Idempotency: check for existing nachweis-signed entry
const bordbuchEntries = await readBordbuch(workspaceRoot, systemId);
const existingSigned = bordbuchEntries.find(
(e) =>
e.kind === "nachweis-signed" &&
e.metadata?.slug === slug &&
e.metadata?.signatureHex === signatureHex,
);
if (existingSigned) {
return {
data: {
slug,
systemId,
signatureHex,
publicKeyHex,
bordbuchEventId: existingSigned.id,
idempotent: true,
},
exitCode: 0,
summary: `[nachweis.sign] ${systemId}: already signed '${slug}' (bordbuch: ${existingSigned.id})`,
nextSteps: [
{
action: `Timestamp the nachweis: pnpm exec werkstatt run nachweis.timestamp --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}
if (dryRun) {
return {
data: {
slug,
systemId,
signatureHex,
publicKeyHex,
bordbuchEventId: null,
idempotent: false,
},
exitCode: 0,
summary: `[nachweis.sign] ${systemId}: DRY RUN — would sign '${slug}'`,
nextSteps: [
{
action: `Sign for real: pnpm exec werkstatt run nachweis.sign --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}
const operationId = generateOperationId();
await acquireLock(workspaceRoot, `system:${systemId}`, operationId, "nachweis.sign", "agent");
await acquireLock(workspaceRoot, `bordbuch:${systemId}`, operationId, "nachweis.sign", "agent");
let bordbuchEventId: string;
try {
const { entry } = await appendAndCommitBordbuch(
workspaceRoot,
systemId,
"nachweis-signed",
`Record '${slug}' signed by operator`,
"agent",
{
writerRole: "nachweis",
metadata: {
slug,
signatureHex,
publicKeyHex,
payloadHash: byteHash(canonicalBytes).replace("sha256:", ""),
},
},
`Bordbuch: nachweis-signed ${systemId} ${slug}`,
);
bordbuchEventId = entry.id;
} finally {
await releaseLock(workspaceRoot, `bordbuch:${systemId}`);
await releaseLock(workspaceRoot, `system:${systemId}`);
}
return {
data: {
slug,
systemId,
signatureHex,
publicKeyHex,
bordbuchEventId,
idempotent: false,
},
exitCode: 0,
summary: `[nachweis.sign] ${systemId}: signed '${slug}' (bordbuch: ${bordbuchEventId})`,
nextSteps: [
{
action: `Timestamp the nachweis: pnpm exec werkstatt run nachweis.timestamp --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}