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