feat(wasm): add opt-in first-call summary delivery (#2193)

Add explicit first-call WASM summary delivery while preserving omitted/complete MCP requests, CLI output, original Evidence identity and provider inputs. Reuse exact retained export views and schema-based capability discovery.

Nine paired public SDK comparisons and ten actual Codex CLI answers preserve selected facts and source references. Record token reductions alongside mixed latency and unverified final resource-counter conditions. Current-main integration passes seven relevant WASM MCP workflows, a fresh real-WABT SQLite workflow and required static gates. Refs #2016.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Ysopwnableandfactory-droid[bot] authored and GitHub committed 2026-10-12 22:01:47 +08:00
1 parent 7c9c1ebdb7
commit 0b7145361e
12 files changed
+499 -88

No files matched your search

@@ -39,6 +39,10 @@ read a single object.
## Offline WASM
Use `inspect_wasm_artifact` with caller-supplied WABT for selected local bytes.
For counts, validation and WAT identity, select `detail: "summary"` on the first
MCP call. The complete original Evidence stays retained; use
`normalized_result.parent_evidence_id` for exact exports or text pages. Omitted
or complete detail keeps the existing complete result.
For a completed result, use `inspect_analysis_view` with its reported retained
reference for a summary or `view: {kind: "wasm", facet: "exports", offset: 0, limit: 32}`.
Facets also include sections, imports, candidates, glue, WAT, headers, details
+7 -1
View File
@@ -412,7 +412,8 @@ Ordinary responses keep their existing complete result contract.
## Retained application Evidence inputs
`analyze_javascript_application`, `inspect_binary_layout`, and `inspect_artifact` accept `detail`:
`analyze_javascript_application`, `inspect_binary_layout`, `inspect_artifact`, and
`inspect_wasm_artifact` accept `detail`:
`complete` (default) returns the complete analysis Evidence; `summary` records
that complete Evidence in the session and returns the derived
`inspect_analysis_view` summary Evidence instead. Its
@@ -476,6 +477,11 @@ The original producer still returns complete Evidence by default. When its
complete MCP reply exceeds the frame, use the exact reported retained
reference with these views, or export the complete source. Views do not run
WABT again and do not turn decoded WAT into original source.
For counts, validation and WAT identity, select `detail: "summary"` on the first
`inspect_wasm_artifact` call. It retains the original complete source without
attempting oversized delivery, and returns the same summary with an exact
`parent_evidence_id` for later export or facet reads. Delivery detail does not
reach WABT or change the original source Evidence ID.
`trace_application_feature`, `trace_javascript_semantics`,
`compare_application_versions`, `compare_source_to_bundle`, and
+7 -2
View File
@@ -137,8 +137,11 @@ inventory. It must be supported by the selected server's advertised schema.
The default `complete` runs the existing calls unchanged. Compare the same
analyst facts and complete source Evidence before crediting a smaller response.
Projection can reduce delivery bytes without reducing upstream analysis time.
For SQLite WASM, summary delivery reads the completed retained result through
the summary and exports views after preserving the original transport error.
For WASM, the runner validates a summary request against the actual advertised
2020-12 input schema, including SDK-generated references. Supporting servers
return the first-call summary; older servers retain their complete path and use
summary/export views after an observed transport constraint. Original errors
remain in the receipts, and the complete source Evidence ID is a compared fact.
`--javascript-question selected` analyzes only the exact Obsidian sources
`package.json` and `main.js`, keeps inventory identity, and reads each observed
`main.js` module by its exact node ID. Package metadata is not a module selector.
@@ -538,3 +541,5 @@ observations, not verified final counters. Their bytes, facts and token reports
remain original observations; resource-qualified performance conclusions need
this additional closeout evidence. No analyzer/build/schema/package behavior is
changed, so unrelated verification is not repeated for this research-driver fix.
The next bounded WASM delivery cohort is documented in [first-call summary measurements](wasm-first-call.md).
@@ -115,6 +115,40 @@
"transport_bytes": 168796487,
"derivation": "selected regular ZIP member ShareX.HelpersLib.dll; no target execution"
}
},
{
"id": "vscode.tree-sitter.wasm",
"file": "tree-sitter.wasm",
"bytes": 206218,
"sha256": "3a31af706ffdf4a7116b064cdd4988df6791823298615009fc5b6bccbd42909b",
"provenance": {
"project": "VS Code Tree-sitter published WASM package",
"version": "0.3.1",
"project_url": "https://github.com/microsoft/vscode-tree-sitter-wasm",
"upstreamFile": "tree-sitter-wasm-0.3.1.tgz",
"url": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.1.tgz",
"transport_sha256": "985684b2cfbe5e8f90499a9f36e37519887c7d4e2697d0457d0f1e923ce5184c",
"transport_bytes": 2053205,
"transport_integrity": "sha512-RJFoomET6FajjG511fmQxeBQfU6M24a0aFZPqpid+ttIxanWf1VGytBG0UmsGjt07qmIPJS8U31D+aecuCucsQ==",
"derivation": "selected regular tar member package/wasm/tree-sitter.wasm; no target execution"
}
},
{
"id": "vscode.tree-sitter-css.wasm",
"file": "tree-sitter-css.wasm",
"bytes": 128668,
"sha256": "8a23977fe271357cce6f254ef88c9bebf3854602d8046605aef6a45c02135c59",
"provenance": {
"project": "VS Code Tree-sitter published WASM package",
"version": "0.3.1",
"project_url": "https://github.com/microsoft/vscode-tree-sitter-wasm",
"upstreamFile": "tree-sitter-wasm-0.3.1.tgz",
"url": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.1.tgz",
"transport_sha256": "985684b2cfbe5e8f90499a9f36e37519887c7d4e2697d0457d0f1e923ce5184c",
"transport_bytes": 2053205,
"transport_integrity": "sha512-RJFoomET6FajjG511fmQxeBQfU6M24a0aFZPqpid+ttIxanWf1VGytBG0UmsGjt07qmIPJS8U31D+aecuCucsQ==",
"derivation": "selected regular tar member package/wasm/tree-sitter-css.wasm; no target execution"
}
}
]
}
+104 -79
View File
@@ -13,6 +13,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { Ajv2020 } from "ajv/dist/2020.js";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const { values } = parseArgs({
@@ -243,6 +244,106 @@ const inspectManagedTarget = async () => {
};
};
/** The same counts/export question for each pinned shipped WASM module. */
const inspectWasmTarget = async () => {
let info;
let recovery = null;
const arguments_ = {
path: values.path,
glue_paths: [],
candidate_paths: [],
};
const definition = catalogDefinitions.get("inspect_wasm_artifact");
if (!definition) throw new Error("Missing advertised WASM definition");
// Validate the real advertised dialect, including SDK-generated local refs.
const supportsSummary = new Ajv2020({
strict: false,
validateFormats: false,
}).compile(definition.inputSchema)({ ...arguments_, detail: "summary" });
try {
info = await call("inspect_wasm_artifact", {
...arguments_,
...(values.delivery === "summary" && supportsSummary
? { detail: "summary" }
: {}),
});
} catch (error) {
const reference =
error instanceof ToolFailure &&
error.value?.error?.code === "resource_constraint"
? error.value.error.details?.reported_limits?.evidence_reference
: undefined;
if (reference?.kind !== "retained-evidence") throw error;
if (values.delivery === "summary") {
info = await call("inspect_analysis_view", {
source: reference,
view: { kind: "summary" },
});
recovery =
"existing completed producer reference read through typed WASM views";
} else {
const path = resolve(values.output, "retained-evidence.json");
await call("export_evidence_bundle", { path });
const bundle = JSON.parse(await readFile(path, "utf8"));
const record = bundle.records.find(
(row) => row.evidence_id === reference.evidence_id,
);
const { parseEvidence } = await import(
pathToFileURL(
resolve(values["runtime-root"], "dist/domain/evidence.js"),
).href
);
info = parseEvidence(record);
recovery =
"complete producer Evidence exported after an observed transport constraint";
}
}
const n = normalized(info);
if (n.kind === "summary") {
const summary = n.summary;
const exportsEvidence = await call("inspect_analysis_view", {
source: { kind: "retained-evidence", evidence_id: n.parent_evidence_id },
view: { kind: "wasm", facet: "exports", offset: 0, limit: 4 },
});
return {
artifact_sha256: targetSha(info),
validation: summary.validation,
section_count: summary.available_counts.sections,
import_count: summary.available_counts.imports,
export_count: summary.available_counts.exports,
exports_first4: normalized(exportsEvidence).item,
wat_sha256: summary.wat.sha256,
wat_code_units: summary.wat.code_units,
tool_profile: summary.tool_profile,
runtime_execution: summary.runtime_execution,
network_fetch: summary.network_fetch,
evidence_ids: [
n.parent_evidence_id,
info.evidence_id,
exportsEvidence.evidence_id,
],
source_evidence_id: n.parent_evidence_id,
recovery,
};
}
return {
artifact_sha256: targetSha(info),
validation: n.validation,
section_count: n.sections.length,
import_count: n.imports.length,
export_count: n.exports.length,
exports_first4: n.exports.slice(0, 4),
wat_sha256: n.wat.sha256,
wat_code_units: n.wat.text.length,
tool_profile: n.tool_profile,
runtime_execution: n.runtime_execution,
network_fetch: n.network_fetch,
evidence_ids: [info.evidence_id],
source_evidence_id: info.evidence_id,
recovery,
};
};
const workflows = {
"obsidian.asar": async () => {
if (values["javascript-question"] === "selected") {
@@ -424,85 +525,9 @@ const workflows = {
evidence_ids: [info.evidence_id],
};
},
"sqlite.wasm": async () => {
let info;
let recovery = null;
try {
info = await call("inspect_wasm_artifact", {
path: values.path,
glue_paths: [],
candidate_paths: [],
});
} catch (error) {
const reference =
error instanceof ToolFailure &&
error.value?.error?.code === "resource_constraint"
? error.value.error.details?.reported_limits?.evidence_reference
: undefined;
if (reference?.kind !== "retained-evidence") throw error;
if (values.delivery === "summary") {
const summaryEvidence = await call("inspect_analysis_view", {
source: reference,
view: { kind: "summary" },
});
const summary = normalized(summaryEvidence).summary;
const exportsEvidence = await call("inspect_analysis_view", {
source: reference,
view: { kind: "wasm", facet: "exports", offset: 0, limit: 4 },
});
return {
artifact_sha256: targetSha(summaryEvidence),
validation: summary.validation,
section_count: summary.available_counts.sections,
import_count: summary.available_counts.imports,
export_count: summary.available_counts.exports,
exports_first4: normalized(exportsEvidence).item,
wat_sha256: summary.wat.sha256,
wat_code_units: summary.wat.code_units,
tool_profile: summary.tool_profile,
runtime_execution: summary.runtime_execution,
network_fetch: summary.network_fetch,
evidence_ids: [
reference.evidence_id,
summaryEvidence.evidence_id,
exportsEvidence.evidence_id,
],
recovery:
"existing completed producer reference read through typed WASM views",
};
}
const path = resolve(values.output, "retained-evidence.json");
await call("export_evidence_bundle", { path });
const bundle = JSON.parse(await readFile(path, "utf8"));
const record = bundle.records.find(
(row) => row.evidence_id === reference.evidence_id,
);
const { parseEvidence } = await import(
pathToFileURL(
resolve(values["runtime-root"], "dist/domain/evidence.js"),
).href
);
info = parseEvidence(record);
recovery =
"complete producer Evidence exported after an observed transport constraint";
}
const n = normalized(info);
return {
artifact_sha256: targetSha(info),
validation: n.validation,
section_count: n.sections.length,
import_count: n.imports.length,
export_count: n.exports.length,
exports_first4: n.exports.slice(0, 4),
wat_sha256: n.wat.sha256,
wat_code_units: n.wat.text.length,
tool_profile: n.tool_profile,
runtime_execution: n.runtime_execution,
network_fetch: n.network_fetch,
evidence_ids: [info.evidence_id],
recovery,
};
},
"sqlite.wasm": inspectWasmTarget,
"vscode.tree-sitter.wasm": inspectWasmTarget,
"vscode.tree-sitter-css.wasm": inspectWasmTarget,
"weth.hex": async () => {
const info = await call("inspect_evm_interface", {
path: values.path,
@@ -0,0 +1 @@
Statically inspect the real shipped WASM module at {{artifact_path}} using only REA tools. Report exact selected artifact SHA-256, valid/invalid status, section count, import count, export count, first four complete export WAT forms in producer order, decoded WAT SHA-256 and code-unit length, WABT release/source commit, and exact runtime_execution/network_fetch states. If complete delivery exceeds the MCP frame, use the exact retained reference for summary and wasm exports views. Preserve original strings, uncertainties and real Evidence IDs. Do not execute WASM, fetch URLs or use shell/file tools. Final JSON fields: artifact_sha256, validation, section_count, import_count, export_count, exports_first4, wat_sha256, wat_code_units, wabt_release, wabt_source_commit, runtime_execution, network_fetch, evidence_ids, limitations.
@@ -0,0 +1,149 @@
# WASM first-call summary delivery
Refs [#2016](https://github.com/morluto/rea/issues/2016). This bounded follow-up
adds optional MCP `inspect_wasm_artifact detail: summary`. Omitted/complete MCP
requests and CLI output remain complete. Full original Evidence is retained;
delivery selection is stripped before provider inputs, and exact export views
read the original parent without another WABT invocation.
## Pinned repeated public SDK cohort
- Control: `18e5d6275965c5f1d3ef9fe373d1792822098003`.
- Candidate: `45831c622a13368494d2a8f3a9718effa91c937c`.
- Runtime file-table SHA-256: control `ef65527c2d41b621f71059b7553050660d801ad905a61a71b81aba266f70e1ec`;
candidate `353f5513ea09b0a627fa99814cebe0a1231c6c8f7e0e12a28205521133478b1a`.
- Actual WABT 1.0.42, identical explicitly selected provider configuration and
effective search-path fingerprint, pinned Node 24.18.0 and locked SDK.
- Three paired fresh-process trials per digest-pinned shipped module, serial
on one CPU: control/candidate, candidate/control, control/candidate.
- Same validation/counts/first-four complete export forms/WAT identity/provider
identity/execution and fetch states; complete original source Evidence ID is
also compared. All nine pairs preserve these selected facts and source IDs.
| Shipped module | Result JSON bytes, control → summary | Calls / errors | Median seconds, control → summary (ranges) |
| ----------------------------- | -----------------------------------: | -------------- | ----------------------------------------------- |
| `sqlite.wasm` | 20,118 → 18,533 | 3/1 → 2/0 | 15.962 (15.719–21.138) → 18.171 (10.242–18.649) |
| `vscode.tree-sitter-css.wasm` | 20,366 → 18,781 | 3/1 → 2/0 | 4.920 (4.885–4.936) → 5.191 (4.971–5.232) |
| `vscode.tree-sitter.wasm` | 6,433,872 → 18,891 | 1/0 → 2/0 | 5.225 (5.172–5.519) → 4.763 (4.729–4.830) |
Byte counts include all investigation call results, including failed delivery
and extra views. They exclude catalog responses, raw wire framing and file
exports; they are not actual model tokens. The complete catalog is 2,359,082
versus 2,362,451 JSON bytes (+3,369 bytes, about 0.14%). An extra export-view
call is included when summary replaces an otherwise fitting complete reply.
| Shipped module | Median owned CPU seconds, control → summary | Median sampled family MiB, control → summary (maxima) |
| ----------------------------- | ------------------------------------------- | ----------------------------------------------------- |
| `sqlite.wasm` | 15.376 → 15.954 | 660.84 (671.27) → 633.07 (671.51) |
| `vscode.tree-sitter-css.wasm` | 5.760 → 6.027 | 692.91 (694.93) → 678.98 (693.88) |
| `vscode.tree-sitter.wasm` | 6.134 → 5.663 | 674.04 (695.66) → 550.66 (553.47) |
All 18 scopes have no sampled abort and their last observations contain no
memory-high/hard/OOM/OOM-kill or task-limit event. Final counters are unverified
under the subsequent closeout audit below. Sampling can miss brief peaks. Application caches are cold per process;
host page caches are not flushed. SDK elapsed time includes startup, discovery,
schema work and persistence, not just provider analysis. Three observations
give descriptive medians/ranges, not a statistical or cross-domain speed claim.
SQLite and the CSS grammar already recovered an oversized complete result
through retained views in the control. The new path removes the initial error
and one call; result-byte savings are correspondingly modest. The Tree-sitter
runtime fits the MCP frame but delivers megabytes of unneeded WAT; its summary
plus exact exports greatly reduces delivery. Full provider analysis and complete
Evidence retention still occur, so this does not establish an engine shortcut.
An earlier 18-run cohort did not activate the treatment: the driver looked
for an inline enum and missed SDK-generated `$ref` definitions. Those original
runs and their source are archived separately and excluded from benefit claims.
The corrected driver validates the actual advertised 2020-12 request schema.
## Real model verification
Ten actual Codex CLI 0.161.0 trials request `gpt-5.6-terra` high: three paired
SQLite repetitions and one pair for each published VS Code component. The
resolved server-side model identity is not inferred from the requested alias.
Same question, fixed developer instructions, four allowlisted REA tools and
explicit WABT settings; private profiles, shell/web/other-agent restrictions
and target-byte preservation remain verified.
All ten answers match the 12 requested producer facts, including exact export
forms and explicit execution/fetch states. Reported Evidence IDs occur in real
responses; all copied profiles are removed. Manual review retains static-analysis
limitations and makes no original-source/runtime claim. Every candidate chooses
`detail: summary` on its first inspection and then reads exports without WABT
rerun. Original control errors and client-context clipping remain in transcripts.
| Module / observations per side | Total input, control → summary | Cached input | Uncached input | Output |
| --------------------------------- | -----------------------------: | ----------------: | --------------: | ------------: |
| `sqlite.wasm` / 3 | 96,832 → 79,264 | 70,656 → 51,456 | 26,215 → 28,004 | 1,470 → 1,512 |
| `vscode.tree-sitter-css.wasm` / 1 | 247,889 → 79,389 | 207,872 → 61,440 | 40,017 → 17,949 | 1,931 → 1,327 |
| `vscode.tree-sitter.wasm` / 1 | 556,424 → 282,165 | 478,208 → 239,360 | 78,216 → 42,805 | 2,560 → 2,110 |
SQLite rows show medians of three observations; VS Code rows are single pairs.
Usage is the CLI-reported total for the turn, with cached/uncached input listed
separately. Lower total input does not imply a proportional monetary saving,
uncached-input reduction or faster answer.
| Module | Calls / observed errors, control → summary | Seconds, control → summary |
| ----------------------------- | ------------------------------------------ | ----------------------------------------------- |
| `sqlite.wasm` | 3/1 → 2/0 | 61.521 (54.301–80.579) → 55.656 (42.960–62.145) |
| `vscode.tree-sitter-css.wasm` | 3/1 → 2/0 | 87.126 → 106.580 |
| `vscode.tree-sitter.wasm` | 4/0 → 2/0 | 56.952 → 104.999 |
SQLite total input falls about 18% and median answer time about 10%, while
its median uncached input rises about 7%. The two VS Code pairs reduce total
input by about 49% and 68%, but observed answer time rises about 84% and 22%.
These data support optional smaller delivery; they do not establish uniform
model speedup. The runtime control also makes one protocol resource-discovery
call, included in the four-call count. Cache state, model reasoning and service
latency are not separated by this small sample.
The model and SDK cohorts ran with the original resource wrapper. Their final
receipts retain last-sampled cgroup counters, not verified final counters.
The subsequent closeout correction regrades all ten existing answers: selected
facts, real IDs, unchanged target bytes and profile cleanup still pass, while
the final resource conditions are **unverified**. No model/build/provider run
was repeated for that review-only correction. Last-sampled peaks are 656–944
MiB for model scopes with no observed limit events; one CPU, 4 GiB hard ceiling,
3 GiB early-stop and no-swap controls were active. This is not a proof of absent
late resource events or a fully resource-qualified performance conclusion.
The original transcripts/usage and regrades remain archived separately.
## Reproduction and verification
Use [the common measurement/model instructions](README.md) with the public
digest-pinned inputs in [corpus.json](corpus.json), `--schemas selected` and
`--delivery summary`. For each artifact run the above three paired orders
against separately frozen runtimes, then `compare.mjs`. Use the same
[WASM interface question](questions/wasm-interface.txt), four exact enabled
tools (`inspect_wasm_artifact`, `inspect_analysis_view`, `get_evidence_bundle`,
`export_evidence_bundle`), explicit WABT settings and resource scope for models.
The VS Code artifacts are two published components from
[@vscode/tree-sitter-wasm 0.3.1](https://github.com/microsoft/vscode-tree-sitter-wasm).
The selected regular archive members have verified digests and package SHA-512
integrity. This is component analysis, not proof of the whole VS Code runtime.
The prototype passes 54 focused assertions and full static/docs gates. After
rebasing, 29 focused assertions and 42 actual installed-package WABT CLI/MCP
cases pass; the final rebased snapshot passes build/fast static and seven WASM
MCP workflows again. Omitted/explicit-complete behavior, retained identity,
fixed-server absence of retention, oversized continuation and invalid WAT
digest remain checked. No version publication or target execution occurs.
The final static/build scope has 51 soft memory-high events and no hard/OOM
events; this is not credited as a performance trial.
Detailed JSON, transcripts, target bytes, profiles and acquisition receipts
stay outside Git. Source inputs, scripts and this concise measured scope are
reviewable in the repository.
## Main integration checkpoint
Prerequisite #2122 is merged at `58fb372071e56da9c5df0e37593ee3f0da6b64bc`.
The follow-up is rebased directly onto latest fetched main. Intervening changes
outside the prerequisite concern CLI/logging/CI planning rather than WASM
production/delivery. Seven focused WASM MCP workflows and one fresh real-WABT
SQLite public-SDK request pass against the new build: selected facts and original
source ID match the pinned candidate, summary uses two calls with no tool error.
Only the relevant integration checks and required pre-push gate are repeated;
earlier measurements retain their original runtimes, not the new main label.
+17 -4
View File
@@ -4,16 +4,29 @@ import {
} from "../../domain/wasm/wasmArtifact.js";
import type { ToolContract } from "../toolContractTypes.js";
import { toolContractMetadata } from "../toolEffects.js";
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
import {
analysisDetailSchema,
analysisDetailOutputSchemaOf,
} from "../analysisDetail.js";
import { wasmArtifactSummaryViewResultSchema } from "../../domain/analysisView/analysisView.js";
/** Delivery selection does not change the provider input or original Evidence. */
export const wasmArtifactInspectionInputSchema =
inspectWasmArtifactInputSchema.extend({
detail: analysisDetailSchema.unwrap().optional(),
});
/** Optional caller-supplied WABT capability with target-free CLI/MCP parity. */
export const INSPECT_WASM_ARTIFACT_CONTRACT = {
name: "inspect_wasm_artifact",
...toolContractMetadata("inspect_wasm_artifact"),
kind: "artifact-provider",
description:
"Validate an explicit local WASM artifact with caller-supplied WABT 1.0.42, retain exact byte SHA-256, sections, upstream import/export WAT forms and decoded WAT inline as Evidence. Optionally associate selected JavaScript glue literals with explicit local candidates while preserving ambiguous URL/basename matches. Configure absolute REA_WABT_BIN_DIRECTORY. No installation, implicit fetch, WASM execution or original-source claim.",
inputSchema: inspectWasmArtifactInputSchema,
outputSchema: evidenceResultOf(wasmArtifactSchema),
'Validate an explicit local WASM artifact with caller-supplied WABT 1.0.42. Omitted or complete detail returns exact byte SHA-256, sections, upstream import/export WAT forms and decoded WAT inline as Evidence. For counts, validation and WAT identity without WAT text, pass detail: "summary" on this first call; it retains the complete original Evidence and returns the same typed summary as inspect_analysis_view, avoiding oversized complete delivery. Read exact imports/exports or WAT pages with inspect_analysis_view using normalized_result.parent_evidence_id. Delivery selection does not change analysis, provider inputs or source identity. Optionally associate selected JavaScript glue literals with explicit local candidates while preserving ambiguous URL/basename matches. Configure absolute REA_WABT_BIN_DIRECTORY. No installation, implicit fetch, WASM execution or original-source claim.',
inputSchema: wasmArtifactInspectionInputSchema,
outputSchema: analysisDetailOutputSchemaOf(
wasmArtifactSchema,
wasmArtifactSummaryViewResultSchema,
),
examples: [
{
title: "Inspect a selected local WASM module",
+7
View File
@@ -302,6 +302,13 @@ export const artifactInventorySummaryViewResultSchema = z.strictObject({
summary: inventorySummarySchema,
});
/** Exact summary carrier for WASM inspection's optional delivery contract. */
export const wasmArtifactSummaryViewResultSchema = z.strictObject({
...summaryViewResultBase,
parent_operation: z.literal("inspect_wasm_artifact"),
summary: wasmSummarySchema,
});
/** Projected facts for one selected view of retained analysis Evidence. */
export const analysisViewResultSchema = z.discriminatedUnion("kind", [
z.strictObject({
+1
View File
@@ -453,6 +453,7 @@ const registerConfiguredAnalysisTools = (
toolLogger,
recordEvidence,
withAdmittedAnalysis,
evidenceById,
);
registerEvmTools(
server,
+25 -1
View File
@@ -2,6 +2,10 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { WasmArtifactService } from "../application/wasm/WasmArtifactService.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import { summarizeRetainedAnalysis } from "../application/analysisView/AnalysisViewService.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
import { wasmArtifactModule } from "../composition/wasm.js";
import type { Logger } from "pino";
import { createEvidenceToolHandler } from "./contractToolHandler.js";
@@ -15,11 +19,31 @@ export const registerWasmTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
withAdmittedAnalysis?: WithAdmittedAnalysis,
evidenceById?: EvidenceLookup,
): void => {
const contract = wasmArtifactModule.contract;
const handler = createEvidenceToolHandler(
server,
(input, options) => service.inspect(input, options),
async (rawInput, options) => {
const parsed = contract.inputSchema.safeParse(rawInput);
if (!parsed.success)
return err(
analysisInputErrorFromIssues(
contract.name,
parsed.error.issues,
rawInput,
{ cause: parsed.error },
),
);
const { detail, ...input } = parsed.data;
const inspected = await service.inspect(input, options);
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, {
recordEvidence,
evidenceById,
})
: inspected;
},
logger,
recordEvidence,
withAdmittedAnalysis,
+143 -1
View File
@@ -6,6 +6,7 @@ import { join } from "node:path";
import { WasmArtifactService } from "../../../src/application/wasm/WasmArtifactService.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { wasmArtifactSummaryViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
import { createHash } from "node:crypto";
import { z } from "zod";
import { createServer } from "../../../src/server/createServer.js";
@@ -33,6 +34,63 @@ const wasmToolSchemas = async (client: Client) => {
return { name: advertised.name, ajv, inputSchema, outputSchema };
};
const assertFirstSummaryDelivery = async (
harness: Awaited<ReturnType<typeof createApplicationMcpHarness>>,
value: ReturnType<typeof wasmArtifactFixture>,
inspect: WasmArtifactService["provider"]["inspect"],
) => {
const {
ajv: wasmAjv,
inputSchema,
outputSchema,
} = await wasmToolSchemas(harness.client);
const summaryInput = { path: value.artifact.path, detail: "summary" };
expect(wasmAjv.validate(inputSchema, summaryInput)).toBe(true);
expect(
wasmAjv.validate(inputSchema, { ...summaryInput, detail: "brief" }),
).toBe(false);
const directSummary = await harness.client.callTool({
name: "inspect_wasm_artifact",
arguments: summaryInput,
});
expect(directSummary.isError).not.toBe(true);
expect(
wasmAjv.validate(outputSchema, directSummary.structuredContent),
JSON.stringify(wasmAjv.errors),
).toBe(true);
const summaryEvidence = parseEvidence(directSummary.structuredContent);
const summary = wasmArtifactSummaryViewResultSchema.parse(
summaryEvidence.normalized_result,
);
expect(summaryEvidence.confidence).toBe("derived");
expect(summaryEvidence.evidence_links).toEqual([summary.parent_evidence_id]);
expect(summary.summary.wat).toMatchObject({
sha256: value.wat.sha256,
code_units: value.wat.text.length,
});
expect(summary.summary.wat).not.toHaveProperty("text");
const retained = harness.session.evidenceById(summary.parent_evidence_id);
if (retained === undefined) throw new Error("Summary lost original Evidence");
expect(retained.normalized_result).toEqual(value);
expect(retained.parameters).toEqual({
path: value.artifact.path,
glue_paths: [],
candidate_paths: [],
});
expect(inspect).toHaveBeenCalledTimes(1);
expect(inspect).toHaveBeenLastCalledWith(
retained.parameters,
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
expect(
wasmAjv.validate(outputSchema, {
...summaryEvidence,
normalized_result: { ...summary, parent_operation: "inventory_artifact" },
}),
).toBe(false);
return { summary, retained };
};
it("directs rejected native WASM opening to the advertised static tool while preserving missing-WABT diagnostics", async () => {
const directory = await createTestTempDirectory("rea-wasm-recovery-");
const path = join(
@@ -84,6 +142,16 @@ it("directs rejected native WASM opening to the advertised static tool while pre
},
},
});
const summary = await client.callTool({
name,
arguments: { ...input, detail: "summary" },
});
expect(parseMcpToolError(summary)).toMatchObject({
error: {
code: "capability_unavailable",
details: { reason: expect.stringContaining("REA_WABT_BIN_DIRECTORY") },
},
});
await client.ping();
expect(createClient).not.toHaveBeenCalled();
expect(await readFile(path)).toEqual(
@@ -157,14 +225,22 @@ it("reads an oversized completed WASM result through its reported retained refer
representation: "decoded-wat" as const,
},
};
const inspect = vi.fn(() =>
Promise.resolve(ok(wasmArtifactExecution(value))),
);
const service = new WasmArtifactService({
close: () => Promise.resolve(),
inspect: () => Promise.resolve(ok(wasmArtifactExecution(value))),
inspect,
});
const harness = await createApplicationMcpHarness({
serverOptions: { wasmArtifact: service },
});
onTestFinished(harness.close);
const { summary, retained } = await assertFirstSummaryDelivery(
harness,
value,
inspect,
);
const reply = await harness.client.callTool({
name: "inspect_wasm_artifact",
arguments: { path: value.artifact.path },
@@ -182,6 +258,17 @@ it("reads an oversized completed WASM result through its reported retained refer
})
.parse(parseMcpToolError(reply).error).details
.reported_limits.evidence_reference;
expect(reference.evidence_id).toBe(summary.parent_evidence_id);
const explicitComplete = await harness.client.callTool({
name: "inspect_wasm_artifact",
arguments: { path: value.artifact.path, detail: "complete" },
});
expect(parseMcpToolError(explicitComplete)).toMatchObject({
error: {
code: "resource_constraint",
details: { reported_limits: { evidence_reference: reference } },
},
});
const catalog = await harness.client.listTools();
const advertised = catalog.tools.find(
(tool) => tool.name === "inspect_analysis_view",
@@ -244,6 +331,17 @@ it("reads an oversized completed WASM result through its reported retained refer
}
const original = harness.session.evidenceById(reference.evidence_id);
expect(original?.normalized_result).toEqual(value);
expect(original).toEqual(retained);
expect(inspect).toHaveBeenCalledTimes(3);
await harness.client.ping();
});
it("rejects inconsistent decoded-WAT digests in portable Evidence without a provider run", async () => {
const value = wasmArtifactFixture();
const harness = await createApplicationMcpHarness({
serverOptions: { environment: {} },
});
onTestFinished(harness.close);
const inconsistent = createEvidence(
{
path: value.artifact.path,
@@ -325,6 +423,50 @@ it("rejects absent or inconsistent WASM source identity before deriving portable
await harness.client.ping();
});
it("requires retention for WASM summary delivery while keeping fixed-server complete delivery", async () => {
const value = wasmArtifactFixture();
const service = new WasmArtifactService({
close: () => Promise.resolve(),
inspect: () => Promise.resolve(ok(wasmArtifactExecution(value))),
});
const session = createTestBinarySession(() => {
throw new Error("No deep provider");
});
const server = createServer(
{ kind: "fixed", analysis: session },
{ wasmArtifact: service },
);
const client = new Client({ name: "wasm-fixed-delivery", version: "1" });
const [a, b] = InMemoryTransport.createLinkedPair();
try {
await server.connect(b);
await client.connect(a);
const summary = await client.callTool({
name: "inspect_wasm_artifact",
arguments: { path: value.artifact.path, detail: "summary" },
});
expect(parseMcpToolError(summary)).toMatchObject({
error: {
code: "capability_unavailable",
message: expect.stringContaining("does not retain Evidence"),
},
});
const complete = await client.callTool({
name: "inspect_wasm_artifact",
arguments: { path: value.artifact.path },
});
expect(complete.isError).not.toBe(true);
expect(parseEvidence(complete.structuredContent).normalized_result).toEqual(
value,
);
await client.ping();
} finally {
await client.close();
await server.close();
await session.close();
}
});
it("retries cleanup of the injected WASM module after server shutdown fails", async () => {
const close = vi
.fn()