/*
RFC-0966: sternsystem.passport.generate — build, sign, and write the
site passport for a Sternsystem. Reads system-config.yaml, system.pin.json,
.env.example, bordbuch, and mission count to assemble SitePassportV1, then
signs with the creator's Ed25519 key (SIGNING_PRIVATE_KEY env var).
- Does not verify the passport — use sternsystem.passport.verify for that.
- Does not manage key rotation — see RFC-0966 Key rotation section.
- RFC-0966: initial passport generate 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 { loadPrivateKey } from "@warpgogol/werkstatt-engine/signing";
import { resolveActor } from "../mission/actor-identity.ts";
import {
buildPassportPayload,
signPassport,
derivePublicKey,
type SignedSitePassport,
} from "./passport.ts";
import { readPassport, writePassport, readSystemState, writeSystemState } from "./registry-io.ts";
export interface SternsystemPassportGenerateData {
command: "sternsystem.passport.generate";
systemId: string;
passportHash: string;
passportPath: string;
creator: string;
generated: boolean;
}
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
function filterEnv(env: Record): Record {
const result: Record = {};
for (const [key, value] of Object.entries(env)) {
if (value !== undefined) {
result[key] = value;
}
}
return result;
}
export async function runSternsystemPassportGenerate(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "id");
if (!systemId) {
throw new Error("[sternsystem.passport.generate] requires --id ");
}
const creatorIdentity = resolveActor(input);
const env = { ...filterEnv(process.env) };
const privateKeyEnv = env["SIGNING_PRIVATE_KEY"];
const privateKeyPath = env["SIGNING_PRIVATE_KEY_PATH"];
if (!privateKeyEnv && !privateKeyPath) {
throw new Error(
"[sternsystem.passport.generate] No signing key configured. Set SIGNING_PRIVATE_KEY or SIGNING_PRIVATE_KEY_PATH env var.",
);
}
let privateKeyBytes: Uint8Array;
try {
if (privateKeyEnv) {
privateKeyBytes = await loadPrivateKey({ pem: privateKeyEnv });
} else {
privateKeyBytes = await loadPrivateKey({ filePath: privateKeyPath!, encoding: "pem" });
}
} catch (err) {
throw new Error(
`[sternsystem.passport.generate] Failed to load private key: ${err instanceof Error ? err.message : String(err)}`,
);
}
const creatorPublicKey = await derivePublicKey(privateKeyBytes);
const existingPassport = await readPassport(workspaceRoot, systemId);
let createdAt = new Date().toISOString();
if (existingPassport) {
createdAt = existingPassport.payload.provenance.createdAt;
}
const payload = await buildPassportPayload({
systemId,
werkstattRoot: workspaceRoot,
creatorIdentity,
creatorPublicKey,
});
payload.provenance.createdAt = createdAt;
const { passportHash, signature } = await signPassport(payload, privateKeyBytes);
const doc: SignedSitePassport = {
payload,
passportHash,
signature,
};
const existingHash = existingPassport?.passportHash;
if (existingHash === passportHash) {
logger.info(
`[sternsystem.passport.generate] ${systemId} passport unchanged (hash: ${passportHash}) — no write needed`,
);
const passportPath = `../systems-cache/${systemId}/passport.json`;
return {
data: {
command: "sternsystem.passport.generate",
systemId,
passportHash,
passportPath,
creator: creatorIdentity,
generated: false,
},
exitCode: 0,
summary: `[sternsystem.passport.generate] ${systemId} passport unchanged — skipped (DNA-58 byte-stability)`,
};
}
const passportPath = await writePassport(workspaceRoot, systemId, doc);
try {
const state = await readSystemState(workspaceRoot, systemId);
if (!state.passportRequired) {
state.passportRequired = true;
await writeSystemState(workspaceRoot, systemId, state);
logger.info(` Set passportRequired: true in system-state.yaml`);
}
} catch {
// Non-fatal — state update skipped
}
logger.success(
`[sternsystem.passport.generate] ${systemId} passport generated (hash: ${passportHash})`,
);
return {
data: {
command: "sternsystem.passport.generate",
systemId,
passportHash,
passportPath,
creator: creatorIdentity,
generated: true,
},
exitCode: 0,
summary: `[sternsystem.passport.generate] ${systemId} passport signed by ${creatorIdentity}`,
};
}