fix: complete task 5 rereview repairs

This commit is contained in:
lda
2026-08-13 22:37:48 +07:00 Verified
parent cca1be891f
commit 0a7996da9f
6 changed files with 330 additions and 33 deletions
@@ -59,30 +59,97 @@ export const hasBoundedInputExpressionNodeBudget = (
return visitExpression(input);
};
/** Find and bound generated expression-shaped values anywhere in an RPC value. */
export const hasBoundedInputExpressionPayload = (
type JsonSchemaRecord = Readonly<Record<string, unknown>>;
const schemaRecord = (value: unknown): JsonSchemaRecord | null =>
isRecord(value) ? value : null;
const localComponentName = (ref: unknown): string | null => {
if (typeof ref !== "string") return null;
const prefix = "#/components/schemas/";
return ref.startsWith(prefix) ? ref.slice(prefix.length) : null;
};
/**
* Bound only expression bindings found through a generated operation schema.
*
* This deliberately follows schema positions instead of inspecting arbitrary
* JSON for expression-shaped objects. Ordinary runtime data can use the same
* `kind`/`value` keys without becoming an input expression.
*/
export const hasBoundedInputExpressionsAtSchema = (
input: unknown,
schema: unknown,
components: Readonly<Record<string, unknown>>,
maxNodes: number = MAX_INPUT_EXPRESSION_NODES,
): boolean => {
const active = new WeakSet<object>();
const visit = (value: unknown): boolean => {
const activeValues = new WeakSet<object>();
const activeComponents = new Set<string>();
const visit = (value: unknown, currentSchema: unknown): boolean => {
const schemaValue = schemaRecord(currentSchema);
if (schemaValue === null) return true;
const componentName = localComponentName(schemaValue.$ref);
if (componentName !== null) {
if (componentName === "StepInputBinding" || componentName === "InputExpressionBinding") {
return isRecord(value) && "expression" in value
? hasBoundedInputExpressionNodeBudget(value.expression, maxNodes)
: true;
}
const component = components[componentName];
if (component === undefined || activeComponents.has(componentName)) return true;
activeComponents.add(componentName);
const valid = visit(value, component);
activeComponents.delete(componentName);
return valid;
}
for (const key of ["allOf", "anyOf", "oneOf"] as const) {
const branches = schemaValue[key];
if (Array.isArray(branches) && !branches.every((branch) => visit(value, branch))) {
return false;
}
}
if (typeof value !== "object" || value === null) return true;
if (active.has(value)) return false;
active.add(value);
const valid = Array.isArray(value)
? value.every(visit)
: isRecord(value) &&
(typeof value.kind === "string" &&
expressionKinds.has(value.kind) &&
((value.kind === "literal" && "value" in value) ||
(value.kind === "path" && "path" in value) ||
(value.kind === "array" && Array.isArray(value.items)) ||
(value.kind === "object" && isRecord(value.fields)))
? hasBoundedInputExpressionNodeBudget(value, maxNodes)
: Object.values(value).every(visit));
active.delete(value);
return valid;
if (activeValues.has(value)) return false;
activeValues.add(value);
const properties = schemaRecord(schemaValue.properties);
if (properties !== null && isRecord(value)) {
for (const [key, propertySchema] of Object.entries(properties)) {
if (key in value && !visit(value[key], propertySchema)) {
activeValues.delete(value);
return false;
}
}
}
const items = schemaValue.items;
if (Array.isArray(value) && items !== undefined) {
for (const item of value) {
if (!visit(item, items)) {
activeValues.delete(value);
return false;
}
}
}
const additionalProperties = schemaValue.additionalProperties;
if (isRecord(value) && schemaRecord(additionalProperties) !== null) {
const knownProperties = properties === null ? new Set<string>() : new Set(Object.keys(properties));
for (const [key, propertyValue] of Object.entries(value)) {
if (!knownProperties.has(key) && !visit(propertyValue, additionalProperties)) {
activeValues.delete(value);
return false;
}
}
}
activeValues.delete(value);
return true;
};
return visit(input);
return visit(input, schema);
};