diff --git a/experiments/atomic-codemode/README.md b/experiments/atomic-codemode/README.md index 5b223477cb..7c27cc1400 100644 --- a/experiments/atomic-codemode/README.md +++ b/experiments/atomic-codemode/README.md @@ -214,3 +214,12 @@ The trace reveals two format sources at each shared call, verified against byte and machine evidence. [Costs and source review](trials.md) also record a direct documentation-lookup miss; named discovery needs fixing before rerunning the same task. No general adapter speedup follows from that first pair. + +Named reference lookup now accepts canonical IDs or exact displayed names, +including space/comma-separated lists. One presentation map supplies direct +registration and documentation names; unmatched terms remain explicit. Selected +schemas come from named contracts, use input semantics (defaulted fields stay +optional), and are cached immutably by Zod schema identity for the process. +Validation stays in the API registry; documentation has its own module. Both +actual MCP catalogs and all fourteen reference schemas validate. The unchanged +PHI question/oracle will be rerun after this discovery correction. diff --git a/experiments/atomic-codemode/api.mjs b/experiments/atomic-codemode/api.mjs index f5558fd64f..5053981fc0 100644 --- a/experiments/atomic-codemode/api.mjs +++ b/experiments/atomic-codemode/api.mjs @@ -3,6 +3,7 @@ import { z } from "zod"; import { KunaFactOwner } from "./provider.mjs"; import { FactFailure } from "./fact-error.mjs"; import { MAX_BYTE_BATCH_BYTES } from "./limits.mjs"; +import { createReference } from "./reference.mjs"; const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u); const snapshot = z.string().startsWith("snap_"); @@ -250,36 +251,6 @@ export const methods = { ), }; -// Compact return contracts let code compose before first observing a response. -const returns = { - bytes: - "data:{results:[{ok,address,source:{evidence_id,sha256},hex,returned_bytes,end_address,coverage:{status,requested_bytes,provider_truncated,notes,original_file_mapping,runtime_memory}}|{ok:false,address,error}]}; ranges are [address,end_address). Completeness concerns requested bytes, not the provider truncation flag or adjacent bytes. Complete native hexdump and metadata remain in Evidence.", - functions: - "data:{source:{evidence_id,sha256},items:[{address,name,aliases,size}],total,next_offset,coverage}; size is an upper bound, or zero if unmeasured. Multiple matching addresses are retained, never selected by catalog order or display name alone.", - instructions: - "data:{results:[{ok,address,source:{evidence_id,sha256},items:[{address,size,status,bytes?,text?,mnemonic?,operands?}],resume_address,coverage}|{ok:false,address,error}]}; decoded rows describe selected machine bytes, not optimized IR branches. Partial batches retain successful windows.", - strings: - "data:{source:{evidence_id,sha256},items:[{address,value,string:{encoding,byte_length}}],total,next_offset,coverage}", - references: - "data:{results:[{ok,address,source:{evidence_id},items:[{source_row,from,to,kind,fromFunction:{address,name}|null,toFunction:{address,name}|null,instruction}],total,next_offset,coverage}|{ok:false,address,error}]}. source_row is the zero-based row in retained native xrefs; Evidence identity plus source_row identifies an occurrence. Coverage preserves examined/excluded/unattributed counts and unknown function scope/discovery. fields projects optional columns; endpoints/kind/source_row stay.", - snapshot: - "data:{snapshot:string,entry,source:{evidence_id,sha256},facets:{code:{availability},recovered_ir:{availability,error?},operations:{availability,scope?,count?,root_count?,definition_frontier_count?,error?},calls:{availability,count?,by_opcode?:[{opcode,known_target_count,unknown_target_count}],coverage?,error?},values:{availability,scope?,count?,error?}},coverage}. Call counts cover all retained recovered calls, including is_dead rows, not complete machine/runtime calls; target knowledge comes from a non-null native target, independent of opcode. An unavailable facet omits counts; an available empty call facet reports zeros. The complete graph can be unavailable while operations reports a bounded projection count; an unavailable operation facet has unknown/null counts, never a fabricated empty set; code remains readable through the same handle.", - operations: - "data:{items:[{id,origin_address,block,source_line,kind,statement,definitions,constants,inputs?,output?,opcode?,is_dead?}],total,next_offset,evidence_id,coverage}. Native inputs:[{input_index,value_id,phi_predecessor,phi_predecessor_edge}], output:value_id|null; native constants include value_id/input_index and exact u64 value offset with printed_size_bytes (native width), not arbitrary-width computed values; definitions:[{definition,use,input_index,value_id,phi_predecessor,phi_predecessor_edge}]. Diagnostic definitions use printed occurrence instead. fields projects columns; identity and native is_dead remain. A call-rooted projection reports retained facts only, with unknown CFG and explicit definition frontier coverage.", - relations: - "data:{items:definition/use edges OR CFG edges{from,to,operation,polarity?,condition_value?,condition_test?} OR controllers{controller,dependent,edge},total,next_offset,evidence_id,coverage,relation_coverage?,unretained_seeds?}. definitions/uses seeds are operation-ID strings; incoming/outgoing/controllers seeds are numeric block IDs. Native def/use edges include input_index,value_id,phi_predecessor; diagnostic edges include occurrence only.", - values: - "data:{items:[{id,space,offset,size_bytes,is_constant,is_input,is_annotation,definition,type_name}],total,next_offset,evidence_id,coverage}; definitions are operation IDs, offsets are exact hex strings. When the graph is unavailable, values may come from a bounded definition projection or only the call boundary; coverage states that scope and operations/definitions may be unreadable.", - calls: - "data:{items:[{id,origin_address,block,opcode,spec_available,is_dead,target:{space,offset}|null,target_name,arguments:[{input_index,value_id,storage:{space,offset,size_bytes}|null,storage_authority}],output}],total,next_offset,missing,evidence_id,coverage}. fields selects target/target_name/arguments/output/prototype/stack_offset/target_value_id/is_dead. Values may come from a bounded call-rooted definition projection or call-boundary set when the graph is unavailable; coverage states that scope. Calls include all retained call operations; null facts are unknown, not proof of no arguments or runtime binding. Candidate cross-function links require matching target identity and exact passing/input storage; join/subregister/alias interpretation remains unknown.", - blocks: - "data:{items:[{id,origin_start,operations,predecessors,successors:[{target,index,polarity,condition_value,condition_test}]}],total,next_offset,evidence_id,coverage}; IDs are local to this snapshot.", - slice: - "data:{operations:[operation rows as above],coverage:{status,boundaries:[{id,reason}],missing,missing_reasons?,source_scope?,source_issues,runtime_reachability,memory_alias_dependencies},value_frontier?:[{value_id?|operation?,input_index?,reason}],evidence_id}", - code: "data:{evidence_id,start_line,language:string|null,text,total_lines,next_offset,coverage}; language is native-reported or unknown. Read/filter full reconstructed text within Code Mode without returning it all.", - evidence: - "data:{source:{evidence_id,sha256,bytes,arguments},text,total_code_units,next_offset}; source has complete native output, including code before the SSA graph.", -}; methods.bytes.input = methods.bytes.input.superRefine((value, context) => { // Pattern failures need not prevent object refinements from running in Zod. // Only the named window contract may admit values to exact u64 arithmetic. @@ -426,32 +397,4 @@ export class AtomicSession { } } -export const reference = (query = "") => { - const keys = Object.keys(methods).filter( - (key) => - query === "" || - key.includes(query) || - methods[key].description.toLowerCase().includes(query.toLowerCase()), - ); - return { - language: - "JavaScript async function body; return a JSON value. All rea methods return {ok,context,data} or {ok:false,error}.", - methods: keys.map((name) => ({ - name: `rea.${name}`, - description: methods[name].description, - input_schema: z.toJSONSchema(methods[name].input), - returns: returns[name], - example: methods[name].example, - })), - limits: { - execution_heap_mib: 64, - max_host_calls: 64, - output_utf8_bytes: 32768, - }, - notes: [ - "No process, filesystem, network, imports or Node globals in the execution environment.", - "Select facts inside code, return only what the question needs, and keep real source IDs and unknowns.", - "A fresh JavaScript environment is used per execute_javascript call; analysis snapshots live until close_binary.", - ], - }; -}; +export const reference = createReference(methods); diff --git a/experiments/atomic-codemode/native-engine.md b/experiments/atomic-codemode/native-engine.md index 1a2f380e63..3a2a99d08e 100644 --- a/experiments/atomic-codemode/native-engine.md +++ b/experiments/atomic-codemode/native-engine.md @@ -542,3 +542,33 @@ stages and workspace gates remain unexecuted; the patch is not upstream-ready. No whole-repository typecheck pass is claimed beyond the previously recorded resource limitation. The [PHI-format task/oracle](projection-oracle.md) are fixed before real-model launches. No model benefit is assumed from these smaller facts. + +## Named reference discovery and input schemas + +The first PHI model pair completes correctly but exposes a documentation boundary: +direct help displays `facts_*` names while lookup only recognizes raw method +substrings. A multi-name query returns no matches and causes a full-reference +fallback. Registration and help now share one explicit name map, with canonical +identity separate from presentation. Exact lists preserve caller order, +deduplicate names and retain unmatched terms. Topic/all-contract lookup remain; +ambiguous labels fail instead of choosing by catalog position. + +The real client verifier also catches that plain `z.toJSONSchema` defaults to +output semantics, making defaulted `offset`/`limit` falsely required in advertised +input schemas. Reference generation now explicitly selects `io:input`. Converted +schemas are frozen and weakly cached by schema identity; a replacement schema +invalidates the conversion, while description/presentation is read separately. +The documentation module removes the adapter rename pass. No target analysis, +live-result caching or native-engine change is involved. + +The shared suite passes 55 tests. `verify-discovery.mjs` checks both real SDK +catalogs and reference responses without opening a target, compiles every catalog +and all fourteen reference schemas with Ajv2020, and validates the same +omitted-default requests against named reference/direct contracts. It proves the +six-name query matches all requested contracts without a miss. The corrected +verifier takes 1.628 family wall / 1.449 CPU seconds, 128.9 MiB kernel peak, +cleanup verified and OOM 0. Its original 1.730 / 1.487 seconds, 128.1 MiB failed +omitted-default check remains recorded. Six contracts occupy 10,835 Code Mode / +10,360 direct bytes, versus 21,746 / 20,699 for all fourteen. Bytes do not prove +token or time savings. The same PHI question/oracle are retained for the next +actual model pair. diff --git a/experiments/atomic-codemode/reference.mjs b/experiments/atomic-codemode/reference.mjs new file mode 100644 index 0000000000..a8c1a0697f --- /dev/null +++ b/experiments/atomic-codemode/reference.mjs @@ -0,0 +1,125 @@ +// Named documentation selection; adapters supply their exact registered labels. +import { z } from "zod"; +import { FactFailure } from "./fact-error.mjs"; +import { freezeFacts } from "./fact-reads.mjs"; + +// Compact return contracts let code compose before first observing a response. +const returns = { + bytes: + "data:{results:[{ok,address,source:{evidence_id,sha256},hex,returned_bytes,end_address,coverage:{status,requested_bytes,provider_truncated,notes,original_file_mapping,runtime_memory}}|{ok:false,address,error}]}; ranges are [address,end_address). Completeness concerns requested bytes, not the provider truncation flag or adjacent bytes. Complete native hexdump and metadata remain in Evidence.", + functions: + "data:{source:{evidence_id,sha256},items:[{address,name,aliases,size}],total,next_offset,coverage}; size is an upper bound, or zero if unmeasured. Multiple matching addresses are retained, never selected by catalog order or display name alone.", + instructions: + "data:{results:[{ok,address,source:{evidence_id,sha256},items:[{address,size,status,bytes?,text?,mnemonic?,operands?}],resume_address,coverage}|{ok:false,address,error}]}; decoded rows describe selected machine bytes, not optimized IR branches. Partial batches retain successful windows.", + strings: + "data:{source:{evidence_id,sha256},items:[{address,value,string:{encoding,byte_length}}],total,next_offset,coverage}", + references: + "data:{results:[{ok,address,source:{evidence_id},items:[{source_row,from,to,kind,fromFunction:{address,name}|null,toFunction:{address,name}|null,instruction}],total,next_offset,coverage}|{ok:false,address,error}]}. source_row is the zero-based row in retained native xrefs; Evidence identity plus source_row identifies an occurrence. Coverage preserves examined/excluded/unattributed counts and unknown function scope/discovery. fields projects optional columns; endpoints/kind/source_row stay.", + snapshot: + "data:{snapshot:string,entry,source:{evidence_id,sha256},facets:{code:{availability},recovered_ir:{availability,error?},operations:{availability,scope?,count?,root_count?,definition_frontier_count?,error?},calls:{availability,count?,by_opcode?:[{opcode,known_target_count,unknown_target_count}],coverage?,error?},values:{availability,scope?,count?,error?}},coverage}. Call counts cover all retained recovered calls, including is_dead rows, not complete machine/runtime calls; target knowledge comes from a non-null native target, independent of opcode. An unavailable facet omits counts; an available empty call facet reports zeros. The complete graph can be unavailable while operations reports a bounded projection count; an unavailable operation facet has unknown/null counts, never a fabricated empty set; code remains readable through the same handle.", + operations: + "data:{items:[{id,origin_address,block,source_line,kind,statement,definitions,constants,inputs?,output?,opcode?,is_dead?}],total,next_offset,evidence_id,coverage}. Native inputs:[{input_index,value_id,phi_predecessor,phi_predecessor_edge}], output:value_id|null; native constants include value_id/input_index and exact u64 value offset with printed_size_bytes (native width), not arbitrary-width computed values; definitions:[{definition,use,input_index,value_id,phi_predecessor,phi_predecessor_edge}]. Diagnostic definitions use printed occurrence instead. fields projects columns; identity and native is_dead remain. A call-rooted projection reports retained facts only, with unknown CFG and explicit definition frontier coverage.", + relations: + "data:{items:definition/use edges OR CFG edges{from,to,operation,polarity?,condition_value?,condition_test?} OR controllers{controller,dependent,edge},total,next_offset,evidence_id,coverage,relation_coverage?,unretained_seeds?}. definitions/uses seeds are operation-ID strings; incoming/outgoing/controllers seeds are numeric block IDs. Native def/use edges include input_index,value_id,phi_predecessor; diagnostic edges include occurrence only.", + values: + "data:{items:[{id,space,offset,size_bytes,is_constant,is_input,is_annotation,definition,type_name}],total,next_offset,evidence_id,coverage}; definitions are operation IDs, offsets are exact hex strings. When the graph is unavailable, values may come from a bounded definition projection or only the call boundary; coverage states that scope and operations/definitions may be unreadable.", + calls: + "data:{items:[{id,origin_address,block,opcode,spec_available,is_dead,target:{space,offset}|null,target_name,arguments:[{input_index,value_id,storage:{space,offset,size_bytes}|null,storage_authority}],output}],total,next_offset,missing,evidence_id,coverage}. fields selects target/target_name/arguments/output/prototype/stack_offset/target_value_id/is_dead. Values may come from a bounded call-rooted definition projection or call-boundary set when the graph is unavailable; coverage states that scope. Calls include all retained call operations; null facts are unknown, not proof of no arguments or runtime binding. Candidate cross-function links require matching target identity and exact passing/input storage; join/subregister/alias interpretation remains unknown.", + blocks: + "data:{items:[{id,origin_start,operations,predecessors,successors:[{target,index,polarity,condition_value,condition_test}]}],total,next_offset,evidence_id,coverage}; IDs are local to this snapshot.", + slice: + "data:{operations:[operation rows as above],coverage:{status,boundaries:[{id,reason}],missing,missing_reasons?,source_scope?,source_issues,runtime_reachability,memory_alias_dependencies},value_frontier?:[{value_id?|operation?,input_index?,reason}],evidence_id}", + code: "data:{evidence_id,start_line,language:string|null,text,total_lines,next_offset,coverage}; language is native-reported or unknown. Read/filter full reconstructed text within Code Mode without returning it all.", + evidence: + "data:{source:{evidence_id,sha256,bytes,arguments},text,total_code_units,next_offset}; source has complete native output, including code before the SSA graph.", +}; +const referenceSchemas = new WeakMap(); +const schemaFor = (input) => { + if (!referenceSchemas.has(input)) + referenceSchemas.set( + input, + freezeFacts(z.toJSONSchema(input, { io: "input" })), + ); + return referenceSchemas.get(input); +}; +function referenceNames(methods, names) { + const keys = Object.keys(methods), + lookup = new Map(keys.map((key) => [key, key])), + labels = {}; + if (Object.keys(names).some((key) => !Object.hasOwn(methods, key))) + throw new FactFailure( + "invalid_input", + "Unknown method in reference presentation", + ); + for (const key of keys) { + const label = names[key] ?? `rea.${key}`; + if ( + typeof label !== "string" || + !label || + (lookup.has(label) && lookup.get(label) !== key) + ) + throw new FactFailure( + "invalid_input", + "Ambiguous or invalid reference method name", + ); + lookup.set(label, key); + labels[key] = label; + } + return { lookup, labels }; +} +function selectReference(methods, query, lookup) { + const normalized = query.trim(); + if (!normalized) + return { keys: Object.keys(methods), selection: "all", unmatched: [] }; + const terms = normalized.split(/[\s,]+/u), + named = terms.map((term) => lookup.get(term)); + if (named.some(Boolean)) + return { + keys: [...new Set(named.filter(Boolean))], + selection: "named", + unmatched: terms.filter((term) => !lookup.has(term)), + }; + const topic = normalized.toLowerCase(); + const keys = Object.keys(methods).filter( + (key) => + key.includes(topic) || + methods[key].description.toLowerCase().includes(topic), + ); + return { keys, selection: "topic", unmatched: keys.length ? [] : [query] }; +} +export const createReference = + (methods) => + (query = "", { names = {}, language, includeExamples = true } = {}) => { + const { lookup, labels } = referenceNames(methods, names), + selected = selectReference(methods, query, lookup); + return { + language: + language ?? + "JavaScript async function body; return a JSON value. All rea methods return {ok,context,data} or {ok:false,error}.", + methods: selected.keys.map((method) => ({ + method, + name: labels[method], + description: methods[method].description, + input_schema: schemaFor(methods[method].input), + returns: returns[method], + ...(includeExamples ? { example: methods[method].example } : {}), + })), + coverage: { + declared_methods: Object.keys(methods).length, + matched_methods: selected.keys.length, + selection: selected.selection, + }, + unmatched_queries: selected.unmatched, + limits: { + execution_heap_mib: 64, + max_host_calls: 64, + output_utf8_bytes: 32768, + }, + notes: [ + "Select exact displayed method names or canonical method IDs, separated by spaces/commas; a topic query searches descriptions. Empty query returns all contracts.", + "No process, filesystem, network, imports or Node globals in the execution environment.", + "Select facts inside code, return only what the question needs, and keep real source IDs and unknowns.", + "A fresh JavaScript environment is used per execute_javascript call; analysis snapshots live until close_binary.", + ], + }; + }; diff --git a/experiments/atomic-codemode/reference.test.mjs b/experiments/atomic-codemode/reference.test.mjs new file mode 100644 index 0000000000..1095733d00 --- /dev/null +++ b/experiments/atomic-codemode/reference.test.mjs @@ -0,0 +1,71 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import Ajv2020 from "ajv/dist/2020.js"; +import { methods, reference } from "./api.mjs"; + +test("named reference lists preserve caller order and exact schemas across presentations", () => { + const names = Object.fromEntries( + Object.keys(methods).map((name) => [name, `facts_${name}`]), + ); + const result = reference( + "facts_values, facts_snapshot facts_values unknown", + { names, includeExamples: false }, + ); + assert.deepEqual( + result.methods.map((m) => m.method), + ["values", "snapshot"], + ); + assert.deepEqual( + result.methods.map((m) => m.name), + ["facts_values", "facts_snapshot"], + ); + assert.deepEqual(result.unmatched_queries, ["unknown"]); + assert.equal(result.coverage.declared_methods, 14); + assert.equal(result.coverage.matched_methods, 2); + assert.equal(result.coverage.selection, "named"); + const javascript = reference("rea.values snapshot"); + assert.deepEqual( + javascript.methods.map((m) => m.method), + ["values", "snapshot"], + ); + assert.deepEqual( + result.methods.map((m) => m.input_schema), + javascript.methods.map((m) => m.input_schema), + ); + const validate = new Ajv2020({ strict: false }).compile( + result.methods[1].input_schema, + ); + assert.equal(validate({ entry: "0xea6d0" }), true); + assert.equal(validate({ snapshot: "wrong contract" }), false); + const validateValues = new Ajv2020({ strict: false }).compile( + result.methods[0].input_schema, + ); + assert.equal(validateValues({ snapshot: "snap_example", ids: ["v1"] }), true); + assert.equal( + methods.values.input.parse({ snapshot: "snap_example" }).limit, + 20, + ); + assert.equal(Object.hasOwn(result.methods[0], "example"), false); +}); + +test("topic/all reference selection and shared schema ownership remain explicit", () => { + assert.equal(reference().methods.length, 14); + assert.equal(reference("no-such-topic").coverage.matched_methods, 0); + assert.ok( + reference("memory aliases").methods.some((m) => m.method === "slice"), + ); + const before = reference("snapshot").methods[0]; + assert.throws(() => { + before.input_schema.properties.entry.type = "number"; + }, TypeError); + const after = reference("snapshot").methods[0]; + assert.equal(after.input_schema.properties.entry.type, "string"); + assert.throws( + () => reference("values", { names: { snapshot: "values" } }), + /Ambiguous/, + ); + assert.throws( + () => reference("values", { names: { unknown: "facts_values" } }), + /Unknown/, + ); +}); diff --git a/experiments/atomic-codemode/server.mjs b/experiments/atomic-codemode/server.mjs index 5c2e451266..7194cadecf 100644 --- a/experiments/atomic-codemode/server.mjs +++ b/experiments/atomic-codemode/server.mjs @@ -7,9 +7,18 @@ import { executeJavaScript } from "./executor.mjs"; import { TARGET_CONTENT_RESULT_META } from "../../dist/server/toolResultEncoding.js"; import { appendFile } from "node:fs/promises"; +const DIRECT_REFERENCE_LANGUAGE = + "Direct MCP calls: use the displayed facts_* tools with the same JSON arguments; results have {ok,context,data}."; const session = new AtomicSession(); const createServer = () => { const server = new McpServer({ name: "rea-codemode", version: "0.1.0" }); + const direct = process.env.REA_ATOMIC_ADAPTER === "direct"; + const apiNames = Object.fromEntries( + Object.keys(methods).map((name) => [ + name, + direct ? `facts_${name}` : `rea.${name}`, + ]), + ); const encode = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }], _meta: TARGET_CONTENT_RESULT_META, @@ -64,26 +73,19 @@ const createServer = () => { ); register( "reference", - "Look up the rea JavaScript API by method name or analyst concept before writing Code Mode calls. Empty query returns the complete API reference.", + "Look up analysis contracts by exact displayed method names or canonical method IDs, separated by spaces or commas, or search one analyst concept. Request only needed methods; empty query returns all contracts.", z.strictObject({ query: z.string().default("") }), - (input) => { - const docs = reference(input.query); - if (process.env.REA_ATOMIC_ADAPTER === "direct") { - docs.language = - "Direct MCP calls: use facts_ with the same JSON arguments; results have {ok,context,data}."; - docs.methods = docs.methods.map((method) => ({ - ...method, - name: method.name.replace("rea.", "facts_"), - example: undefined, - })); - } - return docs; - }, + (input) => + reference(input.query, { + names: apiNames, + includeExamples: !direct, + language: direct ? DIRECT_REFERENCE_LANGUAGE : undefined, + }), ); - if (process.env.REA_ATOMIC_ADAPTER === "direct") { + if (direct) { for (const [name, contract] of Object.entries(methods)) register( - `facts_${name}`, + apiNames[name], contract.description, contract.input, (input, signal) => session.call(name, input, signal), diff --git a/experiments/atomic-codemode/verify-discovery.mjs b/experiments/atomic-codemode/verify-discovery.mjs new file mode 100644 index 0000000000..778a0a530e --- /dev/null +++ b/experiments/atomic-codemode/verify-discovery.mjs @@ -0,0 +1,118 @@ +// Verify actual SDK-converted catalogs and named help without target analysis. +import assert from "node:assert/strict"; +import { writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import Ajv2020 from "ajv/dist/2020.js"; +import { + createVerifierRun, + completeVerifierRun, + assertVerifierCleanupComplete, +} from "../../scripts/lib/verifier-run.mjs"; + +const [output] = process.argv.slice(2); +const run = createVerifierRun(), + results = [], + failures = []; +for (const direct of [false, true]) { + const client = new Client({ + name: "rea-discovery-verifier", + version: "0.1.0", + }); + const transport = new StdioClientTransport({ + command: process.execPath, + args: [resolve("experiments/atomic-codemode/server.mjs")], + env: { ...process.env, REA_ATOMIC_ADAPTER: direct ? "direct" : "codemode" }, + stderr: "pipe", + }); + const call = async (query) => { + const reply = await client.callTool({ + name: "reference", + arguments: { query }, + }); + return JSON.parse(reply.content[0].text); + }; + try { + await client.connect(transport); + const catalog = await client.listTools(); + const prefix = direct ? "facts_" : "rea."; + const ids = [ + "snapshot", + "operations", + "relations", + "values", + "instructions", + "bytes", + ]; + const selected = await call(ids.map((name) => prefix + name).join(" ")); + assert.deepEqual( + selected.methods.map((m) => m.method), + ids, + ); + assert.deepEqual(selected.unmatched_queries, []); + assert.equal(selected.coverage.declared_methods, 14); + const ajv = new Ajv2020({ strict: false }); + for (const tool of catalog.tools) ajv.compile(tool.inputSchema); + for (const method of selected.methods) { + ajv.compile(method.input_schema); + assert.equal(method.name, prefix + method.method); + if (direct) + assert.ok(catalog.tools.some((tool) => tool.name === method.name)); + } + for (const [name, good, bad] of [ + ["snapshot", { entry: "0xea6d0" }, { snapshot: "snap_other" }], + ["values", { snapshot: "snap_other", ids: ["v1"] }, { entry: "0xea6d0" }], + ]) { + const method = selected.methods.find((m) => m.method === name); + const validators = [ajv.compile(method.input_schema)]; + if (direct) + validators.push( + ajv.compile( + catalog.tools.find((t) => t.name === method.name).inputSchema, + ), + ); + for (const validate of validators) { + assert.equal(validate(good), true); + assert.equal(validate(bad), false); + } + } + const missing = await call(`${prefix}values unknown`); + assert.deepEqual( + missing.methods.map((m) => m.method), + ["values"], + ); + assert.deepEqual(missing.unmatched_queries, ["unknown"]); + const all = await call(""); + assert.equal(all.methods.length, 14); + for (const method of all.methods) ajv.compile(method.input_schema); + assert.ok(JSON.stringify(selected).length < JSON.stringify(all).length); + results.push({ + direct, + selected, + all_reference_bytes: Buffer.byteLength(JSON.stringify(all)), + selected_reference_bytes: Buffer.byteLength(JSON.stringify(selected)), + actual_catalog_and_named_schema_checks_passed: true, + }); + } catch (error) { + failures.push(error.stack ?? String(error)); + } finally { + await client.close(); + await transport.close(); + } +} +const lifecycle = await completeVerifierRun(run); +assertVerifierCleanupComplete(lifecycle.process_lineage); +await writeFile( + output, + JSON.stringify({ + results, + failures, + lifecycle, + scope: + "Static contract/catalog checks; no native target analysis or model cost measurement", + }), + { flag: "wx" }, +); +console.log(JSON.stringify({ output, lanes: results.length, failures })); +if (failures.length) process.exitCode = 1;