602 lines
17 KiB
TypeScript
602 lines
17 KiB
TypeScript
import { z } from "zod";
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import {
|
|
HOUSEKEEPING_DOMAIN_IDS,
|
|
type HousekeepingDomainId,
|
|
} from "../../migration/types";
|
|
import type {
|
|
CapabilityRequirement,
|
|
HousekeepingCapabilityContext,
|
|
HousekeepingResult,
|
|
} from "../contracts";
|
|
|
|
export interface HousekeepingCommandContext {
|
|
readonly capability: HousekeepingCapabilityContext;
|
|
readonly correlationId: string;
|
|
readonly ipAddress: string;
|
|
}
|
|
|
|
export interface HousekeepingCommand<I, O> {
|
|
readonly id: string;
|
|
readonly owner: HousekeepingDomainId;
|
|
readonly risk: "safe" | "sensitive";
|
|
readonly capability: CapabilityRequirement;
|
|
/**
|
|
* Registration snapshots structural/built-in Zod graphs and resolvable lazy
|
|
* edges. Stateful custom refinements, transforms, preprocessors, and other
|
|
* executable schema callbacks are rejected.
|
|
*/
|
|
readonly input: z.ZodType<I>;
|
|
readonly requiresReason: boolean;
|
|
readonly rateLimit: Readonly<{ attempts: number; windowMs: number }>;
|
|
readonly execute: (
|
|
context: HousekeepingCommandContext,
|
|
input: I,
|
|
) => Promise<HousekeepingResult<O>>;
|
|
}
|
|
|
|
export type UnsupportedHousekeepingCommandSchemaKind =
|
|
| "custom"
|
|
| "transform"
|
|
| "lazy"
|
|
| "executable";
|
|
|
|
export class UnsupportedHousekeepingCommandSchemaError extends Error {
|
|
readonly name = "UnsupportedHousekeepingCommandSchemaError";
|
|
readonly code = "UNSUPPORTED_COMMAND_INPUT_SCHEMA";
|
|
|
|
constructor(
|
|
readonly commandId: string,
|
|
readonly schemaKind: UnsupportedHousekeepingCommandSchemaKind,
|
|
) {
|
|
super(`unsupported command input schema: ${commandId} (${schemaKind})`);
|
|
}
|
|
}
|
|
|
|
type RegisteredHousekeepingCommand = HousekeepingCommand<unknown, unknown>;
|
|
|
|
type ZodInternalNode = {
|
|
readonly _zod: {
|
|
readonly constr: new (definition: never) => unknown;
|
|
readonly def: unknown;
|
|
};
|
|
};
|
|
|
|
const approvedOwners = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
|
|
const knownCapabilitySlugs = new Set<string>(Object.values(PERMS));
|
|
const commandIdPattern = /^[a-z][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/;
|
|
const commands = new Map<string, RegisteredHousekeepingCommand>();
|
|
let sealed = false;
|
|
|
|
export function registerHousekeepingCommand<I, O>(
|
|
command: HousekeepingCommand<I, O>,
|
|
): void {
|
|
if (sealed) throw new Error("command registry is sealed");
|
|
validateCommand(command);
|
|
if (commands.has(command.id)) {
|
|
throw new Error(`duplicate command id: ${command.id}`);
|
|
}
|
|
|
|
const capability = Object.freeze({
|
|
mode: command.capability.mode,
|
|
slugs: Object.freeze([...command.capability.slugs]),
|
|
}) satisfies CapabilityRequirement;
|
|
const snapshot = Object.freeze({
|
|
id: command.id,
|
|
owner: command.owner,
|
|
risk: command.risk,
|
|
capability,
|
|
input: isolateValidationGraph(command.input, command.id),
|
|
requiresReason: command.requiresReason,
|
|
rateLimit: Object.freeze({ ...command.rateLimit }),
|
|
execute: command.execute,
|
|
}) as RegisteredHousekeepingCommand;
|
|
commands.set(snapshot.id, snapshot);
|
|
}
|
|
|
|
export function sealHousekeepingCommandRegistry(): void {
|
|
sealed = true;
|
|
}
|
|
|
|
export function getHousekeepingCommand(
|
|
id: string,
|
|
): RegisteredHousekeepingCommand | undefined {
|
|
return commands.get(id);
|
|
}
|
|
|
|
export function isHousekeepingCommandId(value: unknown): value is string {
|
|
return isNormalizedIdentifier(value) && commandIdPattern.test(value);
|
|
}
|
|
|
|
function validateCommand<I, O>(command: HousekeepingCommand<I, O>): void {
|
|
if (!isHousekeepingCommandId(command.id)) {
|
|
throw new Error(`invalid command id: ${String(command.id)}`);
|
|
}
|
|
if (
|
|
!isNormalizedIdentifier(command.owner) ||
|
|
!approvedOwners.has(command.owner)
|
|
) {
|
|
throw new Error(`unknown command owner: ${String(command.owner)}`);
|
|
}
|
|
if (!command.id.startsWith(`${command.owner}.`)) {
|
|
throw new Error(`command owner mismatch: ${command.id}`);
|
|
}
|
|
if (command.risk !== "safe" && command.risk !== "sensitive") {
|
|
throw new Error(`invalid command risk: ${command.id}`);
|
|
}
|
|
if (typeof command.requiresReason !== "boolean") {
|
|
throw new Error(`invalid command reason policy: ${command.id}`);
|
|
}
|
|
validateCapability(command.capability, command.id);
|
|
if (!(command.input instanceof z.ZodType)) {
|
|
throw new Error(`command input schema missing: ${command.id}`);
|
|
}
|
|
if (typeof command.execute !== "function") {
|
|
throw new Error(`command execute missing: ${command.id}`);
|
|
}
|
|
if (
|
|
typeof command.rateLimit !== "object" ||
|
|
command.rateLimit === null ||
|
|
!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}`);
|
|
}
|
|
}
|
|
|
|
function validateCapability(
|
|
requirement: CapabilityRequirement,
|
|
commandId: string,
|
|
): void {
|
|
if (
|
|
typeof requirement !== "object" ||
|
|
requirement === null ||
|
|
(requirement.mode !== "any" && requirement.mode !== "all") ||
|
|
!Array.isArray(requirement.slugs) ||
|
|
requirement.slugs.length === 0
|
|
) {
|
|
throw new Error(`invalid command capability: ${commandId}`);
|
|
}
|
|
for (const slug of requirement.slugs) {
|
|
if (!isNormalizedIdentifier(slug) || !knownCapabilitySlugs.has(slug)) {
|
|
throw new Error(`invalid command capability: ${commandId}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isolateValidationGraph<T extends z.ZodType>(
|
|
schema: T,
|
|
commandId: string,
|
|
): T {
|
|
type SchemaCell = {
|
|
current?: z.ZodType;
|
|
reference?: z.ZodType;
|
|
};
|
|
|
|
const graphValues = new WeakMap<object, unknown>();
|
|
const schemaCells = new WeakMap<z.ZodType, SchemaCell>();
|
|
|
|
function unsupported(
|
|
schemaKind: UnsupportedHousekeepingCommandSchemaKind,
|
|
): never {
|
|
throw new UnsupportedHousekeepingCommandSchemaError(commandId, schemaKind);
|
|
}
|
|
|
|
function cloneSchema<S extends z.ZodType>(value: S): S {
|
|
const existing = schemaCells.get(value);
|
|
if (existing?.current) return existing.current as S;
|
|
if (existing) {
|
|
existing.reference ??= z.lazy(() => {
|
|
if (!existing.current) {
|
|
throw new Error("housekeeping schema registration incomplete");
|
|
}
|
|
return existing.current;
|
|
});
|
|
return existing.reference as S;
|
|
}
|
|
|
|
const definition = value.def as { type?: unknown; getter?: unknown };
|
|
if (definition.type === "custom") unsupported("custom");
|
|
if (definition.type === "transform") unsupported("transform");
|
|
|
|
const cell: SchemaCell = {};
|
|
schemaCells.set(value, cell);
|
|
if (definition.type === "lazy") {
|
|
let ownedTarget: z.ZodType | undefined;
|
|
const ownedLazy = z.lazy(() => {
|
|
if (!ownedTarget) {
|
|
throw new Error("housekeeping lazy schema target unavailable");
|
|
}
|
|
return ownedTarget;
|
|
});
|
|
cell.current = ownedLazy;
|
|
if (typeof definition.getter !== "function") unsupported("lazy");
|
|
|
|
let callerTarget: unknown;
|
|
try {
|
|
callerTarget = definition.getter();
|
|
} catch {
|
|
unsupported("lazy");
|
|
}
|
|
if (!(callerTarget instanceof z.ZodType)) unsupported("lazy");
|
|
ownedTarget = cloneSchema(callerTarget);
|
|
return ownedLazy as unknown as S;
|
|
}
|
|
|
|
const clonedDefinition = cloneGraph(value.def);
|
|
const cloned = value.clone(clonedDefinition as typeof value.def);
|
|
cell.current = cloned;
|
|
return cloned;
|
|
}
|
|
|
|
function cloneBuiltInCheck(value: ZodInternalNode): unknown {
|
|
const definition = value._zod.def;
|
|
if (typeof definition !== "object" || definition === null) {
|
|
unsupported("executable");
|
|
}
|
|
const checkDefinition = definition as {
|
|
check?: unknown;
|
|
type?: unknown;
|
|
};
|
|
if (
|
|
checkDefinition.check === "custom" ||
|
|
checkDefinition.type === "custom"
|
|
) {
|
|
unsupported("custom");
|
|
}
|
|
|
|
const clonedDefinition = cloneRecord(definition, true);
|
|
const cloned = new value._zod.constr(clonedDefinition as never) as {
|
|
_zod?: { check?: unknown };
|
|
};
|
|
if (
|
|
typeof (value._zod as { check?: unknown }).check === "function" &&
|
|
typeof cloned._zod?.check !== "function"
|
|
) {
|
|
unsupported("executable");
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
function cloneRecord(value: object, omitBuiltInWhen = false): object {
|
|
const cached = graphValues.get(value);
|
|
if (cached !== undefined) return cached as object;
|
|
|
|
const prototype = Object.getPrototypeOf(value);
|
|
const cloned = Object.create(prototype) as Record<PropertyKey, unknown>;
|
|
graphValues.set(value, cloned);
|
|
for (const key of Reflect.ownKeys(value)) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
if (!descriptor) continue;
|
|
if (
|
|
omitBuiltInWhen &&
|
|
key === "when" &&
|
|
"value" in descriptor &&
|
|
typeof descriptor.value === "function"
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
let resolved: unknown;
|
|
if ("value" in descriptor) {
|
|
resolved = descriptor.value;
|
|
} else if (descriptor.get) {
|
|
try {
|
|
resolved = Reflect.get(value, key);
|
|
} catch {
|
|
unsupported("executable");
|
|
}
|
|
} else {
|
|
unsupported("executable");
|
|
}
|
|
Object.defineProperty(cloned, key, {
|
|
configurable: descriptor.configurable,
|
|
enumerable: descriptor.enumerable,
|
|
value: cloneGraph(resolved),
|
|
writable: "writable" in descriptor ? descriptor.writable : false,
|
|
});
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
function cloneGraph(value: unknown): unknown {
|
|
if (typeof value === "function") unsupported("executable");
|
|
if (typeof value !== "object" || value === null) return value;
|
|
if (value instanceof z.ZodType) return cloneSchema(value);
|
|
|
|
const cached = graphValues.get(value);
|
|
if (cached !== undefined) return cached;
|
|
if (isZodInternalNode(value)) return cloneBuiltInCheck(value);
|
|
if (Array.isArray(value)) {
|
|
const cloned: unknown[] = [];
|
|
graphValues.set(value, cloned);
|
|
for (const item of value) cloned.push(cloneGraph(item));
|
|
return cloned;
|
|
}
|
|
if (value instanceof RegExp) {
|
|
const cloned = new RegExp(value.source, value.flags);
|
|
graphValues.set(value, cloned);
|
|
return cloned;
|
|
}
|
|
if (value instanceof Date) {
|
|
const cloned = new Date(value.getTime());
|
|
graphValues.set(value, cloned);
|
|
return cloned;
|
|
}
|
|
if (value instanceof Map) {
|
|
const cloned = new Map<unknown, unknown>();
|
|
graphValues.set(value, cloned);
|
|
for (const [key, item] of value) {
|
|
cloned.set(cloneGraph(key), cloneGraph(item));
|
|
}
|
|
return cloned;
|
|
}
|
|
if (value instanceof Set) {
|
|
const cloned = new Set<unknown>();
|
|
graphValues.set(value, cloned);
|
|
for (const item of value) cloned.add(cloneGraph(item));
|
|
return cloned;
|
|
}
|
|
return cloneRecord(value);
|
|
}
|
|
|
|
return createReadonlyValidationFacade(cloneSchema(schema));
|
|
}
|
|
|
|
function isZodInternalNode(value: object): value is ZodInternalNode {
|
|
const internal = (value as { _zod?: unknown })._zod;
|
|
return (
|
|
typeof internal === "object" &&
|
|
internal !== null &&
|
|
typeof (internal as { constr?: unknown }).constr === "function" &&
|
|
"def" in internal
|
|
);
|
|
}
|
|
|
|
function createReadonlyValidationFacade<T extends z.ZodType>(schema: T): T {
|
|
const views = new WeakMap<object, unknown>();
|
|
const parseResultMethods = new Set<PropertyKey>([
|
|
"parse",
|
|
"safeParse",
|
|
"parseAsync",
|
|
"safeParseAsync",
|
|
"spa",
|
|
"encode",
|
|
"decode",
|
|
"encodeAsync",
|
|
"decodeAsync",
|
|
"safeEncode",
|
|
"safeDecode",
|
|
"safeEncodeAsync",
|
|
"safeDecodeAsync",
|
|
"toJSONSchema",
|
|
]);
|
|
|
|
function readonlyView(value: unknown): unknown {
|
|
if (
|
|
(typeof value !== "object" && typeof value !== "function") ||
|
|
value === null
|
|
) {
|
|
return value;
|
|
}
|
|
const cached = views.get(value);
|
|
if (cached !== undefined) return cached;
|
|
if (value instanceof z.ZodType) return schemaFacade(value);
|
|
if (typeof value === "function") {
|
|
return functionFacade(value as (...args: never[]) => unknown);
|
|
}
|
|
if (Array.isArray(value)) return arrayFacade(value);
|
|
if (value instanceof Map) return mapFacade(value);
|
|
if (value instanceof Set) return setFacade(value);
|
|
if (value instanceof RegExp) {
|
|
const detached = new RegExp(value.source, value.flags);
|
|
views.set(value, detached);
|
|
return Object.freeze(detached);
|
|
}
|
|
if (value instanceof Date) {
|
|
const detached = new Date(value.getTime());
|
|
views.set(value, detached);
|
|
return Object.freeze(detached);
|
|
}
|
|
return objectFacade(value);
|
|
}
|
|
|
|
function functionFacade(value: (...args: never[]) => unknown): unknown {
|
|
const wrapped = function (this: unknown, ...args: unknown[]) {
|
|
const result = new.target
|
|
? Reflect.construct(value, args)
|
|
: Reflect.apply(value, this, args);
|
|
return readonlyView(result);
|
|
};
|
|
views.set(value, wrapped);
|
|
return Object.freeze(wrapped);
|
|
}
|
|
|
|
function arrayFacade(value: readonly unknown[]): readonly unknown[] {
|
|
const detached: unknown[] = [];
|
|
views.set(value, detached);
|
|
for (const item of value) detached.push(readonlyView(item));
|
|
return Object.freeze(detached);
|
|
}
|
|
|
|
function mapFacade(
|
|
value: ReadonlyMap<unknown, unknown>,
|
|
): ReadonlyMap<unknown, unknown> {
|
|
const detached = new Map<unknown, unknown>();
|
|
views.set(value, detached);
|
|
for (const [key, item] of value) {
|
|
detached.set(readonlyView(key), readonlyView(item));
|
|
}
|
|
return Object.freeze(detached);
|
|
}
|
|
|
|
function setFacade(value: ReadonlySet<unknown>): ReadonlySet<unknown> {
|
|
const detached = new Set<unknown>();
|
|
views.set(value, detached);
|
|
for (const item of value) detached.add(readonlyView(item));
|
|
return Object.freeze(detached);
|
|
}
|
|
|
|
function objectFacade(value: object): object {
|
|
const detached = Object.create(Object.getPrototypeOf(value)) as Record<
|
|
PropertyKey,
|
|
unknown
|
|
>;
|
|
views.set(value, detached);
|
|
copyReadonlyProperties(value, detached, (method) =>
|
|
boundFunctionFacade(method, value),
|
|
);
|
|
return Object.freeze(detached);
|
|
}
|
|
|
|
function boundFunctionFacade(
|
|
method: (...args: never[]) => unknown,
|
|
receiver: object,
|
|
): (...args: unknown[]) => unknown {
|
|
return Object.freeze((...args: unknown[]) =>
|
|
readonlyView(Reflect.apply(method, receiver, args)),
|
|
);
|
|
}
|
|
|
|
function copyReadonlyProperties(
|
|
source: object,
|
|
destination: Record<PropertyKey, unknown>,
|
|
wrapMethod: (
|
|
method: (...args: never[]) => unknown,
|
|
property: PropertyKey,
|
|
) => unknown,
|
|
): void {
|
|
for (const key of Reflect.ownKeys(source)) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
if (!descriptor) continue;
|
|
if ("value" in descriptor) {
|
|
Object.defineProperty(destination, key, {
|
|
configurable: false,
|
|
enumerable: descriptor.enumerable,
|
|
value:
|
|
typeof descriptor.value === "function"
|
|
? wrapMethod(descriptor.value, key)
|
|
: readonlyView(descriptor.value),
|
|
writable: false,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
const getter = descriptor.get;
|
|
const safeGetter = getter
|
|
? () => readonlyView(Reflect.apply(getter, source, []))
|
|
: undefined;
|
|
Object.defineProperty(destination, key, {
|
|
configurable: false,
|
|
enumerable: descriptor.enumerable,
|
|
get: safeGetter,
|
|
});
|
|
}
|
|
}
|
|
|
|
function materializeSchemaPrototypeMethods(target: z.ZodType): void {
|
|
let prototype = Object.getPrototypeOf(target);
|
|
while (prototype && prototype !== Object.prototype) {
|
|
for (const property of Reflect.ownKeys(prototype)) {
|
|
if (Object.hasOwn(target, property)) continue;
|
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
|
|
if (
|
|
!descriptor?.get ||
|
|
!descriptor.set ||
|
|
!descriptor.configurable ||
|
|
descriptor.enumerable
|
|
) {
|
|
continue;
|
|
}
|
|
Reflect.get(target, property, target);
|
|
}
|
|
prototype = Object.getPrototypeOf(prototype);
|
|
}
|
|
}
|
|
function schemaFacade<S extends z.ZodType>(target: S): S {
|
|
const cached = views.get(target);
|
|
if (cached !== undefined) return cached as S;
|
|
|
|
materializeSchemaPrototypeMethods(target);
|
|
const shell = Object.create(Object.getPrototypeOf(target)) as Record<
|
|
PropertyKey,
|
|
unknown
|
|
>;
|
|
let facade: S;
|
|
facade = new Proxy(shell, {
|
|
defineProperty: () => false,
|
|
deleteProperty: () => false,
|
|
get: (publicShell, property, receiver) => {
|
|
if (Object.hasOwn(publicShell, property)) {
|
|
return Reflect.get(publicShell, property, receiver);
|
|
}
|
|
const inherited = Reflect.get(publicShell, property, receiver);
|
|
return readonlyView(inherited);
|
|
},
|
|
set: () => false,
|
|
setPrototypeOf: () => false,
|
|
}) as unknown as S;
|
|
views.set(target, facade);
|
|
copyReadonlyProperties(target, shell, (method, property) =>
|
|
schemaMethodFacade(method, property, target, facade),
|
|
);
|
|
Object.freeze(shell);
|
|
return facade;
|
|
}
|
|
|
|
function schemaMethodFacade<S extends z.ZodType>(
|
|
method: (...args: never[]) => unknown,
|
|
property: PropertyKey,
|
|
target: S,
|
|
facade: S,
|
|
): (...args: unknown[]) => unknown {
|
|
if (property === "apply") {
|
|
return Object.freeze((...args: unknown[]) => {
|
|
const callback = args[0];
|
|
if (typeof callback !== "function") {
|
|
throw new TypeError("schema apply callback missing");
|
|
}
|
|
return Reflect.apply(callback, undefined, [facade]);
|
|
});
|
|
}
|
|
if (property === "register") {
|
|
return Object.freeze((...args: unknown[]) => {
|
|
const registry = args[0];
|
|
if (
|
|
typeof registry !== "object" ||
|
|
registry === null ||
|
|
typeof (registry as { add?: unknown }).add !== "function"
|
|
) {
|
|
throw new TypeError("schema registry missing");
|
|
}
|
|
(
|
|
registry as {
|
|
add(schema: S, metadata?: unknown): unknown;
|
|
}
|
|
).add(facade, args[1]);
|
|
return facade;
|
|
});
|
|
}
|
|
|
|
return Object.freeze((...args: unknown[]) => {
|
|
const result = Reflect.apply(method, target, args);
|
|
if (result instanceof z.ZodType) {
|
|
return isolateValidationGraph(result, "derived-schema");
|
|
}
|
|
return parseResultMethods.has(property) ? result : readonlyView(result);
|
|
});
|
|
}
|
|
|
|
return schemaFacade(schema);
|
|
}
|
|
function isNormalizedIdentifier(value: unknown): value is string {
|
|
return (
|
|
typeof value === "string" &&
|
|
value.length > 0 &&
|
|
value.length <= 160 &&
|
|
value === value.normalize("NFC").trim()
|
|
);
|
|
}
|