feat(housekeeping): dispatch audited commands
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user