mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 22:36:08 +00:00
* fix: emit v-mode-compatible patterns for tool schemas Character classes in emitted patterns carried unescaped '-', '/' and '['. Annex-B and u-mode accept them, so REA's own Zod validation (u) and every u-based validator never saw a defect, but v-mode rejects ClassSetSyntaxCharacters and Ruby rejects [^s[]]. With 6.1.0 tools/list, 117 pattern fields across 11 tools were rejected under v. The characters are escaped as \x2d, \x2f, \x5b and \x5d rather than \-/\//\[, because oxlint --fix removes an escaped '-' inside a character class as a useless escape, so an escape-only change does not survive the pre-commit hook. \x2d-style escapes are valid under Annex B, u and v, are matched identically, and are not useless-escape candidates. 22 pattern literals in 18 files are touched; no other token changes. * fix: replace the Zod offset pattern and guard the advertised catalog The offset datetime site projected Zod's offset class as [ +- ], which no escape in this repository's sources can reach, so evaluate_reconstruction_coverage stayed unreachable for exactly the clients this change targets. It now uses offsetDateTimeSchema in src/domain/offsetDateTime.ts: the same accepted set with the offset class escaped as [ +\x2d ]. Adds tests/boundary/mcp/advertisedPatternsVMode.test.ts, which performs the real tools/list projection, traverses every advertised input and output schema, compiles each pattern under v, and asserts the inventory it walked so a silent traversal collapse fails instead of passing vacuously.
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { emptyArraySchema } from "./emptyArraySchema.js";
|
|
import { evidenceSchema } from "./evidence.js";
|
|
import { prefixedDigestSchema } from "./../domain/digests.js";
|
|
|
|
const addressSchema = z
|
|
.string()
|
|
.regex(
|
|
/^(?:(?:[A-Za-z0-9._~\x2d]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$/u,
|
|
);
|
|
const inputAddressSchema = z
|
|
.string()
|
|
.regex(/^(?:(?:[A-Za-z0-9._~\x2d]|%[0-9a-fA-F]{2})+:)?0[xX][0-9a-fA-F]+$/u)
|
|
.transform((address) => {
|
|
const separator = address.lastIndexOf(":") + 1;
|
|
const space = address
|
|
.slice(0, separator)
|
|
.replace(/%[0-9a-fA-F]{2}/gu, (escape) => escape.toUpperCase());
|
|
return `${space}0x${BigInt(address.slice(separator)).toString(16)}`;
|
|
});
|
|
const evidenceIdSchema = prefixedDigestSchema("ev");
|
|
|
|
/** Normalize a hexadecimal offset while preserving its optional address space. */
|
|
export const parseCallPathAddress = (input: unknown): string =>
|
|
inputAddressSchema.parse(input);
|
|
|
|
/** Strict input for explicit call-path reconstruction from complete dossiers. */
|
|
export const callPathInputSchema = z.strictObject({
|
|
functions: z.array(evidenceSchema).min(1),
|
|
start: z.object({ address: inputAddressSchema }).strict(),
|
|
goal: z.object({ address: inputAddressSchema }).strict(),
|
|
});
|
|
|
|
const citedNodeSchema = z.object({
|
|
address: addressSchema,
|
|
name: z.string().nullable(),
|
|
evidence_links: z.array(evidenceIdSchema).min(1),
|
|
});
|
|
const citedEdgeSchema = z.object({
|
|
source: addressSchema,
|
|
target: addressSchema,
|
|
evidence_links: z.array(evidenceIdSchema).min(1),
|
|
});
|
|
const pathSchema = z
|
|
.object({
|
|
hops: z.number().int().min(0),
|
|
nodes: z.array(citedNodeSchema).min(1),
|
|
edges: z.array(citedEdgeSchema),
|
|
evidence_links: z.array(evidenceIdSchema).min(1),
|
|
})
|
|
.superRefine((path, context) => {
|
|
const edgesConnectNodes = path.edges.every(
|
|
(edge, index) =>
|
|
edge.source === path.nodes[index]?.address &&
|
|
edge.target === path.nodes[index + 1]?.address,
|
|
);
|
|
if (
|
|
path.hops !== path.edges.length ||
|
|
path.nodes.length !== path.edges.length + 1 ||
|
|
!edgesConnectNodes
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Path hops and edges must connect the ordered nodes",
|
|
});
|
|
});
|
|
const resultContextShape = {
|
|
start: addressSchema,
|
|
goal: addressSchema,
|
|
explored: z.object({
|
|
nodes: z.number().int().min(0),
|
|
edges: z.number().int().min(0),
|
|
depth_reached: z.number().int().min(0),
|
|
}),
|
|
evidence_links: z.array(evidenceIdSchema).min(1),
|
|
limitations: z.array(z.string()),
|
|
};
|
|
|
|
/** Evidence-cited directed call-path result containing every shortest path. */
|
|
export const callPathResultSchema = z.union([
|
|
z.object({
|
|
...resultContextShape,
|
|
status: z.literal("found"),
|
|
shortest_hops: z.number().int().min(0),
|
|
search_scope: z.object({ exhaustive: z.boolean() }),
|
|
paths: z.array(pathSchema).min(1),
|
|
}),
|
|
z.object({
|
|
...resultContextShape,
|
|
status: z.literal("not_found"),
|
|
shortest_hops: z.null(),
|
|
search_scope: z.object({ exhaustive: z.literal(true) }),
|
|
paths: emptyArraySchema,
|
|
}),
|
|
z.object({
|
|
...resultContextShape,
|
|
status: z.literal("unknown"),
|
|
shortest_hops: z.null(),
|
|
search_scope: z.object({ exhaustive: z.literal(false) }),
|
|
paths: emptyArraySchema,
|
|
}),
|
|
]);
|
|
|
|
export type CallPathInput = z.infer<typeof callPathInputSchema>;
|
|
export type CallPathResult = z.infer<typeof callPathResultSchema>;
|
|
export type OutputCallPath = z.infer<typeof pathSchema>;
|