/*
RFC-0355 §5.6: bordbuch.append — append a single entry through the controlled writer-role surface.
- Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.
- RFC-0355: initial bordbuch.append command handler.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
*/
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import type { BordbuchEntryKind } from "@warpgogol/werkstatt-engine/schemas";
import { appendBordbuchEntry, validateWriterRole, readBordbuch } from "./bordbuch-io.ts";
import { acquireLock, releaseLock, generateOperationId } from "../werkstatt/index.ts";
export interface BordbuchAppendData {
eventId: string;
systemId: string;
kind: BordbuchEntryKind;
hash: string;
}
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
export async function runBordbuchAppend(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
const kind = flagString(input, "kind") as BordbuchEntryKind | undefined;
const summary = flagString(input, "summary");
const missionId = flagString(input, "mission") ?? null;
const releaseId = flagString(input, "release") ?? null;
const actor = flagString(input, "actor") ?? "agent";
const writerRole = flagString(input, "writer-role");
const metadataStr = flagString(input, "metadata");
if (!systemId) throw new Error("[bordbuch.append] --system is required");
if (!kind) throw new Error("[bordbuch.append] --kind is required");
if (!summary) throw new Error("[bordbuch.append] --summary is required");
if (!writerRole) throw new Error("[bordbuch.append] --writer-role is required");
if (!validateWriterRole(writerRole, kind)) {
throw new Error(
`[bordbuch.append] kind '${kind}' is not allowed for writer-role '${writerRole}'`,
);
}
// Check mission-lifecycle kinds require open mission
if (
kind === "mission-open" ||
kind === "mission-close" ||
kind === "mission-abort" ||
kind === "mission-open-rolled-back"
) {
if (!missionId) {
throw new Error(`[bordbuch.append] --mission is required for kind '${kind}'`);
}
}
let metadata: Record | undefined;
if (metadataStr) {
try {
metadata = JSON.parse(metadataStr);
} catch {
throw new Error("[bordbuch.append] --metadata must be valid JSON");
}
}
const operationId = generateOperationId();
await acquireLock(workspaceRoot, `system:${systemId}`, operationId, "bordbuch.append", actor);
await acquireLock(workspaceRoot, `bordbuch:${systemId}`, operationId, "bordbuch.append", actor);
try {
// Check terminal mission for mission-lifecycle kinds
if (missionId && (kind === "mission-close" || kind === "mission-abort")) {
const entries = await readBordbuch(workspaceRoot, systemId);
const missionEntries = entries.filter((e) => e.missionId === missionId);
const hasOpen = missionEntries.some((e) => e.kind === "mission-open");
const hasTerminal = missionEntries.some(
(e) => e.kind === "mission-close" || e.kind === "mission-abort",
);
if (!hasOpen) {
throw new Error(`[bordbuch.append] mission '${missionId}' has no preceding mission-open`);
}
if (hasTerminal) {
throw new Error(`[bordbuch.append] mission '${missionId}' is already in a terminal state`);
}
}
const entry = await appendBordbuchEntry(workspaceRoot, systemId, kind, summary, actor, {
missionId,
releaseId,
writerRole,
metadata,
});
logger.success(`[bordbuch.append] appended ${entry.id} to ${systemId} Bordbuch`);
return {
data: {
eventId: entry.id,
systemId,
kind,
hash: entry.hash,
},
exitCode: 0,
summary: `[bordbuch.append] appended ${entry.id} to ${systemId} Bordbuch`,
nextSteps: [
{
action: `Commit the bordbuch: pnpm exec werkstatt run bordbuch.commit --site ${systemId}`,
kind: "optional",
},
],
};
} finally {
await releaseLock(workspaceRoot, `bordbuch:${systemId}`);
await releaseLock(workspaceRoot, `system:${systemId}`);
}
}