fix(housekeeping): isolate executable command schemas

This commit is contained in:
Simo committed 2026-08-27 19:55:57 +02:00
1 parent 00618fb2a3
commit 7895188b56
5 files changed
+431 -67

No files matched your search

@@ -1,10 +1,58 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import { housekeepingCommandRegistryReady } from "./bootstrap";
import { registerHousekeepingCommand } from "./registry";
import {
defineHousekeepingCommands,
housekeepingCommandRegistryReady,
registerHousekeepingCommands,
} from "./bootstrap";
import {
type HousekeepingCommand,
registerHousekeepingCommand,
} from "./registry";
describe("housekeeping command bootstrap", () => {
it("preserves heterogeneous concrete input and output types", () => {
const stringCommand: HousekeepingCommand<
{ value: string },
{ length: number }
> = {
id: "system.bootstrap.string-shape",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.view"),
input: z.object({ value: z.string() }),
requiresReason: false,
rateLimit: { attempts: 1, windowMs: 1_000 },
execute: async (context, input) =>
ok({ length: input.value.length }, context.correlationId),
};
const numberCommand: HousekeepingCommand<
{ count: number },
{ doubled: number }
> = {
id: "system.bootstrap.number-shape",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.view"),
input: z.object({ count: z.number().int() }),
requiresReason: false,
rateLimit: { attempts: 2, windowMs: 2_000 },
execute: async (context, input) =>
ok({ doubled: input.count * 2 }, context.correlationId),
};
const commands = defineHousekeepingCommands(stringCommand, numberCommand);
const stringInput: z.input<(typeof commands)[0]["input"]> = {
value: "typed",
};
const numberInput: z.input<(typeof commands)[1]["input"]> = { count: 4 };
expect(commands[0].input.parse(stringInput)).toEqual({ value: "typed" });
expect(commands[1].input.parse(numberInput)).toEqual({ count: 4 });
expect(() => registerHousekeepingCommands(commands)).toThrow(
"command registry is sealed",
);
});
it("registers the complete current list and seals during module initialization", () => {
expect(housekeepingCommandRegistryReady).toBe(true);
expect(() =>
@@ -6,12 +6,37 @@ import {
sealHousekeepingCommandRegistry,
} from "./registry";
const currentHousekeepingCommands =
[] as const satisfies readonly HousekeepingCommand<unknown, unknown>[];
type PreserveHousekeepingCommand<Definition> =
Definition extends HousekeepingCommand<infer _Input, infer _Output>
? Definition
: never;
for (const command of currentHousekeepingCommands) {
registerHousekeepingCommand(command);
type HousekeepingCommandTuple<Commands extends readonly unknown[]> =
Commands & {
readonly [Index in keyof Commands]: PreserveHousekeepingCommand<
Commands[Index]
>;
};
export function defineHousekeepingCommands<
const Commands extends readonly unknown[],
>(...commands: HousekeepingCommandTuple<Commands>): Commands {
return commands;
}
export function registerHousekeepingCommands<
const Commands extends readonly unknown[],
>(commands: HousekeepingCommandTuple<Commands>): void {
for (const command of commands) {
registerHousekeepingCommand(
command as unknown as HousekeepingCommand<unknown, unknown>,
);
}
}
const currentHousekeepingCommands = defineHousekeepingCommands();
registerHousekeepingCommands(currentHousekeepingCommands);
sealHousekeepingCommandRegistry();
export const housekeepingCommandRegistryReady = true;
@@ -385,7 +385,7 @@ describe("dispatchHousekeepingCommand", () => {
]);
expect(audit.entries[1]?.correlationId).toBe(result.correlationId);
});
it("runs sensitive validation and rate preflight before intent and execution", async () => {
it("runs sensitive capability and rate preflight before intent and execution", async () => {
const events: string[] = [];
const audit = auditRecorder(events);
const tracedContext = {
@@ -399,9 +399,7 @@ describe("dispatchHousekeepingCommand", () => {
baseCommand("system.dispatch.preflight-order", {
risk: "sensitive",
requiresReason: true,
input: z.object({ enabled: z.boolean() }).superRefine(() => {
events.push("input");
}),
input: z.object({ enabled: z.boolean() }),
execute: async (context) => {
events.push("execute");
return ok(null, context.correlationId);
@@ -427,7 +425,6 @@ describe("dispatchHousekeepingCommand", () => {
expect(events).toEqual([
"capability",
"input",
"rate",
"audit:intent",
"execute",
@@ -566,15 +563,19 @@ describe("dispatchHousekeepingCommand", () => {
const audit = auditRecorder();
register(
baseCommand("system.dispatch.schema-exception", {
input: z.preprocess(() => {
throw new Error("schema secret exposed");
}, z.object({})),
input: z.object({ value: z.string() }),
execute: async (context) => ok(null, context.correlationId),
}),
);
const throwingInput = Object.defineProperty({}, "value", {
enumerable: true,
get: () => {
throw new Error("schema secret exposed");
},
});
const result = await dispatchHousekeepingCommand(
{ commandId: "system.dispatch.schema-exception", input: {} },
{ commandId: "system.dispatch.schema-exception", input: throwingInput },
dependencies({ audit: audit.writer }),
);
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff