/*
RFC-0966: sternsystem.passport.verify — verify the signature and
content-freshness of a Sternsystem's passport.json. In --offline mode, checks
structure + signature only; without --offline, additionally cross-checks
resource inventory against live state.
- Does not generate or refresh the passport — use sternsystem.passport.generate.
- Does not enforce PASSPORT-01/02/03 rules — that is sternsystem.validate.
- RFC-0966: initial passport verify command handler.
- ADR-0094: apply the post-handover drift tolerance from passport-staleness.ts — a recorded bordbuchHead pointing at the entry just before the latest handover event is legitimate, trailing bookkeeping entries stripped. Verify now agrees with PASSPORT-03 on freshness verdicts.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
*/
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { verifyPassport, buildPassportPayload, computePassportHash } from "./passport.ts";
import {
isBordbuchDriftBookkeepingOnly,
dropTrailingBookkeepingEntries,
} from "./passport-staleness.ts";
import { readPassport } from "./registry-io.ts";
import { readBordbuch } from "../bordbuch/bordbuch-io.ts";
import { failResult } from "@warpgogol/werkstatt-shared/checks/result-helpers";
export interface SternsystemPassportVerifyData {
command: "sternsystem.passport.verify";
systemId: string;
valid: boolean;
passportHash: string | null;
creator: string | null;
staleness: {
bordbuchHeadMatches: boolean;
resourcesMatch: boolean;
};
errors: string[];
}
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
function flagBool(input: KernelCommandInput, key: string): boolean {
const v = input.flags[key];
return v === true;
}
export async function runSternsystemPassportVerify(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "id");
const offline = flagBool(input, "offline");
if (!systemId) {
throw new Error("[sternsystem.passport.verify] requires --id ");
}
const doc = await readPassport(workspaceRoot, systemId);
if (!doc) {
const errors = [`passport.json not found for system '${systemId}'`];
logger.error(`[sternsystem.passport.verify] ${systemId}: passport.json not found`);
return {
...failResult("sternsystem.passport.verify", [
`[sternsystem.passport.verify] ${systemId} passport missing`,
]),
data: {
command: "sternsystem.passport.verify",
systemId,
valid: false,
passportHash: null,
creator: null,
staleness: { bordbuchHeadMatches: false, resourcesMatch: false },
errors,
},
};
}
const { valid, errors } = await verifyPassport(doc);
let bordbuchHeadMatches = true;
let resourcesMatch = true;
if (valid && !offline) {
const freshPayload = await buildPassportPayload({
systemId,
werkstattRoot: workspaceRoot,
creatorIdentity: doc.payload.creator.identity,
creatorPublicKey: doc.payload.creator.publicKey,
});
freshPayload.provenance.createdAt = doc.payload.provenance.createdAt;
const freshHash = computePassportHash(freshPayload);
if (freshHash !== doc.passportHash) {
if (freshPayload.provenance.bordbuchHead !== doc.payload.provenance.bordbuchHead) {
// ADR-0092: a head drift caused only by bookkeeping entries
// (mirror-sync — propagation of already-committed state) does not
// stale the passport; any other new entry does.
try {
const entries = await readBordbuch(workspaceRoot, systemId);
const recordedHead = doc.payload.provenance.bordbuchHead;
if (!isBordbuchDriftBookkeepingOnly(entries, recordedHead)) {
// ADR-0094: post-handover tolerance, identical to PASSPORT-03 —
// the passport is regenerated before the handover bordbuch entry
// is appended, so a recorded head pointing at the entry just
// before the latest handover event is legitimate. Trailing
// bookkeeping entries (mirror-sync) are stripped before locating
// the handover, so a sync after handover still counts.
const significant = dropTrailingBookkeepingEntries(entries);
const lastSignificant = significant[significant.length - 1];
const prevEntry = significant[significant.length - 2];
const expectedHead = prevEntry ? prevEntry.hash : "";
const handoverTolerated =
significant.length > 0 &&
lastSignificant?.kind === "handover" &&
recordedHead === expectedHead;
if (!handoverTolerated) {
bordbuchHeadMatches = false;
}
}
} catch {
// Bordbuch unreadable — fail safe: treat the drift as real
bordbuchHeadMatches = false;
}
}
const freshResources = JSON.stringify(freshPayload.resources);
const docResources = JSON.stringify(doc.payload.resources);
if (freshResources !== docResources) {
resourcesMatch = false;
}
}
}
if (valid && bordbuchHeadMatches && resourcesMatch) {
logger.success(
`[sternsystem.passport.verify] ${systemId} passport valid, signed by ${doc.payload.creator.identity}, resources in sync`,
);
} else {
logger.error(
`[sternsystem.passport.verify] ${systemId} passport invalid: ${[...errors, !bordbuchHeadMatches ? "bordbuchHead drift" : null, !resourcesMatch ? "resources drift" : null].filter(Boolean).join(", ")}`,
);
}
return {
data: {
command: "sternsystem.passport.verify",
systemId,
valid: valid && bordbuchHeadMatches && resourcesMatch,
passportHash: doc.passportHash,
creator: doc.payload.creator.identity,
staleness: { bordbuchHeadMatches, resourcesMatch },
errors,
},
exitCode: valid && bordbuchHeadMatches && resourcesMatch ? 0 : 1,
summary: `[sternsystem.passport.verify] ${systemId} passport ${valid && bordbuchHeadMatches && resourcesMatch ? "valid" : "invalid"}, signed by ${doc.payload.creator.identity}`,
};
}