perf(rust): query selected dependency facts from retained Evidence (#5815)

Add optional package/edge selection and summaries through shared retained Evidence, preserving complete delivery and parent identity.

Fixes #5802
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-21 16:12:04 +08:00
1 parent a5f982de8d
commit 5241597594
24 files changed
+2192 -74

No files matched your search

@@ -207,6 +207,12 @@ explicit path or endpoint and do not need it.
package order, dependency indices, and the section file range. A missing
`.dep-v0` section is null metadata, not proof the binary has no dependencies.
A rustc commit hash is an inference from a read-only `/rustc/<40-hex>/` path.
For selected crates, use `view: {collection: "packages", names: [...],
fields: ["name", "version", "source"]}` on the first call; explicitly include
`dependencies` for direct index references. Use `detail: "summary"` for counts
and ranges, or retained `inspect_analysis_view` Rust package/edge pages for
subsequent filters. Keep the complete parent Evidence accessible; CLI compact
delivery requires `--evidence_output`.
See the
[Rust binary guide](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
+61
View File
@@ -21,6 +21,67 @@ requires an absolute filesystem path and preserves that exact selection:
## Observations
Complete delivery remains the default. For a dependency question, select the
needed records on the first call instead of returning the complete graph:
```json
{
"name": "inspect_rust_binary",
"arguments": {
"path": "/artifacts/application",
"view": {
"collection": "packages",
"names": ["serde", "serde_derive"],
"fields": ["name", "version", "source", "dependencies"]
}
}
}
```
`detail: "summary"` returns section ranges, root index, package/edge/cycle counts
and compiler-hash count. A `view` selects compact delivery automatically;
combining it with explicit `detail: "complete"` is an input error. The complete
parent Evidence is retained in the MCP session and remains exportable. Subsequent
`inspect_analysis_view` calls accept that `parent_evidence_id`, or its portable
Evidence file, without reopening the binary:
```json
{
"source": {
"kind": "retained-evidence",
"evidence_id": "ev_<parent digest>"
},
"view": {
"kind": "rust",
"collection": "edges",
"from_indices": [12],
"limit": 20
}
}
```
Package filters accept exact `names`, `versions` and original `indices`; edge
filters accept original edge `indices`, `from_indices` and `to_indices`.
Filters intersect, and an empty filter array selects nothing. `offset` counts
matching records; `limit` defaults to 64. Pages retain original indices scoped
to the parent Evidence, selected fields, examined coverage and the next offset.
Default package fields omit dependency vectors; explicitly select
`dependencies` to obtain direct references. No dependency path is traversed.
The CLI uses the same views and requires an exclusive output bundle for compact
delivery so the complete Evidence survives process exit:
```bash
rea inspect-rust-binary /artifacts/application --json \
--view '{"collection":"packages","names":["serde"],"fields":["name","version"]}' \
--evidence_output /artifacts/rust-evidence.json
```
Compact results distinguish complete embedded-record coverage from unknown
runtime dependency coverage. A missing section remains unknown rather than an
empty dependency set. Retained views describe the captured artifact digest;
they do not assert that the current file or running application is unchanged.
The inline Evidence result contains the artifact path, SHA-256, byte length,
container format, architecture, word size, and byte order. When a `.dep-v0`
section is present, `auditable_metadata` contains:
@@ -433,6 +433,38 @@
"members": 315,
"expanded_bytes": 1301927
}
},
{
"id": "alpine-ripgrep-rust",
"file": "ripgrep",
"bytes": 3416168,
"sha256": "78387bbe12b2110df8d2ce364390a00c190f4543a2f40b8279e49f6c5c85af9c",
"provenance": {
"project": "ripgrep",
"version": "14.1.1-r0",
"upstreamFile": "ripgrep-14.1.1-r0.apk",
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/ripgrep-14.1.1-r0.apk",
"transport_sha256": "213dd0b047163f6627cc2fdd6fbfe205009476eb4fc48c352a3f5b9ab647f6d4",
"transport_bytes": 1422830,
"aports_revision": "4e899d119e4c983a938436d6f397a509521225d1",
"derivation": "selected regular APK tar member usr/bin/rg; read-only static inspection; never executed"
}
},
{
"id": "alpine-zellij-rust",
"file": "zellij",
"bytes": 17151864,
"sha256": "9a86e9ca47902a869fdf1886f4cf17714f4fa5655427128beadff498cfb392db",
"provenance": {
"project": "zellij",
"version": "0.40.0-r0",
"upstreamFile": "zellij-0.40.0-r0.apk",
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/zellij-0.40.0-r0.apk",
"transport_sha256": "575075945415b221857563e19c5f4c661af9f98bbba54f0994c7385c0e432432",
"transport_bytes": 6722129,
"aports_revision": "3be26a3a26e76e1bf3eff784bffe3956d29d77a0",
"derivation": "selected regular APK tar member usr/bin/zellij; read-only static inspection; never executed"
}
}
]
}
@@ -0,0 +1,79 @@
"""Count completed Rust questions; tokenizer estimates are not model usage."""
import argparse
import hashlib
import json
from pathlib import Path
import tiktoken
def compact(value):
return json.dumps(value, ensure_ascii=False, separators=(",", ":"), allow_nan=False)
def inspect(path, encoding):
report = json.loads((path / "report.json").read_text())
catalog = json.loads((path / "catalog.json").read_text())
transcript = json.loads((path / "transcript.json").read_text())
log = path.with_suffix(".log")
scope = next(json.loads(line) for line in reversed(log.read_text().splitlines())
if line.startswith('{"resource_scope":'))
observed = scope.get("payload_resource_observation") or {}
events = observed.get("resource_events", {})
memory = events.get("memory.events", {})
assert (report["status"] == "passed" and not report["cleanup_failures"]
and not report["receipt_failures"] and scope["abort"] is None
and scope.get("payload_resource_observation_verified") is True
and observed.get("payload_cleanup_verified") is True
and observed.get("payload_exit_code") == 0
and all(memory.get(key) == 0 for key in ["high", "max", "oom", "oom_kill"])
and events.get("pids.events", {}).get("max") == 0), path
assert [event["name"] for event in transcript] == [
"inspect_rust_binary", "export_evidence_bundle"]
count = lambda value: len(encoding.encode(value, disallowed_special=()))
schema_fields = ["name", "description", "inputSchema"]
tool_tokens = lambda tools: count(compact([
{key: tool[key] for key in schema_fields} for tool in tools]))
inputs = tool_tokens(catalog["tools"])
selected = tool_tokens([tool for tool in catalog["tools"] if tool["name"] in [
"inspect_rust_binary", "export_evidence_bundle"]])
arguments = sum(count(compact({"name": event["name"], "arguments": event["input"]}))
for event in transcript)
replies = sum(count(block["text"]) for event in transcript
for block in event["reply"]["content"] if block["type"] == "text")
return {"case": path.name, "lane": report["lane"],
"answer": report["answer"], "parent_evidence_id": report["parent_evidence_id"],
"artifact_sha256": report["artifact_sha256"],
"complete_result_sha256": report["normalized_sha256"],
"catalog_sha256": report["catalog_sha256"],
"catalog_tools": report["catalog_tools"], "calls": report["calls"],
"wall_seconds": scope["elapsed_seconds"],
"cpu_seconds": observed["cpu_stat"]["usage_usec"] / 1e6,
"kernel_family_peak_bytes": observed["memory_peak_bytes"],
"events_at_payload_exit": events,
"final_whole_scope_events": scope.get("resource_events_final"),
"completed_with_verified_cleanup": True,
"token_proxy": {"input_catalog_once": inputs, "arguments": arguments,
"text_replies": replies, "combined": inputs + arguments + replies,
"only_two_selected_tools": selected + arguments + replies,
"complete_transport_catalog": count(compact(catalog))},
"sha256": {file.name: hashlib.sha256(file.read_bytes()).hexdigest()
for file in [path / "report.json", path / "catalog.json",
path / "transcript.json", log]}}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("cases", type=Path, nargs="+")
args = parser.parse_args()
encoding = tiktoken.get_encoding("cl100k_base")
receipt = {"tiktoken": tiktoken.__version__, "encoding": encoding.name,
"actual_model_usage": None,
"projection": "All discovered tool descriptions/input schemas once, plus every call argument and text reply including export. Selected-two-tool and complete transport catalog costs are separate. No model, host injection, history replay or retry observed.",
"recordings": [inspect(path, encoding) for path in args.cases]}
args.output.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + "\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,578 @@
{
"baseline_source": "d672cc23998b6cbb21c485f118595ebfb82fed33",
"candidate_code": "cae2571e7e64a7364b94bb92c2630cba80ec4478",
"runtime_filetables": {
"baseline": {
"sha256": "6497f70d4864489e948b6a6f9611cf1bd9e6d7f60722fcdd41c0984ab7c48d53",
"files": 1917
},
"candidate": {
"sha256": "078e6fd0f837ca252265d12d3a95e346f836762d2474117c51f4c9f7ae629b21",
"files": 1920
}
},
"lock_sha256": "699a14749e3825c62430faeb18224190fee1c9347760494e38e334e180bea6ec",
"source_sha256": {
".agents/skills/reverse-engineer-anything/SKILL.md": "4baf850678f0781cbb989a7ac1452c4cbc5d98e4125bc059b5381c336cc8bc07",
"docs/rust-binaries.md": "3317a494376c3dde0c81a848ea678375db4af61fa85ba8d505b7d89b319c8371",
"experiments/analysis-efficiency/corpus.json": "81c223028c7c6308949e508413dea6aef79b691f6dc7fe02eb6ff81e6d52b0cc",
"experiments/analysis-efficiency/rust-dependency-costs.py": "c7eb7a9bc696e06c4cfba3b0aa730bf8cb9f2bcdec3a371eae3849a47c5f3135",
"experiments/analysis-efficiency/rust-dependency-views.mjs": "7c59302fe00c8a00e4cea06bca8f73974e40a618143ea4f1c7c5e275c9c07df5",
"src/application/rust/RustBinaryService.test.ts": "9b424569f52ed6c5e4629b3bda4fb8a8dc805adc0881e82f1bf39f38618c57a3",
"src/application/rust/RustBinaryService.ts": "caa089f62cdf24ee8a4ceb6eb7bdbf9df90fc724f921197f9e69b680366a98a7",
"src/cli/rustCommands.ts": "18c87736f20437f178d2094f0b46392d167ff510fa8a08a6b121610538fc257b",
"src/contracts/analysisViewToolContracts.ts": "149cd98c2ed22444ac9e59fa9144b6840c3fd13bd1613ddf8da23310766829f2",
"src/contracts/rust/rustToolContracts.ts": "5433f15f581618b3184147c04ccd21e04bc4a3a880b7ea0db6dbc38e5825efab",
"src/domain/analysisView/analysisView.ts": "8122d62bf795f3abae4bd903a641e336f0c2650cab98836051839bfaec4e1d65",
"src/domain/analysisView/goBinaryView.ts": "c434f7d7c198481d2fab2c2f7d5672f0db34256e9c8700a52c65c55def05df0b",
"src/domain/analysisView/rustBinaryView.test.ts": "3c53131b5643ed874214c0ba90e66960d2acdf9d22a881d283ef44aba671dfe4",
"src/domain/analysisView/rustBinaryView.ts": "8c330cf31b3ccb37e3b12a40e8157c50a9d43ac42cf6477dcca969398e0f79ad",
"src/domain/analysisView/selectViewRows.ts": "6eb486166e991045bf0a74cf874c80d2542862147b7ba095e45a61ec26afd1e5",
"src/domain/rust/rustBinary.ts": "90fc6fc20faa0f663dc14e08e8735804ae77abaa7fdb2e918162b358efb42462",
"src/domain/rust/rustBinaryQuery.ts": "ecb2ad8a97e3f3d232f94f5969baaef62143392362d18b4d46ab6babcfe96da2",
"src/server/createServer.ts": "b005d3d71edc98b9eb3422d83b8ddeddcc7db0b1982a1e93ce4578d588dbd8e6",
"src/server/registerRustTools.ts": "e57723a66916f3e2d9208e7202ac50b28536234874fbc3752e9259e0145299ae",
"tests/acceptance/rustBinaryCli.test.ts": "85962f740bd1d2f1fc99601337ab7e7ca3f4e659553d41297aa216e8928c0af4",
"tests/acceptance/rustDependencyViews.test.ts": "b1505497e9dbfb1ce855725a8af501f1ebc82bb2c528b8992e9b33cf8628ef7f",
"tests/boundary/mcp/rustBinaryMcp.test.ts": "4d2c5f172ebcd3407265b195adc7c5bc5dd590d97a443560a6d8c6afda60d08a"
},
"resource_guard_sha256": {
"resource-run.py": "7a9642ab86ba34290564f636eb080cacf38b4185a254028680b238bff172993b",
"resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6"
},
"host": {
"platform": "Linux-6.1.0-54-cloud-amd64-x86_64-with-glibc2.36",
"machine": "x86_64",
"affinity": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"node": "v24.18.0",
"npm": "11.16.0",
"objcopy": "GNU objcopy (GNU Binutils for Debian) 2.40",
"readelf": "GNU readelf (GNU Binutils for Debian) 2.40",
"python": "3.11.2"
},
"artifacts": [
{
"package": "ripgrep-14.1.1-r0.apk",
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/ripgrep-14.1.1-r0.apk",
"package_sha256": "213dd0b047163f6627cc2fdd6fbfe205009476eb4fc48c352a3f5b9ab647f6d4",
"package_bytes": 1422830,
"member": "usr/bin/rg",
"bytes": 3416168,
"sha256": "78387bbe12b2110df8d2ce364390a00c190f4543a2f40b8279e49f6c5c85af9c",
"aports_revision": "4e899d119e4c983a938436d6f397a509521225d1"
},
{
"package": "zellij-0.40.0-r0.apk",
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/zellij-0.40.0-r0.apk",
"package_sha256": "575075945415b221857563e19c5f4c661af9f98bbba54f0994c7385c0e432432",
"package_bytes": 6722129,
"member": "usr/bin/zellij",
"bytes": 17151864,
"sha256": "9a86e9ca47902a869fdf1886f4cf17714f4fa5655427128beadff498cfb392db",
"aports_revision": "3be26a3a26e76e1bf3eff784bffe3956d29d77a0"
}
],
"oracle": [
{
"file": "ripgrep",
"packages": 42,
"edges": 88,
"reply_bytes": 10605,
"root": 33,
"upstream_json_sha256": "ea478aa80f91aa8f5159aba648ddfdda642fa2c98759e54e65623a19065634be",
"evidence_id": "ev_be5662081d3c5b609a5e65a78c732674d2fcc160e5b1173ca0d083316673eaa4",
"matched": true
},
{
"file": "zellij",
"packages": 422,
"edges": 1063,
"reply_bytes": 86805,
"root": 417,
"upstream_json_sha256": "9bcf8d694440a7e064189616aa40b69046e32ee1c70683ef2e0778150b9163ef",
"evidence_id": "ev_8c9124795a5cf45c2bf816bbfa0ced9c3025550e72d4e3facf5fb2e266d3dadb",
"matched": true
}
],
"root_cycle_oracle": [
{
"file": "ripgrep",
"root_index_independently_matched": 33,
"independent_topological_visit_count": 42,
"acyclic": true
},
{
"file": "zellij",
"root_index_independently_matched": 417,
"independent_topological_visit_count": 422,
"acyclic": true
}
],
"tokenizer": {
"tiktoken": "0.12.0",
"encoding": "cl100k_base",
"actual_model_usage": null,
"projection": "All discovered tool descriptions/input schemas once, plus every call argument and text reply including export. Selected-two-tool and complete transport catalog costs are separate. No model, host injection, history replay or retry observed."
},
"samples": [
{
"case": "r1-zellij-before",
"wall_seconds": 5.77,
"cpu_seconds": 5.626476,
"kernel_family_peak_bytes": 644210688,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "f2bd86aedd299c4fe16cca470eeaf135b6553c1bfe345018def503424c5661a6",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "bca9b5474a2124a99535c4aca0c38d9c77146d508822904aae4adc458b05b10e",
"r1-zellij-before.log": "79acdc6c966345d59ba88aafd22b5fb11cdd759c62611e720a47aef308ef070c"
},
"calls_ms": [397.233, 103.9],
"reply_bytes": [185833, 310],
"combined_tokens": 223755
},
{
"case": "r2-zellij-before",
"wall_seconds": 4.957,
"cpu_seconds": 4.781552,
"kernel_family_peak_bytes": 636317696,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "f3f58187dc63116f02586b2b3403848a8a88f6f72c5e322d0be00c9fcb2b7710",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "718aa02bf3fd74099b44418ac220e639719495b3e718357fb4e34748e5499e41",
"r2-zellij-before.log": "3e36a14c0b7e8d4d6e6f8b4b024c090133773f2699a8737d17dc5352b5f490ac"
},
"calls_ms": [380.788, 40.185],
"reply_bytes": [185833, 310],
"combined_tokens": 223755
},
{
"case": "r3-zellij-before",
"wall_seconds": 4.854,
"cpu_seconds": 4.693446,
"kernel_family_peak_bytes": 639963136,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "75d54eb72935201b8946d83a3bef98d93dc1219022bf735935d42934069b7dea",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "940eb7899a07a1115402fbc7b89010b463723b26e129f62a51263b9c9e598b1f",
"r3-zellij-before.log": "0f266f87cf5f2984ac10964f3255ebb76a706695256683c7f80c071b84d1f3a6"
},
"calls_ms": [340.588, 30.985],
"reply_bytes": [185833, 310],
"combined_tokens": 223755
},
{
"case": "r1-zellij-after",
"wall_seconds": 6.277,
"cpu_seconds": 6.056786,
"kernel_family_peak_bytes": 637652992,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "8d34bbdba7cfedc58e281efc372cf6969def10bc99b022e4bc786d447daf4709",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "e09cc44b0ce78a74be6cc9d7c90a78e2173514e5a4cc1dd9f02e5a728c880600",
"r1-zellij-after.log": "a90915676741794e52568dd30604a4e306ebfec77df37982bef6ea3d4411e405"
},
"calls_ms": [517.341, 43.422],
"reply_bytes": [9261, 308],
"combined_tokens": 199584
},
{
"case": "r2-zellij-after",
"wall_seconds": 5.671,
"cpu_seconds": 5.441365,
"kernel_family_peak_bytes": 633688064,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "425d70965ad49048d01a88473c1fb4790a7ef6f4ceb1c5374a652fc239fa96e5",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "96d3bc1798136135beed8e7f2b4eac626124655fb01ca1cdfe1040786ddf9efa",
"r2-zellij-after.log": "74fed3f25bd859ce4f6d4f4e7a6d3aaf31c9f079f730427fffa489a003adcfaa"
},
"calls_ms": [473.788, 28.945],
"reply_bytes": [9261, 308],
"combined_tokens": 199584
},
{
"case": "r3-zellij-after",
"wall_seconds": 5.054,
"cpu_seconds": 4.816254,
"kernel_family_peak_bytes": 622559232,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "349360e9b1c19faaa796106a70a26fd5711b804af807fc1b9c98a6e012530f9e",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "12fc240871df42621b8e80efdb6451136ea317ae364f7c6741aed348a12a73a9",
"r3-zellij-after.log": "18d9edcae6ac46c4034b958ba2e607a524aaf80dfbc671a5fd654f6390ee5c3f"
},
"calls_ms": [359.933, 34.275],
"reply_bytes": [9261, 308],
"combined_tokens": 199584
},
{
"case": "r1-ripgrep-before",
"wall_seconds": 4.859,
"cpu_seconds": 4.643315,
"kernel_family_peak_bytes": 599961600,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "2918f88999120cfba046ad5dc615d12bcc98c034c767a00edb3631a2b8bd622a",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "aa98de6fb6cbd24672e3a5b8aaf22ff1f977276d00c14ebd32e90501bd9eb4aa",
"r1-ripgrep-before.log": "a3963e991e1286e84a03f69f2d7e8a7e313ea262123269777599d93b7e529153"
},
"calls_ms": [181.636, 30.005],
"reply_bytes": [22643, 312],
"combined_tokens": 200603
},
{
"case": "r2-ripgrep-before",
"wall_seconds": 5.057,
"cpu_seconds": 4.853134,
"kernel_family_peak_bytes": 596680704,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "35f8de985a6201efc40f629195a9a801d2f333d605a46220bd4ae313c35f2abd",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "bed19b9d031b3416bc79870fb8d94ed0a2376004dfb9b69b78c56bd4a38912ff",
"r2-ripgrep-before.log": "ec0a5b2933307131af7435d737dde4a72ad3c92f3923e46aaaae7b0b7b7b1fd3"
},
"calls_ms": [189.36, 29.044],
"reply_bytes": [22643, 312],
"combined_tokens": 200603
},
{
"case": "r3-ripgrep-before",
"wall_seconds": 4.753,
"cpu_seconds": 4.542499,
"kernel_family_peak_bytes": 589565952,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "daaf848260488ef347afc4c600fe89c2902d6a68fa65085c0fcae7a20d7c6d17",
"catalog.json": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"transcript.json": "43e570e7421f14aaaf7dc92b8c4a69d12a221851c7e813587eda8ca73e0b1d7f",
"r3-ripgrep-before.log": "6f88e1abbe8ebb8a68cf3a5d678c227516c8506029b0114094b1a42bfd056971"
},
"calls_ms": [201.092, 30.174],
"reply_bytes": [22643, 312],
"combined_tokens": 200603
},
{
"case": "r1-ripgrep-after",
"wall_seconds": 5.159,
"cpu_seconds": 4.945401,
"kernel_family_peak_bytes": 609128448,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "14294e20d39e6413e1dac9836a01dd498b7a669c4fa0a9708fd131452a0b6881",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "dd32475dc2205d3574a0496730a9c60751464c2fcc4a955051e889f1b0547c0d",
"r1-ripgrep-after.log": "dd22e6f0e9eac449e0485a67ae79839958109e5331f4872aa9a527a51aae1766"
},
"calls_ms": [244.153, 30.492],
"reply_bytes": [9207, 310],
"combined_tokens": 199584
},
{
"case": "r2-ripgrep-after",
"wall_seconds": 4.757,
"cpu_seconds": 4.528198,
"kernel_family_peak_bytes": 615854080,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "d45dc890ea69f370b47ccefc2b454dc1d0c3baf2beafd0fbe36ffb91c890e8e3",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "2b060caea34407a0c5c18fac3cad43a4dd048eee54bb760dd56ca95665b6a492",
"r2-ripgrep-after.log": "36cdb1176e1fdeef6e00f360c1a36ddb97506fcd31b426ee9be746da9ece7535"
},
"calls_ms": [249.866, 28.919],
"reply_bytes": [9207, 310],
"combined_tokens": 199584
},
{
"case": "r3-ripgrep-after",
"wall_seconds": 4.758,
"cpu_seconds": 4.512774,
"kernel_family_peak_bytes": 615874560,
"completed_with_verified_cleanup": true,
"sha256": {
"report.json": "61ec33fe49c943a71177562212d255780ee6cf4cce273c1733a9ae6c138c6167",
"catalog.json": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"transcript.json": "eb48ed154e646e3aa1ea9cb1aae99e363bb68ac122777ba4f3ac63d84547db37",
"r3-ripgrep-after.log": "34fd034def0ab7e0c22e9aa89aaaef5f5cc58a9125606400d8e866023578432b"
},
"calls_ms": [224.772, 27.335],
"reply_bytes": [9207, 310],
"combined_tokens": 199584
}
],
"resource_limits": {
"cpu_quota_percent": 100,
"affinity": [15],
"hard_memory_gib": 4,
"early_stop_gib": 3,
"swap_bytes": 0,
"tasks": 192,
"host_preflight_gib": 8,
"host_stop_gib": 4
},
"cache_state": "Fresh process/session every task; unflushed OS caches, controller hashes inputs first; not cold disk.",
"failed_attempts": [
{
"attempt": "baseline runtime build and cached retry",
"outcome": "Commands exited 0; cleanup counters unqualified due extra observed child; no qualified build resource claim. Actual CLI and SDK tasks separately qualify."
},
{
"attempt": "prototype source typechecks",
"outcome": "Failed on new optional subject and expanded result-union typings; fixed and passed final check."
},
{
"attempt": "prototype static gate",
"outcome": "New acceptance function exceeded line limit; refactored and passed."
},
{
"attempt": "first one-CPU acceptance cohort",
"outcome": "Existing cross-Go/Rust acceptance test timed out at its unchanged 30s limit. All nine CLI acceptance tests passed with a bounded two-CPU/4-GiB cohort; no timeout change."
}
],
"rejected_prototype": {
"change": "Replace native JSON.stringify report sizing with shared JS byte counter",
"provider_first_repeat_ms": {
"provider-ripgrep-candidate": {
"first_ms": 76.985,
"repeat_median_ms": 20.225
},
"provider-zellij-baseline": {
"first_ms": 181.069,
"repeat_median_ms": 92.804
},
"provider-ripgrep-baseline": {
"first_ms": 60.221,
"repeat_median_ms": 18.323
},
"provider-zellij-candidate": {
"first_ms": 183.659,
"repeat_median_ms": 76.214
}
},
"decision": "No consistent CPU or memory advantage established across both real artifacts; reverted. Final provider file is byte-identical to baseline."
},
"diagnostic_phase_controls": {
"driver_sha256": "eeca9644b4b7199e45e49447bf83f7b33ac60c9ce1c38908b06f59d72e3b7485",
"method": "Same task driver plus performance.now around client.connect and listTools; baseline complete, candidate complete and candidate selected in forward/reverse order. Private instrumented driver, not the primary cohort.",
"samples": [
{
"case": "control1-baseline",
"connect_ms": 2515.583,
"catalog_ms": 1676.58,
"calls_ms": [431.356, 57.797],
"wall_seconds": 5.46,
"cpu_seconds": 5.243091,
"kernel_family_peak_bytes": 644329472,
"qualified": true,
"report_sha256": "badff5397d47a240272feb4bd2348e3122d5482eea116933b29eddd740768955",
"log_sha256": "9b0eb1bfe51e932a4e8978d165fc26f213b003ee4452f704da1f9ce813830850"
},
{
"case": "control1-candidate-complete",
"connect_ms": 3086.124,
"catalog_ms": 1726.06,
"calls_ms": [485.865, 30.696],
"wall_seconds": 6.164,
"cpu_seconds": 5.94611,
"kernel_family_peak_bytes": 634658816,
"qualified": true,
"report_sha256": "ac65641dcf7dd556017348860484b2a0485628803ae2f9e619cee40007fe8aff",
"log_sha256": "457a2dee6f38d2f57b4616c5b1138abc26c18235844fa4589e89b4f15ca4abf8"
},
{
"case": "control1-candidate-selected",
"connect_ms": 3057.187,
"catalog_ms": 1677.082,
"calls_ms": [431.301, 32.986],
"wall_seconds": 6.063,
"cpu_seconds": 5.798686,
"kernel_family_peak_bytes": 639565824,
"qualified": true,
"report_sha256": "ef8e46c78e81f13c84f1bf0a8a8284078252d5af16302a716144149c136f89f5",
"log_sha256": "8d72501364d0a95fb250e2d0486b9b34076688309748b8dc054a75681e8543e0"
},
{
"case": "control2-baseline",
"connect_ms": 2678.85,
"catalog_ms": 1467.674,
"calls_ms": [413.185, 36.964],
"wall_seconds": 5.364,
"cpu_seconds": 5.183688,
"kernel_family_peak_bytes": 643944448,
"qualified": true,
"report_sha256": "d7e3318331528e1201fd1efc7cdc4edb0624a32e6c6db501504cc77f49374157",
"log_sha256": "d61f437e5c88b2eb994e1f716c05915f0955d5877b1d34207fd39e3d36f85970"
},
{
"case": "control2-candidate-complete",
"connect_ms": 2527.714,
"catalog_ms": 1545.887,
"calls_ms": [436.068, 30.776],
"wall_seconds": 5.36,
"cpu_seconds": 5.159006,
"kernel_family_peak_bytes": 635162624,
"qualified": true,
"report_sha256": "fb432b0f5eeedc74e66144c136954e580f5efb5b86a921ace3bf29477e90f0aa",
"log_sha256": "4e7485b8b2a5bb0619f0e84df96b68e02827e2afee11533e0f12532c12fd84f7"
},
{
"case": "control2-candidate-selected",
"connect_ms": 2642.283,
"catalog_ms": 1465.879,
"calls_ms": [424.082, 32.29],
"wall_seconds": 5.362,
"cpu_seconds": 5.110599,
"kernel_family_peak_bytes": 637669376,
"qualified": true,
"report_sha256": "2d4b794902c9fd76b11a2f7ae87bb06be3cc9effadfb161f6f4debb9044ebcfd",
"log_sha256": "e98cc393c717ae19bb0c880d609a05e2f2d4249701f245c19b2f030b8be305fb"
}
]
},
"cohort_facts": {
"zellij-complete": {
"answer": {
"packages": [
{
"index": 294,
"name": "serde",
"version": "1.0.137",
"source": "crates.io",
"dependencies": [296]
},
{
"index": 296,
"name": "serde_derive",
"version": "1.0.137",
"source": "crates.io",
"dependencies": [256, 264, 331]
}
],
"direct_edges": [
{
"from_index": 294,
"to_index": 296
}
]
},
"parent_evidence_id": "ev_8c9124795a5cf45c2bf816bbfa0ced9c3025550e72d4e3facf5fb2e266d3dadb",
"artifact_sha256": "9a86e9ca47902a869fdf1886f4cf17714f4fa5655427128beadff498cfb392db",
"complete_result_sha256": "a9ce92f47aaa92fb0edc1c288d06c298ae623d731499fd4cedb86ab9c2bbb483",
"catalog_sha256": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"catalog_tools": 212,
"token_proxy": {
"input_catalog_once": 197359,
"arguments": 58,
"text_replies": 26338,
"combined": 223755,
"only_two_selected_tools": 26850,
"complete_transport_catalog": 886882
}
},
"zellij-selected": {
"answer": {
"packages": [
{
"name": "serde",
"version": "1.0.137",
"source": "crates.io",
"dependencies": [296],
"index": 294
},
{
"name": "serde_derive",
"version": "1.0.137",
"source": "crates.io",
"dependencies": [256, 264, 331],
"index": 296
}
],
"direct_edges": [
{
"from_index": 294,
"to_index": 296
}
]
},
"parent_evidence_id": "ev_8c9124795a5cf45c2bf816bbfa0ced9c3025550e72d4e3facf5fb2e266d3dadb",
"artifact_sha256": "9a86e9ca47902a869fdf1886f4cf17714f4fa5655427128beadff498cfb392db",
"complete_result_sha256": "a9ce92f47aaa92fb0edc1c288d06c298ae623d731499fd4cedb86ab9c2bbb483",
"catalog_sha256": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"catalog_tools": 212,
"token_proxy": {
"input_catalog_once": 198278,
"arguments": 83,
"text_replies": 1223,
"combined": 199584,
"only_two_selected_tools": 2256,
"complete_transport_catalog": 891294
}
},
"ripgrep-complete": {
"answer": {
"packages": [
{
"index": 36,
"name": "serde",
"version": "1.0.210",
"source": "crates.io",
"dependencies": []
}
],
"direct_edges": []
},
"parent_evidence_id": "ev_be5662081d3c5b609a5e65a78c732674d2fcc160e5b1173ca0d083316673eaa4",
"artifact_sha256": "78387bbe12b2110df8d2ce364390a00c190f4543a2f40b8279e49f6c5c85af9c",
"complete_result_sha256": "9eb75332f423bcb94f627694cde96fb3f39ce7d47313468e027e8ccf03c38f72",
"catalog_sha256": "411381fb2417945f8a29eb8db330f82b44d8790d3f1e88c7f766b532c528008f",
"catalog_tools": 212,
"token_proxy": {
"input_catalog_once": 197359,
"arguments": 62,
"text_replies": 3182,
"combined": 200603,
"only_two_selected_tools": 3698,
"complete_transport_catalog": 886882
}
},
"ripgrep-selected": {
"answer": {
"packages": [
{
"name": "serde",
"version": "1.0.210",
"source": "crates.io",
"dependencies": [],
"index": 36
}
],
"direct_edges": []
},
"parent_evidence_id": "ev_be5662081d3c5b609a5e65a78c732674d2fcc160e5b1173ca0d083316673eaa4",
"artifact_sha256": "78387bbe12b2110df8d2ce364390a00c190f4543a2f40b8279e49f6c5c85af9c",
"complete_result_sha256": "9eb75332f423bcb94f627694cde96fb3f39ce7d47313468e027e8ccf03c38f72",
"catalog_sha256": "ba99f10270e8ab53d765f124856921f59156482d603fa883240e1b4068eab921",
"catalog_tools": 212,
"token_proxy": {
"input_catalog_once": 198278,
"arguments": 87,
"text_replies": 1219,
"combined": 199584,
"only_two_selected_tools": 2256,
"complete_transport_catalog": 891294
}
}
},
"accounting": "Kernel cgroup CPU/peak memory at payload exit while an observer remains alive, with verified exclusive observer and cleanup. Final whole-scope counters unavailable; observer overhead is included. All qualified samples have zero high/max/OOM/pid events.",
"published_equivalent_source": "cc83fd1ee3827861ea94d0e8f325e4521323bc89",
"integration_base": "a5f982de8dbe4b092cc20ab89d492b7e7028b758",
"candidate_reproduction": "Primary paired cohort uses candidate_code on baseline_source. Integration rebase production files are byte-identical to published_equivalent_source; original measured commit was pushed before rebase. Driver/counter/resource hashes and runtime tables are pinned separately."
}
@@ -0,0 +1,176 @@
# Rust dependency fact views
Issue [#5802](https://github.com/morluto/rea/issues/5802) asks for exact embedded
crate versions, sources and selected direct relationships without receiving the
entire dependency report. The existing path-only operation returned every
package, dependency vector and edge. This work adds opt-in first-call selection,
summary delivery and retained package/edge queries to the existing CLI/MCP
contracts. Complete delivery remains the default.
The producer still reads and validates the complete admitted file and report.
This is a context optimization; it does not establish faster binary analysis or
lower peak memory. It adds no tool, path cache, dependency walker or graph index.
Only successful interpretation of an authenticated immutable Evidence result is
reused, keyed by that exact object in a WeakMap for its lifetime. Mutable imports
are revalidated, and every view checks the artifact subject again.
## Pinned artifacts and independent checks
[Curated receipts](rust-dependency-views.json) pin source commits, runtime file
tables, source/driver/resource hashes, package URLs and all measured samples.
Baseline is `d672cc23998b6cbb21c485f118595ebfb82fed33`; measured candidate is
`cae2571e7e64a7364b94bb92c2630cba80ec4478`, pushed before the integration rebase.
The production files are byte-identical to published
`cc83fd1ee` after rebasing onto `a5f982de8`. Later integration measurements do
not relabel the original cohort.
Official Alpine v3.21/community/x86_64 APKs, not locally rebuilt test applications:
| Artifact | File bytes | Packages / edges | Complete CLI reply bytes |
| ---------------------------------- | ---------: | ---------------: | -----------------------: |
| ripgrep 14.1.1-r0, `usr/bin/rg` | 3,416,168 | 42 / 88 | 10,605 |
| Zellij 0.40.0-r0, `usr/bin/zellij` | 17,151,864 | 422 / 1,063 | 86,805 |
The [corpus manifest](corpus.json) records APK and extracted-file SHA-256,
transport size and aports revision. Acquisition selected only the named regular
tar member, with bounded download/extraction. Neither binary was executed.
HTTPS provenance and recorded digests are not an APK-signature verification.
GNU objcopy/readelf 2.40 independently extracted `.dep-v0` into private output
files without overwriting the input. Python 3.11.2 zlib/JSON parsing, following
the [upstream parsing format](https://github.com/rust-secure-code/cargo-auditable/blob/master/PARSING.md),
matched every exposed package field and dependency edge, omitted revision,
root index, compressed/inflated length, exact section range and full artifact
digest. An independent topological pass visited all 42/422 packages and matched
the empty cyclic sets. This qualifies these two ELF artifacts; PE/Mach-O view
coverage below uses constructed format fixtures, not real shipped applications.
## Equivalent completed SDK tasks
Linux x64, Node 24.18.0/npm 11.16.0. Three alternating before/after,
after/before, before/after pairs per artifact run serially. Each fresh production
stdio SDK session discovers all 212 tools and uses their named output schemas.
OS caches are unflushed; the controller hashes inputs first. These are fresh
process/session runs, not cold-disk runs.
The fixed question is: which versions and sources of `serde` and `serde_derive`
are embedded, and is there a direct edge between those selected packages?
Both lanes make exactly two RPCs: inspect once, then export complete Evidence.
Baseline derives the answer from the complete reply. Candidate selects both
names and `name/version/source/dependencies` in the initial inspection. There
are no extra lookup calls. All runs compare the answer and exported complete
parent against the independently checked report, including identical parent
Evidence IDs and source SHA-256. Zellij has the direct edge 294 → 296;
ripgrep has only `serde`, with no such edge within the embedded records.
Medians of three whole tasks, including startup, discovery, SDK validation,
both calls, answer/export checks and teardown:
| Artifact | Wall seconds, before → after | Family CPU seconds, before → after | Peak family MiB, before → after |
| -------- | ---------------------------: | ---------------------------------: | ------------------------------: |
| Zellij | 4.957 → 5.671 | 4.782 → 5.441 | 610.3 → 604.3 |
| ripgrep | 4.859 → 4.758 | 4.643 → 4.528 | 569.0 → 587.3 |
The main cohort's Zellij elapsed/CPU medians increase about 14%; do not read the
context improvement as a whole-task speedup. Two further forward/reverse
diagnostic controls added clocks around connect and discovery, comparing
baseline complete, candidate complete and candidate selected. In the first
control these took 5.460/6.164/6.063 seconds, with connect times
2.516/3.086/3.057 seconds. In the reverse control all took 5.360–5.364 seconds,
with connect times 2.528–2.679 seconds. Candidate complete also varied, so those
controls do not isolate a persistent regression to compact projection. They
do not erase the original slower cohort. No stable speedup, peak-memory
improvement or exact cause of startup variation is established.
Initial inspector RPC medians were 380.8 → 473.8 ms for Zellij and
189.4 → 244.2 ms for ripgrep. These include first-use named SDK schema validation,
retention, projection and serialization; they are not isolated parser timings.
Extra retained reads need no source reopen, as the CLI/MCP regressions demonstrate;
their warm latency was not benchmarked.
## Context accounting and limits
Offline tiktoken 0.12.0, `cl100k_base`, counts all discovered tool descriptions
and input schemas once, every call argument and every text reply, including
export. It counts the whole scripted task rather than only the selected reply:
| Artifact | All 212 input definitions + calls, before → after | Only the two used definitions + calls, before → after |
| -------- | ------------------------------------------------: | ----------------------------------------------------: |
| Zellij | 223,755 → 199,584 (−10.8%) | 26,850 → 2,256 (−91.6%) |
| ripgrep | 200,603 → 199,584 (−0.5%) | 3,698 → 2,256 (−39.0%) |
The Zellij inspect response, including both MCP text and structured content,
shrinks from 185,833 to 9,261 bytes; ripgrep shrinks from 22,643 to 9,207.
Those byte counts alone are not token measurements. Full transport catalogs,
including all output schemas, grow from 886,882 to 891,294 estimated tokens.
If all of that transport metadata is charged once along with calls, totals are
913,278 → 892,600 for Zellij and 890,126 → 892,600 for ripgrep: the small case
then costs more. Schema expansion has a cost even when Rust is not called.
No AI inference, actual model usage/billing, host schema injection, prompt-history
replays or analyst answer quality was measured. The two-definition column is a
discovery scenario, not an observed routed host. Do not extrapolate these
proxies to a host that repeats schemas or truncates full replies. Opt-in
delivery preserves caller choice and avoids forcing the small-case tradeoff.
## Correctness, resource bounds and rejected changes
Original-index facts, all matching versions, exact intersecting batch filters,
empty selections, selected fields and matching-row pagination share one domain
projector through the existing application layer. Package defaults omit
dependency vectors; callers explicitly request them for direct references.
Edges support both endpoint filters. Complete parent Evidence stays available
for export, and portable/retained queries still work after deleting the source.
Missing metadata remains unknown, cycles are counted without walking paths,
compiler hashes remain inferred, and embedded coverage never implies complete
runtime dependencies. Defaults preserve complete Evidence identity.
Executed tests cover real CLI/stdio SDK schemas, ELF/PE/thin-Mach-O fixtures,
duplicate package names/versions, retained and portable imports, malformed
edge imports, subject mismatch, unknown metadata, contradictory delivery,
retention requirements, bounded warnings and immutable-cache trust. Existing
Go, malformed Rust, cancellation, capacity and cleanup coverage is retained.
Full typecheck/lint/format/knip/module/architecture checks passed before the
integration rebase; final integration checks are recorded in the PR.
Runtime tasks use one pinned CPU, 4-GiB hard/3-GiB early-stop family limits,
no swap, 192 tasks and host-memory preflight/stop guards. All 12 primary and
six diagnostic samples have verified payload exit/cleanup and zero high/max/
OOM/pid events. CPU and peak memory use cgroup counters at payload exit while
the observer remains alive; final whole-scope counters are unavailable.
Build/check guards use 6/8-GiB hard bounds respectively. An existing 30-second
mixed Go/Rust test timed out in the initial single-CPU cohort; all nine CLI
acceptance tests passed with a bounded two-CPU/4-GiB rerun, without changing
the test's timeout. Other corrected prototype failures remain in the receipts.
A proposed JS JSON-size counter replacement had inconsistent provider timing
and no demonstrated memory benefit on these artifacts. It was reverted; the
final Rust provider is unchanged. Full-file input allocation, complete report
construction, ledger retention and schema duplication remain costs. This issue
does not qualify every Rust workflow, larger graphs, real PE/Mach-O applications,
WASM support or current runtime dependency state.
## Reproduction
Acquire the exact public APK URLs from the manifest and extract only the named
regular binary. Verify both SHA-256 values before inspection. Use objcopy
`--dump-section .dep-v0=SECTION INPUT PRIVATE_COPY`, then Python zlib/JSON to
compare the normalized package/edge records and section bytes. Save the verified
complete CLI `normalized_result` as `EXPECTED`.
Build isolated baseline/candidate runtimes with the pinned toolchain. Keep raw
outputs outside Git. For each artifact, alternate three serial pairs:
```bash
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/rust-dependency-views.mjs \
--runtime /absolute/runtime --artifact /absolute/binary \
--expected /absolute/expected-report.json --output /absolute/new-case \
--lane complete
# Candidate uses --lane selected. Both lanes include complete Evidence export.
```
With private `tiktoken==0.12.0`, run
`rust-dependency-costs.py --output FILE CASE...`; each `CASE.log` must contain
qualified guard observations. The counter rejects failed tasks, missing
cleanup and resource events. Guard-stopped attempts are not completed samples.
@@ -0,0 +1,172 @@
/** Equivalent selected dependency questions; raw artifacts/transcripts stay outside Git. */
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { finished } from "node:stream/promises";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
const { values } = parseArgs({
options: {
...Object.fromEntries(
["runtime", "artifact", "expected", "output"].map((name) => [
name,
{ type: "string" },
]),
),
lane: { type: "string" },
},
});
for (const name of ["runtime", "artifact", "expected", "output"])
assert.ok(values[name] && values[name] === resolve(values[name]), name);
assert.ok(["complete", "selected"].includes(values.lane));
const hash = (value) => createHash("sha256").update(value).digest("hex");
const expected = JSON.parse(await readFile(values.expected, "utf8"));
assert.equal(expected.artifact.path, values.artifact);
const names = ["serde", "serde_derive"];
const fields = ["name", "version", "source", "dependencies"];
const choose = (report) =>
(report.auditable_metadata?.packages ?? []).flatMap((row, index) =>
names.includes(row.name)
? [
{
index,
...Object.fromEntries(fields.map((field) => [field, row[field]])),
},
]
: [],
);
const answer = (rows) => {
const targets = new Set(
rows.filter((row) => row.name === "serde_derive").map((row) => row.index),
);
return {
packages: rows,
direct_edges: rows
.filter((row) => row.name === "serde")
.flatMap((row) =>
row.dependencies
.filter((index) => targets.has(index))
.map((to_index) => ({ from_index: row.index, to_index })),
),
};
};
const expectedRows = choose(expected);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-rust-views-"));
const client = new Client({ name: "rust-dependency-views", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
env: {
HOME: account,
PATH: process.env.PATH,
NODE_OPTIONS: "--max-old-space-size=2048",
UV_THREADPOOL_SIZE: "1",
REA_LOG_LEVEL: "silent",
TMPDIR: account,
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log"));
const serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const report = {
lane: values.lane,
artifact_sha256: hash(await readFile(values.artifact)),
normalized_sha256: hash(JSON.stringify(expected)),
calls: [],
};
const transcript = [];
const call = createRetainedQueryCaller(client, (receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
});
const observe = async (name, input, phase) => {
const reply = await call(name, input, phase);
assert.notEqual(reply.isError, true, JSON.stringify(reply));
assert.ok(reply.structuredContent);
return reply.structuredContent;
};
try {
await client.connect(transport);
const catalog = await client.listTools();
assert.equal(catalog.nextCursor, undefined);
report.catalog_tools = catalog.tools.length;
report.catalog_sha256 = hash(JSON.stringify(catalog));
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
const definitions = new Map(catalog.tools.map((tool) => [tool.name, tool]));
const original = client.callTool.bind(client);
client.callTool = (input, options) =>
original(input, {
...options,
toolDefinition: definitions.get(input.name),
});
const selected = values.lane === "selected";
const reply = await observe(
"inspect_rust_binary",
{
path: values.artifact,
...(selected ? { view: { collection: "packages", names, fields } } : {}),
},
"question",
);
const parentId = selected
? reply.normalized_result.parent_evidence_id
: reply.evidence_id;
const rows = selected
? reply.normalized_result.page.rows
: choose(reply.normalized_result);
assert.deepEqual(rows, expectedRows);
report.answer = answer(rows);
assert.deepEqual(report.answer, answer(expectedRows));
report.parent_evidence_id = parentId;
await observe(
"export_evidence_bundle",
{ path: join(values.output, "evidence.json") },
"export",
);
const bundle = JSON.parse(
await readFile(join(values.output, "evidence.json"), "utf8"),
);
const parent = bundle.records.find(
(record) => record.evidence_id === parentId,
);
assert.ok(parent);
assert.deepEqual(parent.normalized_result, expected);
assert.equal(parent.subject.digest.sha256, report.artifact_sha256);
if (selected)
assert.deepEqual(reply.normalized_result.source_coverage, {
auditable_metadata_available: expected.auditable_metadata !== null,
embedded_records:
expected.auditable_metadata === null ? "unknown" : "complete",
runtime_dependencies: "unknown",
});
assert.equal(hash(await readFile(values.artifact)), report.artifact_sha256);
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.failure = String(cause?.stack ?? cause);
process.exitCode = 1;
} finally {
if (
!(await finalizeRetainedQueryReceipt({
report,
client,
transport,
account,
serverLog,
serverLogFinished,
output: values.output,
transcript,
}))
)
process.exitCode = 1;
}
+69 -2
View File
@@ -4,7 +4,7 @@ import {
AnalysisInputError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { parseEvidence } from "../../domain/evidence.js";
import { parseEvidence, type Evidence } from "../../domain/evidence.js";
import type { RustBinary } from "../../domain/rust/rustBinary.js";
import { ok } from "../../domain/result.js";
import { RustBinaryService } from "./RustBinaryService.js";
@@ -59,7 +59,7 @@ it("rejects a relative path and extra arguments before reading", async () => {
const relative = await service.inspect({ path: "program" });
const extra = await service.inspect({
path: report.artifact.path,
detail: "complete",
unrecognized: true,
});
expect(relative.ok).toBe(false);
expect(extra.ok).toBe(false);
@@ -221,3 +221,70 @@ it.each([
expect(reason.cleanupIncomplete).toBe(true);
},
);
it("requires retention and rejects contradictory detail before invoking the reader", async () => {
let calls = 0;
const service = new RustBinaryService({
identity,
inspect: async () => {
calls++;
return ok(report);
},
});
const missing = await service.inspect({
path: report.artifact.path,
detail: "summary",
});
expect(missing.ok).toBe(false);
const conflict = await service.inspect({
path: report.artifact.path,
detail: "complete",
view: { collection: "packages" },
});
if (conflict.ok) throw new Error("Contradictory delivery was accepted");
expect(conflict.error).toBeInstanceOf(AnalysisInputError);
expect(calls).toBe(0);
});
it("retains the unchanged parent and keeps delivery selectors out of provider inputs", async () => {
const inputs: unknown[] = [];
const service = new RustBinaryService({
identity,
inspect: async (input) => {
inputs.push(input);
return ok(report);
},
});
const first = await service.inspect({ path: report.artifact.path });
const explicit = await service.inspect({
path: report.artifact.path,
detail: "complete",
});
if (!first.ok || !explicit.ok) throw new Error("Complete delivery failed");
expect(explicit.value).toEqual(first.value);
const records = new Map<string, Evidence>();
const selected = await service.inspect(
{
path: report.artifact.path,
view: { collection: "packages", names: ["sample"], fields: ["name"] },
},
undefined,
{
recordEvidence: (evidence) => {
records.set(evidence.evidence_id, evidence);
return ok("added" as const);
},
evidenceById: (id) => records.get(id),
},
);
if (!selected.ok) throw selected.error;
expect(selected.value.normalized_result).toMatchObject({
kind: "rust",
parent_evidence_id: first.value.evidence_id,
page: { collection: "packages", rows: [{ index: 0, name: "sample" }] },
});
expect(records.get(first.value.evidence_id)).toEqual(first.value);
expect(inputs).toEqual(
Array.from({ length: 3 }, () => ({ path: report.artifact.path })),
);
});
+29 -20
View File
@@ -8,30 +8,22 @@ import {
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { BINARY_ARCHITECTURES } from "../../domain/binaryTargetTypes.js";
import {
inspectRustBinaryInputSchema,
rustArchitectureForSubject,
rustBinarySchema,
} from "../../domain/rust/rustBinary.js";
import { inspectRustBinaryRequestSchema } from "../../domain/rust/rustBinaryQuery.js";
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
import { err, ok, type Result } from "../../domain/result.js";
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
import {
requireAnalysisViewRetention,
projectRetainedAnalysis,
type AnalysisViewRetention,
} from "../analysisView/AnalysisViewService.js";
const OPERATION = "inspect_rust_binary";
const architectureForSubject = (architecture: string) => {
switch (architecture) {
case "386":
return "x86";
case "amd64":
return "x86_64";
default:
return BINARY_ARCHITECTURES.find(
(supported) => supported === architecture,
);
}
};
/** Shared exact input validation and artifact-bound Evidence for CLI and MCP. */
export class RustBinaryService {
constructor(readonly provider: RustBinaryPort) {}
@@ -44,12 +36,16 @@ export class RustBinaryService {
async inspect(
rawInput: unknown,
options?: ExecutionOptions,
retention: AnalysisViewRetention = {
recordEvidence: undefined,
evidenceById: undefined,
},
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted)
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
const input = inspectRustBinaryInputSchema.safeParse(rawInput);
const input = inspectRustBinaryRequestSchema.safeParse(rawInput);
if (!input.success)
return err(
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
@@ -66,7 +62,13 @@ export class RustBinaryService {
},
]),
);
const inspected = await this.provider.inspect(input.data, options);
const { detail, view, ...parameters } = input.data;
const compact = detail === "summary" || view !== undefined;
if (compact) {
const ready = requireAnalysisViewRetention(OPERATION, retention);
if (!ready.ok) return ready;
}
const inspected = await this.provider.inspect(parameters, options);
if (!inspected.ok) return inspected;
const report = rustBinarySchema.safeParse(inspected.value);
if (!report.success || report.data.artifact.path !== input.data.path)
@@ -76,10 +78,17 @@ export class RustBinaryService {
"Rust dependency reader returned malformed metadata or changed the selected artifact identity.",
),
);
return completeProviderEvidence(
evidenceFor(this.provider.identity, input.data, report.data),
const completed = completeProviderEvidence(
evidenceFor(this.provider.identity, parameters, report.data),
options?.signal,
);
return compact && completed.ok
? projectRetainedAnalysis(
completed.value,
retention,
view === undefined ? { kind: "summary" } : { kind: "rust", ...view },
)
: completed;
}
}
@@ -88,7 +97,7 @@ const evidenceFor = (
parameters: { readonly path: string },
value: ReturnType<typeof rustBinarySchema.parse>,
): Evidence => {
const architecture = architectureForSubject(value.architecture);
const architecture = rustArchitectureForSubject(value.architecture);
const section = value.auditable_metadata;
return createEvidence(
{
+68 -15
View File
@@ -2,7 +2,14 @@ import { z } from "incur";
import { createRustBinaryService } from "../composition/rust.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
import { logCliCommand } from "../cliLogging.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import {
analysisEvidenceDeliveryOptions,
deliverCliAnalysisEvidence,
} from "./analysisEvidenceDelivery.js";
import { inspectRustBinaryRequestSchema } from "../domain/rust/rustBinaryQuery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
@@ -19,22 +26,68 @@ export const registerRustCommands = (
args: z.object({
path: z.string().describe("Local ELF, PE or thin Mach-O image"),
}),
run: ({ args }) =>
withCommandCancellation((signal) => {
const service = createRustBinaryService();
return logCliCommand(logger, CLI_COMMANDS.inspectRustBinary, () =>
finishAnalysisCommand(
() =>
service.inspect(
{ path: absoluteCallerPath(args.path) },
{ signal },
options: z.object({
...analysisEvidenceDeliveryOptions,
detail: z
.enum(["complete", "summary"])
.optional()
.describe(
"Default complete; a view selects compact delivery with --evidence_output",
),
view: z
.string()
.optional()
.describe(
"JSON package or edge selection; retains complete Evidence with --evidence_output",
),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.inspectRustBinary, async () => {
let view: unknown;
try {
view =
options.view === undefined ? undefined : JSON.parse(options.view);
} catch (cause) {
return projectAnalysisError(
new AnalysisInputError("inspect_rust_binary", { cause }, [
{
path: ["view"],
reason: "invalid_format",
message: "view must be a JSON package or edge selection",
},
]),
);
}
const input = inspectRustBinaryRequestSchema.safeParse({
path: absoluteCallerPath(args.path),
...(options.detail === undefined ? {} : { detail: options.detail }),
...(view === undefined ? {} : { view }),
});
if (!input.success)
return projectAnalysisError(
analysisInputErrorFromIssues(
"inspect_rust_binary",
input.error.issues,
{ view },
),
);
const service = createRustBinaryService();
return deliverCliAnalysisEvidence({
run: () => service.inspect({ path: input.data.path }, { signal }),
service,
"rust-auditable",
"inspect_rust_binary",
providerId: "rust-auditable",
operation: "inspect_rust_binary",
signal,
),
);
}),
detail:
input.data.detail ??
(input.data.view === undefined ? "complete" : "summary"),
evidenceOutput: options.evidence_output,
...(input.data.view === undefined
? {}
: { view: { kind: "rust" as const, ...input.data.view } }),
});
}),
),
});
};
+1 -1
View File
@@ -17,7 +17,7 @@ export const ANALYSIS_VIEW_TOOL_CONTRACTS = [
...toolContractMetadata("inspect_analysis_view"),
kind: "application",
description:
'Inspect selected views of completed Frida session status, GDB/objdump command observations, Go build metadata, binary-layout, JavaScript application, native analyze_function, inspect_artifact, inventory_artifact, inspect_wasm_artifact, inspect_macho, decode_interface_builder, inspect_evm_interface, inspect_managed_artifact, inspect_managed_members, inspect_android_package, inspect_dart_aot, inspect_il2cpp_metadata, inspect_model_artifact or inspect_web_network_capture Evidence without repeating analysis. Command summary views preserve ordered GDB records or opaque objdump stdout/stderr once, source parameters, completion facts and parent identity. They do not establish current target state. Historical capture summaries return identity and counts without bodies or archive inventories. Capture views page retained records: view {kind: "capture", detail: "preview" | "complete", offset: 0, limit: 20}; preview selects recorded headline fields and binary descriptors, complete returns exact records with all sidecars. Offsets follow the selected parent record order, preserving original producer ordinals. Paging never reopens the source or reruns the decoder. Go metadata uses kind go, collection dependencies/settings, exact paths/keys or indices, fields and pagination. Row index refers to the original collection; empty selections return no rows, and dependency coverage remains unknown. For inspect_pcap_capture parents, kind pcap pages endpoints, dns-queries, tls-sni, protocols, http-uris or unattributed-addresses. Select an exact query for dns-addresses, or a tshark/capinfos stream for UTF-16 diagnostics text. Page totals count retained facts; source coverage remains explicit. Qlog summaries keep profile/scope and counts; kind qlog pages traces, streams, loss-declarations, incomplete-records, contributions, ranges or final-sizes. Except traces, select trace_index; stream-specific facets also require exact stream_id and direction. Facts retain all-recorded-events-before-selection scope, and interval overlap or endpoint loss declarations do not prove retransmission or network loss. The api-surface view groups retained HAR requests by method, origin and format-rule path template; templated segments are inferences. It reads no headers or bodies and reports no query values. The environment-manifest view projects the same HAR requests on the primary origin (most JSON-like records) into a replayable tool surface: view {kind: "environment-manifest", template_rules: ["after-uid-literal", "opaque-token", "sibling-variance"], include_query_parameter_names: true}. Each tool has a deterministic name, a path template and parameters from the four format rules plus the selected opt-in rules, observed query parameter names, a value-free body shape for POST/PUT/PATCH, status and content-type sets and record ordinals; records are pointers into the parent, excluded groups carry a reason and three fixed unknowns are always stated. Templates, parameters and names are inferences; the view copies no header, cookie or body values and refuses parents inspected with sensitive values. A capture_browser_scenario parent that selected response bodies or header values projects the same way, one record per network transaction in request order; it relies on capture-time redaction of declared secrets instead of that refusal, and a transaction whose URL was redacted is left unrepresented. The environment-state view extracts, from the same parents, the initial state the recorded reads observed: view {kind: "environment-state", template_rules: [...]} with the rules of the manifest it accompanies. It reads response bodies recorded before the first related write and reports entity types with an identity field (proved by a path parameter or inferred from a field name), entities in collection order with per-view values where views differ, documents for non-parameterized non-entity paths written by POST/PUT/PATCH (item-shaped and delete-only paths stay recorded), and a render plan per read tool: item and list views reproduce their latest recordings from the state, and every other read tool is a recorded view with a reason. Types and identity are inferences; values are recorded data, so it refuses parents inspected with sensitive values, and a scenario parent must have selected response bodies; its values keep the capture redaction markers, and a write whose URL it redacted ends the state of every type. For a retained record use source: {kind: "retained-evidence", evidence_id: "<parent_evidence_id>"}. Use normalized_result.parent_evidence_id from a summary receipt. Source kind is required. Portable source uses {kind: "inline", evidence: <complete Evidence>}. A completed Evidence JSON file on this host uses {kind: "evidence-file", path: "<absolute path>"}: REA reads and authenticates it, then projects it like retained Evidence, so a large capture is bounded by the file reader rather than by inline-input accounting. For layout, JavaScript, inventory, WASLine truncated
'Inspect selected views of completed Frida session status, GDB/objdump command observations, Go build metadata, Rust embedded dependencies, binary-layout, JavaScript application, native analyze_function, inspect_artifact, inventory_artifact, inspect_wasm_artifact, inspect_macho, decode_interface_builder, inspect_evm_interface, inspect_managed_artifact, inspect_managed_members, inspect_android_package, inspect_dart_aot, inspect_il2cpp_metadata, inspect_model_artifact or inspect_web_network_capture Evidence without repeating analysis. Command summary views preserve ordered GDB records or opaque objdump stdout/stderr once, source parameters, completion facts and parent identity. They do not establish current target state. Historical capture summaries return identity and counts without bodies or archive inventories. Capture views page retained records: view {kind: "capture", detail: "preview" | "complete", offset: 0, limit: 20}; preview selects recorded headline fields and binary descriptors, complete returns exact records with all sidecars. Offsets follow the selected parent record order, preserving original producer ordinals. Paging never reopens the source or reruns the decoder. Go metadata uses kind go, collection dependencies/settings, exact paths/keys or indices, fields and pagination. Row index refers to the original collection; empty selections return no rows, and dependency coverage remains unknown. Rust metadata uses kind rust, collection packages/edges, exact names/versions or original indices, selected fields, edge from_indices/to_indices and matching-row pagination. Embedded-record coverage is separate from unknown runtime dependencies. For inspect_pcap_capture parents, kind pcap pages endpoints, dns-queries, tls-sni, protocols, http-uris or unattributed-addresses. Select an exact query for dns-addresses, or a tshark/capinfos stream for UTF-16 diagnostics text. Page totals count retained facts; source coverage remains explicit. Qlog summaries keep profile/scope and counts; kind qlog pages traces, streams, loss-declarations, incomplete-records, contributions, ranges or final-sizes. Except traces, select trace_index; stream-specific facets also require exact stream_id and direction. Facts retain all-recorded-events-before-selection scope, and interval overlap or endpoint loss declarations do not prove retransmission or network loss. The api-surface view groups retained HAR requests by method, origin and format-rule path template; templated segments are inferences. It reads no headers or bodies and reports no query values. The environment-manifest view projects the same HAR requests on the primary origin (most JSON-like records) into a replayable tool surface: view {kind: "environment-manifest", template_rules: ["after-uid-literal", "opaque-token", "sibling-variance"], include_query_parameter_names: true}. Each tool has a deterministic name, a path template and parameters from the four format rules plus the selected opt-in rules, observed query parameter names, a value-free body shape for POST/PUT/PATCH, status and content-type sets and record ordinals; records are pointers into the parent, excluded groups carry a reason and three fixed unknowns are always stated. Templates, parameters and names are inferences; the view copies no header, cookie or body values and refuses parents inspected with sensitive values. A capture_browser_scenario parent that selected response bodies or header values projects the same way, one record per network transaction in request order; it relies on capture-time redaction of declared secrets instead of that refusal, and a transaction whose URL was redacted is left unrepresented. The environment-state view extracts, from the same parents, the initial state the recorded reads observed: view {kind: "environment-state", template_rules: [...]} with the rules of the manifest it accompanies. It reads response bodies recorded before the first related write and reports entity types with an identity field (proved by a path parameter or inferred from a field name), entities in collection order with per-view values where views differ, documents for non-parameterized non-entity paths written by POST/PUT/PATCH (item-shaped and delete-only paths stay recorded), and a render plan per read tool: item and list views reproduce their latest recordings from the state, and every other read tool is a recorded view with a reason. Types and identity are inferences; values are recorded data, so it refuses parents inspected with sensitive values, and a scenario parent must have selected response bodies; its values keep the capture redaction markers, and a write whose URL it redacted ends the state of every type. For a retained record use source: {kind: "retained-evidence", evidence_id: "<parent_evidence_id>"}. Use normalized_result.parent_evidence_id from a summary receipt. Source kind is required. Portable source uses {kind: "inline", evidence: <complete Evidence>}. A completed Evidence JSONLine truncated
inputSchema: inspectAnalysisViewInputSchema,
outputSchema: evidenceResultOf(analysisViewResultSchema),
examples: [
+15 -6
View File
@@ -1,7 +1,10 @@
import { rustBinarySchema } from "../../domain/rust/rustBinary.js";
import { inspectRustBinaryRequestSchema } from "../../domain/rust/rustBinaryQuery.js";
import { z } from "zod";
import {
inspectRustBinaryInputSchema,
rustBinarySchema,
} from "../../domain/rust/rustBinary.js";
rustBinarySummaryViewResultSchema,
rustBinaryPageViewResultSchema,
} from "../../domain/analysisView/analysisView.js";
import type { ToolContract } from "../toolContractTypes.js";
import { toolContractMetadata } from "../toolEffects.js";
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
@@ -13,9 +16,15 @@ export const RUST_TOOL_CONTRACTS = [
...toolContractMetadata("inspect_rust_binary"),
kind: "artifact-provider",
description:
"Inspect the cargo-auditable crate dependency list embedded in an explicit local ELF, PE or thin Mach-O image. Returns the artifact SHA-256, the .dep-v0 section file offset and size, compressed and uncompressed sizes, format version, packages in their original order, the root package index, and dependency edges by index. Reads zlib-compressed JSON without installing Rust, opening a disassembler, executing the target, writing temporary files or accessing the network. A missing section is null metadata with a limitation and is not proof that the binary has no dependencies. A rustc commit hash is reported only as an inference when a /rustc/<40-hex>/ path is present in read-only data. Resource guards bound files to 256 MiB, dependency sections to 1 MiB, inflated JSON to the smaller of 8 MiB and 64 times the compressed size, and the complete report to 8 MiB. Malformed, unsupported and oversized inputs receive typed failures rather than partial success.",
inputSchema: inspectRustBinaryInputSchema,
outputSchema: evidenceResultOf(rustBinarySchema),
"Inspect the cargo-auditable crate dependency list embedded in an explicit local ELF, PE or thin Mach-O image. Returns the artifact SHA-256, the .dep-v0 section file offset and size, compressed and uncompressed sizes, format version, packages in their original order, the root package index, and dependency edges by index. Omitted or explicit complete detail preserves the full report. Optional detail summary retains that complete Evidence and returns source ranges, counts and parent_evidence_id. Optional view directly selects packages or edges in this call; it cannot be combined with explicit complete detail. Query the same retained facts with inspect_analysis_view kind rust: exact names/versions or package indices, edge from_indices/to_indices, fields and matching-row pagination. Row indices are original and scoped to the parent Evidence; empty filters select none. Default package fields omit dependency vectors; explicitly select dependencies for direct index references. Every matching observed version is retained. No current runtime state is inferred from historical reads. Reads zlib-compressed JSON without installing Rust, opening a disassembler, executing the target, writing temporary files or accessing the network. A missing section is null metadata with a limitation and is not proof that the binary has no dependencies. A rustc commit hash is reported only as an inference when a /rustc/<40-hex>/ path is present in read-only data. Resource guards bound files to 256 MiB, dependency sections to 1 MiB, inflated JSON to the smaller of 8 MiB and 64 times the compressed size, and the complete report to 8 MiB. Malformed, unsupported and oversized inputs receive typed failures rather than partial success.",
inputSchema: inspectRustBinaryRequestSchema,
outputSchema: evidenceResultOf(
z.union([
rustBinarySchema,
rustBinarySummaryViewResultSchema,
rustBinaryPageViewResultSchema,
]),
),
examples: [
{
title: "Inspect an embedded Rust dependency list",
+29
View File
@@ -23,6 +23,13 @@ import {
goBinaryViewRequestSchema,
projectGoBinaryView,
} from "./goBinaryView.js";
import {
rustBinarySummarySchema,
rustBinaryViewContextSchema,
rustBinaryViewPageSchema,
rustBinaryViewRequestSchema,
projectRustBinaryView,
} from "./rustBinaryView.js";
import {
projectNativeFunctionMatchingView,
nativeFunctionMatchingViewRequestSchema,
@@ -277,6 +284,7 @@ export const analysisViewRequestSchema = z.discriminatedUnion("kind", [
nativeFunctionMatchingViewRequestSchema,
summaryViewSchema,
goBinaryViewRequestSchema,
rustBinaryViewRequestSchema,
desktopViewRequestSchema,
facetViewSchema,
itemViewSchema,
@@ -462,6 +470,24 @@ export const goBinarySummaryViewResultSchema = z.strictObject({
summary: goBinarySummarySchema,
});
/** Compact source identity and counts, retaining the complete embedded graph. */
export const rustBinarySummaryViewResultSchema = z.strictObject({
...summaryViewResultBase,
...rustBinaryViewContextSchema.shape,
parent_operation: z.literal("inspect_rust_binary"),
summary: rustBinarySummarySchema,
});
export const rustBinaryPageViewResultSchema = z.strictObject({
...viewResultBase,
...rustBinaryViewContextSchema.shape,
kind: z.literal("rust"),
parent_operation: z.literal("inspect_rust_binary"),
view: rustBinaryViewRequestSchema,
rust: rustBinarySummarySchema,
page: rustBinaryViewPageSchema,
});
/** Summary `inspect_binary_layout` detail returns; no other view kind. */
export const binaryLayoutSummaryViewResultSchema = z.strictObject({
...summaryViewResultBase,
@@ -665,6 +691,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [
objdumpSummaryViewResultSchema,
fridaSessionStatusSummaryViewResultSchema,
goBinarySummaryViewResultSchema,
rustBinarySummaryViewResultSchema,
binaryLayoutSummaryViewResultSchema,
desktopApplicationSummaryViewResultSchema,
javascriptApplicationSummaryViewResultSchema,
@@ -738,6 +765,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [
view: goBinaryViewRequestSchema,
page: goBinaryViewPageSchema,
}),
rustBinaryPageViewResultSchema,
z.strictObject({
kind: z.literal("item"),
view: itemViewSchema,
@@ -954,6 +982,7 @@ const retainedViewProjectors = new Map<string, typeof projectWasmArtifactView>([
["gdb_console", projectCommandObservationView],
["inspect_with_objdump", projectCommandObservationView],
["inspect_go_binary", projectGoBinaryView],
["inspect_rust_binary", projectRustBinaryView],
...NATIVE_INVENTORY_OPERATIONS.map(
(operation) => [operation, projectNativeInventoryView] as const,
),
+3 -19
View File
@@ -22,6 +22,7 @@ import {
} from "./analysisViewCoverage.js";
import { analysisViewInputError } from "./analysisViewErrors.js";
import { parseAnalysisViewResult } from "./analysisViewParent.js";
import { selectViewRows } from "./selectViewRows.js";
const indexSchema = z.number().int().nonnegative();
const count = z.number().int().nonnegative();
@@ -123,23 +124,6 @@ const parseReport = (
return ok(report);
};
const selectRows = <Source, Row>(
rows: readonly Source[],
view: { readonly offset: number; readonly limit: number },
matches: (row: Source, index: number) => boolean,
project: (row: Source, index: number) => Row,
): { rows: Row[]; total: number } => {
const selected: Row[] = [];
let total = 0;
for (const [index, row] of rows.entries()) {
if (!matches(row, index)) continue;
if (total >= view.offset && selected.length < view.limit)
selected.push(project(row, index));
total++;
}
return { rows: selected, total };
};
/** Query the existing metadata owner's facts; never reopen or reinterpret a file. */
export const projectGoBinaryView = (
parent: AnalysisViewParent,
@@ -259,7 +243,7 @@ export const projectGoBinaryView = (
view.fields === undefined ? undefined : new Set<string>(view.fields);
if (view.collection === "dependencies") {
const paths = view.paths === undefined ? undefined : new Set(view.paths);
const selected = selectRows(
const selected = selectViewRows(
info?.module.dependencies ?? [],
view,
(row, index) =>
@@ -287,7 +271,7 @@ export const projectGoBinaryView = (
});
}
const keys = view.keys === undefined ? undefined : new Set(view.keys);
const selected = selectRows(
const selected = selectViewRows(
info?.module.settings ?? [],
view,
(row, index) =>
@@ -0,0 +1,173 @@
import { expect, it } from "vitest";
import { createEvidence } from "../evidence.js";
import type { RustBinary } from "../rust/rustBinary.js";
import {
projectAnalysisView,
type AnalysisViewParent,
} from "./analysisView.js";
const report: RustBinary = {
artifact: { path: "/artifacts/rust", sha256: "a".repeat(64), bytes: 512 },
format: "elf",
architecture: "amd64",
bits: 64,
byte_order: "little",
auditable_metadata: {
section_name: ".dep-v0",
file_offset: 32,
section_bytes: 64,
compressed_bytes: 64,
uncompressed_bytes: 128,
format_version: null,
packages: [
{
name: "root",
version: "1",
source: "local",
kind: null,
root: true,
dependencies: [0],
},
],
root_package_index: 0,
dependency_edges: [{ from_index: 0, to_index: 0 }],
cyclic_dependency_indices: [0],
},
rustc_commit: null,
limitations: [],
};
const parent = (value = structuredClone(report)): AnalysisViewParent => {
const evidence = createEvidence(
{
path: value.artifact.path,
sha256: value.artifact.sha256,
format: "elf",
architecture: "x86_64",
},
{ id: "rust-auditable", name: "Rust reader", version: "1" },
{
operation: "inspect_rust_binary",
parameters: { path: value.artifact.path },
result: value,
},
);
return {
evidenceId: evidence.evidence_id,
operation: evidence.operation,
subject: evidence.subject,
normalizedResult: value,
limitations: [],
};
};
it("revalidates mutable and merely frozen Rust imports despite a claimed seal", () => {
const mutable = structuredClone(report);
const input = { ...parent(mutable), sealedResult: true };
expect(projectAnalysisView(input, { kind: "summary" }).ok).toBe(true);
Object.freeze(mutable);
mutable.auditable_metadata!.dependency_edges[0]!.to_index = 99;
expect(
projectAnalysisView(input, {
kind: "rust",
collection: "edges",
offset: 0,
limit: 1,
}).ok,
).toBe(false);
});
it("checks exact artifact identity and subject architecture for every Rust view", () => {
for (const changed of [
{ local_path: "/different" },
{ format: "pe" as const },
{ architecture: "arm64" as const },
{ digest: { sha256: "b".repeat(64) } },
]) {
const source = parent();
if (source.subject == null || source.subject.format !== "elf")
throw new Error("Missing ELF source");
const input = { ...source, subject: { ...source.subject, ...changed } };
expect(projectAnalysisView(input, { kind: "summary" }).ok).toBe(false);
expect(
projectAnalysisView(input, {
kind: "rust",
collection: "packages",
offset: 0,
limit: 1,
}).ok,
).toBe(false);
}
});
it("bounds inherited warnings and retains cycle counts without walking a cyclic graph", () => {
const input = { ...parent(), limitations: ["x".repeat(300_000)] };
const result = projectAnalysisView(input, { kind: "summary" });
if (!result.ok) throw result.error;
expect(result.value).toMatchObject({
summary: {
auditable_metadata: {
format_version: null,
counts: { packages: 1, edges: 1, cyclic_packages: 1 },
},
},
source_coverage: {
embedded_records: "complete",
runtime_dependencies: "unknown",
},
});
expect(JSON.stringify(result.value).length).toBeLessThan(5000);
expect(result.value.limitations.join(" ")).toContain(input.evidenceId);
});
it("pages matching original indices and omits dependency vectors unless selected", () => {
const value = structuredClone(report);
value.auditable_metadata!.packages.push({
name: "other",
version: "1",
source: "local",
kind: null,
root: false,
dependencies: [],
});
const source = parent(value);
const first = projectAnalysisView(source, {
kind: "rust",
collection: "packages",
offset: 0,
limit: 1,
});
if (!first.ok || first.value.kind !== "rust")
throw new Error("Rust page failed");
expect(first.value.page.rows).toEqual([
{
index: 0,
name: "root",
version: "1",
source: "local",
kind: null,
root: true,
},
]);
expect(first.value.coverage).toMatchObject({
total: 2,
examined: 1,
exhausted: false,
next_offset: 1,
});
const next = projectAnalysisView(source, {
kind: "rust",
collection: "packages",
offset: 1,
limit: 1,
fields: [],
});
if (!next.ok || next.value.kind !== "rust")
throw new Error("Rust continuation failed");
expect(next.value.page.rows).toEqual([{ index: 1 }]);
expect(next.value.coverage).toMatchObject({
total: 2,
examined: 1,
exhausted: true,
next_offset: null,
});
});
+277
View File
@@ -0,0 +1,277 @@
import { z } from "zod";
import type { AnalysisError } from "../analysisErrorBase.js";
import {
rustArchitectureForSubject,
rustBinarySchema,
rustAuditableMetadataSchema,
rustcCommitSchema,
type RustBinary,
} from "../rust/rustBinary.js";
export {
rustBinaryViewPageSchema,
rustBinaryViewRequestSchema,
} from "../rust/rustBinaryQuery.js";
import { isImmutableJsonSnapshot } from "../immutableJson.js";
import { err, ok, type Result } from "../result.js";
import type {
AnalysisViewParent,
AnalysisViewRequest,
UnsignedAnalysisView,
} from "./analysisView.js";
import {
boundedViewMessages,
completeWithinViewCoverage,
pageViewCoverage,
} from "./analysisViewCoverage.js";
import { analysisViewInputError } from "./analysisViewErrors.js";
import { parseAnalysisViewResult } from "./analysisViewParent.js";
import { selectViewRows } from "./selectViewRows.js";
const count = z.number().int().nonnegative();
export const rustBinarySummarySchema = z.strictObject({
bytes: count,
format: rustBinarySchema.shape.format,
architecture: rustBinarySchema.shape.architecture,
bits: rustBinarySchema.shape.bits,
byte_order: rustBinarySchema.shape.byte_order,
auditable_metadata: rustAuditableMetadataSchema
.omit({
packages: true,
dependency_edges: true,
cyclic_dependency_indices: true,
})
.extend({
counts: z.strictObject({
packages: count,
edges: count,
cyclic_packages: count,
}),
})
.nullable(),
rustc_commit: rustcCommitSchema
.omit({ hashes: true })
.extend({ hashes_count: count })
.nullable(),
});
export const rustBinaryViewContextSchema = z.strictObject({
source_coverage: z.strictObject({
auditable_metadata_available: z.boolean(),
embedded_records: z.enum(["complete", "unknown"]),
runtime_dependencies: z.literal("unknown"),
}),
});
// Exact immutable Evidence identity, not a path or a live-file result cache.
// Mutable/merely frozen imports are validated normally; no failures are cached.
const checkedReports = new WeakMap<object, RustBinary>();
const parseReport = (
parent: AnalysisViewParent,
): Result<RustBinary, AnalysisError> => {
const key = parent.normalizedResult;
const reusable =
parent.sealedResult === true &&
typeof key === "object" &&
key !== null &&
isImmutableJsonSnapshot(key);
const known = reusable ? checkedReports.get(key) : undefined;
if (known !== undefined) return ok(known);
const parsed = parseAnalysisViewResult(rustBinarySchema, parent);
if (!parsed.ok) return parsed;
const report = reusable ? (key as RustBinary) : parsed.value;
if (reusable) checkedReports.set(key, report);
return ok(report);
};
const summarize = (
report: RustBinary,
): z.infer<typeof rustBinarySummarySchema> => {
const {
artifact,
auditable_metadata: metadata,
rustc_commit: compiler,
} = report;
const section =
metadata === null
? null
: {
section_name: metadata.section_name,
file_offset: metadata.file_offset,
section_bytes: metadata.section_bytes,
compressed_bytes: metadata.compressed_bytes,
uncompressed_bytes: metadata.uncompressed_bytes,
format_version: metadata.format_version,
root_package_index: metadata.root_package_index,
counts: {
packages: metadata.packages.length,
edges: metadata.dependency_edges.length,
cyclic_packages: metadata.cyclic_dependency_indices.length,
},
};
return {
format: report.format,
architecture: report.architecture,
bits: report.bits,
byte_order: report.byte_order,
bytes: artifact.bytes,
auditable_metadata: section,
rustc_commit:
compiler === null
? null
: {
evidence_kind: compiler.evidence_kind,
hashes_count: compiler.hashes.length,
},
};
};
/** Query owner-normalized facts without reopening a source or walking dependency paths. */
export const projectRustBinaryView = (
parent: AnalysisViewParent,
view: AnalysisViewRequest,
): Result<UnsignedAnalysisView, AnalysisError> => {
if (view.kind !== "summary" && view.kind !== "rust")
return err(
analysisViewInputError([
{
path: ["view"],
reason: "invalid_value",
message: "Rust Evidence supports summary and rust views.",
},
]),
);
const parsed = parseReport(parent);
if (!parsed.ok) return parsed;
const report = parsed.value;
const subject = parent.subject;
if (
subject == null ||
subject.local_path !== report.artifact.path ||
subject.digest.sha256 !== report.artifact.sha256 ||
subject.format !== (report.format === "macho" ? "mach-o" : report.format) ||
subject.architecture !==
(rustArchitectureForSubject(report.architecture) ?? null)
)
return err(
analysisViewInputError([
{
path: ["source", "subject"],
reason: "invalid_value",
message:
"Rust parent subject must identify the report's exact artifact.",
},
]),
);
const metadata = report.auditable_metadata;
const unknown =
"Embedded dependency records do not authenticate build provenance or establish complete runtime dependencies or target behavior.";
const shared = {
parent_evidence_id: parent.evidenceId,
parent_operation: "inspect_rust_binary" as const,
parent_digest: parent.evidenceId.slice(3),
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
source_coverage: {
auditable_metadata_available: metadata !== null,
embedded_records:
metadata === null ? ("unknown" as const) : ("complete" as const),
runtime_dependencies: "unknown" as const,
},
limitations: boundedViewMessages(
[...parent.limitations, ...report.limitations],
parent.evidenceId,
"limitations",
),
unknowns: boundedViewMessages(
[
unknown,
...(metadata === null
? [
"No cargo-auditable metadata was observed; missing metadata does not establish an empty dependency set.",
]
: []),
],
parent.evidenceId,
"unknowns",
[unknown],
),
};
const summary = summarize(report);
if (view.kind === "summary")
return ok({
kind: "summary",
view,
...shared,
summary,
coverage: completeWithinViewCoverage(1, 1),
});
const indices =
view.indices === undefined ? undefined : new Set(view.indices);
const fields =
view.fields === undefined ? undefined : new Set<string>(view.fields);
if (view.collection === "packages") {
const names = view.names === undefined ? undefined : new Set(view.names);
const versions =
view.versions === undefined ? undefined : new Set(view.versions);
const selected = selectViewRows(
metadata?.packages ?? [],
view,
(row, index) =>
(indices?.has(index) ?? true) &&
(names?.has(row.name) ?? true) &&
(versions?.has(row.version) ?? true),
(row, index) => ({
index,
...((fields?.has("name") ?? true) && { name: row.name }),
...((fields?.has("version") ?? true) && { version: row.version }),
...((fields?.has("source") ?? true) && { source: row.source }),
...((fields?.has("kind") ?? true) && { kind: row.kind }),
...((fields?.has("root") ?? true) && { root: row.root }),
...(fields?.has("dependencies") === true && {
dependencies: row.dependencies,
}),
}),
);
return ok({
kind: "rust",
view,
...shared,
rust: summary,
page: { collection: "packages", rows: selected.rows },
coverage: pageViewCoverage(
view.offset,
selected.rows.length,
selected.total,
),
});
}
const from =
view.from_indices === undefined ? undefined : new Set(view.from_indices);
const to =
view.to_indices === undefined ? undefined : new Set(view.to_indices);
const selected = selectViewRows(
metadata?.dependency_edges ?? [],
view,
(row, index) =>
(indices?.has(index) ?? true) &&
(from?.has(row.from_index) ?? true) &&
(to?.has(row.to_index) ?? true),
(row, index) => ({
index,
...((fields?.has("from_index") ?? true) && {
from_index: row.from_index,
}),
...((fields?.has("to_index") ?? true) && { to_index: row.to_index }),
}),
);
return ok({
kind: "rust",
view,
...shared,
rust: summary,
page: { collection: "edges", rows: selected.rows },
coverage: pageViewCoverage(
view.offset,
selected.rows.length,
selected.total,
),
});
};
+17
View File
@@ -0,0 +1,17 @@
/** Preserve original indices and count every match while projecting only the requested page. */
export const selectViewRows = <Source, Row>(
rows: readonly Source[],
view: { readonly offset: number; readonly limit: number },
matches: (row: Source, index: number) => boolean,
project: (row: Source, index: number) => Row,
): { rows: Row[]; total: number } => {
const selected: Row[] = [];
let total = 0;
for (const [index, row] of rows.entries()) {
if (!matches(row, index)) continue;
if (total >= view.offset && selected.length < view.limit)
selected.push(project(row, index));
total++;
}
return { rows: selected, total };
};
+15
View File
@@ -1,6 +1,21 @@
import { z } from "zod";
import { digestSchema } from "../digests.js";
import { localPathStringSchema } from "../localPath.js";
import { BINARY_ARCHITECTURES } from "../binaryTargetTypes.js";
/** Preserve the format owner's architecture spelling separately from Evidence vocabulary. */
export const rustArchitectureForSubject = (architecture: string) => {
switch (architecture) {
case "386":
return "x86";
case "amd64":
return "x86_64";
default:
return BINARY_ARCHITECTURES.find(
(supported) => supported === architecture,
);
}
};
/** Known revisions used to interpret compatible cargo-auditable documents. */
export const RUST_AUDITABLE_FORMAT_VERSIONS = [0, 1, 8] as const;
+69
View File
@@ -0,0 +1,69 @@
import { z } from "zod";
import {
inspectRustBinaryInputSchema,
rustPackageSchema,
rustAuditableMetadataSchema,
} from "./rustBinary.js";
const edgeSchema = rustAuditableMetadataSchema.shape.dependency_edges.element;
const index = z.number().int().nonnegative();
const page = {
indices: z.array(index).optional(),
offset: index.default(0),
limit: z.number().int().positive().default(64),
};
const packages = z.strictObject({
...page,
collection: z.literal("packages"),
names: z.array(z.string()).optional(),
versions: z.array(z.string()).optional(),
fields: z
.array(rustPackageSchema.keyof())
.optional()
.describe(
"Original index always retained. Default attributes omit dependency vectors; select dependencies explicitly for direct index references.",
),
});
const edges = z.strictObject({
...page,
collection: z.literal("edges"),
from_indices: z.array(index).optional(),
to_indices: z.array(index).optional(),
fields: z.array(edgeSchema.keyof()).optional(),
});
/** Filters intersect; empty arrays select none and offsets count matching rows. */
export const rustBinarySelectionSchema = z.discriminatedUnion("collection", [
packages,
edges,
]);
export const rustBinaryViewRequestSchema = z.discriminatedUnion("collection", [
packages.extend({ kind: z.literal("rust") }),
edges.extend({ kind: z.literal("rust") }),
]);
export const rustBinaryViewPageSchema = z.discriminatedUnion("collection", [
z.strictObject({
collection: z.literal("packages"),
rows: z.array(rustPackageSchema.partial().extend({ index })),
}),
z.strictObject({
collection: z.literal("edges"),
rows: z.array(edgeSchema.partial().extend({ index })),
}),
]);
/** Delivery choices never enter the file-reader port or change original Evidence identity. */
export const inspectRustBinaryRequestSchema = inspectRustBinaryInputSchema
.extend({
detail: z.enum(["complete", "summary"]).optional(),
view: rustBinarySelectionSchema.optional(),
})
.superRefine((value, context) => {
if (value.view !== undefined && value.detail === "complete")
context.addIssue({
code: "custom",
path: ["view"],
message:
"view selects compact delivery; omit view for complete Evidence",
});
});
+1
View File
@@ -777,6 +777,7 @@ const registerConfiguredAnalysisTools = (
toolLogger,
recordEvidence,
withAdmittedAnalysis,
evidenceById,
);
registerWasmTools(
server,
+4 -1
View File
@@ -1,4 +1,5 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import type { RustBinaryService } from "../application/rust/RustBinaryService.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import { toolContract } from "../contracts/toolContracts.js";
@@ -14,11 +15,13 @@ export const registerRustTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
withAdmittedAnalysis?: WithAdmittedAnalysis,
evidenceById?: EvidenceLookup,
): void => {
const contract = toolContract("inspect_rust_binary");
const handler = createEvidenceToolHandler(
server,
(input, options) => service.inspect(input, options),
(input, options) =>
service.inspect(input, options, { recordEvidence, evidenceById }),
logger,
recordEvidence,
withAdmittedAnalysis,
+11 -8
View File
@@ -1,3 +1,4 @@
import { rustBinarySchema } from "../../src/domain/rust/rustBinary.js";
import { createHash } from "node:crypto";
import { deflateSync } from "node:zlib";
import { readFile, writeFile } from "node:fs/promises";
@@ -86,7 +87,9 @@ cliTest(
const evidence = toolContract("inspect_rust_binary").outputSchema.parse(
result.json,
);
expect(evidence.normalized_result.auditable_metadata).toMatchObject({
expect(
rustBinarySchema.parse(evidence.normalized_result).auditable_metadata,
).toMatchObject({
section_name: "__DATA,.dep-v0",
format_version: 1,
root_package_index: 1,
@@ -109,8 +112,9 @@ cliTest("returns null metadata when .dep-v0 is absent", async ({ cli }) => {
const evidence = toolContract("inspect_rust_binary").outputSchema.parse(
result.json,
);
expect(evidence.normalized_result.auditable_metadata).toBeNull();
expect(evidence.normalized_result.limitations.join("\n")).toContain(
const report = rustBinarySchema.parse(evidence.normalized_result);
expect(report.auditable_metadata).toBeNull();
expect(report.limitations.join("\n")).toContain(
"does not mean the binary has no dependencies",
);
});
@@ -134,13 +138,12 @@ cliTest(
const evidence = toolContract("inspect_rust_binary").outputSchema.parse(
result.json,
);
expect(evidence.normalized_result.auditable_metadata?.format_version).toBe(
4,
);
expect(evidence.normalized_result.limitations.join("\n")).toContain(
const report = rustBinarySchema.parse(evidence.normalized_result);
expect(report.auditable_metadata?.format_version).toBe(4);
expect(report.limitations.join("\n")).toContain(
"highest known preceding revision (1)",
);
expect(evidence.normalized_result.limitations.join("\n")).toContain(
expect(report.limitations.join("\n")).toContain(
"this specific revision has not been independently verified",
);
},
@@ -0,0 +1,298 @@
import { readFile, unlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { parseEvidence, createEvidence } from "../../src/domain/evidence.js";
import { rustBinarySchema } from "../../src/domain/rust/rustBinary.js";
import { analysisViewResultSchema } from "../../src/domain/analysisView/analysisView.js";
import { readEvidenceBundle } from "../../src/application/EvidenceBundleFiles.js";
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
import {
createRustImage,
rustDependencyPayload,
} from "../fixtures/rust/image.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
/** Both authenticated retention and portable imports query the same captured facts. */
const assertRetainedPages = async (
call: Awaited<ReturnType<typeof connectLocalToolsMcp>>["call"],
evidenceId: string,
exported: string,
) => {
for (const [filter, expected] of [
[
{ names: ["same"], versions: ["2"], fields: ["name", "version"] },
[{ index: 1, name: "same", version: "2" }],
],
[{ names: ["same"], indices: [0], fields: [] }, [{ index: 0 }]],
[{ names: ["same"], versions: [], fields: [] }, []],
] as const) {
const reply = await call("inspect_analysis_view", {
source: { kind: "retained-evidence", evidence_id: evidenceId },
view: { kind: "rust", collection: "packages", ...filter },
});
expect(reply.isError).not.toBe(true);
expect(
parseEvidence(reply.structuredContent).normalized_result,
).toMatchObject({ page: { rows: expected } });
}
for (const source of [
{ kind: "retained-evidence", evidence_id: evidenceId },
{ kind: "evidence-file", path: exported },
]) {
const reply = await call("inspect_analysis_view", {
source,
view: {
kind: "rust",
collection: "packages",
names: ["same"],
indices: [0, 1, 99],
offset: 1,
limit: 1,
fields: [],
},
});
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
expect(
parseEvidence(reply.structuredContent).normalized_result,
).toMatchObject({
parent_evidence_id: evidenceId,
page: { rows: [{ index: 1 }] },
coverage: {
total: 2,
examined: 1,
exhausted: true,
next_offset: null,
},
});
const edgeReply = await call("inspect_analysis_view", {
source,
view: {
kind: "rust",
collection: "edges",
from_indices: [1, 3],
to_indices: [0],
fields: ["from_index", "to_index"],
},
});
expect(edgeReply.isError).not.toBe(true);
expect(
parseEvidence(edgeReply.structuredContent).normalized_result,
).toMatchObject({
page: {
collection: "edges",
rows: [
{ index: 1, from_index: 1, to_index: 0 },
{ index: 3, from_index: 3, to_index: 0 },
],
},
});
const empty = await call("inspect_analysis_view", {
source,
view: {
kind: "rust",
collection: "packages",
names: [],
versions: ["1"],
fields: ["name"],
},
});
expect(
parseEvidence(empty.structuredContent).normalized_result,
).toMatchObject({
page: { rows: [] },
coverage: { total: 0, exhausted: true },
});
}
};
cliTest(
"queries stable Rust packages and edges through real CLI/MCP and portable Evidence",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-rust-views-");
const { call } = await connectLocalToolsMcp();
for (const format of ["elf", "pe", "macho"] as const) {
const path = join(root, `source.${format}`);
const output = join(root, `${format}-source.json`);
const payload = rustDependencyPayload({
packages: [
{
name: "same",
version: "1",
source: "crates.io",
dependencies: [2],
},
{
name: "same",
version: "2",
source: "git+https://upstream.example/repo",
dependencies: [0, 2],
},
{ name: "leaf", version: "1", source: "local" },
{
name: "app",
version: "1",
source: "local",
dependencies: [0, 1],
root: true,
},
],
});
await writeFile(path, createRustImage({ format, payload }));
const fullReply = await call("inspect_rust_binary", { path });
expect(fullReply.isError).not.toBe(true);
const full = parseEvidence(fullReply.structuredContent);
const report = rustBinarySchema.parse(full.normalized_result);
const explicit = await call("inspect_rust_binary", {
path,
detail: "complete",
});
expect(parseEvidence(explicit.structuredContent)).toEqual(full);
const selection = {
collection: "packages",
names: ["same"],
fields: ["name", "version", "dependencies"],
};
const direct = await call("inspect_rust_binary", {
path,
view: selection,
});
expect(direct.isError, JSON.stringify(direct)).not.toBe(true);
const selected = parseEvidence(
direct.structuredContent,
).normalized_result;
expect(selected).toMatchObject({
kind: "rust",
parent_evidence_id: full.evidence_id,
page: {
collection: "packages",
rows: [
{ index: 0, name: "same", version: "1", dependencies: [2] },
{ index: 1, name: "same", version: "2", dependencies: [0, 2] },
],
},
});
const summary = await call("inspect_rust_binary", {
path,
detail: "summary",
});
expect(summary.isError).not.toBe(true);
expect(
parseEvidence(summary.structuredContent).normalized_result,
).toMatchObject({
kind: "summary",
summary: {
auditable_metadata: {
counts: { packages: 4, edges: 5 },
format_version: null,
},
},
source_coverage: {
embedded_records: "complete",
runtime_dependencies: "unknown",
},
});
const delivered = await cli.run({
arguments: [
"inspect-rust-binary",
path,
"--view",
JSON.stringify(selection),
"--evidence_output",
output,
"--json",
],
});
expect(delivered.exitCode, delivered.stderr).toBe(0);
expect(delivered.json).toMatchObject({
summary: { normalized_result: selected },
});
const bundle = await readEvidenceBundle(output);
if (!bundle.ok) throw bundle.error;
expect(
bundle.value.records.find(
(row) => row.evidence_id === full.evidence_id,
),
).toEqual(full);
const exported = join(root, `${format}-record.json`);
await writeFile(exported, JSON.stringify(full));
await unlink(path);
await assertRetainedPages(call, full.evidence_id, exported);
const malformed = structuredClone(report);
malformed.auditable_metadata!.dependency_edges[0]!.to_index = 999;
if (full.subject == null)
throw new Error("Missing complete Rust subject");
const invalid = createEvidence(
{
path,
sha256: report.artifact.sha256,
format: format === "macho" ? "mach-o" : format,
...(full.subject.architecture === null
? {}
: { architecture: full.subject.architecture }),
},
full.provider,
{
operation: full.operation,
parameters: full.parameters,
result: malformed,
},
);
expect(
parseMcpToolError(
await call("inspect_analysis_view", {
source: { kind: "inline", evidence: invalid },
view: { kind: "rust", collection: "edges" },
}),
).error,
).toMatchObject({ code: "invalid_request" });
}
},
60000,
);
cliTest(
"preserves unknown Rust metadata and refuses invalid compact CLI choices before reading",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-rust-view-missing-");
const path = join(root, "plain.elf");
await writeFile(
path,
createRustImage({ format: "elf", omitSection: true }),
);
const { call } = await connectLocalToolsMcp();
const summary = await call("inspect_rust_binary", {
path,
detail: "summary",
});
expect(summary.isError).not.toBe(true);
const view = analysisViewResultSchema.parse(
parseEvidence(summary.structuredContent).normalized_result,
);
expect(view).toMatchObject({
summary: { auditable_metadata: null },
source_coverage: {
embedded_records: "unknown",
runtime_dependencies: "unknown",
},
});
const missing = join(root, "never-created");
for (const args of [
["--detail", "summary"],
["--view", "{"],
["--detail", "complete", "--view", '{"collection":"packages"}'],
]) {
const refused = await cli.run({
arguments: ["inspect-rust-binary", missing, ...args, "--json"],
});
expect(refused.exitCode).toBe(1);
expect(refused.json).toMatchObject({ code: "invalid_request" });
expect(JSON.stringify(refused.json)).not.toContain("ENOENT");
}
expect((await readFile(path)).length).toBeGreaterThan(0);
},
30000,
);
+9 -2
View File
@@ -1,3 +1,4 @@
import { rustBinarySchema } from "../../../src/domain/rust/rustBinary.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
@@ -43,7 +44,10 @@ mcpTest(
const evidence = toolContract("inspect_rust_binary").outputSchema.parse(
response.structuredContent,
);
expect(evidence.normalized_result.auditable_metadata?.packages).toEqual([
expect(
rustBinarySchema.parse(evidence.normalized_result).auditable_metadata
?.packages,
).toEqual([
expect.objectContaining({ name: "itoa", version: "1.0.18" }),
expect.objectContaining({
name: "sample",
@@ -64,7 +68,10 @@ mcpTest(
const absentEvidence = toolContract(
"inspect_rust_binary",
).outputSchema.parse(absent.structuredContent);
expect(absentEvidence.normalized_result.auditable_metadata).toBeNull();
expect(
rustBinarySchema.parse(absentEvidence.normalized_result)
.auditable_metadata,
).toBeNull();
} finally {
await session.close();
}