/*
RFC-0565: dht.placement command handler. Determines the best workshop for
placing a site by querying DHT capacity entries. Uses least-loaded strategy
by default, with nearest and owner-preference alternatives.
- Do not implement actual site migration — this is advisory placement only.
- Do not implement geographic distance — nearest is based on RTT estimation in future phases.
- Placement scores workshops by reported capacity — the best score wins deterministically.
- RFC-0565: initial implementation — dht.placement handler.
- 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.
*/
import { join } from "node:path";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import type { DHTPlacementResult, DHTPlacementReason, WorkshopCapacity } from "./types.ts";
import { workshopCapacitySchema } from "./types.ts";
import { loadDhtConfig } from "./config.ts";
import { createDhtNode, startDhtNode, stopDhtNode, dhtGet } from "./node.ts";
import { verifyCapacity } from "./capacity.ts";
import {
WerkstattIdentityConfigSchema,
type WerkstattIdentityConfig,
} from "@warpgogol/werkstatt-shared/passport";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
const IDENTITY_FILENAME = "werkstatt.identity.json";
async function loadIdentityConfig(
workspaceRoot: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const identityPath = join(workspaceRoot, IDENTITY_FILENAME);
const raw = await io.readFile(identityPath);
const parsed = JSON.parse(raw);
return WerkstattIdentityConfigSchema.parse(parsed);
}
/**
* Select the best workshop from a list of capacity entries using least-loaded strategy.
* Filters out workshops with 0 available slots or invalid signatures.
*/
function selectLeastLoaded(capacities: WorkshopCapacity[]): {
winner: WorkshopCapacity | null;
reason: DHTPlacementReason;
} {
const available = capacities.filter((c) => c.availableSlots > 0);
if (available.length === 0) {
return { winner: null, reason: "local-fallback" };
}
available.sort((a, b) => {
// Primary: most available slots
if (b.availableSlots !== a.availableSlots) {
return b.availableSlots - a.availableSlots;
}
// Secondary: lowest CPU load
if (a.cpuLoad !== b.cpuLoad) {
return a.cpuLoad - b.cpuLoad;
}
// Tertiary: most disk free
return b.diskFree - a.diskFree;
});
return { winner: available[0]!, reason: "least-loaded" };
}
export async function runDhtPlacement(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot } = context;
const siteId = input.flags["site-id"] as string | undefined;
const strategy = (input.flags["strategy"] as string | undefined) ?? "least-loaded";
const workshopIds = input.flags["workshops"] as string | string[] | undefined;
if (!siteId) {
return {
data: {
siteId: "",
assignedWorkshop: "",
reason: "local-fallback" as DHTPlacementReason,
capacity: null,
diagnostics: ["dht.placement: --site-id flag is required"],
} as DHTPlacementResult,
exitCode: 1,
summary: "[dht.placement] --site-id flag is required",
nextSteps: [
{ action: "Provide the --site-id flag and re-run dht.placement", kind: "required" },
],
};
}
// Load identity config
let identity: WerkstattIdentityConfig;
try {
identity = await loadIdentityConfig(workspaceRoot);
} catch {
return {
data: {
siteId,
assignedWorkshop: "",
reason: "local-fallback" as DHTPlacementReason,
capacity: null,
diagnostics: [
`dht.placement: no ${IDENTITY_FILENAME} found — run identity.bootstrap first (RFC-0558)`,
],
} as DHTPlacementResult,
exitCode: 1,
summary: `[dht.placement] identity not bootstrapped`,
nextSteps: [
{
action: "Run identity.bootstrap first (RFC-0558), then re-run dht.placement",
kind: "required",
},
],
};
}
// Load DHT config
let config;
try {
config = await loadDhtConfig(workspaceRoot);
} catch {
return {
data: {
siteId,
assignedWorkshop: "",
reason: "local-fallback" as DHTPlacementReason,
capacity: null,
diagnostics: [`dht.placement: no werkstatt.dht.json found — run dht.node.init first`],
} as DHTPlacementResult,
exitCode: 1,
summary: `[dht.placement] DHT not initialized`,
nextSteps: [
{ action: "Run dht.node.init first, then re-run dht.placement", kind: "required" },
],
};
}
// Determine workshops to query
const workshops = Array.isArray(workshopIds) ? workshopIds : workshopIds ? [workshopIds] : [];
if (workshops.length === 0) {
return {
data: {
siteId,
assignedWorkshop: "",
reason: "local-fallback" as DHTPlacementReason,
capacity: null,
diagnostics: [`dht.placement: --workshops flag is required (comma-separated or repeated)`],
} as DHTPlacementResult,
exitCode: 1,
summary: `[dht.placement] no workshops specified`,
nextSteps: [
{
action:
"Provide the --workshops flag (comma-separated or repeated) and re-run dht.placement",
kind: "required",
},
],
};
}
// Create ephemeral DHT node
let node;
try {
node = await createDhtNode(config, identity);
await startDhtNode(node, config);
} catch (err) {
return {
data: {
siteId,
assignedWorkshop: "",
reason: "local-fallback" as DHTPlacementReason,
capacity: null,
diagnostics: [
`dht.placement: failed to start DHT node — ${err instanceof Error ? err.message : String(err)}`,
],
} as DHTPlacementResult,
exitCode: 1,
summary: `[dht.placement] DHT node startup failed`,
nextSteps: [
{
action: "Check the DHT configuration and network connectivity, then re-run dht.placement",
kind: "required",
},
],
};
}
try {
// Query capacity for each workshop
const capacities: WorkshopCapacity[] = [];
for (const wsId of workshops) {
const key = `capacity/${wsId}`;
const valueBytes = await dhtGet(node, key, 3);
if (!valueBytes) continue;
try {
const parsed = JSON.parse(new TextDecoder().decode(valueBytes));
const capacity = workshopCapacitySchema.parse(parsed);
// Verify signature
const sigValid = await verifyCapacity(
capacity,
identity.operatorKeyPair.publicKeyMultibase,
);
if (sigValid) {
capacities.push(capacity);
}
} catch {
// Skip malformed entries
}
}
// Select best workshop
let result: { winner: WorkshopCapacity | null; reason: DHTPlacementReason };
if (strategy === "owner-preference") {
// Owner-preference: use the first workshop in the list that has capacity
const preferred = capacities.find((c) => c.availableSlots > 0);
result = preferred
? { winner: preferred, reason: "owner-preference" }
: { winner: null, reason: "local-fallback" };
} else if (strategy === "nearest") {
// Nearest: in the pilot, this is the same as least-loaded (no RTT data)
result = selectLeastLoaded(capacities);
if (result.winner) {
result.reason = "nearest";
}
} else {
// Default: least-loaded
result = selectLeastLoaded(capacities);
}
return {
data: {
siteId,
assignedWorkshop: result.winner?.workshopId ?? "",
reason: result.reason,
capacity: result.winner,
},
exitCode: 0,
summary: result.winner
? `[dht.placement] ${siteId} → ${result.winner.workshopId} (${result.reason})`
: `[dht.placement] no available workshop for ${siteId} (local-fallback)`,
};
} finally {
await stopDhtNode(node);
}
}