/*
RFC-1124: Command handlers for fleet.claims.init, publish, sync, verify,
status. Thin wrappers around claims-repo.ts pure functions.
- Does not implement claims logic — that lives in claims-repo.ts.
- Does not call the RFC-0967 Worker — claims are the authority.
- RFC-1124: initial command handlers for fleet.claims.* surface.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
- RFC-1254: drop unused io-migration leftovers
*/
import path from "node:path";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import {
claimsStatus,
ensureClaimsClone,
loadSigningKeyFromEnv,
publishClaim,
syncClaims,
verifyClaimOffline,
type FleetClaimsConfig,
} from "./claims-repo.ts";
import { failResult } from "@warpgogol/werkstatt-shared/checks/result-helpers";
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 {
return input.flags[key] === true;
}
const DEFAULT_LOCAL_PATH = ".werkstatt/fleet-claims";
// ---------------------------------------------------------------------------
// fleet.claims.init
// ---------------------------------------------------------------------------
export async function runFleetClaimsInit(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const remote = flagString(input, "remote");
if (!remote) {
return {
...failResult("fleet.claims.init", ["--remote flag is required"]),
data: { error: "missing --remote flag" },
};
}
const remotes = remote
.split(",")
.map((r) => r.trim())
.filter(Boolean);
const localPath = flagString(input, "local-path") ?? DEFAULT_LOCAL_PATH;
const workerUrl = flagString(input, "worker-url");
try {
const config: FleetClaimsConfig = { remotes, localPath, workerUrl };
const configPath = path.join(context.workspaceRoot, "werkstatt.fleet.json");
await context.io.writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
const clonePath = await ensureClaimsClone(context.workspaceRoot, config);
return {
exitCode: 0,
summary: `[fleet.claims.init] configured ${remotes.length} remote(s), clone at ${localPath}`,
data: {
command: "fleet.claims.init",
configPath,
clonePath,
remotes,
},
};
} catch (err) {
return {
...failResult("claims.commands", [
`[fleet.claims.init] failed: ${err instanceof Error ? err.message : String(err)}`,
]),
data: { error: err instanceof Error ? err.message : String(err) },
};
}
}
// ---------------------------------------------------------------------------
// fleet.claims.publish
// ---------------------------------------------------------------------------
export async function runFleetClaimsPublish(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const systemId = flagString(input, "id");
if (!systemId) {
return {
...failResult("fleet.claims.publish", ["--id flag is required"]),
data: { error: "missing --id flag" },
};
}
const key = await loadSigningKeyFromEnv();
if (!key) {
return {
...failResult("fleet.claims.publish", [
"no signing key — set SIGNING_PRIVATE_KEY or SIGNING_PRIVATE_KEY_PATH",
]),
data: { error: "no signing key configured" },
};
}
try {
const result = await publishClaim({
systemId,
werkstattRoot: context.workspaceRoot,
signerPrivateKeyHex: key.privateKeyHex,
signerPublicKey: key.publicKeyHex,
authorizationHash: flagString(input, "authorization-hash") ?? null,
push: !flagBool(input, "no-push"),
});
const warn =
result.pushErrors.length > 0
? ` (push failed: ${result.pushErrors.join("; ")} — retry via fleet.claims.sync)`
: "";
return {
exitCode: 0,
summary: `[fleet.claims.publish] ${systemId} claim ${result.claim.passportHash.slice(0, 16)} committed${warn}`,
data: {
command: "fleet.claims.publish",
systemId,
passportHash: result.claim.passportHash,
commit: result.commit,
pushed: result.pushed,
pushErrors: result.pushErrors,
},
};
} catch (err) {
return {
...failResult("claims.commands", [
`[fleet.claims.publish] failed: ${err instanceof Error ? err.message : String(err)}`,
]),
data: { error: err instanceof Error ? err.message : String(err) },
};
}
}
// ---------------------------------------------------------------------------
// fleet.claims.sync
// ---------------------------------------------------------------------------
export async function runFleetClaimsSync(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
try {
const result = await syncClaims(context.workspaceRoot);
const divergedNote =
result.diverged.length > 0
? ` DIVERGED: ${result.diverged.map((d) => `${d.remote} (${d.files.join(",")})`).join("; ")} — manual resolution required, never auto-merged`
: "";
const invalidNote =
result.invalidClaims.length > 0
? ` invalid signatures: ${result.invalidClaims.join(",")}`
: "";
return {
exitCode: result.diverged.length > 0 ? 1 : 0,
summary:
`[fleet.claims.sync] fetched ${result.fetched.length} remote(s), ` +
`${result.fastForwarded ? "fast-forwarded" : "no ff"}, ` +
`${result.pushed ? "pushed" : "not pushed"}${divergedNote}${invalidNote}`,
data: { command: "fleet.claims.sync", ...result },
};
} catch (err) {
return {
...failResult("claims.commands", [
`[fleet.claims.sync] failed: ${err instanceof Error ? err.message : String(err)}`,
]),
data: { error: err instanceof Error ? err.message : String(err) },
};
}
}
// ---------------------------------------------------------------------------
// fleet.claims.verify
// ---------------------------------------------------------------------------
export async function runFleetClaimsVerify(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const systemId = flagString(input, "id");
if (!systemId) {
return {
...failResult("fleet.claims.verify", ["--id flag is required"]),
data: { error: "missing --id flag" },
};
}
try {
const result = await verifyClaimOffline({
systemId,
werkstattRoot: context.workspaceRoot,
});
// invalid-signature and stale are blocking verdicts (RFC-1124 failure modes).
const blocking = result.verdict === "invalid-signature" || result.verdict === "stale";
return {
exitCode: blocking ? 1 : 0,
summary: `[fleet.claims.verify] ${systemId}: ${result.verdict}${
result.claim ? ` (${result.claim.creatorIdentity}, ${result.claim.instanceId})` : ""
}${result.conflicts.length > 0 ? ` — ${result.conflicts.length} conflict(s)` : ""}`,
data: { command: "fleet.claims.verify", ...result },
};
} catch (err) {
return {
...failResult("claims.commands", [
`[fleet.claims.verify] failed: ${err instanceof Error ? err.message : String(err)}`,
]),
data: { error: err instanceof Error ? err.message : String(err) },
};
}
}
// ---------------------------------------------------------------------------
// fleet.claims.status
// ---------------------------------------------------------------------------
export async function runFleetClaimsStatus(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
try {
const result = await claimsStatus(context.workspaceRoot);
return {
exitCode: 0,
summary: result.configured
? `[fleet.claims.status] ${result.claims} claim(s), ${result.unpushedCommits} unpushed commit(s), last sync ${result.lastSyncAt ?? "never"}`
: `[fleet.claims.status] not configured — run fleet.claims.init --remote `,
data: { command: "fleet.claims.status", ...result },
};
} catch (err) {
return {
...failResult("claims.commands", [
`[fleet.claims.status] failed: ${err instanceof Error ? err.message : String(err)}`,
]),
data: { error: err instanceof Error ? err.message : String(err) },
};
}
}