experiment(re)!: preserve partial captures and resolve function entries
Keep validated code available when native graph extraction is unavailable, bind output to the actual input/entry, preserve cancellation observations, and share batch source/error semantics. Add filtered function/alias reads and slice field selection across adapters. Verified 38 focused tests, targeted lint, real stock/native MCP workflows, large SQLite graph-capacity fallback, and real cold/warm MCP reuse. Retain actual model costs and unequal-depth/selection limitations instead of claiming general superiority. BREAKING CHANGE: Experimental native snapshots may succeed with readable code and an unavailable recovered_ir facet. Inspect facets before graph reads; unavailable graph counts are null/unknown. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
1 parent
4f13bd6733
commit
f3f209d03f
16 files changed
+847
-213
No files matched your search
@@ -68,16 +68,23 @@ unknown. Other providers are not yet wired to this experimental service.
|
||||
|
||||
The initial [actual model comparison](trials.md) favors the focused traditional
|
||||
workflow. The seeded value-origin task has lower cumulative input and wall time
|
||||
through Code Mode, but also exposes an IR-to-machine-path error. An equal-quality
|
||||
whole-task advantage remains unproven; the representation fix needs a new trial.
|
||||
through Code Mode, but initially exposes an IR-to-machine-path error. The revised
|
||||
trial correctly explains that machine path with lower observed task costs;
|
||||
broader claims still require more than this single seeded task.
|
||||
|
||||
`instructions` reads selected machine byte/decode windows through the same
|
||||
owner. Recovered operations use `origin_address` and blocks `origin_start`;
|
||||
these locations cannot be substituted for machine branch destinations. The
|
||||
value-origin trial demonstrates why this boundary matters. The experiment
|
||||
currently has eleven fact methods, still behind the same four-tool Code Mode
|
||||
currently has twelve fact methods, still behind the same four-tool Code Mode
|
||||
catalog.
|
||||
|
||||
`functions` provides selected name/alias/entry reads. Native snapshots report
|
||||
code and recovered-IR facets separately, preserving readable code when graph
|
||||
extraction is unavailable. Real capacity and cold/warm MCP checks are documented
|
||||
in [native-engine.md](native-engine.md). Whole-model costs and unequal-depth
|
||||
comparisons remain separate in the trial report.
|
||||
|
||||
Completion requires actual model Code Mode use, independently checked task
|
||||
answers, shared atomic/direct/Code Mode semantics, needed relation coverage,
|
||||
failure/cancellation/identity tests, and equivalent completed-task measurements
|
||||
|
||||
@@ -41,6 +41,22 @@ const definition = (description, input, method, example) => ({
|
||||
example,
|
||||
});
|
||||
export const methods = {
|
||||
functions: definition(
|
||||
"Search retained native callable entries by literal name/alias, exact name, or entry addresses. Names can match several entries, including PLT/import stubs and implementations; preserve all matches and select by address. A name does not establish runtime symbol binding.",
|
||||
{
|
||||
query: z.string().default(""),
|
||||
exact: z.boolean().default(false),
|
||||
case_sensitive: z.boolean().default(true),
|
||||
addresses: z.array(hex).max(128).optional(),
|
||||
fields: z
|
||||
.array(z.enum(["name", "aliases", "size"]))
|
||||
.min(1)
|
||||
.optional(),
|
||||
...page,
|
||||
},
|
||||
"functions",
|
||||
'await rea.functions({query:"format_error",exact:true})',
|
||||
),
|
||||
strings: definition(
|
||||
"Search retained string inventory by literal text. Paging does not establish artifact completeness.",
|
||||
{ query: z.string(), ...page },
|
||||
@@ -54,7 +70,7 @@ export const methods = {
|
||||
'await rea.references({addresses:["0x2000"]})',
|
||||
),
|
||||
snapshot: definition(
|
||||
"Analyze an explicit function entry once; retain native C and SSA, return its handle and coverage. Reuses the same source within this target lease.",
|
||||
"Analyze an explicit function entry once; return a retained capture handle, facets and coverage. Reuses the source within this target lease. A valid code facet remains available when recovered IR exceeds capacity or is malformed; inspect facets before graph reads.",
|
||||
{ entry: hex },
|
||||
"snapshot",
|
||||
'await rea.snapshot({entry:"0x1000"})',
|
||||
@@ -163,12 +179,13 @@ export const methods = {
|
||||
max_operations: z.number().int().min(1).max(256).default(48),
|
||||
stop_at: z.array(z.string()).max(128).default([]),
|
||||
stop_kinds: z.array(kinds).default(["phi", "load"]),
|
||||
fields,
|
||||
},
|
||||
"slice",
|
||||
"await rea.slice({snapshot:s,seeds:branchIds,max_operations:48})",
|
||||
),
|
||||
code: definition(
|
||||
"Read native reconstructed C retained with this exact SSA source; offset is zero-based. A large limit can read the complete function inside Code Mode: filter locally before returning text. The bridge byte budget, not an arbitrary line count, bounds the result. No additional decompilation.",
|
||||
"Read reconstructed code from the same retained function capture, even when its graph facet is unavailable. offset is zero-based. A large limit reads the complete code inside Code Mode for local filtering. The bridge byte budget bounds the result. No additional decompilation.",
|
||||
{
|
||||
snapshot,
|
||||
offset: page.offset,
|
||||
@@ -191,6 +208,8 @@ export const methods = {
|
||||
|
||||
// Compact return contracts let code compose before first observing a response.
|
||||
const returns = {
|
||||
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:
|
||||
@@ -198,7 +217,7 @@ const returns = {
|
||||
references:
|
||||
"data:{results:[{ok,address,source:{evidence_id},items:[{from,to,kind,fromFunction:{address,name}|null,toFunction:{address,name}|null,instruction}],total,next_offset,coverage}|{ok:false,address,error}]}",
|
||||
snapshot:
|
||||
"data:{snapshot:string,entry,source:{evidence_id,sha256},coverage:{operations,blocks,control_flow,unrepresented_outgoing_blocks,issues}}",
|
||||
"data:{snapshot:string,entry,source:{evidence_id,sha256},facets:{code:{availability},recovered_ir:{availability,error?}},coverage}. An unavailable graph has unknown/null counts, never a fabricated empty graph; code remains readable through the same handle.",
|
||||
operations:
|
||||
"data:{items:[{id,origin_address,block,source_line,kind,statement,definitions,constants,inputs?,output?,opcode?}],total,next_offset,evidence_id,coverage}. Native inputs:[{input_index,value_id,phi_predecessor,phi_predecessor_edge}], output:value_id|null; definitions:[{definition,use,input_index,value_id,phi_predecessor,phi_predecessor_edge}]. Diagnostic definitions use printed occurrence instead. fields projects columns; identity remains.",
|
||||
relations:
|
||||
@@ -209,7 +228,7 @@ const returns = {
|
||||
"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,source_issues,runtime_reachability,memory_alias_dependencies},evidence_id}",
|
||||
code: "data:{evidence_id,start_line,text,total_lines,next_offset,coverage}; text is reconstructed C. Use a large limit to inspect/filter the full text within code without returning it all.",
|
||||
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.",
|
||||
};
|
||||
@@ -295,6 +314,12 @@ export class AtomicSession {
|
||||
const contract = methods[name];
|
||||
const parsed = contract.input.parse(input);
|
||||
const data = await this.#owner[contract.method](parsed, signal);
|
||||
if (signal.aborted)
|
||||
throw new FactFailure(
|
||||
"cancelled",
|
||||
"Request was cancelled after observations were collected",
|
||||
{ partial_observation: data },
|
||||
);
|
||||
return { ok: true, context: this.context, data };
|
||||
} catch (error) {
|
||||
return {
|
||||
|
||||
@@ -41,6 +41,35 @@ test("named contracts reject extra keys and stale leases before provider admissi
|
||||
assert.deepEqual(provider.calls, ["close"]);
|
||||
await session.close();
|
||||
});
|
||||
|
||||
test("cancellation after a completed read retains observations under a cancelled outcome", async () => {
|
||||
const controller = new AbortController();
|
||||
const session = new AtomicSession(async () => ({
|
||||
context: { lease: "late-cancel" },
|
||||
close: async () => {},
|
||||
strings: async () => {
|
||||
controller.abort();
|
||||
return {
|
||||
source: { evidence_id: "ev_observed" },
|
||||
items: [{ value: "retained" }],
|
||||
};
|
||||
},
|
||||
}));
|
||||
await session.open("target");
|
||||
const result = await session.call(
|
||||
"strings",
|
||||
{ query: "x" },
|
||||
controller.signal,
|
||||
);
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.error.code, "cancelled");
|
||||
assert.equal(
|
||||
result.error.details.partial_observation.items[0].value,
|
||||
"retained",
|
||||
);
|
||||
assert.equal(result.context.lease, "late-cancel");
|
||||
await session.close();
|
||||
});
|
||||
test("close drains admitted reads and a failed cleanup retains ownership for retry", async () => {
|
||||
let release;
|
||||
let reads = 0;
|
||||
|
||||
@@ -102,6 +102,20 @@ export const executeJavaScript = async (
|
||||
};
|
||||
};
|
||||
|
||||
const retainEvidence = (response, evidence) => {
|
||||
const observation =
|
||||
response.data ?? response.error?.details?.partial_observation;
|
||||
for (const row of [observation, ...(observation?.results ?? [])]) {
|
||||
const id =
|
||||
row?.source?.evidence_id ??
|
||||
row?.evidence_id ??
|
||||
row?.error?.details?.evidence_id;
|
||||
if (id) evidence.add(id);
|
||||
}
|
||||
if (response.error?.details?.evidence_id)
|
||||
evidence.add(response.error.details.evidence_id);
|
||||
};
|
||||
|
||||
const installBridge = (
|
||||
vm,
|
||||
session,
|
||||
@@ -128,15 +142,7 @@ const installBridge = (
|
||||
combined,
|
||||
lease,
|
||||
);
|
||||
for (const row of [response.data, ...(response.data?.results ?? [])]) {
|
||||
const id =
|
||||
row?.source?.evidence_id ??
|
||||
row?.evidence_id ??
|
||||
row?.error?.details?.evidence_id;
|
||||
if (id) state.evidence.add(id);
|
||||
}
|
||||
if (response.error?.details?.evidence_id)
|
||||
state.evidence.add(response.error.details.evidence_id);
|
||||
retainEvidence(response, state.evidence);
|
||||
calls[index].status = response.ok ? "ok" : "error";
|
||||
if (!response.ok) calls[index].error = response.error;
|
||||
calls[index].elapsed_ms = performance.now() - before;
|
||||
|
||||
@@ -48,6 +48,26 @@ test("guest CPU budget is independent from the native-analysis wall deadline", a
|
||||
assert.ok(performance.now() - before < 2000);
|
||||
});
|
||||
|
||||
test("cancelled reads keep partial-observation Evidence in the execution receipt", async () => {
|
||||
const result = await executeJavaScript(
|
||||
{
|
||||
context: { lease: "partial" },
|
||||
call: async () => ({
|
||||
ok: false,
|
||||
error: {
|
||||
code: "cancelled",
|
||||
details: {
|
||||
partial_observation: { source: { evidence_id: "ev_partial" } },
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
"return await rea.strings({query:'x'})",
|
||||
);
|
||||
assert.deepEqual(result.evidence_ids, ["ev_partial"]);
|
||||
assert.equal(result.value.error.code, "cancelled");
|
||||
});
|
||||
|
||||
test("failed compositions keep completed facts and their provenance after draining", async () => {
|
||||
const owner = {
|
||||
context: { lease: "retained" },
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Shared read projections and partial batch semantics; no provider protocols.
|
||||
export const sourceSummary = ({ evidence_id, sha256, bytes, elapsed_ms }) => ({
|
||||
evidence_id,
|
||||
sha256,
|
||||
bytes,
|
||||
elapsed_ms,
|
||||
});
|
||||
|
||||
export const projectFact = (row, fields, identity) =>
|
||||
fields
|
||||
? Object.fromEntries(
|
||||
[...new Set([...identity, ...fields])]
|
||||
.filter((key) => Object.hasOwn(row, key))
|
||||
.map((key) => [key, row[key]]),
|
||||
)
|
||||
: row;
|
||||
|
||||
export const pageFacts = (rows, input, identity = []) => ({
|
||||
items: rows
|
||||
.slice(input.offset, input.offset + input.limit)
|
||||
.map((row) => projectFact(row, input.fields, identity)),
|
||||
total: rows.length,
|
||||
next_offset:
|
||||
input.offset + input.limit < rows.length
|
||||
? input.offset + input.limit
|
||||
: null,
|
||||
});
|
||||
|
||||
export async function readFactBatch(requests, capture, read) {
|
||||
const results = [];
|
||||
for (const request of requests) {
|
||||
let source;
|
||||
try {
|
||||
const captured = await capture(request);
|
||||
source = captured.source;
|
||||
const facts = await read(captured, request);
|
||||
results.push({
|
||||
...facts,
|
||||
ok: true,
|
||||
address: request.address,
|
||||
source: sourceSummary(source),
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
ok: false,
|
||||
address: request.address,
|
||||
error: {
|
||||
code: error.code ?? "provider_failure",
|
||||
message: error.message,
|
||||
details: {
|
||||
...error.details,
|
||||
...(source ? { evidence_id: source.evidence_id } : {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return { results };
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { parseKunaInventory } from "../../dist/kuna/KunaProjectFiles.js";
|
||||
import { requireResult } from "./fact-error.mjs";
|
||||
import { pageFacts } from "./fact-reads.mjs";
|
||||
|
||||
export const parseFunctionFacts = (text) =>
|
||||
Object.freeze(
|
||||
requireResult(parseKunaInventory(text), "invalid_provider_output").map(
|
||||
(entry) =>
|
||||
Object.freeze({ ...entry, aliases: Object.freeze([...entry.aliases]) }),
|
||||
),
|
||||
);
|
||||
|
||||
export function selectFunctionFacts(functions, input) {
|
||||
const addresses = input.addresses && new Set(input.addresses);
|
||||
const normalize = (name) =>
|
||||
input.case_sensitive ? name : name.toLowerCase();
|
||||
const query = normalize(input.query);
|
||||
const matches = functions.filter(
|
||||
(entry) =>
|
||||
(!addresses || addresses.has(entry.address)) &&
|
||||
[entry.name, ...entry.aliases].some((name) =>
|
||||
input.exact
|
||||
? normalize(name) === query
|
||||
: normalize(name).includes(query),
|
||||
),
|
||||
);
|
||||
return {
|
||||
...pageFacts(matches, input, ["address"]),
|
||||
coverage: {
|
||||
scope: "native callable-entry inventory, including import stubs",
|
||||
discovery: "unknown",
|
||||
extents: "upper bounds clipped at the next entry; zero means unmeasured",
|
||||
symbol_binding_at_runtime: "unknown",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { parseFunctionFacts, selectFunctionFacts } from "./function-facts.mjs";
|
||||
import { readFactBatch } from "./fact-reads.mjs";
|
||||
import { FactFailure } from "./fact-error.mjs";
|
||||
|
||||
test("function names retain multiple entries and aliases instead of choosing a PLT or implementation", () => {
|
||||
const facts = parseFunctionFacts(
|
||||
JSON.stringify({
|
||||
functions: [
|
||||
{
|
||||
name: "format_error",
|
||||
address_hex: "0x1000",
|
||||
aliases: ["format_error@plt"],
|
||||
size: 16,
|
||||
},
|
||||
{
|
||||
name: "format_error",
|
||||
address_hex: "0x2000",
|
||||
aliases: ["FormatError"],
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const query = {
|
||||
query: "format_error",
|
||||
exact: true,
|
||||
case_sensitive: true,
|
||||
offset: 0,
|
||||
limit: 20,
|
||||
};
|
||||
assert.deepEqual(
|
||||
selectFunctionFacts(facts, query).items.map((item) => item.address),
|
||||
["0x1000", "0x2000"],
|
||||
);
|
||||
assert.equal(
|
||||
selectFunctionFacts(facts, { ...query, query: "format_error@plt" }).items[0]
|
||||
.address,
|
||||
"0x1000",
|
||||
);
|
||||
assert.equal(
|
||||
selectFunctionFacts(facts, {
|
||||
...query,
|
||||
query: "FORMAT_ERROR",
|
||||
case_sensitive: false,
|
||||
}).total,
|
||||
2,
|
||||
);
|
||||
assert.deepEqual(
|
||||
selectFunctionFacts(facts, {
|
||||
...query,
|
||||
addresses: ["0x2000"],
|
||||
fields: ["size"],
|
||||
}).items,
|
||||
[{ address: "0x2000", size: 0 }],
|
||||
);
|
||||
assert.throws(() => {
|
||||
facts[0].aliases.push("foreign");
|
||||
});
|
||||
});
|
||||
|
||||
test("batch transport and decoder failures preserve successful items and captured source identity", async () => {
|
||||
const attempts = [];
|
||||
const result = await readFactBatch(
|
||||
[{ address: "0x1" }, { address: "0x2" }, { address: "0x3" }],
|
||||
async (request) => {
|
||||
attempts.push(request.address);
|
||||
if (request.address === "0x2")
|
||||
throw new FactFailure("capacity", "transport bound");
|
||||
return {
|
||||
source: {
|
||||
evidence_id: `ev_${request.address}`,
|
||||
sha256: "digest",
|
||||
bytes: 3,
|
||||
elapsed_ms: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
async (_, request) => {
|
||||
if (request.address === "0x3")
|
||||
throw new FactFailure(
|
||||
"invalid_provider_output",
|
||||
"decoder rejected source",
|
||||
);
|
||||
return { items: [{ value: 1 }] };
|
||||
},
|
||||
);
|
||||
assert.deepEqual(attempts, ["0x1", "0x2", "0x3"]);
|
||||
assert.equal(result.results[0].items[0].value, 1);
|
||||
assert.equal(result.results[1].error.code, "capacity");
|
||||
assert.equal(result.results[2].error.details.evidence_id, "ev_0x3");
|
||||
});
|
||||
@@ -73,9 +73,8 @@ Targeted JS checks cover malformed/foreign identities, dangling relations, phi
|
||||
order, exact hex offsets, immutable facts, loop/exit coverage, resource failure,
|
||||
and all 64 four-node DAGs against an independent path-enumeration oracle.
|
||||
Upstream `check-spec` passes. The full upstream corpus, stages and workspace gates
|
||||
have not run; this patch is not declared upstream-ready. SQLite, more complex
|
||||
model tasks, paired warm runs and the revised actual model comparison remain
|
||||
required work.
|
||||
have not run; this patch is not declared upstream-ready. The model comparisons
|
||||
and subsequent boundary findings below do not establish wider provider parity.
|
||||
|
||||
The value-origin model comparison exposed a representation boundary: optimized
|
||||
recovered blocks can absorb a machine predecessor, so an IR edge is not an actual
|
||||
@@ -97,5 +96,32 @@ Windows are capped at 4,096 decoded rows and 16 windows per call, sharing the
|
||||
returns 4,096 rows in an 842,106-byte source, taking 1.227 family wall seconds,
|
||||
1.042 CPU seconds and 112.0 MiB kernel memory peak. Both real stock and patched
|
||||
MCP tests preserve good windows alongside an unmapped-address failure and report
|
||||
the actual `JA 0xe26d` / `JMP 0xd9c9` bytes. Targeted tests now total 33; the
|
||||
representation fix still requires a fresh real-model task trial.
|
||||
the actual `JA 0xe26d` / `JMP 0xd9c9` bytes. The representation revision had 33
|
||||
targeted tests; its subsequent real-model trial is recorded below.
|
||||
|
||||
That fresh trial now verifies the missing machine path correctly; see
|
||||
[actual task costs](trials.md). The SQLite task then exposes a separate missing
|
||||
primitive: name/alias lookup across distinct callable entries. `functions` now
|
||||
reuses the production inventory parser, preserving every matching entry, import
|
||||
stub ambiguity, original aliases, clipped extents and unknown runtime binding.
|
||||
The inventory is read/validated once per lease, and query/field/page selection
|
||||
returns only the requested view. Batch xref and instruction reads share one
|
||||
partial-outcome owner, retaining good observations and source IDs on failures.
|
||||
|
||||
A real `sqlite3VdbeExec` capture at `0xea6d0` also exceeds the graph export's
|
||||
operation capacity while producing 126,519 characters of valid C. The old adapter
|
||||
incorrectly rejects the entire capture. The revised owner validates the function
|
||||
envelope and input identity independently, then models graph availability as a
|
||||
facet result. Code and its language remain available under the same handle;
|
||||
graph counts stay unknown and graph reads return their reason and Evidence ID.
|
||||
The real MCP regression reads 3,654 code lines after the graph failure and verifies
|
||||
that `sqlite3Fts3ErrMsg` has distinct entries `0x268f0` and `0x5efc0`. Native graph
|
||||
capacity remains a real limitation, not a resolved scalability claim.
|
||||
|
||||
`verify-cache.mjs` measures a real cold/warm pair within one open lease. Cold zlib
|
||||
snapshot reading takes 6,934.4 ms / 6.928 family CPU seconds; warm reading takes
|
||||
67.7 ms / 0.068 CPU seconds and preserves the exact snapshot/Evidence ID. The
|
||||
cumulative kernel memory peak is 275.1 MiB for both phases; the warm number is an
|
||||
upper bound on its isolated phase peak. This is an API-only measurement, with no
|
||||
model token result or cross-session cache claim. Targeted tests now total 38,
|
||||
including late cancellation retaining partial facts and their provenance.
|
||||
@@ -1,14 +1,15 @@
|
||||
// Native JSON decoding and resource admission; row-wise validation bounds errors.
|
||||
import { z } from "zod";
|
||||
import { FactFailure } from "./fact-error.mjs";
|
||||
import { ok, err } from "../../dist/domain/result.js";
|
||||
const requireFact = (condition, reason) => {
|
||||
if (!condition) throw new FactFailure("invalid_provider_output", reason);
|
||||
};
|
||||
|
||||
const hex = z.string().regex(/^0x[0-9a-f]+$/u);
|
||||
const ordinal = z.number().int().nonnegative();
|
||||
const valueId = z.string().regex(/^v[0-9a-f]+$/u);
|
||||
const operationId = z.string().regex(/^0x[0-9a-f]+:[0-9a-f]+$/u);
|
||||
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u);
|
||||
const ordinal = z.number().int().nonnegative().max(0x7fffffff);
|
||||
const valueId = z.string().regex(/^v[0-9a-f]{1,16}$/u);
|
||||
const operationId = z.string().regex(/^0x[0-9a-f]{1,16}:[0-9a-f]{1,8}$/u);
|
||||
const edgeId = z.string().regex(/^\d+:\d+$/u);
|
||||
const edge = z.strictObject({
|
||||
id: edgeId,
|
||||
@@ -39,7 +40,7 @@ const value = z.strictObject({
|
||||
id: valueId,
|
||||
space: z.string().nullable(),
|
||||
offset: hex,
|
||||
size_bytes: z.number().int().positive(),
|
||||
size_bytes: z.number().int().positive().max(0x7fffffff),
|
||||
is_constant: z.boolean(),
|
||||
is_input: z.boolean(),
|
||||
is_annotation: z.boolean(),
|
||||
@@ -71,13 +72,18 @@ const factsSchema = z.strictObject({
|
||||
}),
|
||||
});
|
||||
const documentSchema = z.object({
|
||||
binary: z.string(),
|
||||
count: z.literal(1),
|
||||
language: z.string(),
|
||||
error: z.null(),
|
||||
functions: z
|
||||
.array(
|
||||
z.object({
|
||||
address_hex: hex,
|
||||
code: z.string(),
|
||||
error: z.null(),
|
||||
atomic_facts: z.unknown(),
|
||||
atomic_facts: z.unknown().optional(),
|
||||
atomic_facts_error: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.length(1),
|
||||
@@ -115,12 +121,7 @@ const parseFacts = rowParser(factsSchema, {
|
||||
values: (row) => value.parse(row),
|
||||
});
|
||||
|
||||
function preflight(raw) {
|
||||
requireFact(
|
||||
Array.isArray(raw?.functions) && raw.functions.length === 1,
|
||||
"Native source must describe exactly one function",
|
||||
);
|
||||
const facts = raw.functions[0]?.atomic_facts;
|
||||
function preflight(facts) {
|
||||
requireFact(
|
||||
facts && typeof facts === "object",
|
||||
"Native atomic facts facet is unavailable; inspect the retained source for its reason and C",
|
||||
@@ -159,36 +160,76 @@ function preflight(raw) {
|
||||
"Native relationships exceed resource capacity",
|
||||
);
|
||||
}
|
||||
export function parseNativeFunction(text, entry) {
|
||||
const formatFailure = (error) => {
|
||||
if (error instanceof FactFailure) return error;
|
||||
const details =
|
||||
error instanceof z.ZodError
|
||||
? {
|
||||
issues: error.issues.slice(0, 8).map(({ path, code, message }) => ({
|
||||
path,
|
||||
code,
|
||||
message: message.slice(0, 160),
|
||||
})),
|
||||
}
|
||||
: { reason: String(error.message).slice(0, 256) };
|
||||
return new FactFailure(
|
||||
"invalid_provider_output",
|
||||
"Native IR source does not match the declared format",
|
||||
details,
|
||||
);
|
||||
};
|
||||
|
||||
const readGraph = (fn) => {
|
||||
try {
|
||||
if (fn.atomic_facts_error !== undefined) {
|
||||
requireFact(
|
||||
fn.atomic_facts === undefined,
|
||||
"Native graph is both present and reported unavailable",
|
||||
);
|
||||
return err(
|
||||
new FactFailure(
|
||||
"capability_unavailable",
|
||||
"Native recovered IR facet is unavailable",
|
||||
{
|
||||
native_error: fn.atomic_facts_error.slice(0, 1024),
|
||||
native_error_truncated: fn.atomic_facts_error.length > 1024,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
preflight(fn.atomic_facts);
|
||||
return ok(parseFacts(fn.atomic_facts));
|
||||
} catch (error) {
|
||||
return err(formatFailure(error));
|
||||
}
|
||||
};
|
||||
|
||||
export function parseNativeFunction(text, entry, expectedInput) {
|
||||
try {
|
||||
requireFact(
|
||||
Buffer.byteLength(text) <= 8 * 1024 * 1024,
|
||||
"Native JSON source exceeds transport budget",
|
||||
);
|
||||
const raw = JSON.parse(text);
|
||||
preflight(raw);
|
||||
const fn = documentSchema.parse(raw).functions[0];
|
||||
requireFact(
|
||||
fn.address_hex === entry,
|
||||
"Native function entry differs from requested subject",
|
||||
Array.isArray(raw?.functions) && raw.functions.length === 1,
|
||||
"Native source must describe exactly one function",
|
||||
);
|
||||
return { code: fn.code, facts: parseFacts(fn.atomic_facts) };
|
||||
const doc = documentSchema.parse(raw),
|
||||
fn = doc.functions[0];
|
||||
if (expectedInput !== undefined && doc.binary !== expectedInput)
|
||||
throw new FactFailure(
|
||||
"subject_mismatch",
|
||||
"Native source names a different command input",
|
||||
{ expected: expectedInput, observed: doc.binary },
|
||||
);
|
||||
if (fn.address_hex !== entry)
|
||||
throw new FactFailure(
|
||||
"subject_mismatch",
|
||||
"Native function entry differs from requested subject",
|
||||
);
|
||||
return { code: fn.code, language: doc.language, facts: readGraph(fn) };
|
||||
} catch (error) {
|
||||
if (error instanceof FactFailure) throw error;
|
||||
const details =
|
||||
error instanceof z.ZodError
|
||||
? {
|
||||
issues: error.issues.slice(0, 8).map(({ path, code, message }) => ({
|
||||
path,
|
||||
code,
|
||||
message: message.slice(0, 160),
|
||||
})),
|
||||
}
|
||||
: { reason: String(error.message).slice(0, 256) };
|
||||
throw new FactFailure(
|
||||
"invalid_provider_output",
|
||||
"Native IR source does not match the declared format",
|
||||
details,
|
||||
);
|
||||
throw formatFailure(error);
|
||||
}
|
||||
}
|
||||
@@ -340,8 +340,70 @@ function sliceValues(model, values, input) {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseNativeIR(text, entry) {
|
||||
const { code, facts } = parseNativeFunction(text, entry);
|
||||
freeze(facts);
|
||||
return createViews(facts, code, indexNative(facts));
|
||||
const projectedFailure = (error) => ({
|
||||
code: error.code ?? "invalid_provider_output",
|
||||
message: error.message,
|
||||
details: error.details ?? {},
|
||||
});
|
||||
|
||||
export function parseNativeIR(text, entry, expectedInput) {
|
||||
const capture = parseNativeFunction(text, entry, expectedInput);
|
||||
let graph = capture.facts;
|
||||
if (graph.ok) {
|
||||
try {
|
||||
freeze(graph.value);
|
||||
const model = createViews(
|
||||
graph.value,
|
||||
capture.code,
|
||||
indexNative(graph.value),
|
||||
);
|
||||
return Object.freeze({
|
||||
...model,
|
||||
language: capture.language,
|
||||
facets: freeze({
|
||||
code: { availability: "available" },
|
||||
recovered_ir: { availability: "available" },
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
graph = { ok: false, error };
|
||||
}
|
||||
}
|
||||
const error = projectedFailure(graph.error);
|
||||
const unavailable = () => {
|
||||
throw new FactFailure(error.code, error.message, {
|
||||
...error.details,
|
||||
facet: "recovered_ir",
|
||||
});
|
||||
};
|
||||
return Object.freeze({
|
||||
code: capture.code,
|
||||
language: capture.language,
|
||||
facets: freeze({
|
||||
code: { availability: "available" },
|
||||
recovered_ir: { availability: "unavailable", error },
|
||||
}),
|
||||
coverage: freeze({
|
||||
scope: "native reconstructed code; recovered IR facet unavailable",
|
||||
operations: null,
|
||||
blocks: null,
|
||||
control_flow: "unknown",
|
||||
machine_semantic_equivalence: "unknown",
|
||||
issues: [error.message],
|
||||
}),
|
||||
...Object.fromEntries(
|
||||
[
|
||||
"operation",
|
||||
"block",
|
||||
"findConstant",
|
||||
"uses",
|
||||
"incoming",
|
||||
"select",
|
||||
"values",
|
||||
"blocks",
|
||||
"edges",
|
||||
"slice",
|
||||
].map((name) => [name, unavailable]),
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -43,6 +43,10 @@ const op = (block, opcode, inputs, output = null) => ({
|
||||
|
||||
function diamond() {
|
||||
return {
|
||||
binary: "/private/input",
|
||||
count: 1,
|
||||
language: "c",
|
||||
error: null,
|
||||
functions: [
|
||||
{
|
||||
address_hex: "0x1000",
|
||||
@@ -180,7 +184,10 @@ test("malformed identities, dangling relationships, foreign entry and phi reorde
|
||||
]) {
|
||||
const doc = diamond();
|
||||
mutate(doc.functions[0].atomic_facts);
|
||||
assert.throws(() => parseNativeIR(JSON.stringify(doc), "0x1000"));
|
||||
const model = parseNativeIR(JSON.stringify(doc), "0x1000");
|
||||
assert.equal(model.facets.recovered_ir.availability, "unavailable");
|
||||
assert.equal(model.code, "reconstructed C");
|
||||
assert.throws(() => model.select({}));
|
||||
}
|
||||
assert.throws(
|
||||
() => parseNativeIR(JSON.stringify(diamond()), "0x2000"),
|
||||
@@ -203,6 +210,28 @@ test("typed slices retain symbolic input frontiers and precise definition bindin
|
||||
assert.equal(result.coverage.runtime_reachability, "unknown");
|
||||
});
|
||||
|
||||
test("native facet failure preserves C without fabricating an empty graph; source identity still gates both", () => {
|
||||
const doc = diamond();
|
||||
delete doc.functions[0].atomic_facts;
|
||||
doc.functions[0].atomic_facts_error = "atomic operation capacity exceeded";
|
||||
const model = parseNativeIR(JSON.stringify(doc), "0x1000", "/private/input");
|
||||
assert.equal(model.code, "reconstructed C");
|
||||
assert.equal(model.language, "c");
|
||||
assert.equal(model.coverage.operations, null);
|
||||
assert.equal(model.facets.code.availability, "available");
|
||||
assert.equal(model.facets.recovered_ir.availability, "unavailable");
|
||||
assert.throws(
|
||||
() => model.select({}),
|
||||
(error) =>
|
||||
error.code === "capability_unavailable" &&
|
||||
error.details.native_error === doc.functions[0].atomic_facts_error,
|
||||
);
|
||||
assert.throws(
|
||||
() => parseNativeIR(JSON.stringify(doc), "0x1000", "/other/input"),
|
||||
(error) => error.code === "subject_mismatch",
|
||||
);
|
||||
});
|
||||
|
||||
test("oversized or malformed native arrays fail before diagnostic expansion", () => {
|
||||
const capacity = diamond();
|
||||
capacity.functions[0].atomic_facts.operations = Array.from(
|
||||
@@ -210,7 +239,7 @@ test("oversized or malformed native arrays fail before diagnostic expansion", ()
|
||||
() => ({}),
|
||||
);
|
||||
assert.throws(
|
||||
() => parseNativeIR(JSON.stringify(capacity), "0x1000"),
|
||||
() => parseNativeIR(JSON.stringify(capacity), "0x1000").select({}),
|
||||
/capacity/u,
|
||||
);
|
||||
const malformed = diamond();
|
||||
@@ -219,7 +248,7 @@ test("oversized or malformed native arrays fail before diagnostic expansion", ()
|
||||
() => ({}),
|
||||
);
|
||||
assert.throws(
|
||||
() => parseNativeIR(JSON.stringify(malformed), "0x1000"),
|
||||
() => parseNativeIR(JSON.stringify(malformed), "0x1000").select({}),
|
||||
(error) =>
|
||||
error.code === "invalid_provider_output" &&
|
||||
error.details.issues.length <= 8 &&
|
||||
|
||||
@@ -11,19 +11,19 @@ import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { parseNativeIR } from "./native-ir.mjs";
|
||||
import { FactFailure, requireResult } from "./fact-error.mjs";
|
||||
import { parseInstructions } from "./instruction-facts.mjs";
|
||||
import {
|
||||
sourceSummary,
|
||||
pageFacts,
|
||||
projectFact,
|
||||
readFactBatch,
|
||||
} from "./fact-reads.mjs";
|
||||
import { parseFunctionFacts, selectFunctionFacts } from "./function-facts.mjs";
|
||||
import {
|
||||
parseKunaSsaDump,
|
||||
backwardSlice,
|
||||
} from "../atomic-re-tools/kuna-ssa.mjs";
|
||||
|
||||
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
||||
const sourceSummary = ({ evidence_id, sha256, bytes, elapsed_ms }) => ({
|
||||
evidence_id,
|
||||
sha256,
|
||||
bytes,
|
||||
elapsed_ms,
|
||||
});
|
||||
|
||||
export class KunaFactOwner {
|
||||
#lifetime = new AbortController();
|
||||
#sources = new Map();
|
||||
@@ -36,6 +36,7 @@ export class KunaFactOwner {
|
||||
#installation;
|
||||
#retainedBytes = 0;
|
||||
#strings;
|
||||
#functions;
|
||||
#captureDirectory;
|
||||
#nativeIR = false;
|
||||
context;
|
||||
@@ -136,16 +137,18 @@ export class KunaFactOwner {
|
||||
);
|
||||
const file = join(this.#root.path, id);
|
||||
await writeFile(file, snapshot.stdout.text, { flag: "wx" });
|
||||
const source = {
|
||||
const source = Object.freeze({
|
||||
evidence_id: id,
|
||||
sha256: sha256(snapshot.stdout.text),
|
||||
bytes: Buffer.byteLength(snapshot.stdout.text),
|
||||
arguments: actualArguments,
|
||||
display_arguments: actualArguments.map((s) =>
|
||||
s === snapshot.input ? this.#target.path : s,
|
||||
arguments: Object.freeze(actualArguments),
|
||||
display_arguments: Object.freeze(
|
||||
actualArguments.map((s) =>
|
||||
s === snapshot.input ? this.#target.path : s,
|
||||
),
|
||||
),
|
||||
elapsed_ms: performance.now() - start,
|
||||
};
|
||||
});
|
||||
this.#sources.set(id, { source, file });
|
||||
this.#retainedBytes += bytes;
|
||||
if (this.#captureDirectory) {
|
||||
@@ -218,15 +221,30 @@ export class KunaFactOwner {
|
||||
},
|
||||
};
|
||||
}
|
||||
async functions(input, signal) {
|
||||
const capture = await this.#native(
|
||||
"functions",
|
||||
(path) => ["functions", path, "--json"],
|
||||
signal,
|
||||
);
|
||||
this.#functions ??= parseFunctionFacts(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
);
|
||||
return {
|
||||
source: sourceSummary(capture.source),
|
||||
...selectFunctionFacts(this.#functions, input),
|
||||
};
|
||||
}
|
||||
async references(input, signal) {
|
||||
const results = [];
|
||||
for (const address of input.addresses) {
|
||||
try {
|
||||
const capture = await this.#native(
|
||||
return readFactBatch(
|
||||
input.addresses.map((address) => ({ address })),
|
||||
({ address }) =>
|
||||
this.#native(
|
||||
`xref:${address}`,
|
||||
(path) => ["xrefs", path, "--to", address, "--json"],
|
||||
signal,
|
||||
);
|
||||
),
|
||||
async (capture, { address }) => {
|
||||
const parsed = requireResult(
|
||||
parseKunaIncomingReferenceFacts(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
@@ -235,33 +253,15 @@ export class KunaFactOwner {
|
||||
),
|
||||
"invalid_provider_output",
|
||||
);
|
||||
results.push({
|
||||
ok: true,
|
||||
address,
|
||||
source: sourceSummary(capture.source),
|
||||
items: parsed.facts.slice(input.offset, input.offset + input.limit),
|
||||
total: parsed.facts.length,
|
||||
next_offset:
|
||||
input.offset + input.limit < parsed.facts.length
|
||||
? input.offset + input.limit
|
||||
: null,
|
||||
return {
|
||||
...pageFacts(parsed.facts, input),
|
||||
coverage: {
|
||||
scope: "exact target address; not an object extent",
|
||||
discovery: "unknown",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
ok: false,
|
||||
address,
|
||||
error: {
|
||||
code: error.code ?? "provider_failure",
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return { results };
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
async snapshot(input, signal) {
|
||||
const capture = await this.#native(
|
||||
@@ -281,10 +281,11 @@ export class KunaFactOwner {
|
||||
if (!this.#snapshots.has(id))
|
||||
this.#snapshots.set(id, {
|
||||
entry: input.entry,
|
||||
input: capture.input,
|
||||
evidence_id: capture.source.evidence_id,
|
||||
});
|
||||
const parsed = await this.#load(id);
|
||||
if (parsed.block(0).origin_start !== input.entry)
|
||||
if (!this.#nativeIR && parsed.block(0).origin_start !== input.entry)
|
||||
throw new FactFailure(
|
||||
"subject_mismatch",
|
||||
"Native SSA entry does not match the requested function entry",
|
||||
@@ -294,14 +295,17 @@ export class KunaFactOwner {
|
||||
source: sourceSummary(capture.source),
|
||||
entry: input.entry,
|
||||
coverage: parsed.coverage,
|
||||
facets: parsed.facets ?? {
|
||||
code: { availability: "available" },
|
||||
recovered_ir: { availability: "available", coverage: "partial" },
|
||||
},
|
||||
};
|
||||
}
|
||||
async instructions(input, signal) {
|
||||
const results = [];
|
||||
for (const request of input.windows) {
|
||||
let capture;
|
||||
try {
|
||||
capture = await this.#native(
|
||||
return readFactBatch(
|
||||
input.windows,
|
||||
(request) =>
|
||||
this.#native(
|
||||
`instructions:${request.address}:${request.count}`,
|
||||
(path) => [
|
||||
"disassemble",
|
||||
@@ -315,35 +319,15 @@ export class KunaFactOwner {
|
||||
"--json",
|
||||
],
|
||||
signal,
|
||||
);
|
||||
const parsed = parseInstructions(
|
||||
),
|
||||
async (capture, request) =>
|
||||
parseInstructions(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
capture.input,
|
||||
request,
|
||||
input.fields,
|
||||
);
|
||||
results.push({
|
||||
ok: true,
|
||||
address: request.address,
|
||||
source: sourceSummary(capture.source),
|
||||
...parsed,
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
ok: false,
|
||||
address: request.address,
|
||||
error: {
|
||||
code: error.code ?? "provider_failure",
|
||||
message: error.message,
|
||||
details: {
|
||||
...error.details,
|
||||
...(capture ? { evidence_id: capture.source.evidence_id } : {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return { results };
|
||||
),
|
||||
);
|
||||
}
|
||||
async #load(id) {
|
||||
const descriptor = this.#snapshots.get(id);
|
||||
@@ -364,7 +348,7 @@ export class KunaFactOwner {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = this.#nativeIR
|
||||
? parseNativeIR(text, descriptor.entry)
|
||||
? parseNativeIR(text, descriptor.entry, descriptor.input)
|
||||
: parseKunaSsaDump(text);
|
||||
} catch (error) {
|
||||
throw new FactFailure(
|
||||
@@ -383,61 +367,77 @@ export class KunaFactOwner {
|
||||
}
|
||||
return this.#loaded.get(id);
|
||||
}
|
||||
async operations(input) {
|
||||
async #query(input, read) {
|
||||
const model = await this.#load(input.snapshot);
|
||||
return {
|
||||
const evidence_id = this.#snapshots.get(input.snapshot).evidence_id;
|
||||
try {
|
||||
return { ...read(model), evidence_id };
|
||||
} catch (error) {
|
||||
throw new FactFailure(error.code ?? "analysis_failed", error.message, {
|
||||
...error.details,
|
||||
evidence_id,
|
||||
source_retained: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
async operations(input) {
|
||||
return this.#query(input, (model) => ({
|
||||
...model.select(input),
|
||||
evidence_id: this.#snapshots.get(input.snapshot).evidence_id,
|
||||
coverage: model.coverage,
|
||||
};
|
||||
}));
|
||||
}
|
||||
async relations(input) {
|
||||
const model = await this.#load(input.snapshot);
|
||||
if (input.kind === "controllers" && !this.#nativeIR)
|
||||
this.#requireNativeIR();
|
||||
return {
|
||||
if (input.kind === "controllers") this.#requireNativeIR(input.snapshot);
|
||||
return this.#query(input, (model) => ({
|
||||
...model.edges(input),
|
||||
evidence_id: this.#snapshots.get(input.snapshot).evidence_id,
|
||||
coverage: model.coverage,
|
||||
};
|
||||
}));
|
||||
}
|
||||
async slice(input) {
|
||||
const model = await this.#load(input.snapshot);
|
||||
return {
|
||||
...(model.slice
|
||||
return this.#query(input, (model) => {
|
||||
const result = model.slice
|
||||
? model.slice(input)
|
||||
: backwardSlice(model, input.seeds, {
|
||||
maxOperations: input.max_operations,
|
||||
stopAt: input.stop_at,
|
||||
stopKinds: input.stop_kinds,
|
||||
})),
|
||||
evidence_id: this.#snapshots.get(input.snapshot).evidence_id,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
operations: result.operations.map((row) =>
|
||||
projectFact(row, input.fields, [
|
||||
"id",
|
||||
"origin_address",
|
||||
"block",
|
||||
"source_line",
|
||||
]),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
#requireNativeIR() {
|
||||
#requireNativeIR(snapshot) {
|
||||
if (!this.#nativeIR)
|
||||
throw new FactFailure(
|
||||
"capability_unavailable",
|
||||
"This relation requires the pinned experimental native IR engine; the diagnostic SSA format does not contain it",
|
||||
this.#snapshots.has(snapshot)
|
||||
? { evidence_id: this.#snapshots.get(snapshot).evidence_id }
|
||||
: {},
|
||||
);
|
||||
}
|
||||
async values(input) {
|
||||
this.#requireNativeIR();
|
||||
const model = await this.#load(input.snapshot);
|
||||
return {
|
||||
this.#requireNativeIR(input.snapshot);
|
||||
return this.#query(input, (model) => ({
|
||||
...model.values(input),
|
||||
evidence_id: this.#snapshots.get(input.snapshot).evidence_id,
|
||||
coverage: model.coverage,
|
||||
};
|
||||
}));
|
||||
}
|
||||
async blocks(input) {
|
||||
this.#requireNativeIR();
|
||||
const model = await this.#load(input.snapshot);
|
||||
return {
|
||||
this.#requireNativeIR(input.snapshot);
|
||||
return this.#query(input, (model) => ({
|
||||
...model.blocks(input),
|
||||
evidence_id: this.#snapshots.get(input.snapshot).evidence_id,
|
||||
coverage: model.coverage,
|
||||
};
|
||||
}));
|
||||
}
|
||||
async code(input) {
|
||||
const descriptor = this.#snapshots.get(input.snapshot);
|
||||
@@ -446,16 +446,18 @@ export class KunaFactOwner {
|
||||
"unknown_snapshot",
|
||||
"Snapshot is not owned by the current lease",
|
||||
);
|
||||
const code = this.#nativeIR
|
||||
? (await this.#load(input.snapshot)).code
|
||||
: (await this.#sourceText(descriptor.evidence_id)).split(
|
||||
"\n0\nBasic Block",
|
||||
1,
|
||||
)[0];
|
||||
const model = this.#nativeIR ? await this.#load(input.snapshot) : null;
|
||||
const code =
|
||||
model?.code ??
|
||||
(await this.#sourceText(descriptor.evidence_id)).split(
|
||||
"\n0\nBasic Block",
|
||||
1,
|
||||
)[0];
|
||||
const lines = code.split("\n");
|
||||
return {
|
||||
evidence_id: descriptor.evidence_id,
|
||||
start_line: input.offset + 1,
|
||||
language: model?.language ?? null,
|
||||
text: lines.slice(input.offset, input.offset + input.limit).join("\n"),
|
||||
total_lines: lines.length,
|
||||
next_offset:
|
||||
@@ -463,21 +465,15 @@ export class KunaFactOwner {
|
||||
? input.offset + input.limit
|
||||
: null,
|
||||
coverage: {
|
||||
authority: "reconstructed C, not original source",
|
||||
authority: "native reconstructed code",
|
||||
scope: "selected display lines",
|
||||
},
|
||||
};
|
||||
}
|
||||
async evidence(input) {
|
||||
const record = this.#sources.get(input.evidence_id);
|
||||
if (!record)
|
||||
throw new FactFailure(
|
||||
"unknown_evidence",
|
||||
"Evidence is not retained by the current lease",
|
||||
);
|
||||
const text = await this.#sourceText(input.evidence_id);
|
||||
return {
|
||||
source: record.source,
|
||||
source: this.#sources.get(input.evidence_id).source,
|
||||
text: text.slice(input.offset, input.offset + input.limit),
|
||||
total_code_units: text.length,
|
||||
next_offset:
|
||||
@@ -494,6 +490,8 @@ export class KunaFactOwner {
|
||||
this.#sources.clear();
|
||||
this.#work.clear();
|
||||
this.#snapshots.clear();
|
||||
this.#strings = undefined;
|
||||
this.#functions = undefined;
|
||||
}
|
||||
cancel() {
|
||||
this.#lifetime.abort();
|
||||
|
||||
@@ -187,3 +187,56 @@ representation ambiguity needs a structural fix: distinguish IR origin fields
|
||||
from physical instruction facts and expose selected instruction windows, then
|
||||
rerun the unchanged question. Private artifacts are `zlib-value-codemode-v1*`
|
||||
and `zlib-value-traditional-v1*`.
|
||||
|
||||
After separating IR origins and adding instruction windows, the unchanged value
|
||||
question was rerun at `4f13bd673`. The Code Mode answer now reads actual machine
|
||||
bytes and correctly distinguishes recovered predecessor 38 from the intervening
|
||||
`0xe26d` / `0xe270` machine path. Both answers recover the same local value
|
||||
equations, byte-order conditions and unknown invariants. These remain individual
|
||||
stochastic samples, not a general speedup guarantee.
|
||||
|
||||
| Measurement | Code Mode value v2 | Traditional value v2 |
|
||||
| --- | ---: | ---: |
|
||||
| MCP attempts / failures | 7 / 0 | 6 / 1 |
|
||||
| Cumulative input tokens | 296,888 | 573,881 |
|
||||
| Included cached input | 252,160 | 512,128 |
|
||||
| Uncached input | 44,728 | 61,753 |
|
||||
| Output / included reasoning | 3,176 / 594 | 2,830 / 547 |
|
||||
| Model wall seconds | 96.662 | 124.754 |
|
||||
| Family wall seconds | 97.654 | 125.410 |
|
||||
| Family CPU seconds | 19.495 | 44.143 |
|
||||
| Kernel family peak MiB | 534.4 | 797.4 |
|
||||
|
||||
The traditional failure is the client policy rejection of `binary_session`.
|
||||
Its cost remains included. Both runtime/target digests stay unchanged, cleanup
|
||||
is verified and OOM kills are zero. This is evidence of a useful seeded
|
||||
value-tracing case for the revised interface, with the selected-tool and
|
||||
single-sample qualifications retained.
|
||||
|
||||
The initial SQLite diagnostic comparison, also at `4f13bd673`, exposes a further
|
||||
capability/comparison gap:
|
||||
|
||||
| Measurement | Code Mode SQLite v1 | Traditional SQLite v1 |
|
||||
| --- | ---: | ---: |
|
||||
| MCP attempts / failures | 14 / 0 | 17 / 8 |
|
||||
| Cumulative input tokens | 601,445 | 934,237 |
|
||||
| Included cached input | 535,296 | 831,744 |
|
||||
| Uncached input | 66,149 | 102,493 |
|
||||
| Output / included reasoning | 4,790 / 1,668 | 3,878 / 1,121 |
|
||||
| Model wall seconds | 143.469 | 231.469 |
|
||||
| Family CPU seconds | 18.996 | 154.132 |
|
||||
| Kernel family peak MiB | 586.6 | 1,200.2 |
|
||||
|
||||
Both answers find the two malformed-message conditions and distinguish the
|
||||
depth-error, other-status and success paths. The traditional answer additionally
|
||||
resolves the named formatter implementation at `0x5efc0`, tracing free,
|
||||
`sqlite3_vmprintf` and pointer storage; Code Mode stops at its PLT stub and
|
||||
correctly leaves delivery/allocation unknown. These are unequal-depth answers,
|
||||
so the cost difference is not an equal-completion win.
|
||||
|
||||
The selected traditional catalog also omitted `list_procedures`, despite errors
|
||||
advertising it as the discovery route. The trace retains interior-entry failures,
|
||||
failed resource discovery, a client approval-policy rejection and ambiguous name
|
||||
lookups. This selection confound must be corrected in the next focused baseline;
|
||||
the failed attempts and their costs are not discarded. Both runs still preserve
|
||||
target/runtime identity, clean up their profiles/payloads and have zero OOM kills.
|
||||
@@ -0,0 +1,109 @@
|
||||
// A real MCP cold/warm read benchmark. No model runs in this verifier.
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import {
|
||||
createVerifierRun,
|
||||
completeVerifierRun,
|
||||
assertVerifierCleanupComplete,
|
||||
} from "../../scripts/lib/verifier-run.mjs";
|
||||
|
||||
const [binary, output, entry = "0xc1e0"] = process.argv.slice(2);
|
||||
const group = (await readFile("/proc/self/cgroup", "utf8"))
|
||||
.trim()
|
||||
.split("\n")
|
||||
.find((row) => row.startsWith("0::"))
|
||||
?.slice(3);
|
||||
assert.ok(group?.includes("rea-efficiency-"), "Run under resource-run.py");
|
||||
const counters = async () => ({
|
||||
cpu: Number(
|
||||
(await readFile(`/sys/fs/cgroup${group}/cpu.stat`, "utf8")).match(
|
||||
/usage_usec (\d+)/u,
|
||||
)[1],
|
||||
),
|
||||
peak: Number(
|
||||
(await readFile(`/sys/fs/cgroup${group}/memory.peak`, "utf8")).trim(),
|
||||
),
|
||||
});
|
||||
const verifier = createVerifierRun();
|
||||
const client = new Client({ name: "rea-cache-verifier", version: "0.1.0" });
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [resolve("experiments/atomic-codemode/server.mjs")],
|
||||
env: { ...process.env },
|
||||
stderr: "pipe",
|
||||
});
|
||||
const phases = [],
|
||||
failures = [];
|
||||
const call = async (name, args = {}) => {
|
||||
const reply = await client.callTool(
|
||||
{ name, arguments: args },
|
||||
{ timeout: 180000 },
|
||||
);
|
||||
const value = JSON.parse(reply.content[0].text);
|
||||
assert.equal(value.ok, true, JSON.stringify(value));
|
||||
return value;
|
||||
};
|
||||
try {
|
||||
await client.connect(transport);
|
||||
await call("open_binary", { path: binary });
|
||||
for (const phase of ["cold_snapshot", "warm_snapshot"]) {
|
||||
const before = await counters(),
|
||||
start = performance.now();
|
||||
const result = await call("execute_javascript", {
|
||||
code: `const r=await rea.snapshot({entry:${JSON.stringify(entry)}});if(!r.ok)throw new Error(JSON.stringify(r));return r.data;`,
|
||||
});
|
||||
const elapsed = performance.now() - start,
|
||||
after = await counters();
|
||||
phases.push({
|
||||
phase,
|
||||
elapsed_ms: elapsed,
|
||||
family_cpu_seconds: (after.cpu - before.cpu) / 1e6,
|
||||
cumulative_family_memory_peak_bytes: after.peak,
|
||||
snapshot: result.value.snapshot,
|
||||
source: result.value.source,
|
||||
facets: result.value.facets,
|
||||
});
|
||||
}
|
||||
assert.equal(phases[0].snapshot, phases[1].snapshot);
|
||||
assert.equal(phases[0].source.evidence_id, phases[1].source.evidence_id);
|
||||
await call("close_binary");
|
||||
} catch (error) {
|
||||
failures.push(error.stack ?? String(error));
|
||||
} finally {
|
||||
try {
|
||||
await client.close();
|
||||
} catch (error) {
|
||||
failures.push(String(error));
|
||||
}
|
||||
try {
|
||||
await transport.close();
|
||||
} catch (error) {
|
||||
failures.push(String(error));
|
||||
}
|
||||
}
|
||||
const lifecycle = await completeVerifierRun(verifier);
|
||||
assertVerifierCleanupComplete(lifecycle.process_lineage);
|
||||
await writeFile(
|
||||
output,
|
||||
JSON.stringify({
|
||||
phases,
|
||||
failures,
|
||||
lifecycle,
|
||||
scope:
|
||||
"Real MCP, no model/token measurement. CPU deltas cover the owned cgroup. Memory peaks are cumulative; the warm value is an upper bound on its isolated phase peak. Both reads share one immutable captured artifact/lease; this is not a cross-session cache benchmark.",
|
||||
}),
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
output,
|
||||
phases: phases.map(({ phase, elapsed_ms, family_cpu_seconds }) => ({
|
||||
phase,
|
||||
elapsed_ms,
|
||||
family_cpu_seconds,
|
||||
})),
|
||||
}),
|
||||
);
|
||||
if (failures.length) process.exitCode = 1;
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
assertVerifierCleanupComplete,
|
||||
} from "../../scripts/lib/verifier-run.mjs";
|
||||
|
||||
const [binary, output] = process.argv.slice(2);
|
||||
const [binary, output, scenario = "zlib"] = process.argv.slice(2);
|
||||
assert.ok(["zlib", "sqlite-partial"].includes(scenario));
|
||||
const verifier = createVerifierRun();
|
||||
const client = new Client({ name: "rea-codemode-verifier", version: "0.1.0" });
|
||||
const transport = new StdioClientTransport({
|
||||
@@ -48,9 +49,47 @@ try {
|
||||
const ajv = new Ajv2020({ strict: false });
|
||||
for (const tool of catalog.tools) ajv.compile(tool.inputSchema);
|
||||
const opened = await call("open_binary", { path: binary });
|
||||
await call("reference", { query: "relations" });
|
||||
const result = await call("execute_javascript", {
|
||||
code: `
|
||||
if (scenario === "sqlite-partial") {
|
||||
assert.equal(opened.context.analysis_model, "experimental-native-ir-v1");
|
||||
const result = await call("execute_javascript", {
|
||||
code: `
|
||||
const read=async(p)=>{const r=await p;if(!r.ok)throw new Error(JSON.stringify(r));return r.data;};
|
||||
const names=await read(rea.functions({query:"sqlite3Fts3ErrMsg",exact:true}));
|
||||
const entries=await read(rea.functions({query:"sqlite3VdbeExec",exact:true}));
|
||||
const entry=entries.items.find(fn=>fn.address==="0xea6d0");if(!entry)throw new Error("fixture entry missing");
|
||||
const snap=await read(rea.snapshot({entry:entry.address}));
|
||||
const code=await read(rea.code({snapshot:snap.snapshot,limit:4}));
|
||||
const graph=await rea.operations({snapshot:snap.snapshot,limit:1});
|
||||
const evidence=await read(rea.evidence({evidence_id:snap.source.evidence_id,limit:512}));
|
||||
return {names,snap,code,graph,evidence_source:evidence.source};`,
|
||||
});
|
||||
assert.deepEqual(result.value.names.items.map((fn) => fn.address).sort(), [
|
||||
"0x268f0",
|
||||
"0x5efc0",
|
||||
]);
|
||||
assert.equal(result.value.snap.facets.code.availability, "available");
|
||||
assert.equal(
|
||||
result.value.snap.facets.recovered_ir.availability,
|
||||
"unavailable",
|
||||
);
|
||||
assert.equal(result.value.snap.coverage.operations, null);
|
||||
assert.ok(
|
||||
result.value.code.text.length > 0 && result.value.code.total_lines > 1000,
|
||||
);
|
||||
assert.equal(result.value.graph.ok, false);
|
||||
assert.equal(result.value.graph.error.code, "capability_unavailable");
|
||||
assert.equal(
|
||||
result.value.graph.error.details.evidence_id,
|
||||
result.value.snap.source.evidence_id,
|
||||
);
|
||||
assert.equal(
|
||||
result.value.evidence_source.sha256,
|
||||
result.value.snap.source.sha256,
|
||||
);
|
||||
} else {
|
||||
await call("reference", { query: "relations" });
|
||||
const result = await call("execute_javascript", {
|
||||
code: `
|
||||
const found=await rea.strings({query:"incorrect header check",limit:1});
|
||||
if(!found.ok) throw new Error(JSON.stringify(found));
|
||||
const literal=found.data.items[0].address;
|
||||
@@ -62,15 +101,16 @@ const seed=await rea.operations({snapshot:s,constant:literal});
|
||||
const uses=await rea.relations({snapshot:s,kind:"uses",seeds:seed.data.items.map(x=>x.id)});
|
||||
const consumers=await rea.operations({snapshot:s,ids:uses.data.items.map(x=>x.use)});
|
||||
const incoming=await rea.relations({snapshot:s,kind:"incoming",seeds:consumers.data.items.map(x=>x.block)});
|
||||
const slice=await rea.slice({snapshot:s,seeds:incoming.data.items.map(x=>x.operation).filter(Boolean)});
|
||||
return {literal,entry,snapshot:s,consumer_blocks:[...new Set(consumers.data.items.map(x=>x.block))],selected:slice.data.operations.length,guards:slice.data.operations.map(x=>x.statement),coverage:slice.data.coverage,evidence_id:slice.data.evidence_id};`,
|
||||
});
|
||||
assert.equal(result.value.selected, 19);
|
||||
assert.equal(result.value.coverage.status, "partial");
|
||||
assert.ok(result.value.guards.some((s) => s.includes("/ #0x1f")));
|
||||
if (opened.context.analysis_model === "experimental-native-ir-v1") {
|
||||
const native = await call("execute_javascript", {
|
||||
code: `const s=${JSON.stringify(result.value.snapshot)};
|
||||
const slice=await rea.slice({snapshot:s,seeds:incoming.data.items.map(x=>x.operation).filter(Boolean),fields:["statement"]});
|
||||
return {literal,entry,snapshot:s,consumer_blocks:[...new Set(consumers.data.items.map(x=>x.block))],selected:slice.data.operations.length,projected:slice.data.operations.every(op=>!("inputs" in op)&&typeof op.origin_address==="string"),guards:slice.data.operations.map(x=>x.statement),coverage:slice.data.coverage,evidence_id:slice.data.evidence_id};`,
|
||||
});
|
||||
assert.equal(result.value.selected, 19);
|
||||
assert.equal(result.value.projected, true);
|
||||
assert.equal(result.value.coverage.status, "partial");
|
||||
assert.ok(result.value.guards.some((s) => s.includes("/ #0x1f")));
|
||||
if (opened.context.analysis_model === "experimental-native-ir-v1") {
|
||||
const native = await call("execute_javascript", {
|
||||
code: `const s=${JSON.stringify(result.value.snapshot)};
|
||||
const read=async(p)=>{const r=await p;if(!r.ok)throw new Error(JSON.stringify(r));return r.data;};
|
||||
const blocks=await read(rea.blocks({snapshot:s,ids:${JSON.stringify(result.value.consumer_blocks)}}));
|
||||
const controls=await read(rea.relations({snapshot:s,kind:"controllers",seeds:blocks.items.map(b=>b.id)}));
|
||||
@@ -78,33 +118,34 @@ const guards=await read(rea.operations({snapshot:s,block_ids:controls.items.map(
|
||||
const phi=await read(rea.operations({snapshot:s,kind:"phi",fields:["inputs","output"],limit:2}));
|
||||
const values=await read(rea.values({snapshot:s,ids:phi.items.flatMap(o=>o.inputs.map(i=>i.value_id)).filter(Boolean)}));
|
||||
return {blocks,controls,guards,phi,values};`,
|
||||
});
|
||||
assert.equal(native.value.controls.total, 2);
|
||||
assert.deepEqual(
|
||||
native.value.guards.items.map((op) => op.origin_address).sort(),
|
||||
["0xd9ec", "0xda32"],
|
||||
);
|
||||
assert.ok(
|
||||
native.value.phi.items.every((op) =>
|
||||
op.inputs.every(
|
||||
(input) => typeof input.phi_predecessor_edge === "string",
|
||||
});
|
||||
assert.equal(native.value.controls.total, 2);
|
||||
assert.deepEqual(
|
||||
native.value.guards.items.map((op) => op.origin_address).sort(),
|
||||
["0xd9ec", "0xda32"],
|
||||
);
|
||||
assert.ok(
|
||||
native.value.phi.items.every((op) =>
|
||||
op.inputs.every(
|
||||
(input) => typeof input.phi_predecessor_edge === "string",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
native.value.blocks.coverage.control_flow,
|
||||
"complete-for-recovered-graph",
|
||||
);
|
||||
assert.ok(native.value.values.items.length > 0);
|
||||
);
|
||||
assert.equal(
|
||||
native.value.blocks.coverage.control_flow,
|
||||
"complete-for-recovered-graph",
|
||||
);
|
||||
assert.ok(native.value.values.items.length > 0);
|
||||
}
|
||||
const machine = await call("execute_javascript", {
|
||||
code: 'const r=await rea.instructions({windows:[{address:"0xd97e",count:1},{address:"0xe26d",count:2},{address:"0xdeadbeef",count:1}]});if(!r.ok)throw new Error(JSON.stringify(r));return r.data;',
|
||||
});
|
||||
assert.equal(machine.value.results[0].items[0].bytes, "0f87e9080000");
|
||||
assert.match(machine.value.results[0].items[0].text, /e26d/u);
|
||||
assert.equal(machine.value.results[1].items[1].address, "0xe270");
|
||||
assert.match(machine.value.results[1].items[1].text, /d9c9/u);
|
||||
assert.equal(machine.value.results[2].ok, false);
|
||||
}
|
||||
const machine = await call("execute_javascript", {
|
||||
code: 'const r=await rea.instructions({windows:[{address:"0xd97e",count:1},{address:"0xe26d",count:2},{address:"0xdeadbeef",count:1}]});if(!r.ok)throw new Error(JSON.stringify(r));return r.data;',
|
||||
});
|
||||
assert.equal(machine.value.results[0].items[0].bytes, "0f87e9080000");
|
||||
assert.match(machine.value.results[0].items[0].text, /e26d/u);
|
||||
assert.equal(machine.value.results[1].items[1].address, "0xe270");
|
||||
assert.match(machine.value.results[1].items[1].text, /d9c9/u);
|
||||
assert.equal(machine.value.results[2].ok, false);
|
||||
await call("close_binary");
|
||||
} catch (error) {
|
||||
failure = error.stack ?? String(error);
|
||||
|
||||
Reference in new issue
Block a user