feat(housekeeping): dispatch audited commands

This commit is contained in:
Simo committed 2026-08-27 18:30:44 +02:00
1 parent ca666d9f90
commit 6aed71a8b2
8 files changed
+996

No files matched your search

File diff suppressed because it is too large. Load diff
+27
View File
@@ -0,0 +1,27 @@
"use server";
import {
dispatchHousekeepingCommand,
type HousekeepingCommandRequest,
} from "@/features/housekeeping/foundation/commands/dispatcher";
import type { HousekeepingResult } from "@/features/housekeeping/foundation/contracts";
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
import { clientIp, rateLimit } from "@/lib/rate-limit";
import { housekeepingAuditWriter } from "@/lib/services/audit";
export async function executeHousekeepingCommand(
request: HousekeepingCommandRequest,
): Promise<HousekeepingResult<unknown>> {
const [context, ipAddress] = await Promise.all([
getHousekeepingCapabilityContext(),
clientIp(),
]);
return dispatchHousekeepingCommand(request, {
context,
ipAddress,
audit: housekeepingAuditWriter,
rateLimit: async (key, attempts, windowMs) =>
(await rateLimit(key, attempts, windowMs)).ok,
});
}
@@ -0,0 +1,78 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import { confirmHousekeepingCommand } from "./confirmation";
import type { HousekeepingCommand } from "./registry";
function command(
requiresReason: boolean,
risk: "safe" | "sensitive",
): HousekeepingCommand<Record<string, never>, null> {
return {
id: "system.confirmation.test",
owner: "system",
risk,
capability: anyCapability("admin.settings.edit"),
input: z.object({}),
requiresReason,
rateLimit: { attempts: 2, windowMs: 60_000 },
execute: async (context) => ok(null, context.correlationId),
};
}
describe("housekeeping command confirmation", () => {
it("rejects blank reasons when the registered command requires one", () => {
expect(
confirmHousekeepingCommand(
command(true, "sensitive"),
" \t ",
"corr-reason-missing",
),
).toEqual({
ok: false,
error: {
code: "VALIDATION",
messageKey: "errors.housekeeping.validation",
fieldErrors: { reason: ["errors.validation.required"] },
},
correlationId: "corr-reason-missing",
});
});
it("derives confirmation risk from the registered command and normalizes its reason", () => {
expect(
confirmHousekeepingCommand(
command(true, "sensitive"),
" Scheduled maintenance ",
"corr-confirm",
),
).toEqual({
ok: true,
data: {
commandId: "system.confirmation.test",
risk: "sensitive",
requiresReason: true,
reason: "Scheduled maintenance",
},
correlationId: "corr-confirm",
});
});
it("omits an empty optional reason without inventing confirmation metadata", () => {
const result = confirmHousekeepingCommand(
command(false, "safe"),
undefined,
"corr-safe",
);
expect(result).toEqual({
ok: true,
data: {
commandId: "system.confirmation.test",
risk: "safe",
requiresReason: false,
},
correlationId: "corr-safe",
});
});
});
@@ -0,0 +1,32 @@
import { fail, type HousekeepingResult, ok } from "../contracts";
import type { HousekeepingCommand } from "./registry";
export interface HousekeepingCommandConfirmation {
commandId: string;
risk: "safe" | "sensitive";
requiresReason: boolean;
reason?: string;
}
export function confirmHousekeepingCommand(
command: HousekeepingCommand<unknown, unknown>,
reason: string | undefined,
correlationId: string,
): HousekeepingResult<HousekeepingCommandConfirmation> {
const normalizedReason = reason?.normalize("NFC").trim() || undefined;
if (command.requiresReason && !normalizedReason) {
return fail("VALIDATION", "errors.housekeeping.validation", correlationId, {
reason: ["errors.validation.required"],
});
}
return ok(
{
commandId: command.id,
risk: command.risk,
requiresReason: command.requiresReason,
...(normalizedReason === undefined ? {} : { reason: normalizedReason }),
},
correlationId,
);
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import {
getHousekeepingCommand,
type HousekeepingCommand,
registerHousekeepingCommand,
} from "./registry";
function command(
id: string,
owner: "people" | "system" = "people",
): HousekeepingCommand<{ id: number }, { id: number }> {
return {
id,
owner,
risk: "safe",
capability: anyCapability("admin.users.view"),
input: z.object({ id: z.number().int().positive() }),
requiresReason: false,
rateLimit: { attempts: 4, windowMs: 30_000 },
execute: async (context, input) => ok(input, context.correlationId),
};
}
describe("housekeeping command registry", () => {
it("returns the exact command registered under its global ID", () => {
const registered = command("people.registry.lookup");
registerHousekeepingCommand(registered);
expect(getHousekeepingCommand(registered.id)).toBe(registered);
});
it("rejects a duplicate global command ID before it can shadow its owner", () => {
registerHousekeepingCommand(command("people.registry.duplicate"));
expect(() =>
registerHousekeepingCommand(command("people.registry.duplicate")),
).toThrow("duplicate command id: people.registry.duplicate");
});
it("rejects a command whose namespace disagrees with its declared owner", () => {
expect(() =>
registerHousekeepingCommand(
command("people.registry.owner-mismatch", "system"),
),
).toThrow("command owner mismatch: people.registry.owner-mismatch");
});
});
@@ -0,0 +1,78 @@
import type { z } from "zod";
import {
HOUSEKEEPING_DOMAIN_IDS,
type HousekeepingDomainId,
} from "../../migration/types";
import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingResult,
} from "../contracts";
export interface HousekeepingCommandContext {
capability: HousekeepingCapabilityContext;
correlationId: string;
ipAddress: string;
}
export interface HousekeepingCommand<I, O> {
id: string;
owner: HousekeepingDomainId;
risk: "safe" | "sensitive";
capability: CapabilityRequirement;
input: z.ZodType<I>;
requiresReason: boolean;
rateLimit: { attempts: number; windowMs: number };
execute(
context: HousekeepingCommandContext,
input: I,
): Promise<HousekeepingResult<O>>;
}
type RegisteredHousekeepingCommand = HousekeepingCommand<unknown, unknown>;
const approvedOwners = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
const commands = new Map<string, RegisteredHousekeepingCommand>();
export function registerHousekeepingCommand<I, O>(
command: HousekeepingCommand<I, O>,
): void {
validateCommand(command);
if (commands.has(command.id)) {
throw new Error(`duplicate command id: ${command.id}`);
}
commands.set(command.id, command as RegisteredHousekeepingCommand);
}
export function getHousekeepingCommand(
id: string,
): RegisteredHousekeepingCommand | undefined {
return commands.get(id);
}
function validateCommand<I, O>(command: HousekeepingCommand<I, O>): void {
if (typeof command.id !== "string" || !command.id.trim()) {
throw new Error("empty command id");
}
if (!approvedOwners.has(command.owner)) {
throw new Error(`unknown command owner: ${command.owner}`);
}
if (!command.id.startsWith(`${command.owner}.`)) {
throw new Error(`command owner mismatch: ${command.id}`);
}
if (
!command.input ||
typeof (command.input as { safeParse?: unknown }).safeParse !== "function"
) {
throw new Error(`command input schema missing: ${command.id}`);
}
if (
!Number.isInteger(command.rateLimit.attempts) ||
command.rateLimit.attempts <= 0 ||
!Number.isInteger(command.rateLimit.windowMs) ||
command.rateLimit.windowMs <= 0
) {
throw new Error(`invalid command rate limit: ${command.id}`);
}
}