fix(housekeeping): close schema reflection escapes
This commit is contained in:
1 parent
7895188b56
commit
01dceac073
3 files changed
+539
-37
No files matched your search
@@ -5,6 +5,7 @@ import type { HousekeepingCapabilityContext } from "../contracts";
|
||||
import { anyCapability, fail, ok } from "../contracts";
|
||||
import { dispatchHousekeepingCommand } from "./dispatcher";
|
||||
import {
|
||||
getHousekeepingCommand,
|
||||
type HousekeepingCommand,
|
||||
registerHousekeepingCommand,
|
||||
} from "./registry";
|
||||
@@ -150,6 +151,129 @@ describe("dispatchHousekeepingCommand", () => {
|
||||
expect(executed).toBe(false);
|
||||
});
|
||||
|
||||
it("uses unchanged private validation after public descriptor mutation attempts", async () => {
|
||||
const commandId = "system.dispatch.reflective-schema-mutation";
|
||||
let executions = 0;
|
||||
register(
|
||||
baseCommand(commandId, {
|
||||
input: z.object({
|
||||
objectDescriptor: z.string().min(3),
|
||||
objectDescriptors: z.string().min(3),
|
||||
reflectDescriptor: z.string().min(3),
|
||||
}),
|
||||
execute: async (context) => {
|
||||
executions += 1;
|
||||
return ok(null, context.correlationId);
|
||||
},
|
||||
}),
|
||||
);
|
||||
const registered = getHousekeepingCommand(commandId);
|
||||
if (!registered) throw new Error("registered command missing");
|
||||
|
||||
const readers = [
|
||||
["objectDescriptor", Object.getOwnPropertyDescriptor],
|
||||
[
|
||||
"objectDescriptors",
|
||||
(target: object, property: PropertyKey) =>
|
||||
Reflect.get(Object.getOwnPropertyDescriptors(target), property) as
|
||||
| PropertyDescriptor
|
||||
| undefined,
|
||||
],
|
||||
["reflectDescriptor", Reflect.getOwnPropertyDescriptor],
|
||||
] as const;
|
||||
const descriptorValue = (
|
||||
target: object,
|
||||
property: PropertyKey,
|
||||
readDescriptor: (
|
||||
target: object,
|
||||
property: PropertyKey,
|
||||
) => PropertyDescriptor | undefined,
|
||||
): unknown => {
|
||||
const descriptor = readDescriptor(target, property);
|
||||
if (!descriptor) {
|
||||
throw new Error(`missing descriptor: ${String(property)}`);
|
||||
}
|
||||
if ("value" in descriptor) return descriptor.value;
|
||||
if (descriptor.get) return Reflect.apply(descriptor.get, target, []);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
for (const [field, readDescriptor] of readers) {
|
||||
const definition = descriptorValue(
|
||||
registered.input,
|
||||
"def",
|
||||
readDescriptor,
|
||||
) as object;
|
||||
const shape = descriptorValue(
|
||||
definition,
|
||||
"shape",
|
||||
readDescriptor,
|
||||
) as Record<string, z.ZodType>;
|
||||
const child = shape[field];
|
||||
if (!child) throw new Error(`public child missing: ${field}`);
|
||||
const childDefinition = descriptorValue(
|
||||
child,
|
||||
"def",
|
||||
readDescriptor,
|
||||
) as object;
|
||||
const checks = descriptorValue(
|
||||
childDefinition,
|
||||
"checks",
|
||||
readDescriptor,
|
||||
) as readonly object[];
|
||||
const internal = descriptorValue(
|
||||
checks[0] as object,
|
||||
"_zod",
|
||||
readDescriptor,
|
||||
) as object;
|
||||
const checkDefinition = descriptorValue(
|
||||
internal,
|
||||
"def",
|
||||
readDescriptor,
|
||||
) as { minimum: number };
|
||||
try {
|
||||
shape[field] = z.number();
|
||||
} catch {
|
||||
// A readonly facade may reject the assignment.
|
||||
}
|
||||
try {
|
||||
checkDefinition.minimum = 0;
|
||||
} catch {
|
||||
// A readonly facade may reject the assignment.
|
||||
}
|
||||
}
|
||||
|
||||
const forged = await dispatchHousekeepingCommand(
|
||||
{
|
||||
commandId,
|
||||
input: {
|
||||
objectDescriptor: 7,
|
||||
objectDescriptors: 7,
|
||||
reflectDescriptor: 7,
|
||||
},
|
||||
},
|
||||
dependencies(),
|
||||
);
|
||||
expect(forged).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "VALIDATION" },
|
||||
});
|
||||
expect(executions).toBe(0);
|
||||
|
||||
const valid = await dispatchHousekeepingCommand(
|
||||
{
|
||||
commandId,
|
||||
input: {
|
||||
objectDescriptor: "valid",
|
||||
objectDescriptors: "valid",
|
||||
reflectDescriptor: "valid",
|
||||
},
|
||||
},
|
||||
dependencies(),
|
||||
);
|
||||
expect(valid).toMatchObject({ ok: true });
|
||||
expect(executions).toBe(1);
|
||||
});
|
||||
it("rejects a missing required reason before the command can execute", async () => {
|
||||
let executed = false;
|
||||
const audit = auditRecorder();
|
||||
|
||||
Reference in new issue
Block a user