/*
RFC-0715/RFC-0871: nachweis.timestamp command handler — obtains an RFC 3161 timestamp token for a signed Nachweis record, with optional eIDAS qualified assurance metadata.
- Does not sign — that is nachweis.sign.
- Does not verify the timestamp token — that is nachweis.verify-signature.
- RFC-0871: add --timestamp-assurance and --qualification-evidence-ref flags, default rfc3161, fail eidas-qualified without evidence.
- 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-0715
*/
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,
flagString,
flagBool,
type NachweisTimestampResult,
type TimestampAssurance,
} from "./nachweis-n3-types.ts";
import { FreeTsaAdapter, HttpTsaAdapter, type TsaAdapter } from "./tsa-adapter.ts";
export async function runNachweisTimestamp(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
const slug = flagString(input, "slug");
const tsaUrl = flagString(input, "tsa-url");
const dryRun = context.dryRun === true || flagBool(input, "dry-run");
const timestampAssuranceRaw = flagString(input, "timestamp-assurance") ?? "rfc3161";
const qualificationEvidenceRef = flagString(input, "qualification-evidence-ref");
if (!systemId) throw new Error("[nachweis.timestamp] --system is required");
if (!slug) throw new Error("[nachweis.timestamp] --slug is required");
if (timestampAssuranceRaw !== "rfc3161" && timestampAssuranceRaw !== "eidas-qualified") {
throw new Error(
`[nachweis.timestamp] INVALID_ASSURANCE: --timestamp-assurance must be 'rfc3161' or 'eidas-qualified', got '${timestampAssuranceRaw}'.`,
);
}
const timestampAssurance: TimestampAssurance = timestampAssuranceRaw;
if (timestampAssurance === "eidas-qualified" && !qualificationEvidenceRef) {
throw new Error(
`[nachweis.timestamp] TIMESTAMP_QUALIFICATION_EVIDENCE_REQUIRED: --qualification-evidence-ref is required when --timestamp-assurance=eidas-qualified. Provide a URL to the QTSP trust list entry or qualification certificate.`,
);
}
const entitled = await isNachweisEntitled(workspaceRoot, systemId);
if (!entitled) {
return makeSkipResult(
"nachweis.timestamp",
systemId,
) as unknown as KernelCommandResult;
}
// Read bordbuch entries to find the signature
const bordbuchEntries = await readBordbuch(workspaceRoot, systemId);
const signedEntry = bordbuchEntries.find(
(e) => e.kind === "nachweis-signed" && e.metadata?.slug === slug,
);
if (!signedEntry) {
throw new Error(
`[nachweis.timestamp] SIGNATURE_NOT_FOUND: no nachweis-signed Bordbuch entry found for '${slug}'. Run nachweis.sign first.`,
);
}
const signatureHex = signedEntry.metadata?.signatureHex as string | undefined;
if (!signatureHex) {
throw new Error(
`[nachweis.timestamp] SIGNATURE_NOT_FOUND: nachweis-signed entry for '${slug}' has no signatureHex metadata.`,
);
}
const signatureBytes = new Uint8Array(Buffer.from(signatureHex, "hex"));
// Idempotency: check for existing nachweis-timestamped entry
const existingTimestamped = bordbuchEntries.find(
(e) => e.kind === "nachweis-timestamped" && e.metadata?.slug === slug,
);
if (existingTimestamped) {
return {
data: {
slug,
systemId,
timestampTokenBase64: existingTimestamped.metadata?.timestampTokenBase64 as string,
tsaUrl: existingTimestamped.metadata?.tsaUrl as string,
bordbuchEventId: existingTimestamped.id,
idempotent: true,
timestampAssurance:
(existingTimestamped.metadata?.timestampAssurance as TimestampAssurance) ?? "rfc3161",
...(existingTimestamped.metadata?.qualificationEvidenceRef
? {
qualificationEvidenceRef: existingTimestamped.metadata
.qualificationEvidenceRef as string,
}
: {}),
},
exitCode: 0,
summary: `[nachweis.timestamp] ${systemId}: already timestamped '${slug}' (bordbuch: ${existingTimestamped.id})`,
nextSteps: [
{
action: `Approve the nachweis: pnpm exec werkstatt run nachweis.approve --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}
const adapter: TsaAdapter = tsaUrl ? new HttpTsaAdapter("custom", tsaUrl) : FreeTsaAdapter;
if (dryRun) {
return {
data: {
slug,
systemId,
timestampTokenBase64: "",
tsaUrl: adapter.url,
bordbuchEventId: null,
idempotent: false,
timestampAssurance,
...(qualificationEvidenceRef ? { qualificationEvidenceRef } : {}),
},
exitCode: 0,
summary: `[nachweis.timestamp] ${systemId}: DRY RUN — would timestamp '${slug}' via ${adapter.name}`,
};
}
const timestampTokenBytes = await adapter.timestamp(signatureBytes);
const timestampTokenBase64 = Buffer.from(timestampTokenBytes).toString("base64");
const operationId = generateOperationId();
await acquireLock(
workspaceRoot,
`system:${systemId}`,
operationId,
"nachweis.timestamp",
"agent",
);
await acquireLock(
workspaceRoot,
`bordbuch:${systemId}`,
operationId,
"nachweis.timestamp",
"agent",
);
let bordbuchEventId: string;
try {
const { entry } = await appendAndCommitBordbuch(
workspaceRoot,
systemId,
"nachweis-timestamped",
`Record '${slug}' timestamped via ${adapter.name}`,
"agent",
{
writerRole: "nachweis",
metadata: {
slug,
timestampTokenBase64,
tsaUrl: adapter.url,
tsaName: adapter.name,
timestampAssurance,
...(qualificationEvidenceRef ? { qualificationEvidenceRef } : {}),
},
},
`Bordbuch: nachweis-timestamped ${systemId} ${slug}`,
);
bordbuchEventId = entry.id;
} finally {
await releaseLock(workspaceRoot, `bordbuch:${systemId}`);
await releaseLock(workspaceRoot, `system:${systemId}`);
}
return {
data: {
slug,
systemId,
timestampTokenBase64,
tsaUrl: adapter.url,
bordbuchEventId,
idempotent: false,
timestampAssurance,
...(qualificationEvidenceRef ? { qualificationEvidenceRef } : {}),
},
exitCode: 0,
summary: `[nachweis.timestamp] ${systemId}: timestamped '${slug}' via ${adapter.name} (bordbuch: ${bordbuchEventId})`,
nextSteps: [
{
action: `Approve the nachweis: pnpm exec werkstatt run nachweis.approve --site ${systemId} --slug ${slug}`,
kind: "optional",
},
],
};
}