/*
RFC-1038: Registers composition.* commands for desired-state inspection,
reconciliation, and overlay management.
- Does not implement reconciliation logic — see runtime/reconciler.ts.
- Does not implement overlay resolution — see runtime/overlay.ts.
- RFC-1038: initial implementation — composition.desired-state.inspect, composition.reconcile, composition.overlay.apply, composition.overlay.inspect commands.
- RFC-1038 Phase 3: wire all commands to real actualState, desired state persistence, and overlay store.
*/
import type {
KernelCommandInput,
KernelExecutionReport,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import { resolveOverlays, inspectOverlays } from "./overlay.ts";
import { reconcile, reconcileFromPersisted } from "./reconciler.ts";
import { applyOverlay, getActiveOverlays, removeOverlay } from "./overlay-store.ts";
import { loadPersistedDesiredState } from "./desired-state-persistence.ts";
import type {
DesiredStateOverlay,
ModuleExport,
MutableActualState,
KernelCommandDefinition,
} from "@warpgogol/werkstatt-shared/kernel";
import type { ComponentScope } from "@warpgogol/werkstatt-shared/component";
function makeReport(
commandName: string,
data: unknown,
ok: boolean,
exitCode: number,
summary: string,
context: KernelRuntimeContext,
durationMs: number,
): KernelExecutionReport {
return {
commandName,
data,
exitCode,
ok,
summary,
metadata: { name: commandName } as KernelCommandDefinition,
logs: context.logger.getEvents(),
filesModified: [],
timing: { durationMs, exceededTimeout: false },
};
}
async function runDesiredStateInspect(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const start = Date.now();
const desired = await loadPersistedDesiredState(context.workspaceRoot);
const activeOverlays = getActiveOverlays();
const effectiveDesired =
desired && activeOverlays.length > 0 ? resolveOverlays(desired, activeOverlays) : desired;
const data = {
desired: effectiveDesired
? {
components: Array.from(effectiveDesired.components.entries()) as Array<[string, unknown]>,
requiredCapabilities: effectiveDesired.requiredCapabilities,
profileId: effectiveDesired.profileId,
}
: null,
actual: {
components: Array.from(context.actualState.components.entries()).map(([id, entry]) => ({
componentId: id,
version: entry.declaration.version,
state: entry.state,
})),
},
overlays: inspectOverlays(activeOverlays),
};
return makeReport(
"composition.desired-state.inspect",
data,
true,
0,
"Desired and actual state inspection",
context,
Date.now() - start,
);
}
async function runCompositionReconcile(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const start = Date.now();
const result = await reconcileFromPersisted(
context.workspaceRoot,
[],
context.actualState as MutableActualState,
{ profileId: context.site?.name ?? "werkstatt" },
);
return makeReport(
"composition.reconcile",
result,
result.applied,
result.applied ? 0 : 1,
result.applied
? "Reconciliation applied"
: `Reconciliation not applied: ${result.failures.map((f) => f.reason).join(", ")}`,
context,
Date.now() - start,
);
}
async function runOverlayApply(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const start = Date.now();
const overlayId = input.flags["overlay-id"] as string | undefined;
const scope = input.flags["scope"] as string | undefined;
const removeComponent = input.flags["remove"] as string | undefined;
if (!overlayId || !scope) {
return makeReport(
"composition.overlay.apply",
{ error: "Missing required --overlay-id and --scope flags" },
false,
1,
"Missing required --overlay-id and --scope flags",
context,
Date.now() - start,
);
}
const overlay: DesiredStateOverlay = {
id: overlayId,
scope: scope as ComponentScope,
context: { scope: scope as ComponentScope },
addOrReplace: new Map(),
remove: removeComponent ? [removeComponent] : [],
priority: 100,
};
applyOverlay(overlay);
try {
const desired = await loadPersistedDesiredState(context.workspaceRoot);
const activeOverlays = getActiveOverlays();
const effectiveDesired = desired ? resolveOverlays(desired, activeOverlays) : null;
let reconciliation: { applied: boolean; failures: Array<{ id: string; reason: string }> } = {
applied: true,
failures: [],
};
if (effectiveDesired) {
const result = await reconcile(effectiveDesired, context.actualState as MutableActualState);
reconciliation = {
applied: result.applied,
failures: result.failures,
};
}
return makeReport(
"composition.overlay.apply",
{
overlayId,
scope,
applied: true,
reconciliation,
effectiveComponentCount: effectiveDesired?.components.size ?? 0,
},
true,
0,
`Overlay ${overlayId} applied`,
context,
Date.now() - start,
);
} catch (e) {
removeOverlay(overlayId);
return makeReport(
"composition.overlay.apply",
{ error: e instanceof Error ? e.message : String(e) },
false,
1,
`Overlay apply failed: ${e instanceof Error ? e.message : String(e)}`,
context,
Date.now() - start,
);
}
}
async function runOverlayInspect(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise {
const start = Date.now();
const activeOverlays = getActiveOverlays();
const summary = inspectOverlays(activeOverlays);
const desired = await loadPersistedDesiredState(context.workspaceRoot);
const effectiveDesired =
desired && activeOverlays.length > 0 ? resolveOverlays(desired, activeOverlays) : desired;
const data = {
overlays: summary,
effectiveDesiredState: effectiveDesired
? {
componentCount: effectiveDesired.components.size,
requiredCapabilities: effectiveDesired.requiredCapabilities,
profileId: effectiveDesired.profileId,
}
: null,
};
return makeReport(
"composition.overlay.inspect",
data,
true,
0,
`${activeOverlays.length} overlay(s) active`,
context,
Date.now() - start,
);
}
export const compositionModule: ModuleExport = {
name: "composition",
version: "1.0.0",
declarations: [],
commands: [
{
name: "composition.desired-state.inspect",
coverage: "operator" as const,
modulePath: "packages/werkstatt-engine/src/runtime/composition.module.ts",
description:
"RFC-1038: Inspect current desired and actual state. Returns JSON with desired components, actual components, and their states.",
scope: "workspace",
mutatesState: false,
cacheable: false,
flags: {},
execute: runDesiredStateInspect,
},
{
name: "composition.reconcile",
coverage: "operator" as const,
modulePath: "packages/werkstatt-engine/src/runtime/composition.module.ts",
description:
"RFC-1038: Reconcile desired state with actual state. Computes delta and applies changes transactionally.",
scope: "workspace",
mutatesState: true,
cacheable: false,
flags: {},
execute: runCompositionReconcile,
},
{
name: "composition.overlay.apply",
coverage: "operator" as const,
modulePath: "packages/werkstatt-engine/src/runtime/composition.module.ts",
description:
"RFC-1038: Apply an overlay to the desired state and trigger reconciliation. Required flags: `--overlay-id`, `--scope`.",
scope: "workspace",
mutatesState: true,
cacheable: false,
flags: {
"overlay-id": {
kind: "string",
required: true,
description: "Unique overlay ID.",
},
scope: {
kind: "string",
required: true,
description: "Scope: per-command, per-mission, per-session, per-workshop, per-fleet.",
},
add: {
kind: "string",
description: "Component ID to add or replace.",
},
remove: {
kind: "string",
description: "Component ID to remove.",
},
},
execute: runOverlayApply,
},
{
name: "composition.overlay.inspect",
coverage: "operator" as const,
modulePath: "packages/werkstatt-engine/src/runtime/composition.module.ts",
description: "RFC-1038: Inspect active overlays and their effects on the desired state.",
scope: "workspace",
mutatesState: false,
cacheable: false,
flags: {},
execute: runOverlayInspect,
},
],
pipelines: [],
};