Compare commits

..
Author SHA1 Message Date
N0zoM1z0 e65a13f54d fix(experiments): bound execution and record cross-domain findings
Reject timer overflow at the named execution contract and validate published SDK schemas through real stdio clients. Preserve explicit unknowns and value types in fact grading.

Record sixteen serial Terra-high trials, guided inventory questions and a migration decision that retains existing REA APIs. Twelve fact/reference checks pass; remaining managed trials expose classification omissions or transcript truncation. Twelve actual stdio tests pass, including guest allocation recovery, cancellation and retained partial Evidence.
2026-10-11 19:29:48 +00:00
N0zoM1z0 c11a75cbe9 test(experiments): compare source facts across equivalent report forms 2026-10-11 18:33:39 +00:00
N0zoM1z0 22326e8199 feat(experiments): add typed discovery and reproducible cross-domain questions 2026-10-11 18:18:22 +00:00
N0zoM1z0 3f6dcbc096 perf(experiments): stage discovery and verify managed agent facts 2026-10-11 18:02:47 +00:00
N0zoM1z0 89a7ca8486 fix(experiments): preserve partial evidence and queued cancellation ownership 2026-10-11 17:26:35 +00:00
N0zoM1z0 15cc7ce6a6 test(experiments): expand real-provider corpus and grade Codex projections 2026-10-11 17:10:53 +00:00
N0zoM1z0 d0b60a97ab perf(experiments): trim discovery and record real Codex health trials 2026-10-11 16:40:08 +00:00
N0zoM1z0 e3d4ac12b3 feat(experiments): add optional codemode MCP bridge and Codex trials 2026-10-11 16:20:16 +00:00
N0zoM1z0 b5e4920823 docs(experiments): pin codemode baseline and real-artifact corpus 2026-10-11 15:50:19 +00:00
856 changed files with 43262 additions and 86621 deletions
@@ -2,7 +2,7 @@
name: reverse-engineer-anything
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version: "39"
version: "38"
---
# REA
@@ -77,12 +77,6 @@ explicit path or endpoint and do not need it.
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. See the
[EVM bytecode guide](https://github.com/morluto/rea/blob/main/docs/evm-bytecode.md).
- Embedded Go compiler, module or build-setting metadata: `inspect_go_binary`
with the explicit local file. This built-in target-free inspection requires no
Go installation or disassembler. Preserve raw module bytes and source ranges;
absent metadata does not prove a binary is not Go, and unparsed text records
remain explicit. See the
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/go-binaries.md).
- Explicit Linux ELF file for offline layout, symbols, relocations or static
mitigation evidence: `inspect_binary_layout`. This target-free operation uses
caller-supplied pwntools without opening a disassembler database. Preserve its
@@ -121,14 +115,8 @@ ask when matches are ambiguous. Never choose an example app on the user's behalf
In a target-free session, use `open_binary` to bind any archive/package or
native target whose analysis tool operates on the active target. Do not call an
unadvertised tool; inspect `binary_session` with `{}` and its
`result.tool_availability` for availability reasons and remediation. Expand each
group's `names` into entries, inheriting the inventory-level client requirements
unless the group overrides them. In its closed effect and annotation vocabularies,
listed flags are true and omitted known flags are false. The grouped name union
is the complete tool inventory; use `tools/list` for canonical tool schemas.
Provider candidate facts describe possible providers, while top-level session
capabilities describe the current route and `analysis_provider_binding` names the
selected provider. Do not treat those scopes as interchangeable.
`result.tool_availability` for availability reasons and remediation. The tool
list stays complete; availability depends on the operation, target, and host.
## Work summary-first
@@ -11,14 +11,14 @@ main and an older release expose the same arguments.
## Choose the remedy from the observed failure
| Observation | Meaning and next step |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. |
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
| Observation | Meaning and next step |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Resolve only the prerequisite needed for the task; a complete tool catalog does not imply every operation is available. |
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
If the client cannot launch its shell/sandbox helper or read the installed skill,
inspect its launch diagnostic. For example, Codex can refuse helper aliases when
@@ -46,7 +46,7 @@ rea setup --client codex --dry-run --json
Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
`cursor`, `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`,
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `grok_build`, `omp`, `pi`,
`hermes`, `dsh`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
`hermes`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
For unsupported clients, use version-pinned manual stdio registration or CLI.
@@ -46,9 +46,3 @@ observed exit signal stays in `exit.signal`, `exit.code` is null for every
deadline exit, and the code of a finalizer that exited on its own is in
`exit.finalization.exit_code` (null on a signal exit or when no exit was
observed).
Use `inspect_analysis_view` with `view: {kind: "api-surface"}` on retained
`inspect_web_network_capture` HAR Evidence to group observed request lines by
method, origin and path template. Format matches are inferences, not handler
identity or schemas. Group pages cite parent record ordinals; consult record
coverage and exclusions before claiming completeness. No requests are replayed.
@@ -9,22 +9,7 @@ provenance; inputs and recovered code are never executed. Recovery requires a
caller-supplied Wakaru on Linux x64; see [the recovery guide](https://github.com/morluto/rea/blob/main/docs/javascript-recovery.md).
Use `analyze_javascript_application` directly on the operator-supplied ASAR or
extracted tree. When the question concerns known source paths and the connected
schema advertises `source_paths`, select exact inventoried paths such as
`["package.json", "main.js", "preload.js"]`. Include supporting manifests or
source maps explicitly; dependencies are not implicitly analyzed. REA retains
the complete artifact inventory and identity but reads source text and analyzes
only selected files. The result and summary record `source_selection`; files
outside scope and their relationships remain unknown, so partial application
coverage does not mean the selected source failed. Use repeated `--source-path`
on either static application CLI route. Omit the selection when the question
requires complete application analysis. `detail: "summary"` reduces delivery
and does not by itself reduce analysis work.
Exact `#alias` specifiers resolve through the importer's
enclosing package `imports` map to package-local files; nested `node_modules`
packages use their own map. Wildcard, recursive, and external-package import
targets stay unresolved with that limitation. When size is unknown or large and the connected schema advertises
extracted tree. When size is unknown or large and the connected schema advertises
`detail`, request `detail: "summary"`. This returns an `inspect_analysis_view`
Evidence record with a distinct ID and a `normalized_result.parent_evidence_id`
for the complete retained application analysis. Source-map contents are part of
@@ -1,16 +1,5 @@
# Native, managed, and packaged artifacts
## Go build metadata
Use `inspect_go_binary` for an explicit local ELF, PE or thin Mach-O when the
question concerns the embedded compiler version, module/dependency versions,
local replacements or build settings. No `open_binary`, Go installation or
target execution is required. The CLI equivalent is `inspect-go-binary PATH`.
Preserve artifact SHA-256, original file ranges, framed module bytes and every
unparsed module-text line. A missing record is an unknown, not proof of a
non-Go binary; observed metadata does not authenticate its claims. Function,
source and type recovery remain a separate native-analysis question.
## Recorded Linux crashes
Use `inspect_recorded_crash` with an explicit Linux x86-64 ELF core path to
@@ -61,19 +50,10 @@ runs and may change files or use the network. Hardened-runtime targets need the
`get-task-allow` entitlement.
Use `binary_session` with no arguments to check the open target, selected
provider, and alignment. Its default `result.tool_availability` carries a
complete inventory grouped by shared facts. Expand each group's `names` and
inherit the inventory-level client requirements unless a group overrides them;
availability, reasons, remediation, modes, and flags remain inline. The
`effects` and `annotations` arrays name true flags from fixed vocabularies, so an
omitted known flag is false. `tools/list` remains the canonical source for full
tool schemas when target or provider state changes.
Provider capability groups carry provider identity and ordered limitations.
Prepend each set's `shared_limitations` to a group's `limitations` to recover the
operation's full ordered list. Candidate capabilities describe possible
providers. Top-level session capabilities describe the current route, and
`analysis_provider_binding` identifies the selected provider.
provider, and alignment. Its default `result.tool_availability` includes the
complete tool inventory with availability reasons and remediation. When choosing
a tool, use its entry in that result; `tools/list` retains the complete catalog
when the target or provider state changes.
## DOS MZ with Ghidra
@@ -170,13 +150,6 @@ For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
analysis: the entry is `0x10100`, file offset zero, with imposed real-mode segment
context. PSP/stack/device state remains unmodeled. See [DOS guide](https://github.com/morluto/rea/blob/main/docs/ghidra-dos.md).
For other headerless images (embedded firmware without ELF/Mach-O/PE/MZ
headers), open with `format: "raw-binary"`, `architecture` (`x86`, `x86_64`,
`arm`, or `arm64`), and `entry_point` (`0x` hex). File offset 0 is mapped at
that address. Hopper and IDA refuse this interpretation; Ghidra uses
`BinaryLoader` with a little-endian generic language. Big-endian, Thumb-only,
and Cortex-M variants are unsupported. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
### Function annotations in Ghidra
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic and session-scoped; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits until close. CLI `annotate-native-function` returns the updated analysis before discarding the session. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier.
+1 -1
View File
@@ -493,7 +493,7 @@ jobs:
tests/acceptance/webSourceLocationCli.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
tests/boundary/filesystem/asarInventoryFreshness.test.ts
tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
tests/conformance/providers/ghidra/windowsCapabilities.test.ts
src/ghidra/GhidraInstallation.test.ts
tests/boundary/providers/ghidra/ghidraTransport.test.ts
-58
View File
@@ -1,58 +0,0 @@
name: Real Go binary metadata
on:
workflow_dispatch:
pull_request:
paths:
- ".github/workflows/real-go-binary.yml"
- "src/go/**"
- "src/domain/go/**"
- "src/domain/dosMz.ts"
- "src/application/go/**"
- "src/contracts/go/**"
- "src/composition/go.ts"
- "src/cli/goCommands.ts"
- "src/server/registerGoTools.ts"
- "src/server/createServer.ts"
- "src/cli.ts"
- "src/cliCommandNames.ts"
- "src/contracts/toolContracts.ts"
- "src/contracts/toolEffects.ts"
- "src/artifacts/readStableArtifact*.ts"
- "src/artifacts/ArtifactReader.ts"
- "src/process/PrivateRuntimeRoot.ts"
- "scripts/verify-go-binary.mjs"
- "scripts/lib/mcp-verifier-results.mjs"
- "scripts/rea.mjs"
- "package.json"
- "package-lock.json"
- ".nvmrc"
permissions:
contents: read
concurrency:
group: real-go-binary-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: --max-old-space-size=768
HUSKY: "0"
jobs:
real-go-binary:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- uses: actions/setup-go@v5
with:
go-version: "1.26.1"
cache: false
- run: npm ci
- name: Verify compiler-produced ELF, PE and Mach-O through public CLI and MCP
run: npm run verify:go:binary
+2 -2
View File
@@ -1,6 +1,6 @@
{
"_comment": "Test files remain included so the gate measures duplication across both src/ and tests/. The 3.0% limit leaves headroom for ordinary changes while keeping duplication visible.",
"threshold": 3.0,
"_comment": "Test files are intentionally NOT excluded: the gate used to report zero clones because it skipped **/*.test.ts, hiding all test-tree duplication. Current actual is ~2.47% lines across src+ and tests/, down from 3.4% when tests were included. The 2.6% limit leaves modest headroom for ordinary changes; lower it as duplication is removed.",
"threshold": 2.6,
"reporters": ["consoleFull"],
"ignore": [
"node_modules/**",
+1 -5
View File
@@ -14,14 +14,10 @@ Preserve caller-selected inputs and local evidence, including output, URLs, path
Dependencies flow inward: domain semantics and contracts, provider adapters, shared application workflows, then CLI/MCP adapters. Keep engine protocols and provider-specific code out of domain/application layers. See [docs/architecture.mermaid](docs/architecture.mermaid) when changing composition.
Give each interpretation and resource a clear owner. Consumers must use that owner's parsed facts rather than reinterpret raw source. Preserve identity, omission, uncertainty, and source evidence across boundaries. Retain ownership of resources whose cleanup failed. Reuse `src/process/` supervision and identity primitives; a PID and executable path alone do not establish ownership after exit or reuse.
Keep proof of ownership separate from validity, editability, and readiness. Malformed owned data may still be safely removable. Unresolved identity or applicability must remain unknown rather than be treated as absent, foreign, or aligned.
Give each interpretation and resource a clear owner. Preserve identity, omission, uncertainty, and source evidence across boundaries. Retain ownership of resources whose cleanup failed. Reuse `src/process/` supervision and identity primitives; a PID and executable path alone do not establish ownership after exit or reuse.
Assess brittleness in touched code during implementation, debugging, review, and maintenance. Fix the producing representation or ownership rule before adding downstream repairs. Normalize once at the owning boundary using format-aware parsers. Remove superseded representations, aliases, wrappers, and redundant tests after verifying affected consumers. Introduce abstractions only when they remove observed complexity.
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
Parse unknown boundary values and model expected failures with the tagged error algebra and `Result`. Preserve partial facts and meaningful failure reasons. Keep identity and lookup values separate from display formatting; preserve source values and explicit unknowns when normalization loses information. Absolute paths, file URLs, and HTTP paths have different semantics.
Validate advertised JSON Schemas after SDK conversion against their declared dialect and actual clients. Bind handlers to named contracts with exact types; catalog ordering must never select an operation or schema.
+3 -5
View File
@@ -9,6 +9,7 @@
**See a feature you like. Understand how it works, down to the binary level.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#what-you-can-analyze)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -154,7 +155,6 @@ Additional tools and host support depend on the target:
| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](https://rea.tools/guides/native/) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
@@ -245,9 +245,7 @@ Ghidra and IDA use your existing installations. See [provider setup](docs/instal
<summary><strong>Do I need to start Hopper first?</strong></summary>
REA starts Hopper when an operation needs it. On macOS, a first-run dialog may
ask you to choose demo mode or activate your license. Complete that prompt in the
same user session before your first CLI/MCP operation or unattended job. Open
Hopper manually if needed. See
ask you to choose demo mode or activate your license. See
[Hopper startup and troubleshooting](docs/installation.md#launcher-paths-and-troubleshooting).
</details>
@@ -317,7 +315,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
## Star history
🎉 **110,000 GitHub stars — thank you!**
🎉 **90,000 GitHub stars — thank you!**
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
+2 -1
View File
@@ -9,6 +9,7 @@
**وجدت ميزة تعجبك؟ افهم طريقة عملها حتى مستوى الشيفرة الثنائية.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -260,7 +261,7 @@ npx rea-agents@latest setup
## سجل النجوم
🎉 **110,000 نجمة على GitHub — شكرًا لكم!**
🎉 **90,000 نجمة على GitHub — شكرًا لكم!**
شكرًا لكل من يستخدم REA، ويبلّغ عن الأخطاء، ويقترح ميزات، ويختبر إصدارات البناء، ويساهم بالإصلاحات.
+2 -1
View File
@@ -9,6 +9,7 @@
**Eine Funktion gefällt dir? Verstehe, wie sie funktioniert – bis hinunter auf die Binärebene.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Melde Sicherheitslücken gemäß [SECURITY.md](SECURITY.md).
## Sternverlauf
🎉 **110.000 GitHub-Sterne – vielen Dank!**
🎉 **90.000 GitHub-Sterne – vielen Dank!**
Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds testen und Fehlerbehebungen beitragen.
+2 -1
View File
@@ -9,6 +9,7 @@
**¿Ves una función que te gusta? Entiende cómo funciona, hasta el nivel binario.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Informa de vulnerabilidades siguiendo [SECURITY.md](SECURITY.md).
## Historial de estrellas
🎉 **110.000 estrellas en GitHub: ¡gracias!**
🎉 **90.000 estrellas en GitHub: ¡gracias!**
Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban compilaciones y contribuyen con correcciones.
+2 -1
View File
@@ -9,6 +9,7 @@
**قابلیتی دیده‌اید که دوستش دارید؟ سازوکارش را تا سطح باینری درک کنید.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -243,7 +244,7 @@ npx rea-agents@latest setup
## تاریخچهٔ ستاره‌ها
🎉 **۱۱۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
🎉 **۹۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
از همهٔ کسانی که از REA استفاده می‌کنند، خطاها را گزارش می‌دهند، قابلیت‌های جدید پیشنهاد می‌کنند، buildها را آزمایش می‌کنند و در بهبود پروژه مشارکت دارند سپاسگزاریم.
+2 -1
View File
@@ -9,6 +9,7 @@
**Repérez une fonctionnalité qui vous plaît. Comprenez son fonctionnement, jusqu'au niveau du binaire.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -262,7 +263,7 @@ Signalez les vulnérabilités via [SECURITY.md](SECURITY.md).
## Historique des étoiles
🎉 **110 000 étoiles GitHub — merci !**
🎉 **90 000 étoiles GitHub — merci !**
Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent des fonctionnalités, testent les builds et contribuent des correctifs.
+2 -1
View File
@@ -9,6 +9,7 @@
**Melihat fitur yang Anda sukai. Pahami cara kerjanya hingga ke tingkat biner.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -283,7 +284,7 @@ Laporkan kerentanan melalui [SECURITY.md](SECURITY.md).
## Riwayat Bintang
🎉 **110.000 bintang GitHub, terima kasih!**
🎉 **90.000 bintang GitHub, terima kasih!**
Terima kasih kepada semua orang yang menggunakan REA, melaporkan bug, meminta fitur, menguji build, dan berkontribusi dalam perbaikan.
+2 -1
View File
@@ -9,6 +9,7 @@
**Vedi una funzionalità che ti piace. Capisci come funziona, fino al livello binario.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#cosa-puoi-analizzare)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -262,7 +263,7 @@ Segnala vulnerabilità tramite [SECURITY.md](SECURITY.md).
## Cronologia delle stelle
🎉 **110.000 stelle su GitHub — grazie!**
🎉 **90.000 stelle su GitHub — grazie!**
Grazie a tutti coloro che usano REA, segnalano bug, propongono funzionalità, testano le build e contribuiscono con correzioni.
+2 -1
View File
@@ -9,6 +9,7 @@
**気に入った機能を見つけたら、その仕組みをバイナリのレベルまで理解する。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -260,7 +261,7 @@ npx rea-agents@latest setup
## スター履歴
🎉 **GitHub スター 110,000 件、ありがとうございます!**
🎉 **GitHub スター 90,000 件、ありがとうございます!**
REA の利用、バグ報告、機能の要望、ビルドのテスト、修正への貢献に感謝します。
+2 -1
View File
@@ -9,6 +9,7 @@
**마음에 드는 기능을 발견했다면, 바이너리 수준까지 작동 원리를 이해하세요.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -260,7 +261,7 @@ npx rea-agents@latest setup
## 스타 기록
🎉 **GitHub 스타 110,000개, 감사합니다!**
🎉 **GitHub 스타 90,000개, 감사합니다!**
REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
+2 -1
View File
@@ -9,6 +9,7 @@
**Widzisz funkcję, która Ci się podoba? Dowiedz się, jak działa, aż do poziomu kodu binarnego.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Zgłaszaj podatności zgodnie z [SECURITY.md](SECURITY.md).
## Historia gwiazdek
🎉 **110 000 gwiazdek na GitHubie — dziękujemy!**
🎉 **90 000 gwiazdek na GitHubie — dziękujemy!**
Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują nowe funkcje, testują kompilacje i przesyłają poprawki.
+2 -1
View File
@@ -9,6 +9,7 @@
**Viu uma funcionalidade de que gostou? Entenda como ela funciona, até o nível binário.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Relate vulnerabilidades conforme [SECURITY.md](SECURITY.md).
## Histórico de estrelas
🎉 **110.000 estrelas no GitHub — muito obrigado!**
🎉 **90.000 estrelas no GitHub — muito obrigado!**
Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam builds e contribuem com correções.
+2 -1
View File
@@ -9,6 +9,7 @@
**Нашли интересную функцию? Разберитесь, как она работает, вплоть до двоичного кода.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ npx rea-agents@latest setup
## История звёзд
🎉 **110 000 звёзд на GitHub — спасибо!**
🎉 **90 000 звёзд на GitHub — спасибо!**
Спасибо всем, кто использует REA, сообщает об ошибках, предлагает новые функции, тестирует сборки и помогает с исправлениями.
+2 -1
View File
@@ -9,6 +9,7 @@
**เห็นฟีเจอร์ที่ชอบแล้วอยากรู้ว่าทำงานอย่างไร? เข้าใจได้ลึกถึงระดับไบนารี**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ npx rea-agents@latest setup
## ประวัติดาว
🎉 **110,000 ดาวบน GitHub — ขอบคุณทุกคน!**
🎉 **90,000 ดาวบน GitHub — ขอบคุณทุกคน!**
ขอบคุณทุกคนที่ใช้ REA รายงานบั๊ก เสนอฟีเจอร์ ทดสอบบิลด์ และช่วยแก้ไข
+2 -1
View File
@@ -9,6 +9,7 @@
**Beğendiğiniz bir özellik mi gördünüz? İkili kod düzeyine kadar nasıl çalıştığını anlayın.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Güvenlik açıklarını [SECURITY.md](SECURITY.md) üzerinden bildirin.
## Yıldız geçmişi
🎉 **GitHub'da 110.000 yıldız — teşekkürler!**
🎉 **GitHub'da 90.000 yıldız — teşekkürler!**
REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve düzeltmelere katkıda bulunan herkese teşekkürler.
+2 -1
View File
@@ -9,6 +9,7 @@
**Побачили цікаву функцію? Зрозумійте, як вона працює, аж до рівня двійкового коду.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ npx rea-agents@latest setup
## Історія зірок
🎉 **110 000 зірок на GitHub — дякуємо!**
🎉 **90 000 зірок на GitHub — дякуємо!**
Дякуємо всім, хто використовує REA, повідомляє про помилки, пропонує нові функції, тестує збірки й допомагає з виправленнями.
+2 -1
View File
@@ -9,6 +9,7 @@
**Thấy một tính năng bạn thích? Hiểu cách nó hoạt động, đến tận mức mã nhị phân.**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -258,7 +259,7 @@ Báo cáo lỗ hổng theo [SECURITY.md](SECURITY.md).
## Lịch sử sao
🎉 **110.000 sao trên GitHub — xin cảm ơn!**
🎉 **90.000 sao trên GitHub — xin cảm ơn!**
Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính năng, kiểm thử bản dựng và đóng góp bản sửa lỗi.
+2 -1
View File
@@ -9,6 +9,7 @@
**看到喜歡的功能,弄清它的工作原理,深入到二進位層面。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -260,7 +261,7 @@ npx rea-agents@latest setup
## Star 歷史
🎉 **GitHub Star 達到 110,000 個,感謝大家!**
🎉 **GitHub Star 達到 90,000 個,感謝大家!**
感謝每一位使用 REA、報告錯誤、提出功能需求、測試建置和貢獻修復的朋友。
+2 -1
View File
@@ -9,6 +9,7 @@
**看到喜欢的功能,弄清它的工作原理,深入到二进制层面。**
[![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents)
[![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml)
[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/mcp-contracts.md#generated-catalog)
[![Node.js requirements](https://img.shields.io/badge/Node.js-requirements-339933?style=flat-square&logo=nodedotjs&logoColor=white)](#current-status)
[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything)
@@ -260,7 +261,7 @@ npx rea-agents@latest setup
## Star 历史
🎉 **GitHub Star 达到 110,000 个,感谢大家!**
🎉 **GitHub Star 达到 90,000 个,感谢大家!**
感谢每一位使用 REA、报告错误、提出功能需求、测试构建和贡献修复的朋友。
+2 -16
View File
@@ -126,20 +126,6 @@ class ReaJadxBridge {
return result;
}
private JadxSession.SmartCode methodBody(JadxSession current, ClassSnapshot cls,
MethodSnapshot selected, JavaMethod method) {
if (!selected.shortId().equals("<clinit>()V"))
return current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
// Java code generation can fold all or part of <clinit> into field declarations.
// Its method text cannot establish coverage, even when a static block remains.
JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), method.getName());
if (!smali.getFound() || smali.getBlocks().size() != 1)
throw new IllegalArgumentException("Static initializer disassembly requires one exact method: " + selected.shortId());
return new JadxSession.SmartCode(
TextUtilKt.truncateToBytes(smali.getBlocks().get(0), current.getMaxSourceBytes()),
"smali", true, List.of("static_initializer_disassembly"));
}
private CallToolResult handle(String name, JsonObject args) {
if (name.equals("rea_jvm_status")) return json(Map.of(
"engine_reported_version", BuildInfo.VERSION,
@@ -164,7 +150,7 @@ class ReaJadxBridge {
int index = integer(args, "overload_index", 0);
if (index >= overloads.size()) throw new InputFailure("overload_index", "overload_index out of range: " + index);
JavaMethod method = method(cls, overloads.get(index));
JadxSession.SmartCode code = methodBody(current, cls, overloads.get(index), method);
JadxSession.SmartCode code = current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
return json(Map.of("class_name", cls.summary().get("full_name"),
"method_name", overloads.get(index).name(), "full_name", method.getFullName(),
"overload_index", index, "overload_count", overloads.size(),
@@ -225,7 +211,7 @@ class ReaJadxBridge {
String description = switch (name) {
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
case "get_method_body" -> "Source for an exact class and stable same-name overload index, with smali fallback.";
default -> "Complete static references to an exact class or unique method; source lines are unresolved.";
};
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
@@ -1,38 +0,0 @@
import java.util.Map;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.util.headless.HeadlessScript;
/** Disable the named auto-analyzers before default auto-analysis. */
public final class ReaGhidraAnalysisOptions extends HeadlessScript {
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
if (arguments.length != 1 || arguments[0].isBlank()) {
throw new IllegalArgumentException(
"ReaGhidraAnalysisOptions requires one comma-separated analyzer-name argument");
}
String[] names = arguments[0].split(",");
AutoAnalysisManager analysis = AutoAnalysisManager.getAnalysisManager(currentProgram);
Map<String, String> options = getCurrentAnalysisOptionsAndValues(currentProgram);
for (String requestedName : names) {
monitor.checkCancelled();
String name = requestedName.trim();
if (analysis.getAnalyzer(name) == null || !options.containsKey(name)) {
throw new IllegalArgumentException("Unknown Ghidra analyzer for this program: " + name);
}
}
for (String requestedName : names) {
monitor.checkCancelled();
setAnalysisOption(currentProgram, requestedName.trim(), "false");
}
options = getCurrentAnalysisOptionsAndValues(currentProgram);
for (String requestedName : names) {
monitor.checkCancelled();
String name = requestedName.trim();
if (!"false".equals(options.get(name))) {
throw new IllegalStateException("Ghidra analyzer remained enabled: " + name);
}
}
}
}
+4 -29
View File
@@ -182,17 +182,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
sessionDefaultAddressSpace =
currentProgram.getAddressFactory().getDefaultAddressSpace();
if (currentProgram.getExecutableFormat().equals("Raw Binary")) {
boolean dosCom = currentProgram.getLanguageID().getIdAsString()
.equals("x86:LE:16:Real Mode");
String preparation = dosCom ? "REA DOS COM prepared" : "REA raw binary prepared";
if (!currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getBoolean(preparation, false)) {
throw new IllegalStateException(
dosCom ? "REA DOS COM entry/context preparation failed" :
"REA raw binary entry preparation failed"
);
}
if (currentProgram.getExecutableFormat().equals("Raw Binary") &&
!currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getBoolean("REA DOS COM prepared", false)) {
throw new IllegalStateException("REA DOS COM entry/context preparation failed");
}
if (!Application.getApplicationVersion().equals(descriptor.providerVersion)) {
throw new IllegalStateException("Ghidra provider version does not match the session");
@@ -450,11 +443,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
String seeds = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA analysis seeds", null);
if (seeds != null) result.add("analysis_seeds", JsonParser.parseString(seeds));
// Written by ReaGhidraMipsSoftFloat after analysis; REA requires it
// exactly when the committed profile declares soft-float storage.
String softFloat = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA MIPS soft-float storage", null);
if (softFloat != null) result.add("mips_soft_float_storage", JsonParser.parseString(softFloat));
return result;
}
@@ -908,19 +896,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.add("procedure", function == null ? JsonNull.INSTANCE : GSON.toJsonTree(canonicalAddress(function.getEntryPoint())));
result.addProperty("architecture", currentProgram.getLanguageID().getIdAsString());
result.addProperty("mode", currentProgram.getLanguage().getLanguageDescription().getVariant());
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:")) {
JsonArray decoderContext = new JsonArray();
var modeRegister = currentProgram.getRegister("ISA_MODE");
if (modeRegister != null) {
var modeValue = currentProgram.getProgramContext().getValue(modeRegister, address, false);
JsonObject context = new JsonObject();
context.addProperty("register", modeRegister.getName());
context.add("value", modeValue == null ? JsonNull.INSTANCE : GSON.toJsonTree(modeValue.toString()));
context.addProperty("source", "ghidra-program-context");
decoderContext.add(context);
}
result.add("decoder_context", decoderContext);
}
JsonArray operands = new JsonArray();
JsonArray references = new JsonArray();
JsonArray destinations = new JsonArray();
@@ -1,14 +0,0 @@
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
// Compile the no-op activator with REA's scripts. Otherwise Ghidra generates
// one afterward and opens a second Java compiler and file manager.
public final class ReaGhidraBundleActivator implements BundleActivator {
@Override
public void start(BundleContext context) {
}
@Override
public void stop(BundleContext context) {
}
}
-176
View File
@@ -1,176 +0,0 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.database.SpecExtension;
import ghidra.program.model.data.AbstractFloatDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DoubleDataType;
import ghidra.program.model.data.FloatDataType;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.program.model.data.IntegerDataType;
import ghidra.program.model.data.ParameterDefinition;
import ghidra.program.model.data.TypeDef;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.XmlEncode;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Soft-float o32 passes and returns float/double values exactly where same-size integers go.
* Ghidra's MIPS32 compiler specifications model only hard-float o32, so every typed float
* prototype (functions, externals, thunks via their targets, and function-pointer definitions)
* would otherwise read $f12/$f14/$f0. Derive a program-specific prototype model from the
* program's own default o32 model with its floating-point entries and rules removed, install it
* as a specification extension, and assign it to every callable whose formal types use floats.
*/
public final class ReaGhidraMipsSoftFloat extends GhidraScript {
static final String REPORT_KEY = "REA MIPS soft-float storage";
static final String POLICY = "o32-soft-float-prototype-v1";
static final String PROTOTYPE = "__rea_soft_float";
private static final Pattern FPU_REGISTER = Pattern.compile("f\\d+(_\\d+)?");
@Override
public void run() throws Exception {
String processor = currentProgram.getLanguage().getProcessor().toString();
if (!processor.equals("MIPS") || currentProgram.getDefaultPointerSize() != 4)
throw new IllegalStateException(
"REA soft-float storage requires a 32-bit MIPS program, not " + processor);
CompilerSpec spec = currentProgram.getCompilerSpec();
PrototypeModel base = spec.getDefaultCallingConvention();
new SpecExtension(currentProgram)
.addReplaceCompilerSpecExtension(softFloatPrototype(base, spec), monitor);
PrototypeModel soft = currentProgram.getCompilerSpec().getCallingConvention(PROTOTYPE);
if (soft == null)
throw new IllegalStateException("REA soft-float prototype was not installed");
requireIntegerStorage(
soft.getStorageLocations(
currentProgram,
new DataType[] {
FloatDataType.dataType, IntegerDataType.dataType, DoubleDataType.dataType,
FloatDataType.dataType, DoubleDataType.dataType
},
false),
"soft-float prototype self-check");
int functions = 0;
List<Function> callables = new ArrayList<>();
currentProgram.getFunctionManager().getFunctions(true).forEach(callables::add);
currentProgram.getFunctionManager().getExternalFunctions().forEach(callables::add);
for (Function function : callables) {
// A thunk's signature is its target's; the target is in this list.
if (function.isThunk() || !usesFloat(function)) continue;
if (function.hasCustomVariableStorage()) function.setCustomVariableStorage(false);
function.setCallingConvention(PROTOTYPE);
List<VariableStorage> storage = new ArrayList<>();
storage.add(function.getReturn().getVariableStorage());
for (Parameter parameter : function.getParameters()) storage.add(parameter.getVariableStorage());
requireIntegerStorage(storage.toArray(VariableStorage[]::new), function.getName(true));
functions += 1;
}
int definitions = 0;
Iterator<DataType> types = currentProgram.getDataTypeManager().getAllDataTypes();
while (types.hasNext()) {
if (!(types.next() instanceof FunctionDefinition definition) || !usesFloat(definition))
continue;
definition.setCallingConvention(PROTOTYPE);
definitions += 1;
}
// Written last: any failure above leaves no report, and REA refuses the session.
currentProgram.getOptions(Program.PROGRAM_INFO).setString(
REPORT_KEY,
"{\"policy\":\"" + POLICY + "\",\"functions\":" + functions +
",\"definitions\":" + definitions + "}");
println("REA MIPS soft-float prototype: functions=" + functions + " definitions=" + definitions);
}
/** The default o32 model without floating-point registers or float-consuming rules. */
private static String softFloatPrototype(PrototypeModel base, CompilerSpec spec) throws Exception {
XmlEncode encoder = new XmlEncode();
base.encode(encoder, spec.getPcodeInjectLibrary());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Document document =
factory.newDocumentBuilder().parse(new InputSource(new StringReader(encoder.toString())));
Element root = document.getDocumentElement();
if (!root.getTagName().equals("prototype"))
throw new IllegalStateException("Unexpected default prototype encoding " + root.getTagName());
root.setAttribute("name", PROTOTYPE);
List<Node> removed = new ArrayList<>();
collect(root.getElementsByTagName("pentry"), "metatype", "float", null, removed);
collect(root.getElementsByTagName("consume"), "storage", "float", "rule", removed);
if (removed.isEmpty())
throw new IllegalStateException("Default prototype has no floating-point entries to remove");
for (Node node : removed) node.getParentNode().removeChild(node);
StringWriter xml = new StringWriter();
var transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(document), new StreamResult(xml));
return xml.toString();
}
/** Select matching elements, or their nearest `ancestor` element when one is named. */
private static void collect(
NodeList nodes, String attribute, String value, String ancestor, List<Node> selected) {
for (int index = 0; index < nodes.getLength(); index += 1) {
Element element = (Element) nodes.item(index);
if (!value.equals(element.getAttribute(attribute))) continue;
Node target = element;
while (ancestor != null && target != null && !ancestor.equals(target.getNodeName()))
target = target.getParentNode();
if (target != null && !selected.contains(target)) selected.add(target);
}
}
private static void requireIntegerStorage(VariableStorage[] storage, String owner) {
for (VariableStorage item : storage) {
if (item.isBadStorage() || item.isUnassignedStorage())
throw new IllegalStateException("REA soft-float storage unresolved for " + owner);
List<Register> registers = item.getRegisters();
if (registers == null) continue; // stack-only storage
for (Register register : registers) {
if (FPU_REGISTER.matcher(register.getName()).matches())
throw new IllegalStateException(
"REA soft-float storage still uses " + register.getName() + " for " + owner);
}
}
}
private static boolean usesFloat(Function function) {
if (isFloat(function.getReturn().getFormalDataType())) return true;
for (Parameter parameter : function.getParameters()) {
if (isFloat(parameter.getFormalDataType())) return true;
}
return false;
}
private static boolean usesFloat(FunctionDefinition definition) {
if (isFloat(definition.getReturnType())) return true;
for (ParameterDefinition argument : definition.getArguments()) {
if (isFloat(argument.getDataType())) return true;
}
return false;
}
private static boolean isFloat(DataType type) {
DataType current = type;
while (current instanceof TypeDef typeDef) current = typeDef.getBaseDataType();
return current instanceof AbstractFloatDataType;
}
}
-36
View File
@@ -1,36 +0,0 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
/** Seed the caller-supplied raw-binary entry before default auto-analysis. */
public final class ReaGhidraPrepareRaw extends GhidraScript {
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
if (arguments.length != 2) {
throw new IllegalArgumentException(
"Raw binary preparation requires the committed language and entry address"
);
}
String expectedLanguage = arguments[0];
String entryHex = arguments[1];
if (!currentProgram.getLanguageID().getIdAsString().equals(expectedLanguage) ||
!currentProgram.getExecutableFormat().equals("Raw Binary")) {
throw new IllegalArgumentException(
"Raw binary preparation requires the committed language and BinaryLoader import"
);
}
Address entry = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(entryHex);
Address start = currentProgram.getMinAddress();
if (entry == null || !entry.equals(start)) {
throw new IllegalArgumentException(
"Raw binary import must map file offset 0 at the caller-supplied entry"
);
}
addEntryPoint(entry);
if (!disassemble(entry) || createFunction(entry, "entry") == null) {
throw new IllegalStateException("Raw binary entry cannot be decoded as a function");
}
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA raw binary prepared", true);
}
}
+34 -76
View File
@@ -112,11 +112,6 @@ enum RunTokenReadError: Error {
case ambiguousEnvironmentBoundary
}
struct ParsedRunToken {
let runId: String?
let environmentEntryCount: Int
}
@_silgen_name("csops")
private func csops(
_ pid: Int32,
@@ -127,67 +122,19 @@ private func csops(
private let csOpsStatus: UInt32 = 0
private let csPlatformBinary: UInt32 = 0x0400_0000
private let csRestrict: UInt32 = 0x0000_0800
/** Read the target's current code-signing flags when csops permits inspection. */
private func codeSigningFlags(pid: Int32) -> UInt32? {
/**
* Whether the target image is an Apple platform binary. Current macOS omits
* the environment of platform binaries from KERN_PROCARGS2 for every caller,
* so this distinguishes a policy-withheld environment from a process whose
* environment is genuinely absent or transiently unreadable.
*/
func isPlatformBinary(pid: Int32) -> Bool {
var flags: UInt32 = 0
let result = withUnsafeMutablePointer(to: &flags) { pointer in
csops(pid, csOpsStatus, UnsafeMutableRawPointer(pointer), MemoryLayout<UInt32>.size)
}
return result == 0 ? flags : nil
}
/** Derive one process-token observation from parser facts and native flags. */
func processRunTokenObservation(
pid: Int32,
parse: Result<ParsedRunToken, RunTokenReadError>,
codeSigningFlags: () -> UInt32?
) -> RunTokenObservation {
switch parse {
case .success(let parsed):
if let runId = parsed.runId {
return RunTokenObservation(pid: pid, state: "readable", run_id: runId, reason: nil)
}
// A non-empty environment without REA's token proves this candidate is
// unrelated. CS_RESTRICT cannot turn that readable fact into uncertainty.
if parsed.environmentEntryCount > 0 {
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
}
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
case .failure(let failure):
switch failure {
case .environmentUnavailable:
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "environment_unavailable")
case .ambiguousEnvironmentBoundary:
if let flags = codeSigningFlags(), (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
case .duplicateToken:
return unavailable(pid: pid, reason: "duplicate_run_token")
case .invalidTokenEncoding:
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
case .appleVectorUnavailable:
return unavailable(pid: pid, reason: "apple_vector_unavailable")
case .invalidBuffer:
return unavailable(pid: pid, reason: "malformed_procargs")
}
}
return result == 0 && (flags & csPlatformBinary) != 0
}
private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
@@ -204,19 +151,33 @@ private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
return unavailable(pid: pid, reason: "sysctl_failed_\(errno)")
}
bytes = Array(bytes.prefix(size))
let parse: Result<ParsedRunToken, RunTokenReadError>
do {
parse = .success(try parseProcessEnvironment(in: bytes, pointerSize: pointerSize))
} catch let failure as RunTokenReadError {
parse = .failure(failure)
let runId = try token(in: bytes, pointerSize: pointerSize)
// A complete empty environment is readable for an ordinary image. Apple
// platform images can expose the same layout after the kernel withholds
// their environment, so they remain explicitly unverified without a token.
if runId == nil && isPlatformBinary(pid: pid) {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
return RunTokenObservation(pid: pid, state: "readable", run_id: runId, reason: nil)
} catch RunTokenReadError.duplicateToken {
return unavailable(pid: pid, reason: "duplicate_run_token")
} catch RunTokenReadError.invalidTokenEncoding {
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
} catch RunTokenReadError.environmentUnavailable {
return unavailable(
pid: pid,
reason: isPlatformBinary(pid: pid)
? "platform_binary_environment_withheld"
: "environment_unavailable"
)
} catch RunTokenReadError.appleVectorUnavailable {
return unavailable(pid: pid, reason: "apple_vector_unavailable")
} catch RunTokenReadError.ambiguousEnvironmentBoundary {
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
} catch {
parse = .failure(.invalidBuffer)
return unavailable(pid: pid, reason: "malformed_procargs")
}
return processRunTokenObservation(
pid: pid,
parse: parse,
codeSigningFlags: { codeSigningFlags(pid: pid) }
)
}
func readRunTokens(pids: [Int32]) -> [RunTokenObservation] {
@@ -240,10 +201,7 @@ func readRunTokens(pids: [Int32]) -> [RunTokenObservation] {
}
}
func parseProcessEnvironment(
in bytes: [UInt8],
pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size
) throws -> ParsedRunToken {
func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size) throws -> String? {
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBuffer }
let argc = bytes.withUnsafeBytes { $0.loadUnaligned(as: Int32.self).littleEndian }
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer }
@@ -319,7 +277,7 @@ func parseProcessEnvironment(
found = decoded
}
}
return ParsedRunToken(runId: found, environmentEntryCount: environmentEntries.count)
return found
}
private func readString(
-5
View File
@@ -206,11 +206,6 @@ def inspect_elf(path, cache):
"visibility": raw.st_other.visibility, "section_index": raw.st_shndx,
})
if h.sh_type in ("SHT_REL", "SHT_RELA"):
if h.sh_size % h.sh_entsize != 0:
raise LayoutFailure(
"format",
f"ELF relocation section {index} size is not a multiple of its {h.sh_entsize}-byte entries.",
)
if image.header.e_type == "ET_REL" and h.sh_info >= len(all_sections):
raise LayoutFailure("format", f"ELF relocation section {index} references missing target section {h.sh_info}.")
symbol_table = None
-2
View File
@@ -26,7 +26,6 @@ export default defineConfig({
text: "Investigation guides",
items: [
{ text: "Native binaries", link: "/native-investigation" },
{ text: "Go build metadata", link: "/go-binaries" },
{
text: "JavaScript and Electron",
link: "/javascript-artifact-reconstruction",
@@ -55,7 +54,6 @@ export default defineConfig({
{ text: "IDA", link: "/ida-provider" },
{ text: "Ghidra on Windows", link: "/windows-ghidra-p0" },
{ text: "Ghidra DOS analysis", link: "/ghidra-dos" },
{ text: "Ghidra raw binary", link: "/raw-binary" },
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
{ text: "Ghidra semantics", link: "/ghidra-provider" },
],
+2 -11
View File
@@ -88,7 +88,7 @@ existing analysis families directly, for example
| `list_adb_packages` | read | `pm list packages -f` with scope; base APK device paths |
| `resolve_adb_packages` | read | Case-insensitive package-name substring search with exact-match flag |
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, permission count |
| `list_adb_directory` | read | NUL-framed immediate children, including hidden entries: kinds, permissions, owner, group, sizes, symlink targets |
| `list_adb_directory` | read | `ls -la`: kinds, permissions, owner, group, sizes, symlink targets |
| `pull_adb_package` | read | Complete split APK set from `pm path`, each file digested |
| `pull_adb_file` | read | One absolute device path pulled and digested |
| `capture_adb_screen` | read | One `screencap -p` frame (exec-out, no device-side file) with dimensions |
@@ -102,22 +102,13 @@ existing analysis families directly, for example
## Interpret the result
- **Directory names retain whitespace and newlines.** Symlink targets do too,
including trailing newlines. Records containing invalid UTF-8 remain as
labelled hex in `unparsed_lines` and make directory coverage partial.
- **Inferences carry their basis.** Device `kind` reports
`kind_basis` (serial prefix or USB transport); APK `role` reports
`role_basis: file_name`; `emulator_observed` is the build's own
`ro.kernel.qemu` value, `null` when absent.
- **Device paths are not host paths.** `base_apk_device_path` values live on
the device filesystem. Only pulls and captures produce host-local paths,
each paired with byte size and SHA-256 digest. A pulled file's host name is
the device path's final POSIX segment. REA preserves the original device
path and filename; a missing final segment or `.`/`..` is refused before
output creation. Windows hosts follow [Windows filename rules](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file),
while POSIX hosts retain valid POSIX filenames such as names with spaces or
colons. A package pull checks reported names against the destination
filesystem before pulling, and refuses names that would share a local path.
each paired with byte size and SHA-256 digest.
- **Splits are part of the answer.** `pm path` enumerates the base APK and
every installed split; pulls cover the whole set. System packages whose
APKs keep other names report the `unknown` role rather than guessing.
+1 -11
View File
@@ -94,12 +94,6 @@ fallback for an overloaded method instead of attributing all bodies to one
selected overload. Native/abstract methods with no decompiled body report
`body_status: "not_available"` without inferring which implementation flag caused it.
Static initializers (`<clinit>`) return smali with the
`static_initializer_disassembly` marker. Java decompilation can move their
instructions into field declarations, leaving an empty or incomplete method
body. The selected method's disassembly preserves those instructions and retains
the same source byte limit and explicit truncation reporting.
## Interpret the result
- Package inspection retains decoded manifest XML, summary declarations,
@@ -107,11 +101,7 @@ the same source byte limit and explicit truncation reporting.
upstream engine and should be checked against the retained XML when needed.
Signature verification is explicitly `not_performed`.
- Class search consumes all pages and checks their counts. An empty `query`
inventories all class-name rows; class names are decompiler representations.
Repeated names remain repeated in `matches` so the inventory row count and
producer evidence stay visible. `duplicate_names` identifies matching names
with multiple rows and their occurrence counts; a name alone does not
distinguish which provider class node a later lookup selects.
inventories all class names; class names are decompiler representations.
- Class inventories read parsed metadata without generating source and return
full reported display types, fields and inner class names. Synthetic members
that code generation hides can appear in the inventory; it stays consistent
+1 -42
View File
@@ -37,44 +37,8 @@ module page. See
[JavaScript application workflows](javascript-application-workflows.md) for
views, feature traces and comparisons that take the same saved Evidence.
## Managed assemblies (.NET)
For a .NET PE/CLI assembly, inspect its identity and deployment form before
inspecting members. These commands read metadata and Common Intermediate
Language (CIL) without loading or executing the assembly and require no native
analysis engine:
```bash
rea inspect-managed-artifact /absolute/path/Assembly.dll --json
rea inspect-managed-members /absolute/path/Assembly.dll --json
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser --json
```
Replace `Example.Parser` with an exact `full_name` from the member inspection,
or a declaring type name you already know. Omit `--type` to inspect all members,
or repeat it to select several types.
Selection limits decoding to the selected types; member references remain an
assembly-wide inventory, so the output can still be large.
Choose the workflow from the artifact's contents rather than its `.exe` or
`.dll` extension. The managed parser requires PE/CLI metadata; it does not
unpack .NET single-file hosts or decode IL2CPP metadata. NativeAOT method bodies
require native analysis. See the [managed-code guide](managed-code-analysis.md)
for supported deployment forms, selection semantics, and coverage limits.
## Native analysis
Inspect embedded Go build metadata without installing Go or configuring a
disassembler:
```bash
rea inspect-go-binary /absolute/path/to/program --json
```
The shared CLI/MCP workflow reports compiler version, modules, replacements,
build settings, original bytes and SHA-256. See [Go binaries](go-binaries.md)
for supported containers and absent or malformed metadata.
Configure [Hopper or Ghidra](installation.md#hopper), or the
[IDA adapter](ida-provider.md), before analyzing a native target. Substitute
your target, search text and function name or address in these examples:
@@ -95,8 +59,7 @@ alone, use `rea instructions`. A macOS `.app` bundle can be supplied directly.
Run `rea --help` or a command's `--help` for arguments and output options.
The [generated catalog](product-catalog.json) lists all CLI commands and MCP
tools. Provider-specific target support is described in the
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md),
[raw binary guide](raw-binary.md), and
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md), and
[Windows Ghidra guide](windows-ghidra-p0.md).
## Choose a provider
@@ -150,10 +113,6 @@ an exact result when the target bytes, operation, parameters, provider and
settings match. Mutations and cursor-dependent calls are excluded from the
cache. Snapshot files are local and use owner-only permissions.
Dispatching a metadata write invalidates the active session's snapshot cache,
even if its reply fails: the provider may already have applied the write.
Close and reopen the target before importing or saving another snapshot.
Snapshots retain eligible target-scoped question histories in full, including
mutation Evidence recorded while another target was active. Observations and
related questions must still belong to the saved target. If those dependencies
+15 -159
View File
@@ -13,14 +13,13 @@ retains class, byte order, `e_type`, raw `e_flags`, and inspected ABI declaratio
Recognition does not mean every provider or MIPS variant is supported. Existing
PE, managed and Mach-O architecture sets are unchanged.
The Ghidra lane admits ELF32 `ET_EXEC` and `ET_REL`, little or big endian, with
explicit o32 and `EF_MIPS_ARCH_32R2` flags and no machine-specific or ASE
encoding flags. These are header declarations, not proof that every instruction
conforms to that ISA. ELF64, n32/n64, unspecified ABI, microMIPS,
machine-specific variants and PSP PRX are not covered by this standard-instruction
lane. MIPS16 shared images have the separate bounded profile below; other shared
or compressed images remain unsupported. Their family may be identified, but provider selection returns the
specific unsupported constraint rather than guessing a compatible profile.
The first Ghidra lane admits ELF32 `ET_EXEC`, little or big endian, with explicit
o32 and `EF_MIPS_ARCH_32R2` flags and no machine-specific or ASE encoding flags.
These are header declarations, not proof that every instruction conforms to
that ISA. ELF64, n32/n64, unspecified ABI, MIPS16/microMIPS, machine-specific
variants, shared/relocatable objects and PSP PRX are not covered by this lane.
Their family may be identified, but provider selection returns the specific
unsupported constraint rather than guessing a compatible profile.
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
@@ -40,124 +39,20 @@ absent or supply a default ABI.
The field layout follows
[`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h).
Provider admission requires record version 0, MIPS32r2, 32-bit GPRs, no CPR2,
no ISA extension, ASE or unknown general flags, and one of two floating-point
declarations: 32-bit CPR1 with a double-precision or FPXX ABI, or no CPR1 with
the soft-float ABI. Other combinations, including single-float, FP64 and
unspecified ABIs, are refused. Missing records remain unknown rather than
defaulting to a compatible ABI, and this lane refuses them. Unknown record
values are retained for diagnosis but do not gain provider support. These checks
bind the declared interpretation; the fixtures do not establish full
Provider admission requires record version 0, MIPS32r2, 32-bit GPR/CPR1, no CPR2,
and a double-precision or FPXX ABI declaration with no ISA extension, ASE or
unknown general flags. Missing records remain unknown rather than defaulting
to a compatible ABI, and this initial lane refuses them. Unknown record values
are retained for diagnosis but do not gain provider support. These checks bind
the declared interpretation; the integer-only fixture does not establish full
floating-point behavioral conformance.
### MIPS16 shared images
A separate profile admits ELF32 `ET_DYN`, either byte order, declaring explicit
o32, MIPS32r2 and exactly the MIPS16 ASE. Machine variants, n32, FP64 and NAN2008
remain refused. Source inspection must examine the complete dynamic relocation
declarations through uniquely mapped file-backed `PT_LOAD` ranges. This profile
verifies `R_MIPS_NONE` and symbol-index-zero `R_MIPS_REL32`; section declarations
must describe that same dynamic table. Unavailable section headers and additional
section relocation tables (including `--emit-relocs` output) remain unsupported.
Symbol-bearing REL32,
other relocation types, RELA and PLT declarations remain unsupported. A real
negative control shows that Ghidra can normalize a symbol-bearing MIPS16 function
pointer to an even address, losing the source mode tag. REA refuses that shape
rather than rewriting the source or silently repairing the analysis result.
The profile pins the endian-matched `MIPS:<LE|BE>:32:default` processor and default
compiler specification, and checks the bridge-reported language after import.
The generic processor preserves mixed MIPS32/MIPS16 code through per-address
`ISA_MODE` context; a global `16e` processor would impose one mode across the
image. A caller-selected incompatible processor or compiler specification is
explicitly refused without replacing the requested choice.
An observed ABI flags record must agree with the header and MIPS16 ASE, and
must declare the same verified floating-point combinations as the standard
lane. With no observed record, every ABI declaration table must have been
completely examined. The header still declares o32, but floating-point ABI
semantics remain unknown and are stated in Evidence; selecting the default
compiler specification does not establish floating-point storage. Partial
section tables do not prove record absence. The existing soft-float prototype
policy applies when the source explicitly declares soft-float.
Returned addresses are analysis coordinates, not runtime load addresses.
`inspect_native_load_image` preserves original relocation bytes, current loaded
memory bytes and provider status. External GOT bindings remain unverified
without their defining images. The provider's `modified_sha256` describes its
ephemeral relocated image and can differ from `original_sha256`; it does not
mean that the caller's artifact was written. `inspect_native_instruction` retains the program
language variant in `mode` and reports actual per-address `ISA_MODE` separately
in `decoder_context`, including null when unavailable. Artifact bytes and digests
remain unchanged; this workflow does not execute the shared library.
The optional real-provider check builds eight source-owned GNU controls, covering
both byte orders, mixed MIPS32/MIPS16 code, declared and absent ABI records, and
the rejected symbol-bearing and additional section relocations. It compares source instruction bytes,
function symbols and the local function-pointer tag with actual CLI/MCP results,
checks context through a snapshot round trip, and verifies process cleanup.
It requires caller-supplied GNU MIPS cross-binutils and Ghidra; no fixture target
is executed or patched after linking. Floating-point behavior of compressed
functions is not established by these integer controls.
```bash
REA_MIPS_AS=/absolute/path/to/mips-linux-gnu-as \
REA_MIPS_LD=/absolute/path/to/mips-linux-gnu-ld \
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra \
npm run verify:ghidra:mips-shared
```
MIPS header and ABI record facts are committed in the analysis profile so
interpretation changes invalidate profile-bound snapshots. The support-lane
identity is versioned independently of file identity. Evidence, lifecycle
outputs and saved snapshots preserve the `mips` family. Hopper and IDA adapters
do not implicitly gain MIPS support. Windows Ghidra P0 remains PE x86/x86-64 only.
### Soft-float prototype
Ghidra's MIPS32 compiler specifications model hard-float o32 only: a typed
`float` or `double` parameter is bound to `$f12`/`$f14` and returned in `$f0`.
Soft-float o32 passes those values in integer registers and stack slots, so a
typed prototype (for example from DWARF) would make the decompiler read FPU
registers the code never writes and drop the real dataflow. That applies to
functions, external functions and indirect calls through typed function
pointers alike.
For soft-float targets REA commits
`mips_soft_float_storage: o32-soft-float-prototype-v1` in the analysis profile
and runs `ReaGhidraMipsSoftFloat.java` after auto-analysis. The script encodes
the program's own default o32 prototype, removes its floating-point parameter
and return entries and the rules that consume floating-point storage, and
installs the result as the program-specific `__rea_soft_float` prototype through
a Ghidra specification extension. Deriving it keeps the byte-order-specific
aggregate rules and the hidden struct-return pointer. The script then assigns
that prototype, with dynamic storage, to every non-thunk function and external
function, and to every function-pointer definition, whose declared types use
`float` or `double`. Thunks follow their targets. Types are kept.
The script fails, and writes no report, if the derived prototype still places a
float in an FPU register or any reassigned function does. It records the
function and definition counts last; REA requires that report exactly when the
profile commits the policy and refuses the session otherwise, before serving any
operation. Session Evidence carries the policy and counts as limitations.
Callables without typed float signatures keep Ghidra's default prototype, under
which Ghidra already infers integer-register parameters. Hard-float profiles do
not gain the policy, so their digests are unchanged.
### Relocatable objects
`ET_REL` objects, such as Linux kernel modules (`.ko`), are imported by Ghidra's
ELF loader, which assigns each allocated section an analysis address and applies
the object's REL relocations there. Undefined symbols are placed in Ghidra's
`EXTERNAL` block: a thunk for code, a label for data. These addresses are
analysis coordinates, not the addresses a kernel or linker would choose, and
session Evidence says so. `inspect_native_load_image` reports each relocation
with Ghidra's status; HI16 entries report `PARTIAL` because Ghidra applies them
together with their paired LO16. Relocatable targets commit
`mips_support_lane: elf32-rel-o32-arch32r2-standard-v1`, separate from the
executable lane, whose identity executable targets keep. Kernel modules are
usually soft-float and therefore also use the soft-float storage policy.
## Use
With a caller-installed Ghidra and compatible JDK on a supported Linux/macOS host:
@@ -175,10 +70,7 @@ REA's ELF parser.
## Verification
### Executables
The source-owned freestanding fixture is `tests/conformance/c/mips.c`; the
soft-float variant also links `tests/conformance/c/mips-soft-float.c`. The
The source-owned freestanding fixture is `tests/conformance/c/mips.c`. The
optional cross-target lane requires a caller-supplied Clang with MIPS targets,
LLD, GNU `readelf`, Ghidra and its compatible JDK; ordinary host-native checks
do not acquire these tools. `REA_MIPS_CLANG` selects Clang and
@@ -189,8 +81,7 @@ These are test-time prerequisites, not REA runtime providers.
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips
```
The lane compiles both byte orders with hard-float and soft-float ABIs and never
executes the targets. GNU readelf
The lane compiles both byte orders and never executes the targets. GNU readelf
independently supplies header/ABI facts and symbol addresses, compared with the
production REA target resolver before provider startup. Its version, bounded
command output and selected tool path are retained in the verification report.
@@ -213,44 +104,9 @@ all program-header payloads. It checks the actual Ghidra language, entry-address
decompilation, distinct artifact digest and missing-section limitations through
CLI Evidence and an MCP-produced snapshot. Caller-selected Ghidra heap limits
are passed to these child workflows.
For soft-float fixtures the lane also checks, through `inspect_native_api`, the
exact parameter and return types and storage of float, double, mixed (pair
alignment and stack spill) and struct-returning functions; that a caller's
decompilation receives every source float constant without FPU inputs,
including an indirect call through a typed function pointer; and that the
snapshot profile and Evidence retain the policy and outcome counts.
Pseudocode presence is a liveness check, not proof of semantic equivalence.
Instruction decoding and reader agreement likewise do not establish runtime,
delay-slot execution or complete floating-point semantics. Assertion regressions
reject incorrect byte order, constants, destinations and loaded identities.
Parser/profile tests are narrower checks, not substitutes for this real-provider
run. Report the actual lanes run and leave unexecuted verification pending.
### Relocatable objects
This lane has its own command and fixture:
```bash
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips-relocatable
```
It compiles `tests/conformance/c/mips-relocatable.c` with `-c -G0` in both byte
orders, plus a little-endian soft-float `-mlong-calls` variant matching Linux
module options. GNU readelf independently supplies sections, symbols and REL
entries. For every relocation in an allocated section, the lane reads the
loaded word through `read_bytes` and requires it to encode `S + A`: `S` from
Ghidra's own section placement (`list_segments`) or `EXTERNAL` placement, `A`
from the original file bytes. HI16/LO16 are checked as the effective address
each LO16 forms with each HI16 it resolves, including several HI16s before one
LO16, so a missing signed-low carry fails and a HI16 without a following LO16
fails. The lane also requires matching Ghidra relocation entries and statuses,
section file offsets and sizes, coverage of a shared HI16, a LO16 resolving
several pending HI16s (from an assembly block, since Clang does not emit that
from C), a LO16 carry, external code and data and `R_MIPS_32` words, direct
calls from the entry
function, the relocatable support lane in the snapshot profile, the
relocatable limitation in Evidence and an unchanged source object. The fixture
also calls `powf`, an external whose float prototype comes from Ghidra's C
library archive; every variant must decompile `powf(1.5,2.5)` without FPU
inputs, and the soft-float variant must report that the soft-float prototype
covered it.
-99
View File
@@ -1,99 +0,0 @@
# Go binary build metadata
`inspect_go_binary` / `inspect-go-binary` answers which Go compiler version,
modules, replacements and build settings are embedded in one selected local
executable or library. It reads the file without running it and requires no
Go installation, active binary target, Hopper, Ghidra or IDA.
```bash
rea inspect-go-binary /absolute/path/to/application --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_go_binary",
"arguments": { "path": "/artifacts/application" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, byte length,
container format, architecture, word size and byte order. A recognized
`build_info` record contains:
- `go_version`, preserved as embedded UTF-8 text rather than forced into a
semantic version, or `null` when the embedded bytes are not UTF-8;
- `go_version_bytes_base64`, preserving the exact compiler-string bytes;
- exact file offsets and byte lengths for the header, compiler string and module
string;
- `module_text`, the decoded module-information body, or `null` when the complete
body cannot be represented as UTF-8;
- `module_bytes_base64`, the original module-string bytes including Go framing;
- `module.path`, the embedded package path;
- `module.main`, the main module's path, version, checksum and optional
replacement;
- `module.dependencies`, with the same fields and their original order;
- `module.settings`, retaining ordered key/value entries and duplicate keys;
- `module.unparsed_lines`, `module.unparsed_line_bytes_base64` and
`module.complete`, so unfamiliar, malformed, conflicting or undecodable records
do not disappear behind a complete result.
Missing checksums are `null`; empty fields and development/local replacement
versions remain unchanged. Build settings such as `GOOS`, `GOARCH` or `vcs.revision`
are reported when present. Their absence does not establish a build choice.
Go strings may contain arbitrary bytes. Non-UTF-8 records remain available as
base64 while other decodable module records are still reported; the reader does
not insert replacement characters or reject the entire artifact for those
strings.
## Supported boundary
The built-in TypeScript reader supports ELF, PE and thin Mach-O images with Go's
inline or historical pointer build-info encoding. Source ranges refer to the
original stable file snapshot, including pointer-backed strings. It does not
scan arbitrary byte strings as compiler metadata or interpret a fat Mach-O
container as one selected architecture.
Historical moduleless Go binaries can store their empty module-string header in
declared zero-initialized virtual memory rather than file-backed bytes. The reader
recognizes this only when the complete optional module header lies in that memory
without a conflicting file mapping. Its module contents are empty and
`module_location` has zero bytes, anchored at the build-info field containing the
module-header pointer. Compiler strings and other unmapped pointers remain strict
file-backed reads.
`build_info: null` means no recognized record was found in the inspected image.
It does not prove that the binary is not Go. Stripping, old builds, deliberate
removal and obfuscation can limit metadata. A malformed supported container or
recognized build-info record returns an input-format failure. Unsupported
containers receive a separate unsupported-target failure.
Symlinks and nonregular files are rejected. The shared stable reader detects
replacement and observed in-place changes during acquisition. Missing files,
host permission denial, changed artifacts and cancellation retain distinct
errors. The selected file is never modified; this inspection creates no
temporary files, starts no subprocesses and accesses no network endpoint.
Resource guards bound the selected file to 256 MiB, aggregate embedded strings
to 1 MiB, structural table decoding to 16 MiB and the complete normalized metadata
report to 8 MiB. Oversized results fail without partial success. MCP also applies
its [response transport budget](mcp-contracts.md), which counts both text and
structured content; a report within the metadata limit can still require export
from retained Evidence. These limits are not operating-system memory or CPU
isolation.
Embedded metadata is evidence of file content. It does not authenticate a
compiler, dependency, commit or supply chain, and it does not establish runtime
behavior. Function names, source mappings, types and decompiled code are outside
this tool's coverage. Use the [native workflow](native-investigation.md) when
those questions require a disassembler.
## Real producer verification
`npm run verify:go:binary` exercises compiler-built fixtures through the public
CLI and MCP, comparing their metadata with Go's standard `debug/buildinfo`
reader. The verification lane requires Go; normal inspection does not.
+1 -1
View File
@@ -98,7 +98,7 @@ For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <cl
## Coverage and evidence
Both profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. In the modern headless profile, targets that are not proven x86-only use processor-neutral `callgraph` edges instead of the x86 instruction checks in upstream `callees` (mrexodia/ida-pro-mcp#549). REA requests depth zero: the pinned upstream emits the root's outgoing edges without scanning child functions, then REA batches function-name lookup for those addresses. The graph covers recognized function destinations only; imports and other non-function targets remain unknown. A server without `callgraph` returns an empty relationship list with an explicit coverage limitation. Graph and path workflows retain those limitations alongside observed edges. A failed or inconsistent graph reply remains an error. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
Both profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
The legacy profile additionally supports direct callers. Its producer may report call-site or interior addresses; REA resolves the reported function symbol to its canonical entry. The modern profile does not prove direct callers from its code/data xref labels, so that capability is unavailable and the dossier marks it unknown.
+19 -129
View File
@@ -89,11 +89,8 @@ running server or its registration.
The npm release checked on **2026-10-11** was **6.4.0**, published from
[`a56f7bb6`](https://github.com/morluto/rea/commit/a56f7bb668ab23fec8d33f7af353ac7ed2fe1895).
The actual registry package accepted dry-run setup plans for the 17 client IDs
in that release, including Qwen Code, Pi and Hermes. DeepSeek Harness (`dsh`)
is on main and is not in npm 6.4.0; `rea setup --client dsh` against `@latest`
reports an unsupported client until a release containing it is published.
OpenCode's plan selected
The actual registry package accepted dry-run setup plans for all 17 client IDs
listed below, including Qwen Code, Pi and Hermes. OpenCode's plan selected
`OPENCODE_CONFIG_DIR` even when that directory did not yet exist. These checks
used an isolated home on macOS with Node 24; they verify planning and argument
admission, without writing client configuration or establishing live integration
@@ -165,10 +162,6 @@ listed after the table because its connector is not one of these files:
| OMP | `omp` |
| Pi | `pi` |
| Hermes | `hermes` |
| DeepSeek Harness | `dsh` |
`dsh` is main-only until a release after 6.4.0. The registry check above does
not cover it.
For GitHub Copilot CLI 1.0.95, `gpt-4.1` model metadata can block ordinary
chat with REA's full input-schema profile before sending a model request
@@ -189,35 +182,6 @@ schema constraints; REA still validates complete canonical inputs. Full-profile
`gpt-5.4` metadata is also verified locally. Live model API acceptance and
Windows remain unverified; see [native client verification](testing.md).
Claude Code normally defers MCP tool definitions through tool search. It
[turns tool search off](https://code.claude.com/docs/en/mcp#configure-tool-search)
when `ANTHROPIC_BASE_URL` points to a non-first-party host, such as DeepSeek's
Anthropic-compatible endpoint. It then sends every REA tool's name, description
and input schema with every request
([#2019](https://github.com/morluto/rea/issues/2019)). On main at 04089d5be,
with 172 tools, that payload measured about 496 KB with the default profile and
about 290 KB with `compact`. By the o200k and cl100k tokenizers, that is roughly
120k and 68k tokens. Compaction cannot remove tool definitions. If they nearly
fill the window Claude Code assumes, the context refills after every compaction
and Claude Code reports that autocompact is thrashing. To avoid this:
- Make Claude Code assume the model's actual context window. DeepSeek's Claude
Code guide selects `deepseek-flash[1m]`; the `[1m]` suffix makes Claude Code
assume a one-million-token window. For other unrecognized model IDs, set
`CLAUDE_CODE_MAX_CONTEXT_TOKENS` to the window the provider documents.
- If the endpoint forwards `tool_reference` blocks, set `ENABLE_TOOL_SEARCH=true`
to restore deferred loading. DeepSeek's Anthropic API guide does not list
`tool_reference` among its supported content blocks, so this setting may cause
its requests to fail.
- To shrink the upfront catalog, add
`"REA_MCP_INPUT_SCHEMA_PROFILE": "compact"` to the `env` object of the
`mcpServers.rea` entry in `~/.claude.json` (`$CLAUDE_CONFIG_DIR/.claude.json`
when that variable is set), then restart Claude Code. Setup keeps this
override.
The sizes above come from REA's own `tools/list`. A live Claude Code session
against DeepSeek's API is unverified.
For [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md),
setup writes a `type: "stdio"` entry under `mcpServers.rea` in the user-level
`~/.pi/agent/mcp.json` and installs the bundled skill under `~/.agents/skills`.
@@ -295,15 +259,11 @@ configuration and skill destinations; doctor and uninstall use those same paths.
### DeepSeek Harness
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
rather than a `mcpServers` object. `rea setup --client dsh` appends (or
replaces) the REA entry in the home patch `$DSH_HOME/cordis.patch.yml`
(`~/.dsh/cordis.patch.yml` when `DSH_HOME` is unset), preserving unrelated
patches and comments. `rea doctor --client dsh` checks that same entry id.
`rea uninstall` removes it by scanning supported clients; uninstall takes no
`--client` flag. The home patch applies to every Harness profile, so the
registration is written once and no per-profile edit is needed. The written
entry is:
DeepSeek Harness uses a Cordis YAML patch sequence rather than a `mcpServers`
object. REA's `setup --client` does not edit this format. For Harness
`0.2.0-rc.2`, append the following entry to `$DSH_HOME/cordis.patch.yml`
(`~/.dsh/cordis.patch.yml` when `DSH_HOME` is unset). Preserve existing patches
and replace the executable paths with absolute paths from your installation:
```yaml
- insert:
@@ -314,20 +274,17 @@ entry is:
transport: stdio
command: /absolute/path/to/node
args: ["/absolute/path/to/rea/scripts/rea.mjs", mcp]
cwd: !!js process.cwd()
```
Setup preserves caller-owned Cordis expressions and replaces launcher fields
with literal values that doctor can verify. It refuses to edit an expression
that produces the entire REA config, or a group-targeted REA insertion. Use a
literal config and a root-level insertion as shown above.
For a global npm installation, the launcher is under
`$(npm root -g)/rea-agents/scripts/rea.mjs`. Restart Harness after setup; it
exposes tools as `mcp__rea__<tool_name>`. Run `rea setup --skill --dry-run`,
review the destination, then `rea setup --skill --yes` to install the workflow
in `~/.agents/skills` (`$DSH_AGENTS_HOME/skills` when configured); Harness
discovers that directory, and its native `skill` tool can load
`reverse-engineer-anything`.
`$(npm root -g)/rea-agents/scripts/rea.mjs`. Check the Node executable with
`command -v node`. Register `serverName: rea` only once in each active profile,
then restart Harness. It exposes tools as `mcp__rea__<tool_name>`.
Run `rea setup --skill --dry-run`, review the destination, then
`rea setup --skill --yes` to install the workflow in `~/.agents/skills`.
Harness discovers that directory, or `$DSH_AGENTS_HOME/skills` when configured;
its native `skill` tool can load `reverse-engineer-anything`.
REA's optional [native client verification](testing.md#end-to-end-integration-and-golden-evidence)
checks headless chat, skill loading, all tool input schemas and actual analysis
@@ -395,12 +352,6 @@ Other integrations, including Qwen Code, use `~/.agents/skills`.
A mixed selection plans each distinct destination. Selecting the skill without a client uses the shared
directory. Setup leaves existing skill copies in other locations untouched.
`rea setup --skill-client claude_code --yes --json` refreshes and verifies the
selected skill destination independently of MCP registration selection. The
final `doctor.scope.skill_clients` records which skill destinations were
checked; `doctor.scope.clients` continues to describe required registrations.
An unselected shared skill does not affect a client-only refresh.
`doctor --skill --json` verifies the selected copies against the bundled
instructions and references. Consumers should use `identity.skill.state`,
`installed_version`, and `installed_tool_count` for skill readiness. The
@@ -610,13 +561,8 @@ need Xvfb, Python 3, X11 and XTEST; approved setup installs those dependencies
on supported distributions. Add the curl installer's reported executable
directory, commonly `~/.local/bin`, to your shell `PATH` if needed.
REA starts Hopper when needed. On macOS, complete its demo or license prompt in
the same user session before the first CLI/MCP operation or unattended job. Open
Hopper manually if needed; its launcher can also bring the dialog forward.
If startup reports `Cannot launch Hopper! Connection is invalid.` or waits for
bridge readiness, check Hopper for a pending demo, license, or loader dialog.
Complete the dialog and retry. If REA reports `cleanup_incomplete`, follow the
retained-resource guidance below before reopening the target.
REA starts Hopper when needed. On macOS, its launcher can bring a window or
first-run dialog forward; choose demo mode or activate your existing license.
Hopper serializes analysis requests. Cancelling a wait can leave provider work
running, which the session reports. Successful decompilation is cached until
a relevant rename or comment changes it.
@@ -645,11 +591,6 @@ if document closure is unconfirmed, REA retains the backing image and reports
MCP-exit behavior.
Startup deadlines report missing bridge readiness and preserve the launcher
outcome; a successful helper exit does not prove that a loader dialog completed.
If an owned provider process exits, request failures preserve its observed exit
code or signal and bounded output in `details.launcher`. An exit can precede
output-stream closure; `output_closed: false` keeps that uncertainty explicit.
Later requests retain the known exit instead of reducing it to socket loss.
An external GUI's helper exit does not establish that the GUI itself exited.
Cursor navigation returns the observed object start when Hopper snaps an interior
address; adjacent-object navigation rejects unmapped inputs and document ends.
Native API text rejects NUL characters and unpaired Unicode surrogates before
@@ -822,32 +763,6 @@ is no fixed per-operation or response-size ceiling. Unresolved computed calls
remain unknown, reference-kind provenance is preserved, and provider-specific
pseudocode is never treated as original source or Hopper-equivalent text.
### Missing macOS native decompiler
If `rea doctor --json` reports `native_decompiler` as missing on macOS x64,
the extracted Ghidra archive may include the decompiler source without the
macOS executable. For Ghidra 12.1.4, you can build it manually with an existing
Xcode Command Line Tools installation, `make`, and `bison`:
```bash
cd "$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/src/decompile/cpp"
make ghidra_opt
decompiler_dir="$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/os/mac_x86_64"
mkdir -p "$decompiler_dir"
test ! -e "$decompiler_dir/decompile" && test ! -L "$decompiler_dir/decompile" &&
install -m 755 ghidra_opt "$decompiler_dir/decompile"
rea doctor --json
```
The copy step requires the destination to be absent and preserves an existing
decompiler. This recipe is specific to macOS x64: the
[Ghidra 12.1.4 Makefile](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile)
explicitly selects `-arch x86_64` on Darwin. For other architectures or a Gradle
build, follow the matching release's
[native component instructions](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/GhidraDocs/GettingStarted.md#building-native-components).
REA accepts the matching executable under either `os/<platform>/` or
`build/os/<platform>/`; it does not build the component during setup or diagnosis.
### Ghidra import language and analysis seeds
Ghidra detects the language from the executable header. To set it (for example, Borland C++ PE images):
@@ -869,27 +784,6 @@ Each line is `address<TAB>kind[<TAB>name]`, where kind is `function`, `code` or
Label seeds can name mapped uninitialized storage such as `.bss`; function and code seeds require initialized bytes.
To skip a named Ghidra auto-analyzer (for example, when default auto-analysis
stalls in one analyzer on a Swift/Objective-C arm64 Mach-O):
```bash
export REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer"
```
The value is a comma-separated list of exact Ghidra analyzer names. A pre-script
disables each name before auto-analysis, the analysis profile records the
override in `analyzer_preset` (for example
`ghidra-default+disabled:Objective-C Message Analyzer`), and a session fails to
open when its committed profile and the setting differ. A name starts with a
letter or digit and contains only letters, digits, spaces, periods, underscores,
and hyphens; at most 32 names are admitted, each at most 120 characters.
Names must identify analyzers registered for the selected program. Unknown names
or options that remain enabled abort the headless import before auto-analysis
and the bridge, so an unsuccessful override cannot produce a session claiming
that the analyzer was disabled. For Windows PE resource analysis, the exact name
is `WindowsResourceReference`. Disabling an analyzer skips the facts it would
have produced.
### Ghidra heap and CPU controls
Set resource controls in the environment that launches the CLI or MCP server,
@@ -951,12 +845,8 @@ verification workflows.
Unexpected request failures retain their original internal cause. CLI and MCP
errors expose Error names, messages, and available codes under
`details.diagnostics.failure_cause`, including bounded nested causes and explicit
truncation markers. Darwin ownership-helper compilation failures also retain
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
compiler remains distinguishable from a compiler that exited with an error.
Other rejection values retain their primitive value or an explicit type.
Shutdown warnings include the failure
`details.diagnostics.failure_cause`; other rejection values retain their
primitive value or an explicit type. Shutdown warnings include the failure
kind, message, and diagnostics, while process and temporary-project cleanup
continues. These messages redact known bridge authentication tokens and
preserve local paths and other analysis context.
+18 -169
View File
@@ -66,45 +66,6 @@ code-point order. Dependency manifests remain package observations but do not
declare Electron main or renderer entries. If only dependency manifests are
present, the inventory artifact remains the graph root.
## Select sources before analysis
For a question about known entry sources, supply exact artifact-relative file
paths in `source_paths`, or repeat `--source-path` on either CLI route:
```bash
rea analyze /absolute/path/to/app.asar \
--source-path package.json --source-path main.js --json
```
```json
{
"input_path": "/absolute/path/to/app.asar",
"source_paths": ["package.json", "main.js"],
"detail": "summary"
}
```
REA inventories and verifies the complete artifact, including unselected bytes,
but allocates source text and performs static analysis only for selected files.
Include supporting manifests, HTML or source maps explicitly when needed;
imports do not expand the selection. Paths must match relevant inventoried
files exactly, including nested ASAR prefixes. Unknown paths, aliases and globs
return an `invalid_request` correction. Selected files are analyzed in request
order before unselected scopes are projected. Repeated paths preserve the
caller's request and first-occurrence priority, analyzing the file once.
Omit the selection for the existing complete
application workflow.
The result and summary retain `source_selection.requested_paths` and
`source_selection.unselected_files`. Every relevant file keeps its original
inventory identity; unselected files report `text_status:
"outside-analysis-scope"`. Their relationships remain unknown, and application
coverage is partial while any file remains outside scope. Selection does not
claim whole-application completeness or change integrity policy. Summary detail
only reduces delivery; source selection reduces text acquisition and analysis.
Explicit native provider or snapshot routing rejects `--source-path` rather
than ignoring it.
## ASAR integrity
ASAR inventory checks Electron integrity metadata for embedded archive entries
@@ -174,24 +135,6 @@ default). Reaching it fails the current source with the same memory resource
failure and reports `worker_process_memory_limit_bytes`, also a coverage limit;
the next source starts a new analyzer. Bun elsewhere enforces no analyzer limit.
The default semantic capacities are 100000 nodes across the application and
20000 per source. Large bundles may explicitly select `max_semantic_nodes` and
`max_semantic_nodes_per_file` (CLI: `--max-semantic-nodes` and
`--max-semantic-nodes-per-file`), each from 1 to 1000000. The per-file offer is
also bounded by the remaining application capacity. Raising these controls does
not bypass worker heap, execution deadline or parent result admission. Truncated
coverage reports both effective node ceilings; omitted relationships remain
unknown.
While sources remain, the sequential worker reserves one next compact static
checkpoint at its 8 MiB bound with the same 6x retention expansion used at
admission. Earlier checkpoints are already charged to retained bytes; the
remaining inventory does not multiply the reserve. Streamed file transfers keep the default 512 MiB
allowance, and can grow with the parent's offered retention budget (8x byte
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The parent
still independently checks actual bytes, record overhead, digests and source
identities before adopting results.
A source that exhausts the analyzer heap does not terminate the CLI/MCP
process. Completed static facts are retained, other files can still be analyzed,
and unexamined semantic relationships remain explicit unknowns. The parent
@@ -216,11 +159,9 @@ completed partial Evidence. MCP failure details return
after recording succeeds, so `inspect_analysis_view` can open that analysis
directly without serializing the entire partial graph into the error response.
CLI failure output retains the complete partial Evidence.
Cancellation after source analysis begins likewise retains completed facts,
including cancellation during analyzer cleanup, graph construction, or Evidence
sealing. REA finishes projecting those owned facts cooperatively to return partial
Evidence. An SDK client may reject its cancelled request before receiving that
reply. On the same MCP connection, inspect the Evidence bundle
Cancellation after source analysis begins likewise
retains completed facts; an SDK client may reject its cancelled request before
receiving that reply. On the same MCP connection, inspect the Evidence bundle
and use the retained analysis ID with `inspect_analysis_view`. Owned analyzer
cleanup is verified before its private files are released. Cleanup uncertainty
is reported separately and retains ownership for retry.
@@ -290,11 +231,6 @@ unknown instead of silently treating them as absent or verified.
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
counterparts while resolving to inventoried source files. This substitution
requires import or require semantics. Literal Electron preload, renderer, and
utility-process runtime paths retain the runtime filename selected by the caller.
Selected bounded text is then parsed as inert data to recover:
- `package.json` metadata and declared main or renderer entrypoints;
@@ -384,68 +320,18 @@ Source-map contents are parsed as local input. The graph stores original source
Only explicitly present BrowserWindow values are observations. REA does not
substitute version-dependent Electron defaults for omitted `webPreferences`.
Unresolved option objects, bridge keys, API objects, and IPC channel expressions
Dynamic option objects, bridge keys, API objects, and IPC channel expressions
remain unknown and make coverage partial.
Recognized literal module-table bootstraps also support bounded lexical
substitution through module exports, import helper bodies, local wrapper calls,
returned closures, object spreads, and callback parameters. Recovered Electron
paths retain source ranges, module identities, literal channels and branch
conditions as static candidates. Unknown spreads, reassigned bindings, accessors,
dynamic channels and unsupported syntax do not become exact implementations.
This pass neither executes the bundle nor proves that runtime guards pass.
An ambiguous or unresolved handler retains its channel registration but does
not acquire a path to one arbitrarily selected callback. Unknown factory
conditions retain both return branches, including fallthrough. Short-circuit
selection distinguishes truthiness from nullishness; uncertain `??` paths
retain a nullish guard rather than a falsy guard.
These paths appear as contextual `javascript-expression` nodes. An exact `api`
seed such as `desktop.copy` can trace an exposed member through a unique IPC
pairing to an external API call. `addListener` is normalized to `on`. Semantic
projection retains these boundary paths before bulk function definitions, within
the existing per-file node budget; omitted general semantics remain explicitly
partial. Wrapper expansion is bounded by one million visits/substitutions and
128 nested substitutions, with exhaustion reported as unknown coverage.
Electron API identity follows proven lexical module aliases. Namespace interop
also preserves identity when the complete Rolldown helper operations, lexical
roles, live getters, and captured intrinsic origins match, or when a stable
helper simply returns its module parameter. Harmless single-statement control
blocks preserve recognition; added operations still require proof. Literal
member and destructuring projections are retained from proven results.
Omitted or literal node-mode arguments preserve named exports; a module-valued
default projection requires a truthy literal node mode. Computed or extra
arguments remain unresolved.
Recognition uses structure rather than emitted names; other helper bodies and ambiguous
runtime fallbacks remain unresolved. Captured function identities survive later global-property
replacement, including copies through initialized aliases. Reads before an
alias initializer remain unresolved. Visible intrinsic writes before capture or
conversion are checked in their owning callable scope, including identifier leaves in nested/default/rest
destructuring and loop targets. Property keys and shadowed local bindings do not
overwrite the global intrinsic identity. Cross-call side effects, including
writes inside directly invoked local functions, are not evaluated. Other
reassignments, dynamic selections, and visibly overwritten namespace members
are unresolved.
Known Object/Reflect mutators remain recognizable through literal destructuring
of intrinsic objects and stable bound-function aliases. Calls retain bound
argument prefixes and the original bound receiver through `call`, `apply`, and
`Reflect.apply`, including generic `Function.prototype` invocation and binding
creation. Arguments follow each actual callable invocation rather than property
chain depth; rebinding retains the original bound receiver.
Visibly replaced invocation intrinsics produce unknown effects instead
of using their former call semantics; captured function aliases retain their
earlier identity. Unexamined targets remain unresolved. `Reflect.set` examines
its explicit receiver as well as its target, retaining unknown receiver effects.
Creating an unused binding does not itself mutate an intrinsic. Variable
redeclarations retain a single unconditional initializer
when they introduce no assignment or second initializer.
Stable identifier chains to fresh object, array, function,
undecorated class or RegExp syntax keep those targets separate from intrinsic
objects when initialized before use; opaque, decorated, reassigned or
not-yet-initialized targets remain unresolved. Writes during class initialization
are still retained. Function declarations are available before their textual
position; variable and class initializers must precede use.
Electron API identity follows proven lexical module aliases. Bundled CommonJS
namespace imports are also followed through the interop helpers that bundlers
and compilers emit (`__toESM`, `__importStar`, `_interopRequireWildcard`),
including the `electron = __toESM(electron)` rewrap that Rolldown emits. This
step is inferred: a helper is recognized by its emitted name and a single
program-scope function definition, and its body is not evaluated, so a local
function that reuses one of these names is trusted to pass the module through.
Other reassigned bindings, dynamic selections, and visibly overwritten namespace
members are unresolved.
Direct and destructuring assignment, loop, update, and delete targets are checked
through namespace aliases; unrelated member writes do not discard observed API
identity. This static check does
@@ -454,7 +340,7 @@ do not establish that an application has no Electron boundary usage.
IPC pairing is an inference, not an observation. A renderer `invoke` pairs only
with one unique `ipcMain.handle`/`handleOnce` candidate on the same exact literal
channel; a renderer send pairs only with one unique `ipcMain.on`/`once`/`addListener`
channel; a renderer send pairs only with one unique `ipcMain.on`/`once`
candidate. Dynamic channels are never paired, and multiple compatible handlers
are reported as ambiguous without adding a caller-to-handler edge. A matching
channel still does not prove registration order, reachability, or runtime use.
@@ -539,53 +425,16 @@ such as `./web3:app.js` still names a local artifact.
Package `exports` fallback arrays are supported both at the top level and under
the root `"."` entry. The resolver selects targets in declared order using the
same conditional and invalid-entry handling as nested exports arrays.
Nonempty arrays containing only inactive conditions remain unmatched, allowing
the enclosing condition object to continue to a later active condition.
Empty arrays and explicit null targets keep their blocking outcome.
Refusal evidence distinguishes those source values and retains condition names
from unmatched nested targets rather than reporting array indices.
For imports and requires, the active Node conditions include `module-sync`;
earlier active conditions retain precedence over later ones.
Package self-references use the importer's enclosing package scope before
installed dependencies. ESM requires the parsed package name to equal the
metadata name; a scope-only name such as `@scope` cannot capture `@scope/pkg`.
CommonJS retains Node's raw-request name/prefix matching. Both paths preserve
the original specifier and select exports under their own loader conditions.
Exact package-private `#alias` specifiers resolve against the importer's
enclosing `package.json` `imports` map — the nearest same-container package
scope, stopping at a `node_modules` directory so a nested package cannot inherit
an ancestor's aliases. Selected targets are package-local `./` files with no
extension or index fallback, using the same condition, array, and null selection
as exports subpaths. A missing key, explicit null, or out-of-scope alias keeps
an unresolved edge with that reason. Wildcard keys (`#internal/*`), recursive
`#` targets, and external-package import targets remain unresolved.
Exact keys such as `#/x` are literal lookup identities. `#` and names ending
in `/` are invalid. Imports arrays skip invalid parent-relative, absolute, and
URL targets before selecting a later package-local file; an unsupported bare
package or recursive alias keeps its unresolved outcome rather than selecting
a later fallback.
ESM relative module paths, legacy package subpaths without exports, and selected
package exports targets use URL suffix handling and one percent-decoding pass.
Bare specifiers retain their complete query/fragment text for exports key matching.
For dependency lookup, ESM constructs a package metadata URL instead of treating
the parsed package name as a literal directory. A root request such as `legacy?x`
does not load either `node_modules/legacy?x` or `node_modules/legacy`; an exact
self-reference with that metadata name can still select its exports first.
Without an exports map, ESM subpaths select exact files; directory imports and
extension fallback are refused. CommonJS relative paths and legacy package main
ESM relative module paths and selected package exports targets use URL suffix handling
and one percent-decoding pass. CommonJS relative paths and legacy package main
fields retain literal filename punctuation. Exports targets must start with
`./` and exclude `.`, `..`, and `node_modules` path segments, including their
encoded forms, before URL normalization. Arrays skip invalid exports targets
in declared order, including URLs that escape the package root after normalization.
Selected package targets use the normalized URL path for lookup; tabs and
newlines in the URL text are removed by the URL parser. CommonJS package targets
reject encoded separators anywhere in the selected URL; ESM checks its pathname.
URL decoding and file lookup failures for a selected target do not select a
later entry. Rejected references retain the package metadata
in declared order; URL decoding and file lookup failures for a selected target
do not select a later entry. Rejected references retain the package metadata
path, original target, failed constraint, and importing source location.
HTML script references resolve to exact inventoried files after applying the
+1 -3
View File
@@ -36,9 +36,7 @@ application starts, and REA never resumes it or sends
`Runtime.runIfWaitingForDebugger`. Resume a paused target with a separate
caller-owned debugger before requesting application runtime observations.
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets already marked attached are rejected rather than displacing another debugger. Local paths are canonicalized after symlink resolution.
`list_javascript_runtime_targets` returns every available target in one inline
array.
-46
View File
@@ -58,34 +58,6 @@ reports `resource-limit` with unavailable or partial coverage; omitted facts do
not become empty observations. Ambiguous PE section mappings are rejected
rather than interpreted according to section order.
### Selected member inspection
When whole-assembly member decoding reaches the representation budget, select
known declaring types before their signatures and CIL bodies are decoded:
```bash
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser
```
MCP uses `inspect_managed_members` with
`{"path":"/absolute/path/Assembly.dll","type_names":["Example.Parser"]}`.
Repeat `--type` or supply multiple unique `type_names` for several types.
Names match the reported `full_name` exactly. Top-level names are namespace
qualified; nested types currently report bare names, which can select several
types with that name.
The result preserves the requested names in `selection.type_names` and reports
unmatched names separately. If metadata names are unreadable, limitations retain
that uncertainty. With no selection, `selection` is null and whole-assembly
inspection keeps its existing resource policy. Selection restricts types,
fields, methods, call edges and field accesses; member references remain an
assembly-wide inventory, so output size is not guaranteed to be small.
Coverage describes the selected members. Comparisons leave members outside the
opposite observation's selection unknown, and application graphs report their
selected scope as partial. Neither makes an absence claim about the whole
assembly. Older member Evidence without `selection` remains readable.
## Analysis objective
REA's managed-code track is intended to answer five different questions without
@@ -154,24 +126,6 @@ Member inspection binds build-local tokens, signatures, row offsets, method
bodies, and decoded relationships to that artifact and MVID. Missing or malformed
facts remain explicit in coverage and per-member status.
Metadata table streams may carry the CLR-compatible `ExtraData` extension
(`HeapSizes` flag `0x40`), which Microsoft readers honor although it is not
specified by ECMA-335. REA skips its fixed four-byte word after the row counts
before decoding table rows; the word's value is not a length. An incomplete
word is reported as a metadata coverage failure at its file offset, without
reading beyond the declared table stream.
For unoptimized `#-` metadata, nonempty `FieldPtr` and `MethodPtr` tables
translate TypeDef list positions to physical member definitions. REA resolves
these indirections for member ownership and local attribute-constructor types;
field and method tokens still identify the original definition rows.
`field_list` and `method_list` retain the metadata's list coordinates, which
refer to the pointer table when present rather than necessarily contiguous
definition rows. Malformed or ambiguous pointers leave the owner unknown with
partial coverage while preserving readable member and attribute facts.
The retained inverse indexes are admitted against the existing representation
budget before allocation.
`exported_types` reports ExportedType rows as declared. Facade assemblies such
as .NET `System.Runtime.dll` provide their types this way: `forwarder: true`
marks a type forwarder, and `implementation_token` names the AssemblyRef, File,
+19 -103
View File
@@ -113,48 +113,17 @@ Schema does not express those cross-field invariants.
Call `binary_session` with `{}` and read `result.tool_availability` to choose a
callable operation for the current target, provider, host, and negotiated client
capabilities. The default result contains the complete inventory in groups that
share status facts. `tool_availability.client_requirements` is the baseline for
each group; a group's optional `client_requirements` replaces that baseline for
the group's tools. Expand each group's `names` into one tool entry, carrying its
surface, availability, reason, remediation, default-mode status, modes, and
client requirements. Compare the union of group names with `tools/list` when
checking that a client received the full catalog. An operation absent from all
groups is missing from the complete inventory; it is not an implicit
unavailable entry.
The `effects` and `annotations` arrays list the true members of their fixed
vocabularies. A known member omitted from either array is false. This applies
only to these closed flag sets; it does not make an omitted tool, provider, mode,
or limitation false. Keep availability reasons, remediation, client-feature
requirements, and mode facts inline with the grouped tool names. The optional
inputs `expected_package_version`, `expected_catalog_digest`, and
capabilities. The default result includes the complete inventory with each
tool's availability, reason, and remediation. Each entry also reports required
and optional negotiated client features plus the currently missing features.
The optional inputs `expected_package_version`, `expected_catalog_digest`, and
`expected_server_path` compare the live session with the caller's expectations.
`server_identity.catalog` contains catalog counts and schema digests. Grouped
tool availability carries the name set and current per-tool facts; `tools/list`
is the canonical source for complete tool definitions, including input and
output schemas. Clients that previously read `server_identity.catalog.tools`
should expand `tool_availability.groups` and use `tools/list` for schemas. The
same catalog identity summary is returned by CLI `capabilities` and `providers`;
MCP tool definitions remain available through `tools/list`.
Provider capabilities are grouped by provider identity and shared facts. In each
set, `shared_limitations` is the ordered common prefix; append a group's ordered
`limitations` suffix to recover each operation's exact limitations. Group
`effects` arrays use the same true-only closed-vocabulary rule as tool
availability. Expand every group operation while retaining its provider
identity, availability, reason, rejection code, effects, and ordered
limitations.
`binary_session.analysis_provider_candidates` is authoritative for deep-engine
discovery. Each candidate's capability sets describe that possible provider;
they do not select it. The top-level `capabilities` describe the current route,
while `analysis_provider_binding` identifies the concrete selected route. These
fields are not interchangeable. Target-free discovery is sorted by provider ID,
reports host availability and `unknown` target support, and does not create an
analysis client. `open_binary.provider_id` accepts a concrete provider ID or
`auto`; it uses the same parser and selection policy as CLI `--provider` and
discovery. Target-free discovery is sorted by provider ID, reports host
availability and `unknown` target support, and does not create an analysis
client. `open_binary.provider_id` accepts a concrete provider ID or `auto`; it
uses the same parser and selection policy as CLI `--provider` and
`REA_ANALYSIS_PROVIDER`. A successful deep open exposes one immutable provider,
concrete version, selection source, and complete analysis profile through
`analysis_provider_binding`. Ambiguity and unknown, unavailable, or unsupported
@@ -181,26 +150,19 @@ structured data and matching text projection. Oversized errors are retained
as Evidence before delivery of a bounded diagnostic with an export reference;
if retention is unavailable or fails, the diagnostic reports that reason.
REA accepts ordinary `tools/call` progress tokens. The numeric `progress` counts
emitted status observations and strictly increases, including when a waiting
phase has completed no additional work. Updates are rate-bounded to at most one
intermediate update per 100 ms and always allow a terminal update. `total` is
omitted because the number of future status observations is unknown. Read
`message` for the phase and observed work; REA does not fabricate percentages.
REA accepts ordinary `tools/call` progress tokens. Updates are monotonic,
rate-bounded to at most one intermediate update per 100 ms, and always allow a
terminal update. Unknown totals are omitted; REA does not fabricate percentages.
`capture_process_scenario` uses that boundary only when the client supplies a
progress token. Notifications put the lifecycle phase, elapsed time, and final
cleanup status in `message`. A client
progress token. Notifications set `progress` to the count of collected terminal
frames, process samples, and interaction events, omit `total`, and put the
lifecycle phase, elapsed time, and final cleanup status in `message`. A client
that does not send a progress token receives no progress notifications. The
tool result is unchanged either way.
Provider calls receive the request cancellation signal. Artifact traversal,
hashing, version comparisons, Hopper requests, and process capture
check the same signal. Cancellation is distinct from timeout. A cleanup failure
uses `cleanup_incomplete` and lists only the owned resource kinds that remain.
An atomic Evidence bundle or snapshot write can publish its complete output
before staging cleanup fails. In that case, `details.published: true`,
`details.published_path`, and `details.bytes` retain the publication receipt.
Use that receipt to distinguish completed publication from a write that never
committed. Staging resources remain owned until cleanup is verified.
Native call tracing and process capture retain available observations in
`details.partial_observation` on failure, including when cleanup succeeds.
The observation reports its partial coverage; cleanup details describe host
@@ -268,18 +230,6 @@ engine readiness or a percentage of Ghidra auto-analysis. A longer server
startup allowance does not extend the client's deadline; progress also does not
extend it unless the client explicitly implements that policy.
While Ghidra startup is pending, REA sends a waiting notification every ten
seconds when the request has a progress token. Its message gives elapsed waiting
time; import, auto-analysis and bridge readiness may all still be pending.
Waiting notifications stop when startup settles or the caller cancels. They do
not measure engine work completed.
The pinned SDK can reset the request's inactivity timeout on progress with
`resetTimeoutOnProgress: true`. Also set `maxTotalTimeout` to bound the complete
request; the provider's startup deadline still applies. These options belong in
the same second argument of `callTool`. Without that explicit client policy, the
default 60-second request deadline still expires despite waiting notifications.
The 240-second setting is a measured example, not a universal timeout: a
controlled Linux x64/WSL2 fixture took about 64 seconds for its first overview
with Ghidra 12.1.4 on a mounted NTFS installation, native JDK 21, two CPUs, and a
@@ -315,10 +265,9 @@ observed process-family member remained after final close. This was a small
1,024-byte fixture, so the longer request setting remains an example, not a
guarantee for larger targets. No macOS cold-start recovery was exercised.
With a supplied progress token, that earlier Windows run received an operation-start
With a supplied progress token, the Windows run received an operation-start
notification before timeout and start/completion notifications for the
successful retry, before startup waiting notifications were added. It received
no intermediate import or auto-analysis progress.
successful retry. It received no intermediate import or auto-analysis progress.
These operation markers do not measure analysis work completed or extend the
client's deadline. Each new provider session still imports the target into a
fresh temporary project.
@@ -346,16 +295,6 @@ directly to `compare_functions`, and `inspect_artifact` Evidence to
`compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
retained session history or an explicit bundle for transfer.
`get_call_graph` returns a `graph` keyed by traversal depth and a top-level
`limitations` array with deduplicated execution caveats. Each node keeps its
relationship coverage and read-specific `limitations`, alongside observed
`calls`. When a provider does not classify its caveats, they remain on that
node and coverage is `unknown`. An empty call list with partial or unknown
coverage is not proof that the function has no calls.
`trace_call_path` reports each limited expansion in `residual_unknowns` and
sets `truncated: true`; any path already supported by observed edges remains
visible. Tool errors remain in their existing error or failure fields.
REA prepares complete MCP results within the pinned stdio client's 10 MiB
receive-buffer budget, including both text and structured representations and
room for the JSON-RPC envelope. Tool errors carry only their text
@@ -367,18 +306,8 @@ session. Its exact reference is reported in
`details.reported_limits.evidence_reference`; call `inspect_analysis_view` with
that reference for a summary, one section/module, or a stable page, or call
`export_evidence_bundle` with a destination path. Complete
bundle exports stream canonical JSON into an atomically published file.
For oversized native function Evidence, `details.reported_limits.continuation`
contains a ready-to-call `inspect_analysis_view` request for the retained
pseudocode's first page. Use its `name` and `arguments` directly, then follow
`normalized_result.coverage.next_offset` for subsequent pages. The suggested
limit is the view contract's default, not a guarantee that arbitrary source or
metadata fits; reduce the limit or export when needed. Continuations are
offered for completed `analyze_function` results only after session recording is
acknowledged; the complete result and its retention reference remain intact.
A broad follow-up or `get_evidence_bundle` can also exceed the response budget; exporting
bundle exports stream canonical JSON into an atomically published file. A broad
follow-up or `get_evidence_bundle` can also exceed the response budget; exporting
preserves the complete session without sending it through a single MCP frame.
To discover existing records without their large payloads, call
`get_evidence_bundle` with `detail: "summary"`. Its `result.records` contain
@@ -419,13 +348,6 @@ analysis, so later views and application workflows read it without repeating
analysis or moving the complete record through the response. A server without
session retention refuses summary detail with `capability_unavailable`.
`inspect_analysis_view` also accepts `api-surface` for retained HAR capture Evidence,
grouping request lines by method, origin and format-rule path template. Templated
segments are inferences. Because paging follows the full grouping, every page is
inferred Evidence whenever any group is templated, and derived Evidence only when
no group was templated.
See [derived API surfaces](web-network-captures.md#derived-api-surface).
`inspect_analysis_view` projects a caller-selected view of already completed
`inspect_binary_layout`, `analyze_javascript_application`, or `analyze_function`
Evidence. Native views select procedure, pseudocode, assembly, basic blocks,
@@ -513,13 +435,7 @@ Tool annotations describe effects and are hints, not authorization controls
([MCP ToolAnnotations](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations)).
`readOnlyHint` includes session state: an analysis call that records additive
Evidence is marked non-read-only even when it leaves the target unchanged.
`destructiveHint` is true when a tool may discard data (overwriting or clearing
analyst metadata, replacing files, dropping session state), runs or drives code
REA does not own (`executesForeignCode`: process, LLDB, Electron, browser, and
native UI scenarios), or changes an attached Android device (`mutatesDevice`:
push, install, uninstall, start, and stop). Ordinary Evidence recording is not
destructive. The effect record in `binary_session` and the generated catalog
carries each flag separately.
`destructiveHint` describes possible data loss, not ordinary Evidence recording.
Effect metadata covers possible behavior across supported inputs: DMG inventory
can launch `hdiutil` and create an owned temporary mount directory; extraction
creates a fresh output directory on every call. Comparing supplied web captures
+7 -26
View File
@@ -6,20 +6,11 @@ coverage and unknowns. The CLI and MCP use the same application workflows.
## Static inspection
`inspect_macho.selected_architecture` identifies the exact selected lipo slice
and its `container_file_offset` in the input file. A thin Mach-O has a known
offset of zero; an unknown universal-slice offset stays null. The Evidence
subject format describes the input container: `mach-o-universal` for FAT files,
including those with one slice. The normalized result describes the selected
Mach-O image. Architecture
`file_offset` values are container-relative. Entrypoint, segment and section
`file_offset` values retain their source meaning and are relative to the
selected Mach-O slice. Raw load-command fields retain their original values.
Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
ranges to container offsets by adding the observed slice offset. Zero-fill
segments do not produce file ranges. When the selected slice's container offset
is unknown, those mapped locations are omitted with an explicit limitation;
architecture inventory locations remain available.
`inspect_macho` retains otool segment `file_offset` values relative to the
selected Mach-O slice. Its evidence file-offset ranges address the original
input file and include the observed lipo slice offset for universal binaries.
If that offset is unavailable, segment evidence locations are omitted with an
explicit limitation; architecture inventory locations remain available.
Hopper's `address_to_file_offset` combines the native image-relative mapping with
original-file coordinates only after checking the source against the session's
@@ -62,10 +53,6 @@ complete observation.
classes are preserved. Select `root`, `offset` and `limit` explicitly.
UID 0 cannot distinguish conditional nil from ordinary nil. Limits are
64 MiB of input, 200,000 objects/references and 128 nesting levels.
XML dictionary entries decoded as `__proto__` are omitted with an explicit
count because REA results cannot represent that key. XML tag whitespace and
comments do not change that handling. Other values and the original archive
digest are preserved; key interpretation follows the pinned plist decoder.
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
compiled storyboard and nib resources into objects, outlets, actions,
connections and controller/class names. `verify:interface-builder` compiles
@@ -205,14 +192,8 @@ rea inspect-native-data-type <binary> --address <typed-data-address> --provider
Instruction facts include bytes, length, decoder text, ordered register/scalar/
address tokens, typed references and flow destinations. Mid-instruction, data,
outside-memory and undecodable addresses have separate outcomes. Effective
memory base/index/displacement roles remain unavailable. Ghidra reports its program
language variant in `mode`; other providers retain their own mode label. Ghidra
MIPS results additionally expose `decoder_context`
with the observed `ISA_MODE` register at the queried address and its provider
source. A null value is unavailable, not mode zero; an empty array reports no
available context register, and omission preserves older captures or providers
that do not expose this facet. The context is an analysis observation, not proof
of a runtime processor state.
memory base/index/displacement roles and per-instruction context mode remain
unavailable; `mode` is the program language variant.
`inspect_native_instruction` also returns `incoming_references` to the exact
queried address, including data, interior-instruction and undecodable addresses.
-76
View File
@@ -1,76 +0,0 @@
# Headerless raw-binary analysis with Ghidra
REA admits a headerless executable when the caller supplies the CPU family and
the address where file offset 0 should be mapped. That interpretation is not
inferred from a `.bin` suffix or from unrecognized bytes.
## Open and inspect
```bash
rea inspect /absolute/path/to/ecu.bin --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
rea function /absolute/path/to/ecu.bin 0x8000000 --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
rea inspect-native-load-image /absolute/path/to/ecu.bin --target-format raw-binary --architecture arm --entry-point 0x8000000 --provider ghidra --json
```
```json
{
"name": "open_binary",
"arguments": {
"path": "/absolute/path/to/ecu.bin",
"format": "raw-binary",
"architecture": "arm",
"entry_point": "0x8000000",
"provider_id": "ghidra"
}
}
```
`architecture` is one of `x86`, `x86_64`, `arm`, or `arm64`. `entry_point` is a
hexadecimal address of the form `0x…`. The complete file is mapped starting at
that address, and Ghidra seeds an `entry` function there before auto-analysis.
ARM and AArch64 entries must be 4-byte aligned; x86 entries may be byte-aligned.
`open_binary` and an open `binary_session` return the canonical `entry_point`
for raw targets so callers can verify the selected mapping directly.
Omitted `format` keeps header-based detection; architecture and entry without
`format=raw-binary` are rejected.
Ghidra imports a private snapshot with `BinaryLoader` and these little-endian
generic languages and compiler specifications:
| architecture | language_id | compiler_spec_id |
| ------------ | ------------------- | ---------------- |
| x86 | `x86:LE:32:default` | `gcc` |
| x86_64 | `x86:LE:64:default` | `gcc` |
| arm | `ARM:LE:32:v7` | `default` |
| arm64 | `AARCH64:LE:64:v8A` | `default` |
Compiler specifications select analysis conventions; they do not identify the
original compiler or ABI of a headerless target. Ghidra's x86 languages do not
provide a compiler specification named `default`.
Those settings, the caller-supplied entry, and `language_selection:
caller-supplied-v1` are committed in the analysis profile. A different entry
or architecture is a different profile; snapshots from header-detected targets
do not replay.
The mapped start is independent of Ghidra's program image base, which remains
`0x0` for BinaryLoader. Ghidra may report additional language-defined entry
points, including unmapped ARM vectors. Load-image verification requires the
caller-supplied entry and preserves those other entries as observations.
Independent raw verification streams the complete private snapshot digest and
extent with bounded memory and supports cancellation during the read.
Hopper and IDA refuse raw-binary targets. Windows Ghidra P0 continues to admit
PE applications only.
## Limitations
- Headers are not discovered from file bytes. This increment does not guess
load addresses, vector tables, or memory maps.
- Big-endian, Thumb-only, Cortex-M, and other Ghidra processor variants are
unsupported.
- Reset vectors, memory-mapped I/O, and firmware boot sequences are not
emulated.
- Independent load-image verification covers source identity, mapped file
bytes, the bound language, and the caller-supplied entry. Original bytes
stay unchanged.
+16 -48
View File
@@ -112,12 +112,8 @@ Set `REA_VERIFY_HERMES_STICKY_PROFILE=1` to exercise native Hermes selection of
a named sticky profile with `HERMES_HOME` still pointing to its root.
`verify:gemini-client` requires an installed Gemini CLI (verified with
`@google/gemini-cli@0.63.0` on POSIX and native Windows); select it with
`REA_VERIFY_GEMINI_COMMAND`. A selected `.js`, `.mjs` or `.cjs` entry point runs
under the verifier's Node. On Windows, select the npm install's
`node_modules/@google/gemini-cli/bundle/gemini.js`, because only a `gemini.cmd`
shim reaches `PATH`.
The optional lane uses native `GEMINI_CLI_HOME` discovery in an isolated
`@google/gemini-cli@0.63.0`); select it with `REA_VERIFY_GEMINI_COMMAND`.
The optional POSIX lane uses native `GEMINI_CLI_HOME` discovery in an isolated
Git project, checks setup plans, backups and idempotence, activates the installed
personal skill, validates all forwarded input JSON Schemas against their declared
dialect, forwards the complete REA catalog and checks full JavaScript
@@ -125,7 +121,7 @@ Evidence for a Unicode path. Use `-- chat` for ordinary chat with REA enabled,
or `REA_VERIFY_RUNTIME_ROOT` for a production-only installed REA package.
The local fixture exercises the real Gemini API adapter, including its
`untrusted_context` tool-result envelope. Token counts are synthetic; live
Google API compatibility remains unverified. The lane disables
Google API and native Windows compatibility remain unverified. The lane disables
the client's memory-based relaunch to preserve the caller's Node heap budget.
`verify:opencode-client` requires an installed OpenCode (verified with
@@ -143,11 +139,8 @@ directory, so default OS-home discovery remains unverified. Live model APIs and
native Windows are also unverified.
`verify:claude-client` requires installed Claude Code (verified with
`@anthropic-ai/claude-code@2.1.296` on POSIX and native Windows); select it
with `REA_VERIFY_CLAUDE_COMMAND`. On Windows, select
`claude.exe`: npm installs put only a `claude.cmd` shim on `PATH`, and the real
executable is under `node_modules/@anthropic-ai/claude-code/bin`.
The optional lane uses native `CLAUDE_CONFIG_DIR` discovery, an independent
`@anthropic-ai/claude-code@2.1.296`); select it with `REA_VERIFY_CLAUDE_COMMAND`.
The optional POSIX lane uses native `CLAUDE_CONFIG_DIR` discovery, an independent
Git workspace, user settings and default built-in tools. It preserves caller
preferences and backups, checks idempotent setup, waits through the native
`WaitForMcpServers` tool when discovery is pending, and validates the complete
@@ -156,8 +149,8 @@ native `Skill`, checks its full body and verifies named-schema JavaScript Eviden
for a Unicode path. Use `-- chat` for ordinary chat with REA enabled, or
`REA_VERIFY_RUNTIME_ROOT` for a production-only installed package. The local
Anthropic Messages/SSE fixture preserves native resource-hint envelopes in its
request artifacts. Usage is synthetic; live Anthropic API and bare-mode
personal-skill activation remain unverified by this lane.
request artifacts. Usage is synthetic; live Anthropic API, native Windows and
bare-mode personal-skill activation remain unverified by this lane.
`verify:copilot-client` requires installed GitHub Copilot CLI (verified with
`@github/copilot@1.0.95`); select it with `REA_VERIFY_COPILOT_COMMAND`.
@@ -272,13 +265,9 @@ unverified; skill discovery uses an explicit isolated custom directory.
`verify:deepseek-client` requires an installed DeepSeek Harness (`dsh`;
verified with `@deepseek-ai/dsh@0.2.0-rc.2`) and Git. Set
`REA_VERIFY_DEEPSEEK_COMMAND` to select its executable. This optional POSIX lane
seeds caller model settings and a stale Cordis registration in an isolated
`DSH_HOME`, then runs public `setup --client dsh` dry-run, setup, repeated setup,
and scoped doctor. It checks the backup and unrelated patches before booting
Harness with the generated registration. After native execution it uninstalls
through the public CLI and checks that only caller patches remain. `HOME` and
`USERPROFILE` point to the disposable account. The native default
`~/.agents/skills` search path discovers
installs the skill through `rea setup --skill`, writes the native Cordis MCP
patch into an isolated `DSH_HOME`, and binds `HOME` and `USERPROFILE` to the
disposable account. The native default `~/.agents/skills` search path discovers
the installed skill without a `DSH_AGENTS_HOME` override. A separate Git root prevents inherited project skills from
masking a missing installation. The default `call` mode checks skill loading,
complete catalog discovery, the actual forwarded input schemas and regexes,
@@ -289,19 +278,11 @@ context capacity declared by its default DeepSeek Flash model; it does not
establish live DeepSeek API or native Windows coverage. Receipts include
request artifacts, result digests and owned-process lineage.
The DSH acceptance journey also exercises public setup, approval, doctor,
generated-launcher MCP startup and uninstall without an installed Harness.
That journey proves REA workflow behavior, not native Cordis expression
evaluation. Filesystem regressions retain tagged values and unrelated rows
across alias dependencies and check refusal of unsafe YAML without mutation.
Full E2E tests invoke the production command dispatcher and real providers,
without fake launchers, runners or responses. `verify:keyed-archive` writes
Foundation binary, XML, and integer archives using one run-owned Swift module
cache. It checks CLI and separate stdio MCP parity, pagination, escaping-path
rejection, an XML graph golden, and numeric meaning for safe controls, unsafe
integers, boxed numbers, and integral-valued reals. `verify:asset-catalog`
compiles source-owned colors with
without fake launchers, runners or responses. `verify:keyed-archive` writes an
actual Foundation binary archive, runs the CLI and a separate stdio MCP
subprocess, checks parity and pagination, rejects an escaping path and checks
an XML graph golden. `verify:asset-catalog` compiles source-owned colors with
`actool`, invokes real `assetutil`, then checks CLI/MCP results, exact catalog
digest, every raw metadata field, pagination and malformed input rejection.
Neither artifact workflow requires Hopper or launches it. Both run in macOS CI.
@@ -477,7 +458,7 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
| Ghidra lane | Supported runner/target | Additional local tools |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:seeds` | POSIX; raw x86 fixture; seed ordering, BSS labels, analyzer readback and unknown-name rejection | Ghidra 12.1.4 and full JDK 21 |
| `npm run verify:ghidra:seeds` | POSIX host; source-owned 32-bit x86 raw fixture; function order and BSS labels | Ghidra 12.1.4 and full JDK 21 |
| `npm run verify:ghidra:swift` | macOS x64/arm64 Mach-O | Host Swift compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:switch` | Linux x64 ELF; GCC/Clang optimized and stripped switch fixtures | GCC, Clang, GNU nm/objdump/strip, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:aarch64-jump-table` | Any supported Ghidra host; AArch64 ELF; signed/unsigned byte/halfword tables; host ARM64 Mach-O | Clang with AArch64 target support, Ghidra 12.1.4, and full JDK 21 |
@@ -812,16 +793,6 @@ truncation constraint, while leading-zero coordinates still resolve correctly.
It has the same Ghidra/JDK prerequisites as the MZ lane. Neither lane claims DOS
runtime or PC-98 device execution.
`npm run verify:ghidra:raw` uses a generated ARM little-endian headerless image
through the compiled CLI and stdio MCP server. With the same Ghidra/JDK
prerequisites, it checks preparation and bridge startup, verified load mappings,
instruction and byte readback, canonical-entry session reuse, replacement when
the entry changes without `provider_id`, unchanged source bytes, and owned
cleanup. Six-byte x86 and x86_64 controls also verify supported compiler
specifications, independent mappings, unchanged bytes, and decompilation of a
known return value. AArch64 remains covered by deterministic tests rather than
this real-provider lane. See [raw binary analysis](raw-binary.md).
## Apple Interface Builder archives
`npm run verify:interface-builder` compiles the source-owned AppKit XIB into a
@@ -1400,10 +1371,7 @@ generation. It installs nothing. Without configuration, the default invocation
reports a named skip; `--require-tools` makes missing configuration fail.
The verifier creates and validates real modules with custom sections, multiple
import/export kinds, escaped names, an empty module, and a bulk-memory
DataCount section. Debug-name fixtures cover compiler-generated module names
and preserved name-section payloads with empty, multiline, Unicode, long and
NUL-containing names. It compares exact WAT,
import/export kinds, escaped names and empty modules. It compares exact WAT,
section output and selected/tool digests through the compiled CLI and real MCP
SDK, validates advertised schemas, checks identical CLI/MCP Evidence IDs and
session bundle readback, retains invalid/truncated module diagnostics, preserves
-5
View File
@@ -55,11 +55,6 @@ satisfy. A plan without execution does not establish runtime behavior.
- Keep observed, derived, and inferred relationships distinct. Preserve
unresolved edges and partial provider facts instead of requiring every
provider to supply the same metadata.
- For caller/callee reads, `relationship_coverage` describes only the
provider-observed direct relationship set. `complete` means that set was
returned without a known read limit; it does not claim every possible runtime
edge. Provider and execution caveats stay in `limitations` and do not imply
incomplete relationship coverage. Missing coverage remains `unknown`.
- Declare process, filesystem, network, UI, and mutation effects truthfully;
enforce the requested target and lifecycle.
- Add limits only for real format, protocol, authority, or measured resource
+1 -11
View File
@@ -4,15 +4,6 @@
local WASM bytes using caller-supplied WABT 1.0.42. No active binary target is
required. REA does not install tools or fetch artifacts and does not execute WASM.
`open_binary` is not the WASM inspection route. A recognized WASM v1 header
returns `target_unavailable` with category `unsupported_target` and constraint
`wasm_requires_static_inspection`, including the selected path in a valid
`inspect_wasm_artifact` call. Use that target-free tool or
`rea inspect-wasm-artifact <path> --format json` with the toolchain below.
Check `binary_session` for configured capability availability. A recognized
header is not validation of the complete module; missing WABT is reported
separately as an unavailable capability, not as missing native engine support.
Set `REA_WABT_BIN_DIRECTORY` to an absolute directory containing executable
`wasm-validate`, `wasm-objdump` and `wasm2wat` (with `.exe` on Windows). Missing
executables, unsupported banners and inaccessible tools produce actionable
@@ -46,8 +37,7 @@ into a private workspace as read-only `module.wasm`; its digest is rechecked
before and after each tool. WABT validates with `--enable-all`,
reports sections through `wasm-objdump -h -x`, and decodes through
`wasm2wat --enable-all`. The result retains selected byte SHA-256 and size,
section payload ranges, full header/detail output (including module-name
preambles), exact import/export WAT
section payload ranges, full header/detail output, exact import/export WAT
forms, decoded WAT and its UTF-8 SHA-256. Producer format drift and invalid or
truncated modules are failures, with validation diagnostics and partial Evidence
retaining selected byte identity and tool provenance.
-74
View File
@@ -100,11 +100,6 @@ resources follow the same explicit declarations without changing error types;
sensitive issue pointers identify a real ancestor instead of a fabricated
coordinate. Header/cookie authentication values and known
transport URL userinfo are excluded structurally, including native backups.
HAR redaction and API-surface projection share the same recorded-URL parser.
Canonical userinfo spans are removed without rewriting the remaining source
text. Credential-bearing spellings without a safe raw span, such as slashless
or extra-slash HTTP URLs, are excluded as a whole transport field. Ordinary
extension text and noncredential path/query values remain unchanged.
Generated free-text limitations follow the same literal declarations, including
global, per-record and Evidence explanations added during projection. Contract
@@ -117,75 +112,6 @@ wire fragments. Native dictionary duplicate keys have the unchanged upstream
decoder's last-value semantics; this adapter does not claim to recover discarded
duplicates or the original numeric spelling of parsed native floats.
## Derived API surface
Pass retained HAR Evidence to `inspect_analysis_view` with
`view: {"kind":"api-surface","offset":0,"limit":50}`. Omitted offset and limit
are 0 and 50. The view groups request lines by method, WHATWG URL origin and
path template, sorted by origin, template and method. Query strings, fragments,
credentials, headers and bodies are not copied into groups.
| Rule | Exact segment format | Placeholder |
| ---------- | ---------------------------------------------------- | ------------ |
| `numeric` | ASCII digits only | `{numeric}` |
| `uuid` | RFC 9562 textual UUID, any version, case-insensitive | `{uuid}` |
| `hex` | At least 32 hexadecimal digits | `{hex}` |
| `iso-date` | Valid calendar date `YYYY-MM-DD` | `{iso-date}` |
Rules apply in table order to the recorded, still-encoded request path, read
before WHATWG dot-segment removal, so `/a/%2e%2e/admin` and `/admin` stay distinct. Segment indexes start
at zero after the leading slash. Each matched segment lists its observed literals
and `basis: "inferred"`. `templated_groups` counts templated groups across every
page. Because paging follows the full grouping, every page is inferred Evidence
when that count is nonzero, and derived Evidence only when no group was templated. Version prefixes, short opaque IDs
and digit-bearing route names stay literal. Samples merge only when method,
origin, and exact segment rules match. The view does not infer further
cross-sample patterns.
Groups cite parent record ordinals and distinct statuses and MIME types (lowercase
type/subtype without parameters). An invalid type/subtype essence is unknown;
parameters do not affect this projection. Flags report single samples, mixed
statuses or mixed content types.
`coverage` pages groups; `records` separately reports examined, total and excluded
parent records. Ordinal subsets carry a limitation. Null URLs are excluded as
`url-redacted`; unparseable URLs, URLs without an origin, spellings without
`scheme://authority` (such as `https:host/path`), and authorities containing a
tab or newline as `url-unparseable`.
Unavailable request objects or methods are explicitly excluded. Unavailable
response fields, including HAR's `status: 0` and an empty `mimeType`, remain
unknown, not invented statuses or MIME types.
The view makes no handler-identity, schema or causality claim. It performs no
network activity. Native mitmproxy captures return a typed unsupported error.
Parents inspected with `sensitive_values`, or whose Evidence does not record
`sensitive_value_count`, return a typed unsupported error whose reason is the
canonical token `sensitive_values_unretained`. The view would write path
templates and limitation text that it cannot check against declarations the
parent does not retain, so the refusal does not repeat declaration text.
One accumulator owns group identity, observed-value/status/MIME sets, ordinals,
exclusions and deduplicated limitations. It scans path spans without splitting
literal paths into arrays. Before schema parsing, it reserves the capture clone
and validation scratch by walking the parent without cloning it. Inline requests
borrow the Evidence value through CLI/MCP envelope validation; the application
admits the entire portable envelope before authenticated parsing or hashing.
The envelope clone, hash scratch and capture clone stay charged during grouping.
Temporary URL/path/MIME work and retained state must
fit together within a 96 MiB projection representation budget. A whole group or
merge is admitted before allocating its retained sets, map entries or array
values. Fixed group storage, distinct text, exclusions and limitations count as
well as matched segments. Per-record reservations are released after each
record; repeated values do not accumulate another retained charge. Only the
requested page is materialized as output groups.
The representation budget uses conservative storage and worst-case JSON-escaping
allowances; it is not an enforced V8 heap or process-memory limit. It is separate
from the decoder's actual reply-byte limit and MCP transport admission. Exceeding
it returns `resource_constraint` without a partial view. Limitations on the
parent envelope, capture and examined records are deduplicated onto the view.
Portable captures must retain unique ordinals within their declared total;
caller-selected subsets can preserve any requested ordinal order.
## Resource and lifecycle boundaries
Complete evidence has a 32 MiB input, 96 MiB decoder reply and 64-level nesting
+174
View File
@@ -0,0 +1,174 @@
# REA Code Mode experiment
Frozen baseline: `fb94d9dde0ccda83ae520ac6fb22b1d283562cf3`, fetched from
`origin/main` on 2026-10-11. Branch: `experiment/codemode-bridge-20261011`.
This is a local experiment, not a change to the published CLI or MCP API.
The analyst question is whether composing REA's existing named tools in code
improves investigation efficiency enough to justify an optional bridge. Existing
users must keep their current names, input schemas, Evidence and session behavior.
Provider analysis settings must stay identical between comparison profiles.
The bridge owns one upstream stdio MCP connection and discovers its complete live
catalog. Only discovery and JavaScript execution are exposed downstream. Code
calls `tools.<original_name>(original_arguments)` and receives the original MCP
result envelope. REA still validates input, chooses providers, retains Evidence,
and owns analysis resources. There is no second tool implementation or schema
catalog to keep synchronized. A fresh QuickJS WASM context exposes only those
tool calls, JSON helpers and explicit output; it has no host filesystem, network,
process or module loader.
Start the bridge without changing an existing registration:
```bash
node bridge.mjs /absolute/path/to/node /absolute/path/to/rea/scripts/rea.mjs mcp
```
Register that command as a separate, temporary MCP server for a trial. Existing
REA registrations keep their original tools. `rea_search` accepts a query or exact
names; use `schemas: false` for the complete lightweight name/description list.
Query discovery returns names/descriptions by default; exact-name discovery
returns original input schemas and derived input/output TypeScript declarations.
These declarations help write code; they do not replace JSON validation contracts.
`schemas` explicitly overrides that choice.
Start with one domain word and request all selected exact names in one call.
Request `output_schemas: true` when a
result contract is needed; the original output schema is then returned unchanged.
`catalog_sha256` identifies the SDK catalog; it must not be substituted for REA's
`server_identity.catalog.digests` fields.
`rea_execute` accepts a JavaScript async function body. For example:
```js
const session = json(await tools.binary_session({})).result;
return { open: session.open, identity: session.server_identity };
```
The returned projection is not a new Evidence record. `calls` preserves operation,
argument/result digests, upstream errors and actual producer Evidence IDs when
provided, including original `evidence_links`. The upstream ledger retains complete original Evidence for later views
and exports. Non-Evidence session responses have no invented Evidence ID.
Calls and executions sharing one active target are serialized; `Promise.all`
currently provides composition rather than parallel provider execution.
`npm test` runs actual upstream and downstream stdio MCP against this checkout's
compiled runtime. The standalone experiment's twelve tests cover discovery parity,
original results/errors, bulk promise settlement, host capability absence, CPU
deadline recovery, retained Evidence after an error or provider timeout,
queued-call cancellation, unresolved promises and the
two-tool downstream boundary, guest heap exhaustion/reuse, and SDK-converted
schemas validated as [JSON Schema 2020-12](https://modelcontextprotocol.io/specification/2025-11-25/basic/index#schema-dialect).
Execution rejects delays above Node's signed 32-bit timer range instead of
allowing Node to coerce them to 1 ms. These tests do not establish real native/Android/
firmware provider behavior. A lifecycle regression revealed that the QuickJS job
pump can allocate an additional context: the execution scope now owns every
context at creation and releases them before the runtime. Increasing memory did
not fix that regression.
Install experimental dependencies separately with `npm ci` in this directory.
They do not change the production package or lockfile. Use the repository's pinned
Node and npm versions. No binary, provider database or raw third-party source is
committed. Corpus identities and upstream download locations are in `corpus.json`.
Measure three baselines: normal full-catalog MCP, discovery-only MCP, and Code
Mode. Count discovery, input code, intermediate results, final output, errors and
retries. Report actual wall time separately from model-facing turns and local
token estimates. Existing summary/page/workflow tools are fair alternatives;
do not manufacture a gain by forcing full output on the direct-tool baseline.
Replay measurements establish mechanism costs, not model task accuracy or
inference latency. Real-agent trials must additionally verify supported facts,
source identity, omissions and preserved Evidence references.
`benchmark.mjs` replays a fixed question against real providers with identical
original arguments. For example:
```bash
python3 resource-run.py -- node benchmark.mjs --artifact newpipe.apk \
--mode bridge --artifact-root /absolute/path/artifacts \
--providers /absolute/path/selected-provider-settings.json \
--output /absolute/path/new-result-directory
```
Modes are `direct`, `discovery` and `bridge`. The discovery mode isolates catalog
cost while retaining original results; it is not a new production MCP client.
Raw responses remain outside Git. Result summaries separate complete envelopes
from single text content and count code explicitly. Measured findings and failures
are recorded in [results.md](results.md).
`--artifact obsidian.asar --workflow obsidian-inventory` asks a distinct package
inventory question using existing tools. It must not be compared as an equivalent
completion of the JavaScript AST question. Workflow names and original arguments
are retained in replay summaries.
`agent-trial.py` runs a real local Codex CLI with a disposable account/config and
one MCP registration. It copies authentication only, removes the profile on normal
completion, timeout and handled termination, and retains JSONL events, the answer
and measured API usage outside the repo.
It defaults to `gpt-5.6-terra` with high reasoning, as requested. Run one task under
the resource wrapper:
```bash
python3 resource-run.py -- python3 agent-trial.py \
--mode direct --prompt /absolute/path/question.txt \
--output /absolute/path/new-trial-directory
```
Reproducible questions are in [questions/](questions/). Supply a template with
`--prompt questions/android.txt --artifact-path /absolute/path/NewPipe_v0.29.1.apk`
(or another matching corpus target). The runner preserves the supplied path in
the rendered prompt and records template and rendered-prompt digests. Native's
entry-address seed is derived from this corpus's jq header and is not reusable
for another build. Run comparisons serially with the same question and provider
settings; retain failures as well as successful repetitions.
Use `--mode bridge` for the experimental server; `--mode client-code` requests
Codex's own Code Mode. Optional `--providers` selects a JSON object of explicit
provider settings. No ambient environment snapshot is retained. A configured flag
or a model's `tool_mode` does not prove the model actually composed tools or
filtered an intermediate result. Inspect the transcript, and mark accuracy as
unassessed until the requested facts and source support have been reviewed.
`grade-trial.py` checks Android (default), managed, native, firmware or
electron-inventory (`--task NAME`) against a successful
real-provider replay and verifies cited IDs in producer values, their original
Evidence links or the bridge audit. This verifies references observed in the
transcript; the grader does not resolve every cited payload. It accepts
JSON or one complete JSON fence, without repairing prose or partial values. This
checks requested producer facts and reference existence, not runtime behavior or
independent decompiler correctness. Unparseable result text is reported explicitly;
IDs in a truncated JSON fragment cannot satisfy the automated reference check.
The Android projection includes the provider's overload index, so a supported
signature with that field omitted fails this stronger assertion.
An omitted DEX descriptor differs from an explicit null, and booleans differ from
numeric counts. Native containing-procedure reports compare resolved identity;
the full analyzed procedure is verified separately.
Firmware's run-local region UUIDs remain in answers but are excluded from parity
against an independently acquired report. Classification evidence, explicit
unknowns and coverage remain part of the requested fact checks.
The question comparison accepts grouped original path records, extra known
coverage counts and explicitly named firmware length/coverage report fields.
Derived extent ends must agree with offset plus reported length. It verifies
engine version and executable digest rather than display capitalization; a manual
review is still needed for limitations and claims beyond these assertions.
Grades record the grader source digest, and earlier strict grades are retained
when the comparison changes. These are report parsers, not REA API aliases.
The completed serial cohort is in [agent-results.json](agent-results.json), with
source/configuration fingerprints, measured API usage and sampled cgroup stats.
The recommendation and migration boundaries are in [decision.md](decision.md).
Raw transcripts and original replay replies remain outside Git.
The supplied feedback is a research lead. Its Ghidra timing claim also changed
auto-analysis options, so it does not isolate Code Mode's effect. Relevant primary
sources: [Cloudflare Code Mode](https://blog.cloudflare.com/code-mode/),
[MCP patterns](https://developers.cloudflare.com/agents/model-context-protocol/codemode/),
[Anthropic code execution](https://www.anthropic.com/engineering/code-execution-with-mcp),
and [QuickJS WASM](https://github.com/justjake/quickjs-emscripten).
Run expensive commands with `python3 resource-run.py -- COMMAND ARGS...` on Linux
with a user systemd manager. It uses a transient scope with one CPU, a 4 GiB hard
memory ceiling, a 3 GiB early-stop threshold, no swap, and a task ceiling. It
refuses to start when less than 8 GiB is available and stops if host available
memory drops below 4 GiB. These limits protect the host; a resource abort is a
failed measurement, never a successful benchmark. Also cap Node and Java heaps.
The task ceiling is 192. The wrapper records observed memory/task events and
stops a trial that touches the task limit. A limit event invalidates a measurement
even when the application later returns successfully.
+995
View File
@@ -0,0 +1,995 @@
{
"baseline_sha": "fb94d9dde0ccda83ae520ac6fb22b1d283562cf3",
"scope": "real_cli_requested_fact_parity_and_observed_references_not_independent_runtime_truth",
"runtime_source_commit": "22326e8199975f31d8b7c99aaf5fd64140fbe00e",
"artifact_ids": "corpus.json",
"resource_limits": {
"cpu_affinity": [15],
"cpu_quota_percent": 100,
"hard_memory_bytes": 4294967296,
"early_stop_bytes": 3221225472,
"tasks_max": 192
},
"notes": [
"Profiles differ in required mode-specific instructions; original model-selected arguments may differ.",
"Two Android/managed repetitions are descriptive samples, without confidence intervals or a causal speed claim.",
"Managed classification container omissions fail a stronger complete-object assertion; remaining requested facts match.",
"Normal-MCP managed JSONL truncation leaves one ID unavailable to the complete-envelope reference parser.",
"Uncached input is input minus cached input, not a currency cost estimate."
],
"trials": [
{
"trial": "android-direct-5",
"task": "android",
"mode": "direct",
"artifact": "newpipe.apk",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 67.329,
"input_tokens": 107007,
"cached_input_tokens": 83712,
"uncached_input_tokens": 23295,
"output_tokens": 1670,
"completed_mcp_calls": 3,
"original_operations": [
"inspect_android_package",
"search_android_classes",
"inspect_android_class"
],
"fact_assertions": 6,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "7129418f2d4428d86cc737124b53636ba4a46de522e702e8861180fc175acb23",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "3424dd6ba375f96d88fd0ac38102998948fa0fc46e08af8fae414572ba97a62f",
"peak_memory_bytes_sampled": 1336168448,
"peak_tasks_sampled": 108,
"cpu_stat_last_observed": {
"usage_usec": 23974584,
"user_usec": 21485565,
"system_usec": 2489019,
"nr_periods": 636,
"nr_throttled": 45,
"throttled_usec": 67840,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "android-bridge-7",
"task": "android",
"mode": "bridge",
"artifact": "newpipe.apk",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 48.231,
"input_tokens": 113256,
"cached_input_tokens": 91648,
"uncached_input_tokens": 21608,
"output_tokens": 1456,
"completed_mcp_calls": 3,
"original_operations": [
"inspect_android_package",
"search_android_classes",
"inspect_android_class"
],
"fact_assertions": 6,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "7129418f2d4428d86cc737124b53636ba4a46de522e702e8861180fc175acb23",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "27e234285e493b50929c0f98938284771d0d4a3787045872f2f517b2297aefeb",
"peak_memory_bytes_sampled": 1659629568,
"peak_tasks_sampled": 126,
"cpu_stat_last_observed": {
"usage_usec": 27717810,
"user_usec": 25042151,
"system_usec": 2675658,
"nr_periods": 458,
"nr_throttled": 50,
"throttled_usec": 73614,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "managed-bridge-2",
"task": "managed",
"mode": "bridge",
"artifact": "sharex.dll",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 73.145,
"input_tokens": 116974,
"cached_input_tokens": 92672,
"uncached_input_tokens": 24302,
"output_tokens": 2065,
"completed_mcp_calls": 3,
"original_operations": [
"inspect_managed_artifact",
"inspect_managed_native_boundaries"
],
"fact_assertions": 7,
"fact_mismatches": ["classification"],
"strict_grade_passed": false,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "fe5a74687126fca7aee78b3ba51f03401983ffeae593924b7c738d50d0f04e61",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "06cbdda60ac930d00ade7e115cd4ba234527e297e6cfd931eedc175e00efa1c7",
"peak_memory_bytes_sampled": 939290624,
"peak_tasks_sampled": 103,
"cpu_stat_last_observed": {
"usage_usec": 12114716,
"user_usec": 9701516,
"system_usec": 2413200,
"nr_periods": 630,
"nr_throttled": 16,
"throttled_usec": 15944,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "managed-direct-2",
"task": "managed",
"mode": "direct",
"artifact": "sharex.dll",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 33.15,
"input_tokens": 108096,
"cached_input_tokens": 75776,
"uncached_input_tokens": 32320,
"output_tokens": 2315,
"completed_mcp_calls": 2,
"original_operations": [
"inspect_managed_artifact",
"inspect_managed_native_boundaries"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": false,
"cited_ids_observed": false,
"unreadable_result_texts": 1,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "fe5a74687126fca7aee78b3ba51f03401983ffeae593924b7c738d50d0f04e61",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "5d6957541bd979252d019817e631f5d889ff2dc3efb7f03b9de1033fe34d8430",
"peak_memory_bytes_sampled": 688373760,
"peak_tasks_sampled": 108,
"cpu_stat_last_observed": {
"usage_usec": 5863642,
"user_usec": 4359666,
"system_usec": 1503976,
"nr_periods": 282,
"nr_throttled": 4,
"throttled_usec": 6216,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "electron-inventory-direct-1",
"task": "electron-inventory",
"mode": "direct",
"artifact": "obsidian.asar",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 89.494,
"input_tokens": 266121,
"cached_input_tokens": 238848,
"uncached_input_tokens": 27273,
"output_tokens": 3215,
"completed_mcp_calls": 3,
"original_operations": [
"open_binary",
"inspect_artifact",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "22941dc5d77ce06824e40b01abd3f725aa773d131648d7bba2e607b410ccbef9",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "fcd0df1a3ae0b5103143addaa4922323354ddee7ba3d0e3020de64f05b6fe3d3",
"peak_memory_bytes_sampled": 703275008,
"peak_tasks_sampled": 115,
"cpu_stat_last_observed": {
"usage_usec": 7720494,
"user_usec": 5691854,
"system_usec": 2028640,
"nr_periods": 782,
"nr_throttled": 7,
"throttled_usec": 15453,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "electron-inventory-bridge-1",
"task": "electron-inventory",
"mode": "bridge",
"artifact": "obsidian.asar",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 145.577,
"input_tokens": 462809,
"cached_input_tokens": 407040,
"uncached_input_tokens": 55769,
"output_tokens": 5624,
"completed_mcp_calls": 8,
"original_operations": [
"open_binary",
"inspect_artifact",
"close_binary",
"open_binary",
"inspect_artifact",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "22941dc5d77ce06824e40b01abd3f725aa773d131648d7bba2e607b410ccbef9",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "ab7e25e4345a0e9bba6a06bb0a79aa39e1bf1308b6a66f8173a62bbc9e2d9fc9",
"peak_memory_bytes_sampled": 1029238784,
"peak_tasks_sampled": 112,
"cpu_stat_last_observed": {
"usage_usec": 14650718,
"user_usec": 12106416,
"system_usec": 2544302,
"nr_periods": 1224,
"nr_throttled": 24,
"throttled_usec": 30318,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "firmware-bridge-1",
"task": "firmware",
"mode": "bridge",
"artifact": "openwrt.bin",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 81.944,
"input_tokens": 152886,
"cached_input_tokens": 126976,
"uncached_input_tokens": 25910,
"output_tokens": 2192,
"completed_mcp_calls": 3,
"original_operations": ["inspect_firmware_regions"],
"fact_assertions": 5,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "93c94e26dc4a4b1985b3f8c4246ca5aff886f75dc2349a717ee940243dc73b80",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "a5604d0d3140f6232851a81a952e51daf43c8e7f12ba062fac68a8fae0bef708",
"peak_memory_bytes_sampled": 927666176,
"peak_tasks_sampled": 104,
"cpu_stat_last_observed": {
"usage_usec": 11575373,
"user_usec": 9366624,
"system_usec": 2208749,
"nr_periods": 744,
"nr_throttled": 18,
"throttled_usec": 26000,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "firmware-direct-1",
"task": "firmware",
"mode": "direct",
"artifact": "openwrt.bin",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 30.161,
"input_tokens": 103224,
"cached_input_tokens": 74752,
"uncached_input_tokens": 28472,
"output_tokens": 1761,
"completed_mcp_calls": 1,
"original_operations": ["inspect_firmware_regions"],
"fact_assertions": 5,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "93c94e26dc4a4b1985b3f8c4246ca5aff886f75dc2349a717ee940243dc73b80",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "c0bbd1aec08c9c30061f92064355c3213f680fab545a050271096e85db771a71",
"peak_memory_bytes_sampled": 686739456,
"peak_tasks_sampled": 109,
"cpu_stat_last_observed": {
"usage_usec": 5700866,
"user_usec": 4167696,
"system_usec": 1533169,
"nr_periods": 266,
"nr_throttled": 6,
"throttled_usec": 3978,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "native-direct-1",
"task": "native",
"mode": "direct",
"artifact": "jq",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 327.15,
"input_tokens": 151343,
"cached_input_tokens": 122368,
"uncached_input_tokens": 28975,
"output_tokens": 2458,
"completed_mcp_calls": 4,
"original_operations": [
"open_binary",
"resolve_containing_procedure",
"analyze_function",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "234bfe93d09f9c76c1a64383c11cf7c174eafa4e4ad563124f705a7d5c8920aa",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "681f38e4ba9b441e7816e868b30e347005fe074907349cd0022fc44302c5b14a",
"peak_memory_bytes_sampled": 1328496640,
"peak_tasks_sampled": 109,
"cpu_stat_last_observed": {
"usage_usec": 263737951,
"user_usec": 256864795,
"system_usec": 6873156,
"nr_periods": 3192,
"nr_throttled": 456,
"throttled_usec": 756678,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "native-bridge-1",
"task": "native",
"mode": "bridge",
"artifact": "jq",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 334.642,
"input_tokens": 285961,
"cached_input_tokens": 245504,
"uncached_input_tokens": 40457,
"output_tokens": 2129,
"completed_mcp_calls": 4,
"original_operations": [
"open_binary",
"analyze_function",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "234bfe93d09f9c76c1a64383c11cf7c174eafa4e4ad563124f705a7d5c8920aa",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "6c64953708df4a06b69fb57e5d70141147e4495adabd7340e87e727fcd340742",
"peak_memory_bytes_sampled": 1441775616,
"peak_tasks_sampled": 123,
"cpu_stat_last_observed": {
"usage_usec": 271095966,
"user_usec": 263888347,
"system_usec": 7207618,
"nr_periods": 3267,
"nr_throttled": 450,
"throttled_usec": 784719,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "android-bridge-8",
"task": "android",
"mode": "bridge",
"artifact": "newpipe.apk",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 134.555,
"input_tokens": 159370,
"cached_input_tokens": 134400,
"uncached_input_tokens": 24970,
"output_tokens": 3250,
"completed_mcp_calls": 7,
"original_operations": [
"inspect_android_package",
"search_android_classes",
"inspect_android_class",
"inspect_android_package",
"inspect_android_package",
"search_android_classes",
"inspect_android_class"
],
"fact_assertions": 6,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "7129418f2d4428d86cc737124b53636ba4a46de522e702e8861180fc175acb23",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "f1743a341d334bd41760b54980aa70d5f3c8c1aed57c3ae9e470be63449c594d",
"peak_memory_bytes_sampled": 1659744256,
"peak_tasks_sampled": 115,
"cpu_stat_last_observed": {
"usage_usec": 29619173,
"user_usec": 26495167,
"system_usec": 3124005,
"nr_periods": 1229,
"nr_throttled": 60,
"throttled_usec": 84762,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "android-direct-6",
"task": "android",
"mode": "direct",
"artifact": "newpipe.apk",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 71.624,
"input_tokens": 153031,
"cached_input_tokens": 117504,
"uncached_input_tokens": 35527,
"output_tokens": 1501,
"completed_mcp_calls": 3,
"original_operations": [
"inspect_android_package",
"search_android_classes",
"inspect_android_class"
],
"fact_assertions": 6,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "7129418f2d4428d86cc737124b53636ba4a46de522e702e8861180fc175acb23",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "b8d0422eec6bfd31a9a6ed79ac29e9487c2e68c47ff200904ec239daee9720c3",
"peak_memory_bytes_sampled": 1348767744,
"peak_tasks_sampled": 115,
"cpu_stat_last_observed": {
"usage_usec": 23033331,
"user_usec": 20566262,
"system_usec": 2467069,
"nr_periods": 674,
"nr_throttled": 40,
"throttled_usec": 61573,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "managed-direct-3",
"task": "managed",
"mode": "direct",
"artifact": "sharex.dll",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 75.229,
"input_tokens": 108593,
"cached_input_tokens": 75776,
"uncached_input_tokens": 32817,
"output_tokens": 2774,
"completed_mcp_calls": 2,
"original_operations": [
"inspect_managed_artifact",
"inspect_managed_native_boundaries"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": false,
"cited_ids_observed": false,
"unreadable_result_texts": 1,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "fe5a74687126fca7aee78b3ba51f03401983ffeae593924b7c738d50d0f04e61",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "64f0769b7ad83f8d86c005a48de7a0cf397043d77378983fabfaa226dcb70f87",
"peak_memory_bytes_sampled": 651026432,
"peak_tasks_sampled": 110,
"cpu_stat_last_observed": {
"usage_usec": 6252846,
"user_usec": 4688766,
"system_usec": 1564079,
"nr_periods": 635,
"nr_throttled": 7,
"throttled_usec": 11469,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "managed-bridge-3",
"task": "managed",
"mode": "bridge",
"artifact": "sharex.dll",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 40.609,
"input_tokens": 125519,
"cached_input_tokens": 96000,
"uncached_input_tokens": 29519,
"output_tokens": 2274,
"completed_mcp_calls": 3,
"original_operations": [
"inspect_managed_artifact",
"inspect_managed_native_boundaries"
],
"fact_assertions": 7,
"fact_mismatches": ["classification"],
"strict_grade_passed": false,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "fe5a74687126fca7aee78b3ba51f03401983ffeae593924b7c738d50d0f04e61",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "5fbc3cf0b41224f2ef44ab996b174c8a48fe07a818ac2ad334ef59e5ddf26de6",
"peak_memory_bytes_sampled": 1005625344,
"peak_tasks_sampled": 113,
"cpu_stat_last_observed": {
"usage_usec": 11185145,
"user_usec": 9300437,
"system_usec": 1884707,
"nr_periods": 366,
"nr_throttled": 21,
"throttled_usec": 24147,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "electron-inventory-guided-bridge-1",
"task": "electron-inventory",
"mode": "bridge",
"artifact": "obsidian.asar",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 85.909,
"input_tokens": 101994,
"cached_input_tokens": 61440,
"uncached_input_tokens": 40554,
"output_tokens": 2563,
"completed_mcp_calls": 3,
"original_operations": [
"open_binary",
"inspect_artifact",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "f070cdf721805d590c1bd5255ecf1f647ea165885cd5052af35c30ba531da815",
"developer_instructions_sha256": "247ca3314340f748d1479cc1a0a1f33a234231f17b5768044034bdfe517299c6",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "566391b8a6db73228fec00dc7cbf0eaf56ed899178a405d5d8f0c8002fb05d89",
"peak_memory_bytes_sampled": 1007996928,
"peak_tasks_sampled": 109,
"cpu_stat_last_observed": {
"usage_usec": 13304542,
"user_usec": 10842042,
"system_usec": 2462499,
"nr_periods": 787,
"nr_throttled": 19,
"throttled_usec": 27407,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"trial": "electron-inventory-guided-direct-1",
"task": "electron-inventory",
"mode": "direct",
"artifact": "obsidian.asar",
"requested_model": "gpt-5.6-terra",
"reasoning": "high",
"codex_version": "codex-cli 0.161.0",
"wall_seconds": 67.54,
"input_tokens": 263484,
"cached_input_tokens": 221952,
"uncached_input_tokens": 41532,
"output_tokens": 2499,
"completed_mcp_calls": 4,
"original_operations": [
"open_binary",
"inspect_artifact",
"inspect_artifact",
"close_binary"
],
"fact_assertions": 7,
"fact_mismatches": [],
"strict_grade_passed": true,
"cited_ids_observed": true,
"unreadable_result_texts": 0,
"grader_sha256": "707e4eef05634db5075ac64382500b9eea03b38f828358315e54381f9d4a8979",
"prompt_sha256": "f070cdf721805d590c1bd5255ecf1f647ea165885cd5052af35c30ba531da815",
"developer_instructions_sha256": "eaddc85aa5b141538ec280d640c62db799231b394679a17f2e6cc6bc2146a95c",
"provider_settings_sha256": "1db4e19adcb32bb64a3baad58a1b4ef04b52ba648bab29678693f9ab8dbdabb4",
"experiment_sources_sha256": {
"agent-trial.py": "3c96d0f7b4b3dd02d168258d8aedd79049bb8647f1ebb174b056bd8eaa582502",
"bridge.mjs": "c1afb88a3045f233006826ffd466590b33027cc7b95f9c6c551ba1aa93fa4727",
"package-lock.json": "6dab2b2dba5101992e9a881ce1820475bd304511dc44362bb1614b08e9b37a87"
},
"events_sha256": "564711cf28211bb7059a04b919f1b6925a816ded3e2dbeb9678382568f700e58",
"peak_memory_bytes_sampled": 728817664,
"peak_tasks_sampled": 114,
"cpu_stat_last_observed": {
"usage_usec": 8708686,
"user_usec": 6609914,
"system_usec": 2098771,
"nr_periods": 597,
"nr_throttled": 12,
"throttled_usec": 19564,
"nr_bursts": 0,
"burst_usec": 0
},
"resource_abort": null,
"resource_events_last_observed": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
}
]
}
+205
View File
@@ -0,0 +1,205 @@
"""Evaluate one real Codex CLI task with an isolated REA MCP registration."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import signal
import subprocess
import tempfile
import time
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--mode', choices=['direct', 'client-code', 'bridge'], required=True)
parser.add_argument('--prompt', type=Path, required=True)
parser.add_argument('--artifact-path', help='Replace {{artifact_path}} in a question template')
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--model', default='gpt-5.6-terra')
parser.add_argument('--reasoning', default='high')
parser.add_argument('--timeout', type=int, default=900)
parser.add_argument('--codex', default='/usr/bin/codex')
parser.add_argument('--providers', type=Path)
args = parser.parse_args()
def terminate(signum, _frame):
# The outer resource scope terminates descendants together. Ensure the
# runner's copied credentials are also removed on a resource abort.
raise SystemExit(128 + signum)
signal.signal(signal.SIGTERM, terminate)
repository = Path(__file__).resolve().parents[2]
experiment = Path(__file__).resolve().parent
node = shutil.which('node')
if subprocess.check_output([node, '--version'], text=True).strip() != 'v24.18.0':
raise SystemExit('Use pinned Node 24.18.0 on PATH')
args.output.mkdir(parents=True, exist_ok=False, mode=0o700)
profile = Path(tempfile.mkdtemp(prefix='profile-', dir=args.output))
trial_codex_home = profile / 'codex'
account = profile / 'account'
workspace = profile / 'workspace'
for directory in [trial_codex_home, account, workspace]:
directory.mkdir(mode=0o700)
source_auth = Path(os.environ.get('CODEX_HOME', str(Path.home() / '.codex'))) / 'auth.json'
auth = trial_codex_home / 'auth.json'
started = time.monotonic()
timeout = False
exit_code = None
environment = dict(os.environ)
environment.update({
'CODEX_HOME': str(trial_codex_home), 'HOME': str(account),
'XDG_CONFIG_HOME': str(account / '.config'),
'XDG_CACHE_HOME': str(account / '.cache'),
'XDG_DATA_HOME': str(account / '.local/share'),
'NODE_OPTIONS': '--max-old-space-size=512',
})
provider_env = {'REA_LOG_LEVEL': 'silent', 'NODE_OPTIONS': '--max-old-space-size=512'}
if args.providers:
# This explicit caller file supplies only selected provider settings.
provider_env.update(json.loads(args.providers.read_text()))
command_args = [str(repository / 'scripts/rea.mjs'), 'mcp']
if args.mode == 'bridge':
command_args = [str(experiment / 'bridge.mjs'), node, *command_args]
instruction = (
'Use the registered REA MCP server for this investigation. Static analysis '
'only: do not execute target code, fetch URLs, launch a GUI, or modify targets. '
'Preserve observations, derivations, inferences, limitations, and real Evidence IDs. '
'An Evidence value has evidence_id, normalized_result and limitations; '
'non-Evidence session values have result. '
'The analyzed target digest is Evidence.subject.digest.sha256. '
'normalized_result.engine.artifact_sha256 identifies the analysis engine '
'artifact and must never be substituted for the target digest. '
'Prefer summary/focused views where appropriate. Do not invent successful facts '
'after a tool error. No other agents. Stop once the requested facts are supported.'
)
if args.mode == 'bridge':
instruction += (
' Original REA operations are available through rea_search and rea_execute. '
'Discover tool names/schemas on demand. Execute JavaScript bodies using '
'tools.<original_name>(original_arguments), json(reply) to parse the original '
'MCP result, and return only the needed facts plus their Evidence IDs and '
'limitations. Compose known dependent calls in one script when useful. '
'Select needed fields from normalized_result inside the script; do not '
'return entire Evidence, raw_result or full method/class inventories when '
'the question requests a subset. '
'Await every call; the upstream session persists across scripts.'
)
if args.mode == 'client-code':
instruction += (
' Use the client JavaScript executor to compose registered MCP calls when '
'possible and select the requested normalized_result fields before returning '
'intermediate output to model context. Keep real Evidence IDs and limitations.'
)
quote = json.dumps
config = '\n'.join([
'approval_policy = "never"', 'sandbox_mode = "read-only"',
'web_search = "disabled"', 'model = ' + quote(args.model),
'model_reasoning_effort = ' + quote(args.reasoning),
'developer_instructions = ' + quote(instruction),
'[features]', 'multi_agent = false', 'shell_snapshot = false',
'shell_tool = false', 'unified_exec = false',
'code_mode = ' + ('true' if args.mode == 'client-code' else 'false'),
'code_mode_only = ' + ('true' if args.mode == 'client-code' else 'false'),
'[history]', 'persistence = "none"',
'[mcp_servers.rea]', 'command = ' + quote(node),
'args = ' + quote(command_args), 'startup_timeout_sec = 60',
'tool_timeout_sec = 600', '[mcp_servers.rea.env]',
*[key + ' = ' + quote(value) for key, value in provider_env.items()],
'[mcp_servers.rea.tools.rea_execute]', 'approval_mode = "approve"',
]) + '\n'
# MCP read-only hints cover normal static tools. Explicitly authorize only
# the session-opening primitive needed by the requested native tasks.
config += '[mcp_servers.rea.tools.open_binary]\napproval_mode = "approve"\n'
for tool in ['analyze_javascript_application', 'inspect_analysis_view',
'inspect_android_package', 'search_android_classes',
'inspect_android_class', 'inspect_android_method',
'inspect_firmware_regions', 'inspect_managed_artifact',
'inspect_managed_members', 'inspect_managed_native_boundaries',
'inspect_artifact', 'inspect_wasm_artifact',
'resolve_containing_procedure', 'analyze_function', 'close_binary']:
config += '[mcp_servers.rea.tools.' + tool + ']\napproval_mode = "approve"\n'
try:
if source_auth.exists():
shutil.copyfile(source_auth, auth)
auth.chmod(0o600)
(trial_codex_home / 'config.toml').write_text(config)
(trial_codex_home / 'config.toml').chmod(0o600)
prompt = args.prompt.read_text()
template_digest = hashlib.sha256(prompt.encode()).hexdigest()
if '{{artifact_path}}' in prompt:
if not args.artifact_path or not os.path.isabs(args.artifact_path):
raise ValueError('A question template requires an absolute --artifact-path')
prompt = prompt.replace('{{artifact_path}}', args.artifact_path)
elif args.artifact_path:
raise ValueError('--artifact-path requires the literal template marker')
(args.output / 'prompt.txt').write_text(prompt)
(args.output / 'trial-settings.json').write_text(json.dumps({
'mode': args.mode, 'model': args.model, 'reasoning': args.reasoning,
'experiment_commit': subprocess.check_output(
['git', 'rev-parse', 'HEAD'], cwd=repository, text=True).strip(),
'experiment_sources_sha256': {
name: hashlib.sha256((experiment / name).read_bytes()).hexdigest()
for name in ['agent-trial.py', 'bridge.mjs', 'package-lock.json']
},
'developer_instructions': instruction,
'developer_instructions_sha256': hashlib.sha256(instruction.encode()).hexdigest(),
'prompt_sha256': hashlib.sha256(prompt.encode()).hexdigest(),
'question_template_sha256': template_digest,
'provider_settings_sha256': hashlib.sha256(
json.dumps(provider_env, sort_keys=True).encode()).hexdigest(),
'features': {'shell_tool': False, 'unified_exec': False,
'multi_agent': False, 'code_mode': args.mode == 'client-code',
'code_mode_only': args.mode == 'client-code'},
}, indent=2) + '\n')
invocation = [args.codex, '--no-daemon', 'exec', '--ephemeral',
'--skip-git-repo-check', '--ignore-rules', '--json',
'--color', 'never', '-C', str(workspace),
'--output-last-message', str(args.output.resolve() / 'answer.txt'), '-']
with (args.output / 'events.jsonl').open('w') as events, (args.output / 'stderr.log').open('w') as errors:
child = subprocess.Popen(invocation, env=environment, cwd=workspace,
stdin=subprocess.PIPE, stdout=events, stderr=errors, text=True)
child.stdin.write(prompt)
child.stdin.close()
try:
exit_code = child.wait(timeout=args.timeout)
except subprocess.TimeoutExpired:
timeout = True
child.terminate()
try:
exit_code = child.wait(timeout=10)
except subprocess.TimeoutExpired:
child.kill()
exit_code = child.wait()
usage = []
tools = []
event_types = {}
for line in (args.output / 'events.jsonl').read_text().splitlines():
event = json.loads(line)
kind = event.get('type', 'unknown')
event_types[kind] = event_types.get(kind, 0) + 1
if 'usage' in event:
usage.append(event['usage'])
item = event.get('item', {})
if item.get('type') == 'mcp_tool_call' and kind == 'item.completed':
tools.append({k: item.get(k) for k in ['server', 'tool', 'arguments', 'status', 'error']})
summary = {
'mode': args.mode, 'requested_model': args.model, 'reasoning': args.reasoning,
'baseline_sha': 'fb94d9dde0ccda83ae520ac6fb22b1d283562cf3',
'codex_version': subprocess.check_output([args.codex, '--version'], text=True).strip(),
'exit_code': exit_code, 'timeout': timeout,
'wall_seconds': round(time.monotonic() - started, 3),
'usage': usage, 'completed_mcp_calls': tools, 'event_types': event_types,
'factual_correctness': 'not_assessed',
'scope': 'real_cli_trial_requires_transcript_and_fact_review',
}
(args.output / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
print(json.dumps(summary))
finally:
# Always remove copied credentials, config, caches and profile state.
shutil.rmtree(profile)
raise SystemExit(124 if timeout else exit_code)
if __name__ == '__main__':
main()
+369
View File
@@ -0,0 +1,369 @@
import { createHash } from "node:crypto";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { getEncoding } from "js-tiktoken";
import { CodeModeBridge, connectUpstream, json } from "./bridge.mjs";
// Replay isolates transport/discovery costs. It does not simulate model reasoning.
const workflows = {
"sharex.dll": {
tools: ["inspect_managed_artifact", "inspect_managed_native_boundaries"],
question:
"Report assembly/module identity, target framework and declared P/Invoke libraries with their uncertainty.",
code: `
const artifact = await call("inspect_managed_artifact", {path: input.path});
const boundaries = await call("inspect_managed_native_boundaries", {});
const n = artifact.normalized_result;
return {artifact_sha256: artifact.subject?.digest.sha256,
classification: n.classification, assembly: n.assembly, module: n.module,
target_frameworks: n.target_frameworks, coverage: n.coverage,
native_boundaries: boundaries.normalized_result,
evidence_ids: [artifact.evidence_id, boundaries.evidence_id],
limitations: [...artifact.limitations, ...boundaries.limitations]};`,
},
"sqlite.wasm": {
tools: ["inspect_wasm_artifact"],
question:
"Report exact WASM validation, all exported names and runtime/network observation states without executing it.",
code: `
const wasm = await call("inspect_wasm_artifact", {path: input.path});
const n = wasm.normalized_result;
return {artifact_sha256: wasm.subject?.digest.sha256,
validation: n.validation, sections: n.sections, exports: n.exports,
runtime_execution: n.runtime_execution, network_fetch: n.network_fetch,
evidence_ids: [wasm.evidence_id], limitations: [...wasm.limitations, ...n.limitations]};`,
},
"obsidian.asar": {
tools: ["analyze_javascript_application", "inspect_analysis_view"],
question:
"Report application identity, analysis coverage/counts and the first eight module identities.",
code: `
const summary = await call("analyze_javascript_application", {
input_path: input.path, detail: "summary", max_heap_mb: 1024,
analysis_timeout_ms: 300000, integrity_policy: "fail"
});
const parent = summary.normalized_result.parent_evidence_id;
const page = await call("inspect_analysis_view", {
source: {kind: "retained-evidence", evidence_id: parent},
view: {kind: "page", collection: "modules", offset: 0, limit: 8}
});
return {artifact_sha256: summary.subject?.digest.sha256,
summary: summary.normalized_result, modules: page.normalized_result,
evidence_ids: [summary.evidence_id, parent, page.evidence_id],
limitations: [...summary.limitations, ...page.limitations]};`,
},
"obsidian-inventory": {
artifact: "obsidian.asar",
tools: ["open_binary", "inspect_artifact", "close_binary"],
question:
"Report ASAR identity, complete inventory manifest, entry-kind counts, root package/main/preload occurrences, integrity contradictions and inventory coverage; do not claim AST or runtime analysis.",
code: `
await call("open_binary", {path: input.path});
try {
const inspected = await call("inspect_artifact", {integrity_policy: "fail"});
const n = inspected.normalized_result;
const inventory = n.substeps.find(x => x.operation === "inventory_artifact").evidence;
const graph = inventory.normalized_result;
const counts = {};
for (const row of graph.occurrences)
counts[row.entry_kind] = (counts[row.entry_kind] ?? 0) + 1;
return {artifact_sha256: inspected.subject.digest.sha256,
manifest: graph.manifest, entry_kind_counts: counts,
requested_paths: graph.occurrences.filter(x => ["package.json", "main.js", "preload.js"].includes(x.logical_path)),
integrity_contradictions: graph.integrity_contradictions,
coverage: n.coverage, unexplored_branches: n.unexplored_branches,
evidence_ids: [inspected.evidence_id, inventory.evidence_id],
limitations: [...inspected.limitations, ...n.limitations, ...inventory.limitations]};
} finally { await call("close_binary", {}); }`,
},
"newpipe.apk": {
tools: [
"inspect_android_package",
"search_android_classes",
"inspect_android_class",
],
question:
"Report package/version, exact MainActivity matches and its onCreate method descriptors.",
code: `
const pkg = await call("inspect_android_package", {path: input.path});
const found = await call("search_android_classes", {path: input.path, query: "MainActivity"});
if (!found.normalized_result.matches.includes("org.schabi.newpipe.MainActivity"))
throw new Error("Requested shipped MainActivity was not found");
const cls = await call("inspect_android_class", {
path: input.path, class_name: "org.schabi.newpipe.MainActivity"
});
return {artifact_sha256: pkg.subject?.digest.sha256,
package_name: pkg.normalized_result.package_name,
version_name: pkg.normalized_result.version_name,
version_code: pkg.normalized_result.version_code,
matches: found.normalized_result.matches, coverage: found.normalized_result.coverage,
onCreate: cls.normalized_result.methods.filter(x => x.name === "onCreate"),
evidence_ids: [pkg.evidence_id, found.evidence_id, cls.evidence_id],
limitations: [...pkg.limitations, ...found.limitations, ...cls.limitations]};`,
},
"openwrt.bin": {
tools: ["inspect_firmware_regions"],
question:
"Report firmware identity, every reported region offset/signature and examined coverage.",
code: `
const inspected = await call("inspect_firmware_regions", {path: input.path});
return {artifact_sha256: inspected.subject?.digest.sha256,
result: inspected.normalized_result, evidence_ids: [inspected.evidence_id],
limitations: inspected.limitations};`,
},
jq: {
tools: [
"open_binary",
"resolve_containing_procedure",
"analyze_function",
"close_binary",
],
question:
"Resolve the observed ELF entry address and report the recovered function's first 512 pseudocode units and caller/callee counts.",
code: `
await call("open_binary", {path: input.path, provider_id: "ghidra"});
try {
const found = await call("resolve_containing_procedure", {address: input.entry_address});
const fn = await call("analyze_function", {procedure: input.entry_address});
const n = fn.normalized_result;
return {artifact_sha256: fn.subject?.digest.sha256,
entry_address: input.entry_address, containing_procedure: found.normalized_result, procedure: n.procedure,
pseudocode_prefix: n.pseudocode.slice(0, 512),
caller_count: n.callers.length, callee_count: n.callees.length,
evidence_ids: [found.evidence_id, fn.evidence_id],
limitations: [...found.limitations, ...fn.limitations, ...n.limitations]};
} finally { await call("close_binary", {}); }`,
},
};
const { values } = parseArgs({
options: {
artifact: { type: "string" },
workflow: { type: "string" },
mode: { type: "string", default: "direct" },
"artifact-root": { type: "string" },
providers: { type: "string" },
output: { type: "string" },
},
});
if (
!workflows[values.workflow ?? values.artifact] ||
!["direct", "discovery", "bridge"].includes(values.mode) ||
!values["artifact-root"] ||
!values.output
)
throw new Error(
"Use --artifact ID [--workflow NAME] --mode direct|discovery|bridge --artifact-root ABS --output NEW_DIR [--providers JSON]",
);
const experiment = dirname(fileURLToPath(import.meta.url));
const repository = resolve(experiment, "../..");
const corpus = JSON.parse(
await readFile(resolve(experiment, "corpus.json"), "utf8"),
);
const artifact = corpus.artifacts.find((x) => x.id === values.artifact);
const workflowName = values.workflow ?? values.artifact;
const workflow = workflows[workflowName];
if (!artifact || artifact.id !== (workflow.artifact ?? workflowName))
throw new Error(
"Selected workflow does not match the frozen corpus artifact",
);
const artifactPath = resolve(values["artifact-root"], artifact.file);
const bytes = await readFile(artifactPath);
const sha256 = createHash("sha256").update(bytes).digest("hex");
if (sha256 !== artifact.sha256 || bytes.length !== artifact.bytes)
throw new Error("Artifact identity differs from frozen corpus");
await mkdir(values.output, { recursive: false, mode: 0o700 });
const encoding = getEncoding("cl100k_base");
const measure = (value) => {
const text = typeof value === "string" ? value : JSON.stringify(value);
return {
bytes: Buffer.byteLength(text),
cl100k_estimated_tokens: encoding.encode(text).length,
};
};
const modelText = (reply) =>
reply.content
.filter((item) => item.type === "text")
.map((item) => item.text)
.join("\n");
const environment = values.providers
? JSON.parse(await readFile(values.providers, "utf8"))
: {};
const started = performance.now();
const upstream = await connectUpstream({
command: process.execPath,
args: [resolve(repository, "scripts/rea.mjs"), "mcp"],
env: environment,
});
const connected = performance.now();
const records = [];
const realCall = upstream.call;
upstream.call = async (name, args, options) => {
const began = performance.now();
try {
const reply = await realCall(name, args, { timeout: 600_000, ...options });
const parsed = json(reply);
records.push({
name,
arguments: args,
elapsed_ms: performance.now() - began,
is_error: reply.isError === true,
input: measure(args),
output: measure(reply),
text_output: measure(modelText(reply)),
evidence_id: parsed.evidence_id,
reply,
});
return reply;
} catch (error) {
records.push({
name,
arguments: args,
elapsed_ms: performance.now() - began,
failure: String(error),
input: measure(args),
});
throw error;
}
};
const bridge = new CodeModeBridge(upstream);
const input = { path: artifactPath };
if (artifact.id === "jq") {
if (bytes.subarray(0, 6).toString("hex") !== "7f454c460201")
throw new Error("Expected ELF64 little-endian header for entry seed");
input.entry_address = `0x${bytes.readBigUInt64LE(24).toString(16)}`;
}
const code = `const input = ${JSON.stringify(input)};
async function call(name, args) {
const reply = await tools[name](args);
const parsed = json(reply);
if (reply.isError) throw new Error(JSON.stringify(parsed));
return parsed;
}
${workflow.code}`;
let discovery;
let value;
let failure;
let bridgeReply;
const recovery = [];
const operationStarted = performance.now();
try {
if (values.mode !== "direct")
discovery = await bridge.search({ names: workflow.tools });
if (values.mode === "bridge") {
bridgeReply = await bridge.execute({ code, timeout_ms: 900_000 });
value = bridgeReply.value;
failure = bridgeReply.bridge_error;
} else {
const tools = Object.fromEntries(
upstream.catalog.map((tool) => [
tool.name,
(args) => upstream.call(tool.name, args),
]),
);
const AsyncFunction = Object.getPrototypeOf(
async function () {},
).constructor;
value = await new AsyncFunction("tools", "json", code)(tools, json);
}
} catch (error) {
failure = String(error);
} finally {
const partials = new Set(
records.flatMap((record) => {
if (!record.reply?.isError) return [];
try {
const partial = json(record.reply).error?.details?.partial_observation;
return partial?.kind === "retained-evidence"
? [partial.evidence_id]
: [];
} catch {
return []; // Preserve non-JSON failures in calls.json, without inventing IDs.
}
}),
);
for (const evidence_id of partials) {
// Capture already retained partial facts before disposing the replay session.
// Recovery is not a successful completion of the failed analyst workflow.
try {
const reply = await realCall(
"inspect_analysis_view",
{
source: { kind: "retained-evidence", evidence_id },
view: { kind: "summary" },
},
{ timeout: 60_000 },
);
recovery.push({ parent_evidence_id: evidence_id, reply });
} catch (error) {
recovery.push({
parent_evidence_id: evidence_id,
failure: String(error),
});
}
}
await upstream.close();
}
const catalogExposure = values.mode === "direct" ? upstream.catalog : discovery;
const summary = {
scope: "deterministic_real_provider_replay_not_model_accuracy",
baseline_sha: "fb94d9dde0ccda83ae520ac6fb22b1d283562cf3",
artifact: artifact.id,
workflow: workflowName,
artifact_sha256: sha256,
mode: values.mode,
question: workflow.question,
startup_ms: connected - started,
operation_ms: performance.now() - operationStarted,
original_calls: records.map(({ reply: _reply, ...metadata }) => metadata),
exposed_catalog: catalogExposure ? measure(catalogExposure) : null,
direct_input_definitions: measure(
upstream.catalog.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
),
execution_code: values.mode === "bridge" ? measure(code) : null,
exposed_tool_results:
values.mode === "bridge"
? [measure(bridgeReply)]
: records.filter((x) => x.output).map((x) => x.output),
exposed_text_results:
values.mode === "bridge"
? [measure(bridgeReply)]
: records.filter((x) => x.text_output).map((x) => x.text_output),
projection: value,
failure: failure ?? null,
partial_recovery: recovery,
correctness: failure
? "failed_workflow"
: "requires_cross_profile_fact_review",
estimator:
"cl100k_base_compact_MCP_JSON_including_envelope_not_actual_model_tokens",
};
await writeFile(
resolve(values.output, "summary.json"),
JSON.stringify(summary, null, 2),
);
await writeFile(resolve(values.output, "calls.json"), JSON.stringify(records));
await writeFile(resolve(values.output, "code.js"), code);
console.log(
JSON.stringify({
artifact: artifact.id,
mode: values.mode,
calls: records.length,
startup_ms: summary.startup_ms,
operation_ms: summary.operation_ms,
failure: summary.failure
? String(
typeof summary.failure === "object"
? summary.failure.message
: summary.failure,
).slice(0, 1000)
: null,
}),
);
if (failure) process.exitCode = 1;
+426
View File
@@ -0,0 +1,426 @@
import { createHash } from "node:crypto";
import { pathToFileURL } from "node:url";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { compile } from "json-schema-to-typescript";
import { newQuickJSWASMModule, Scope } from "quickjs-emscripten";
import { z } from "zod";
// Node maps larger delays to 1 ms. Reject them before creating a timer.
const executeInputSchema = z.strictObject({
code: z.string(),
timeout_ms: z.number().int().positive().max(2_147_483_647).optional(),
memory_bytes: z
.number()
.int()
.positive()
.max(128 * 1024 * 1024)
.optional(),
});
const digest = (value) =>
createHash("sha256").update(JSON.stringify(value)).digest("hex");
export const json = (reply) => {
if (reply.structuredContent !== undefined) return reply.structuredContent;
const texts = reply.content.filter((item) => item.type === "text");
if (texts.length !== 1) throw new Error("Expected one REA JSON text block");
return JSON.parse(texts[0].text);
};
/** One upstream session, original tool names, complete live catalog. */
export async function connectUpstream({
command,
args,
env,
stderr = "inherit",
}) {
const transport = new StdioClientTransport({ command, args, env, stderr });
const client = new Client({
name: "rea-codemode-experiment",
version: "0.0.0",
});
try {
await client.connect(transport, { timeout: 60_000 });
const catalog = [];
let cursor;
do {
const page = await client.listTools(cursor ? { cursor } : undefined);
catalog.push(...page.tools);
cursor = page.nextCursor;
} while (cursor !== undefined);
if (new Set(catalog.map((tool) => tool.name)).size !== catalog.length)
throw new Error("Duplicate upstream tool names");
return {
client,
catalog,
call: (name, args, options) =>
client.callTool({ name, arguments: args }, options),
close: () => client.close(),
};
} catch (error) {
await client.close();
throw error;
}
}
export class CodeModeBridge {
constructor(upstream) {
this.upstream = upstream;
this.byName = new Map(upstream.catalog.map((tool) => [tool.name, tool]));
this.catalogDigest = digest(upstream.catalog);
this.declarations = new Map();
this.queue = Promise.resolve();
}
/** The live catalog is immutable for this connection; derive each type once. */
declarationsFor(tool) {
if (!this.declarations.has(tool.name)) {
const options = { bannerComment: "", format: false, unknownAny: true };
const pending = Promise.all([
compile(tool.inputSchema, `${tool.name}_Input`, options),
tool.outputSchema
? compile(
{ ...tool.outputSchema, title: `${tool.name}_Output` },
`${tool.name}_Output`,
options,
)
: undefined,
]);
this.declarations.set(tool.name, pending);
}
return this.declarations.get(tool.name);
}
/** Search exposes original schemas and generated input types on demand. */
async search({ query = "", names = [], schemas, output_schemas = false }) {
const includeSchemas = schemas ?? (names.length > 0 || output_schemas);
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
const selected = this.upstream.catalog.filter((tool) =>
names.length
? names.includes(tool.name)
: terms.every((term) =>
`${tool.name} ${tool.description ?? ""}`
.toLowerCase()
.includes(term),
),
);
// Failed all-word searches keep their original semantics. Offer only the
// best positive lexical matches as names, without expanding their schemas.
const ranked =
selected.length || !terms.length || names.length
? []
: this.upstream.catalog.map((tool) => ({
tool,
score: terms.filter((term) =>
`${tool.name} ${tool.description ?? ""}`
.toLowerCase()
.includes(term),
).length,
}));
const bestScore = Math.max(0, ...ranked.map((row) => row.score));
const suggestions = ranked
.filter((row) => row.score === bestScore && bestScore > 0)
.map(({ tool }) => ({ name: tool.name, description: tool.description }));
const tools = [];
for (const tool of selected) {
const [inputType, outputType] = includeSchemas
? await this.declarationsFor(tool)
: [];
tools.push(
includeSchemas
? {
...tool,
...(!output_schemas ? { outputSchema: undefined } : {}),
typescript: inputType,
output_typescript: outputType,
}
: {
name: tool.name,
description: tool.description,
annotations: tool.annotations,
},
);
}
return {
catalog_sha256: this.catalogDigest,
total_tools: this.byName.size,
matched_tools: tools.length,
tools,
...(suggestions.length ? { suggestions } : {}),
};
}
/** Serialize scripts sharing the active target; no implicit batch races. */
execute(input, options = {}) {
const parsed = executeInputSchema.parse(input);
const work = this.queue.then(() => this.run(parsed, options));
this.queue = work.catch(() => undefined);
return work;
}
async run(
{ code, timeout_ms = 120_000, memory_bytes = 128 * 1024 * 1024 },
options,
) {
const controller = new AbortController();
const signal = options.signal
? AbortSignal.any([controller.signal, options.signal])
: controller.signal;
signal.throwIfAborted();
const deadline = Date.now() + timeout_ms;
const timer = setTimeout(
() => controller.abort(new Error("Code Mode deadline exceeded")),
timeout_ms,
);
const QuickJS = await newQuickJSWASMModule();
const runtime = QuickJS.newRuntime();
// Pending-job execution can create another context in the runtime.
// Own contexts at creation, including those allocated by the job pump.
const contexts = new Scope();
const newContext = runtime.newContext.bind(runtime);
runtime.newContext = (options) => contexts.manage(newContext(options));
runtime.setMemoryLimit(memory_bytes);
runtime.setMaxStackSize(1024 * 1024);
runtime.setInterruptHandler(() => signal.aborted || Date.now() > deadline);
const vm = runtime.newContext();
const pending = new Set();
const calls = [];
let toolQueue = Promise.resolve();
let finished = false;
let promise;
let value;
let failure;
const checkedEval = (source) => {
const result = vm.evalCode(source);
if (result.error) {
const error = vm.dump(result.error);
result.error.dispose();
throw new Error(JSON.stringify(error));
}
return result.value;
};
const pump = () => {
const result = runtime.executePendingJobs();
if (result.error) {
failure = vm.dump(result.error);
result.error.dispose();
finished = true;
}
};
const fromJson = (data) =>
checkedEval(`JSON.parse(${JSON.stringify(JSON.stringify(data))})`);
const callHandle = vm.newFunction("call", (nameHandle, argumentsHandle) => {
const name = vm.getString(nameHandle);
const args = JSON.parse(vm.getString(argumentsHandle));
if (!this.byName.has(name))
throw new Error(`Unknown upstream tool: ${name}`);
signal.throwIfAborted();
const deferred = vm.newPromise();
const audit = { name, arguments_sha256: digest(args) };
calls.push(audit);
const work = toolQueue.then(async () => {
const started = performance.now();
try {
signal.throwIfAborted();
const reply = await this.upstream.call(name, args, {
signal,
timeout: Math.max(1, deadline - Date.now()),
onprogress: options.onprogress,
});
audit.elapsed_ms = performance.now() - started;
audit.result_sha256 = digest(reply);
audit.is_error = reply.isError === true;
try {
const record = json(reply);
if (record.evidence_id) audit.evidence_id = record.evidence_id;
if (Array.isArray(record.evidence_links))
audit.evidence_links = record.evidence_links;
const partial = record.error?.details?.partial_observation;
if (partial?.kind === "retained-evidence")
audit.partial_evidence_id = partial.evidence_id;
} catch {
/* Non-JSON MCP content is still preserved in reply. */
}
const handle = fromJson(reply);
deferred.resolve(handle);
handle.dispose();
} catch (error) {
audit.transport_error = error.message;
const handle = fromJson({ name: error.name, message: error.message });
deferred.reject(handle);
handle.dispose();
} finally {
deferred.dispose();
pump();
}
});
const settled = work.catch((error) => {
failure = { message: error.message };
finished = true;
});
toolQueue = settled;
pending.add(settled);
void settled.then(() => pending.delete(settled));
// The callback transfers its returned handle to QuickJS. Keep the
// deferred's original host handle until settlement, with a separate
// guest handle; otherwise bulk promise cleanup shares one lifetime.
return deferred.handle.dup();
});
const doneHandle = vm.newFunction("done", (data) => {
value = JSON.parse(vm.getString(data));
finished = true;
});
const failHandle = vm.newFunction("fail", (error) => {
failure = vm.dump(error);
finished = true;
});
try {
vm.setProp(vm.global, "__call", callHandle);
vm.setProp(vm.global, "__done", doneHandle);
vm.setProp(vm.global, "__fail", failHandle);
checkedEval(`
const tools = Object.freeze(Object.fromEntries(${JSON.stringify([...this.byName.keys()])}.map(
name => [name, args => __call(name, JSON.stringify(args))]
)));
const json = reply => {
if (reply.structuredContent !== undefined) return reply.structuredContent;
const texts = reply.content.filter(item => item.type === 'text');
if (texts.length !== 1) throw new Error('Expected one REA JSON text block');
return JSON.parse(texts[0].text);
};
`).dispose();
promise = checkedEval(`(async () => {\n${code}\n})().then(
value => __done(JSON.stringify(value === undefined ? null : value)),
error => __fail({name: error?.name, message: error?.message ?? String(error)})
)`);
pump();
while (!finished && !signal.aborted) {
if (pending.size) await Promise.race(pending);
else
throw new Error(
"Guest promise cannot progress: no pending tool calls",
);
pump();
}
signal.throwIfAborted();
if (calls.some((call) => !call.result_sha256 && !call.transport_error)) {
controller.abort(
new Error("Code returned before awaiting all tool calls"),
);
await Promise.allSettled(pending);
failure = {
message:
"Code returned before awaiting all tool calls; calls were cancelled",
};
} else await Promise.allSettled(pending);
} catch (error) {
failure = { name: error.name, message: error.message };
controller.abort(error);
await Promise.allSettled(pending);
} finally {
clearTimeout(timer);
promise?.dispose();
callHandle.dispose();
doneHandle.dispose();
failHandle.dispose();
contexts.dispose();
runtime.dispose();
}
return {
catalog_sha256: this.catalogDigest,
calls,
...(failure ? { bridge_error: failure } : { value }),
};
}
}
export function createBridgeServer(bridge) {
const server = new McpServer({
name: "rea-codemode-experiment",
version: "0.0.0",
});
const reply = (result) => ({
content: [{ type: "text", text: JSON.stringify(result) }],
...(result.bridge_error ? { isError: true } : {}),
});
server.registerTool(
"rea_search",
{
description:
"Discover original upstream REA tools. Start with one domain word, e.g. query=android; query words must all occur. Query/empty discovery defaults to names and descriptions. Then supply all selected exact names together to obtain exact input schemas plus derived input/output TypeScript for code authoring. schemas=true/false overrides this choice. Request output_schemas=true only for original JSON validation contracts. rea_search and rea_execute are already declared bridge tools, not upstream catalog entries. catalog_sha256 hashes the SDK catalog, not REA server_identity.catalog.digests. Code calls tools.<name>(original_arguments); json(reply) parses the original MCP value.",
inputSchema: z.strictObject({
query: z.string().default(""),
names: z.array(z.string()).default([]),
schemas: z.boolean().optional(),
output_schemas: z.boolean().default(false),
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async (input) => reply(await bridge.search(input)),
);
server.registerTool(
"rea_execute",
{
description:
"Run a JavaScript async function body with tools.<original_REA_name>(original_arguments), json(reply) and return. Await every call. json(reply) returns the original REA value: Evidence has normalized_result, evidence_id and limitations; session values have result. Select the needed normalized_result fields inside code before returning them with Evidence IDs and limitations. One upstream session persists. Calls run in order, including Promise.all. All original REA effects, validation and retention apply; projections are caller selections, not new Evidence. No host IO or imports. Call audit and Evidence IDs survive script errors. Default deadline 120s and guest memory 128 MiB; use longer deadline for native analysis.",
inputSchema: executeInputSchema,
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: true,
},
},
async (input, context) =>
reply(
await bridge.execute(input, {
signal: context.mcpReq.signal,
onprogress:
context.mcpReq._meta?.progressToken === undefined
? undefined
: (progress) =>
context.mcpReq.notify({
method: "notifications/progress",
params: {
...progress,
progressToken: context.mcpReq._meta.progressToken,
},
}),
}),
),
);
return server;
}
async function main() {
const [command, ...args] = process.argv.slice(2);
if (!command) throw new Error("Usage: node bridge.mjs COMMAND [ARG...]");
const upstream = await connectUpstream({ command, args, env: process.env });
const server = createBridgeServer(new CodeModeBridge(upstream));
let closing = false;
const close = async () => {
if (closing) return;
closing = true;
await server.close();
await upstream.close();
};
process.once("SIGINT", () => void close());
process.once("SIGTERM", () => void close());
const transport = new StdioServerTransport();
transport.onclose = () => void close();
await server.connect(transport);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
+313
View File
@@ -0,0 +1,313 @@
import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { fileURLToPath } from "node:url";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import Ajv2020 from "ajv/dist/2020.js";
import { CodeModeBridge, connectUpstream, json } from "./bridge.mjs";
const entrypoint = fileURLToPath(
new URL("../../scripts/rea.mjs", import.meta.url),
);
const fixture = fileURLToPath(
new URL("../../tests/conformance/readiness/javascript-cli", import.meta.url),
);
const analysisArgs = {
input_path: fixture,
detail: "summary",
max_heap_mb: 128,
};
let upstream;
let bridge;
before(async () => {
upstream = await connectUpstream({
command: process.execPath,
args: [entrypoint, "mcp"],
env: { ...process.env, REA_LOG_LEVEL: "silent" },
});
bridge = new CodeModeBridge(upstream);
});
after(async () => {
await upstream?.close();
});
test("discovery preserves exact current REA schemas and names", async () => {
const result = await bridge.search({
names: ["binary_session"],
output_schemas: true,
});
assert.equal(result.total_tools, upstream.catalog.length);
const original = upstream.catalog.find(
(tool) => tool.name === "binary_session",
);
const { typescript, output_typescript, ...discovered } = result.tools[0];
assert.deepEqual(discovered, original);
assert.match(typescript, /export interface BinarySession_Input/);
assert.match(output_typescript, /export interface BinarySession_Output/);
const inputOnly = await bridge.search({ names: ["binary_session"] });
assert.deepEqual(inputOnly.tools[0].inputSchema, original.inputSchema);
assert.equal(inputOnly.tools[0].outputSchema, undefined);
assert.equal(inputOnly.tools[0].output_typescript, output_typescript);
const all = await bridge.search({ schemas: false });
assert.deepEqual(
all.tools.map((tool) => tool.name),
upstream.catalog.map((tool) => tool.name),
);
const query = await bridge.search({ query: "android" });
assert.ok(query.tools.length > 0);
assert.ok(query.tools.every((tool) => tool.inputSchema === undefined));
const querySchemas = await bridge.search({ query: "android", schemas: true });
assert.ok(querySchemas.tools.every((tool) => tool.inputSchema !== undefined));
const outputRequested = await bridge.search({
query: "android",
output_schemas: true,
});
assert.ok(
outputRequested.tools.every((tool) => tool.outputSchema !== undefined),
);
const missed = await bridge.search({
query: "android impossible_query_word",
});
assert.equal(missed.matched_tools, 0);
assert.ok(
missed.suggestions.some((tool) => tool.name === "inspect_android_package"),
);
assert.ok(missed.suggestions.every((tool) => tool.inputSchema === undefined));
});
test("raw original result and parsed result survive a real MCP call", async () => {
const result = await bridge.execute({
code: "const r = await tools.binary_session({}); return {raw:r, parsed:json(r)};",
});
assert.equal(result.bridge_error, undefined);
assert.deepEqual(json(result.value.raw), result.value.parsed);
assert.equal(result.calls[0].name, "binary_session");
assert.match(result.calls[0].result_sha256, /^[a-f0-9]{64}$/);
assert.equal(result.calls[0].is_error, false);
});
test("Promise.all calls settle in order without losing pending work", async () => {
const result = await bridge.execute({
code: "return await Promise.all([tools.binary_session({}), tools.binary_session({}), tools.binary_session({})]);",
});
assert.equal(result.bridge_error, undefined);
assert.equal(result.value.length, 3);
assert.equal(result.calls.length, 3);
assert.ok(result.calls.every((call) => call.result_sha256));
});
test("upstream invalid-input result remains an original MCP error envelope", async () => {
const args = {
input_path: "/nonexistent/rea-codemode-artifact",
detail: "summary",
};
const direct = await upstream.call("analyze_javascript_application", args);
const result = await bridge.execute({
code: `return await tools.analyze_javascript_application(${JSON.stringify(args)});`,
});
assert.equal(result.bridge_error, undefined);
assert.equal(result.value.isError, true);
assert.deepEqual(json(result.value).error, json(direct).error);
assert.equal(result.calls[0].is_error, true);
});
test("guest has no Node, filesystem, fetch or module-loading capabilities", async () => {
const result = await bridge.execute({
code: "return [typeof process,typeof require,typeof fetch,typeof Deno,Function('return typeof process')()];",
});
assert.deepEqual(result.value, Array(5).fill("undefined"));
const imported = await bridge.execute({
code: "return await import('node:fs');",
});
assert.ok(imported.bridge_error);
});
test("CPU deadline interrupts runaway code and the next execution works", async () => {
const result = await bridge.execute({
code: "while(true) {}",
timeout_ms: 20,
});
assert.ok(result.bridge_error);
assert.deepEqual(result.calls, []);
assert.equal((await bridge.execute({ code: "return 42;" })).value, 42);
});
test("guest allocation pressure fails within its heap budget and permits reuse", async () => {
const result = await bridge.execute({
code: "return new Array(1_000_000).fill('x');",
memory_bytes: 2 * 1024 * 1024,
});
assert.match(result.bridge_error.message, /memory/i);
assert.deepEqual(result.calls, []);
assert.equal((await bridge.execute({ code: "return 42;" })).value, 42);
assert.throws(() =>
bridge.execute({ code: "return 1;", timeout_ms: 2_147_483_648 }),
);
});
test("error after successful call preserves original Evidence reference", async () => {
const result = await bridge.execute({
code: `await tools.analyze_javascript_application(${JSON.stringify(analysisArgs)}); throw new Error('after-call');`,
});
assert.match(result.bridge_error.message, /after-call/);
assert.ok(result.calls[0].evidence_id);
assert.ok(result.calls[0].evidence_links.length > 0);
const readback = await upstream.call("get_evidence_bundle", {
detail: "summary",
});
assert.equal(readback.isError, undefined);
assert.ok(
JSON.stringify(json(readback)).includes(result.calls[0].evidence_id),
);
for (const evidence_id of result.calls[0].evidence_links) {
const view = json(
await upstream.call("inspect_analysis_view", {
source: { kind: "retained-evidence", evidence_id },
view: { kind: "summary" },
}),
);
assert.equal(view.normalized_result.parent_evidence_id, evidence_id);
}
});
test("provider timeout retains its actual partial Evidence reference", async () => {
const result = await bridge.execute({
code: `const reply = await tools.analyze_javascript_application(${JSON.stringify({ ...analysisArgs, analysis_timeout_ms: 1 })}); return json(reply);`,
});
const partial = result.value.error.details.partial_observation;
assert.equal(partial.kind, "retained-evidence");
assert.equal(result.calls[0].is_error, true);
assert.equal(result.calls[0].partial_evidence_id, partial.evidence_id);
const view = json(
await upstream.call("inspect_analysis_view", {
source: partial,
view: { kind: "summary" },
}),
);
assert.equal(view.normalized_result.parent_evidence_id, partial.evidence_id);
});
test("cancelling queued calls releases deferred handles and permits reuse", async () => {
let callStarted;
const started = new Promise((resolve) => {
callStarted = resolve;
});
const cancelled = new AbortController();
const controlled = new CodeModeBridge({
catalog: upstream.catalog,
async call(name, args, options) {
const reply = await upstream.call(name, args, options);
callStarted();
// A controlled delivery delay after a real result makes the queued
// cancellation deterministic, without simulating provider behavior.
await new Promise((resolve) => setTimeout(resolve, 50));
return reply;
},
});
const execution = controlled.execute(
{
code: "return await Promise.all([tools.binary_session({}), tools.binary_session({})]);",
},
{ signal: cancelled.signal },
);
await started;
cancelled.abort(new Error("controlled queued cancellation"));
const result = await execution;
assert.ok(result.bridge_error);
assert.equal(result.calls.length, 2);
assert.match(
result.calls[1].transport_error,
/controlled queued cancellation/,
);
assert.equal((await controlled.execute({ code: "return 42;" })).value, 42);
});
test("a guest promise without any producer fails without hanging", async () => {
const result = await bridge.execute({
code: "return await new Promise(() => {});",
});
assert.match(result.bridge_error.message, /cannot progress/);
});
test("bridge's two tools are usable through actual downstream stdio MCP", async () => {
const transport = new StdioClientTransport({
command: process.execPath,
args: [
fileURLToPath(new URL("./bridge.mjs", import.meta.url)),
process.execPath,
entrypoint,
"mcp",
],
env: { ...process.env, REA_LOG_LEVEL: "silent" },
stderr: "inherit",
});
const client = new Client({ name: "codemode-boundary-test", version: "1" });
try {
await client.connect(transport);
const catalog = await client.listTools();
assert.deepEqual(
catalog.tools.map((tool) => tool.name),
["rea_search", "rea_execute"],
);
const ajv = new Ajv2020({ strict: true });
const byName = new Map(catalog.tools.map((tool) => [tool.name, tool]));
for (const tool of catalog.tools) {
assert.equal(
tool.inputSchema.$schema ??
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2020-12/schema",
);
assert.equal(
ajv.validateSchema(tool.inputSchema),
true,
ajv.errorsText(),
);
}
const validate = ajv.compile(byName.get("rea_execute").inputSchema);
assert.equal(
validate({ code: "return 1;", timeout_ms: 2_147_483_647 }),
true,
);
assert.equal(
validate({ code: "return 1;", timeout_ms: 2_147_483_648 }),
false,
);
assert.equal(
validate({ code: "return 1;", memory_bytes: 128 * 1024 * 1024 + 1 }),
false,
);
const found = json(
await client.callTool({
name: "rea_search",
arguments: { names: ["binary_session"] },
}),
);
assert.equal(found.matched_tools, 1);
const result = json(
await client.callTool({
name: "rea_execute",
arguments: {
code: "return json(await tools.binary_session({}));",
},
}),
);
assert.equal(result.value.result.open, false);
const invalid = await client.callTool({
name: "rea_execute",
arguments: { code: 7 },
});
assert.equal(invalid.isError, true);
const oversized = await client.callTool({
name: "rea_execute",
arguments: { code: "return 1;", timeout_ms: 2_147_483_648 },
});
assert.equal(oversized.isError, true);
const oversizedMemory = await client.callTool({
name: "rea_execute",
arguments: { code: "return 1;", memory_bytes: 128 * 1024 * 1024 + 1 },
});
assert.equal(oversizedMemory.isError, true);
} finally {
await client.close();
}
});
+90
View File
@@ -0,0 +1,90 @@
{
"schema_version": 1,
"artifacts": [
{
"id": "sharex.dll",
"file": "ShareX.dll",
"bytes": 2162688,
"sha256": "191a191be964e434daf0f9d76d1e0d8ddce90d7e248cba2a6b5ac9577f288d53",
"provenance": {
"project": "ShareX",
"version": "19.0.2",
"upstreamFile": "ShareX-19.0.2-portable.zip",
"url": "https://github.com/ShareX/ShareX/releases/download/v19.0.2/ShareX-19.0.2-portable.zip",
"transport_sha256": "967b5d838df5ce25fb5fd7cbd16f1bd797427e2df16907b022b74a4bc0fb9d54",
"transport_bytes": 168796487,
"derivation": "selected regular ZIP member ShareX.dll"
}
},
{
"id": "sqlite.wasm",
"file": "sqlite3.wasm",
"bytes": 868907,
"sha256": "2ee8f3dab694532afc8840e07703127287662d08b74e6ff50491ce63f00d5752",
"provenance": {
"project": "SQLite Wasm",
"version": "3.53.4-build1",
"upstreamFile": "sqlite-wasm-3.53.4-build1.tgz",
"url": "https://registry.npmjs.org/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.53.4-build1.tgz",
"transport_sha256": "24f46db4ce5248fa8f8f24b67e81909ab1a9c152f42a4796a32c6d385a6fb62b",
"transport_bytes": 977616,
"transport_integrity": "sha512-U8pvL0patYHMgieD8JxxYJTlh9m1LUX8+BDYb7MiKQzBI89f2qbdmdcG/PvnPaLm9wsBbv4VlVG453ixnxBOnA==",
"derivation": "selected regular tar member package/dist/sqlite3.wasm"
}
},
{
"id": "obsidian.asar",
"file": "obsidian-1.14.4.asar",
"bytes": 27477966,
"sha256": "146ef8470d8cbb4a6db209bdc19bf2bad13350444186c0fd93c31087b142b92f",
"provenance": {
"project": "Obsidian",
"version": "1.14.4",
"upstreamFile": "obsidian-1.14.4.asar",
"transport": {
"file": "obsidian-1.14.4.asar.gz",
"url": "https://github.com/obsidianmd/obsidian-releases/releases/download/v1.14.4/obsidian-1.14.4.asar.gz",
"sha256": "d1ed428c363968774f0f3906e67d8a53a058ece0b6cdd924b07865977b2ada21",
"bytes": 9200670
},
"derivation": "gzip decompression"
}
},
{
"id": "newpipe.apk",
"file": "NewPipe_v0.29.1.apk",
"bytes": 11507122,
"sha256": "18447bfb1e06d113edc88df93f471827280de06f6f3d4dc42f56f28b9c1bab79",
"provenance": {
"project": "NewPipe",
"version": "0.29.1",
"upstreamFile": "NewPipe_v0.29.1.apk",
"url": "https://github.com/TeamNewPipe/NewPipe/releases/download/v0.29.1/NewPipe_v0.29.1.apk"
}
},
{
"id": "jq",
"file": "jq-linux-amd64",
"bytes": 2267912,
"sha256": "b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f",
"provenance": {
"project": "jq",
"version": "1.8.2",
"upstreamFile": "jq-linux-amd64",
"url": "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
}
},
{
"id": "openwrt.bin",
"file": "openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin",
"bytes": 7405862,
"sha256": "f15b16b36df7fce8a5cf1302320fdb428c3bb6fbc2949a4858afa257f4f364a2",
"provenance": {
"project": "OpenWrt Archer C7 v5",
"version": "25.12.5",
"upstreamFile": "openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin",
"url": "https://downloads.openwrt.org/releases/25.12.5/targets/ath79/generic/openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin"
}
}
]
}
+87
View File
@@ -0,0 +1,87 @@
# Decision from the frozen Code Mode experiment
Keep REA's existing CLI/MCP contracts as the default. Improve Evidence-aware
workflow guidance and retain this two-tool bridge as an optional experiment.
The measured Codex profiles do not justify a broad API refactor or a default
switch to the external executor.
## What supports that choice
The [serial cohort](agent-results.json) contains sixteen real Codex CLI trials,
configured with `gpt-5.6-terra` high, across Android, managed PE/CLI, Electron ASAR
inventory, firmware and native Ghidra analysis. Twelve pass the strict requested
fact/reference checks. Two bridge managed answers omit the classification's
container field; two normal-MCP managed transcripts truncate a large producer
envelope, preventing complete-envelope verification of one cited ID. Their
remaining requested facts match the real-provider oracle. The complete-container
assertion is stronger than the question's general classification wording.
The bridge's Android repetitions take 48.231 and 134.555 seconds, compared with
67.329 and 71.624 for normal MCP. The faster bridge trial composes three original
operations once; the slower one performs seven original calls across five scripts.
The faster trial uses typed discovery; this does not make the model reliably
choose that workflow. Uncached input is lower in both bridge repetitions;
output and latency vary. This is not a stable overall efficiency win.
Managed bridge latency also varies, and its two answers omit the same field.
Firmware's one-operation question takes 81.944 seconds with the bridge versus
30.161 directly. Ghidra takes 334.642 versus 327.150 seconds, with sampled aggregate
CPU times of 271.096 and 263.738 seconds. The bridge performs three original
operations versus four directly, deriving containing identity from the analyzed
procedure. Native analysis cost remains dominant with unchanged default settings.
Obsidian inventory exposes a useful contract boundary: nested Evidence types leave
their normalized payload generic. The model initially returns the whole inventory
and repeats analysis. Giving both profiles the concrete manifest/occurrences
layout yields a single focused bridge workflow: 101994 total input tokens versus
263484 directly, but 40554 versus 41532 uncached tokens and 85.909 versus 67.540
seconds. Clear producer/layout guidance matters; total context compression alone
does not establish lower cost or latency. Cache pricing is not estimated here.
Deterministic replays demonstrate large projection savings across real providers.
They measure a known script, not the model's ability to discover and write it.
SQLite WASM fails the frozen WABT parser on an unpadded DataCount row; Obsidian's
full AST analysis times out with a retained partial reference. Inventory does not
replace AST analysis. Neither failure is counted as a Code Mode success.
## Migration scope
Current users retain original operation names, arguments, provider choices,
Evidence and session behavior. A client with equivalent execution support can use
the existing registration and focused workflows. The installed Codex transcript
does not expose its executor source, so its exact internal composition is unknown.
For clients that need the bridge, use a separate opt-in profile for an investigation.
The bridge owns one original REA connection and looks up live contracts by name.
Its session and Evidence ledger are separate from another REA registration; do
not mix target lifecycle calls or same-session IDs across those registrations.
Derived TypeScript assists code authoring; original JSON schemas remain available.
Original producer IDs, links, errors and partial-reference IDs survive auditing;
caller projections do not become new Evidence records.
The outer execution tool combines original effects and client requirements.
This prototype has not forwarded roots/elicitation/sampling or preserved a client's
per-original-operation approval flow. That capability boundary must be addressed
before offering it as a production replacement. The guest's lack of host IO does
not remove effects reachable through original REA tools.
An additive producer-description improvement explaining nested payload layout is
a smaller next candidate than changing names or inventing another workflow API.
Original inputs and output defaults should remain intact. Provider analysis
settings must be evaluated separately from discovery and projection improvements.
## Verification limits
These are descriptive samples with matching questions/provider settings per pair,
mode-specific instructions and model-selected arguments. They do not provide
confidence intervals, independent decompiler truth, Android/.NET runtime behavior,
full native-function coverage, complete firmware reconstruction or Windows/IDA
provider verification. Citations are checked against observed producer values and
links, not resolved individually after session exit.
Sixteen trials finish without sampled memory/task-limit events or resource aborts.
Their maximum sampled memory is 1659744256 bytes (about 1.55 GiB), with at most
126 tasks, under one CPU and a 4 GiB hard ceiling/3 GiB early stop. Copied profiles
are removed. Twelve actual stdio tests additionally cover cancellation, partial
Evidence readback, guest allocation failure/recovery, and advertised schema
validation. The earlier 2 GiB build-scope kill remains a documented failed build.
+249
View File
@@ -0,0 +1,249 @@
"""Check requested JSON facts and real Evidence references against a replay oracle."""
import argparse
import hashlib
import json
from pathlib import Path
def required_field(row, key):
return row[key] if key in row else {'missing_field': key}
def same_fact(actual, expected):
if isinstance(actual, bool) or isinstance(expected, bool):
return type(actual) is type(expected) and actual == expected
if isinstance(expected, dict):
return isinstance(actual, dict) and actual.keys() == expected.keys() and all(
same_fact(actual[key], value) for key, value in expected.items())
if isinstance(expected, list):
return isinstance(actual, list) and len(actual) == len(expected) and all(
same_fact(a, b) for a, b in zip(actual, expected))
return actual == expected
def evidence_ids(value):
if isinstance(value, str):
return {value} if value.startswith('ev_') and len(value) == 67 else set()
if isinstance(value, dict):
return set().union(*(evidence_ids(v) for v in value.values()))
if isinstance(value, list):
return set().union(*(evidence_ids(v) for v in value))
return set()
def read_answer(path):
source = path.read_text().strip()
lines = source.splitlines()
# Accept a single complete JSON fence, never repair prose or partial JSON.
if len(lines) >= 3 and lines[0] == '```json' and lines[-1] == '```':
source = '\n'.join(lines[1:-1])
answer_format = 'markdown_json'
else:
answer_format = 'json'
answer = json.loads(source)
if not isinstance(answer, dict):
raise ValueError('Answer is not a JSON object')
return answer, answer_format
def requested_facts(value, task):
if task == 'native':
facts = {field: value.get(field) for field in [
'artifact_sha256', 'entry_address', 'containing_procedure',
'procedure', 'pseudocode_prefix', 'caller_count', 'callee_count']}
containing = facts['containing_procedure']
if isinstance(containing, dict) and containing.get('found', True) is True and \
containing.get('query_address', facts['entry_address']) == facts['entry_address']:
identity = containing.get('procedure', containing)
if isinstance(identity, dict):
facts['containing_procedure'] = {key: identity.get(key) for key in ['address', 'name']}
return facts
if task == 'electron-inventory':
facts = {field: value.get(field) for field in [
'artifact_sha256', 'manifest', 'entry_kind_counts', 'requested_paths',
'integrity_contradictions', 'coverage', 'unexplored_branches']}
paths = facts['requested_paths']
if isinstance(paths, dict) and set(paths) == {'package.json', 'main.js', 'preload.js'}:
if all(isinstance(rows, list) and all(isinstance(row, dict) and
row.get('logical_path') == name for row in rows)
for name, rows in paths.items()):
paths = [row for rows in paths.values() for row in rows]
if isinstance(paths, list) and all(isinstance(row, dict) and
isinstance(row.get('logical_path'), str) for row in paths):
paths = sorted(paths, key=lambda row: row['logical_path'])
facts['requested_paths'] = paths
contradictions = facts['integrity_contradictions']
if isinstance(contradictions, dict) and set(contradictions) == {'inspection', 'inventory'}:
if all(rows == [] for rows in contradictions.values()):
facts['integrity_contradictions'] = []
branches = facts['unexplored_branches']
if isinstance(branches, list) and all(isinstance(row, dict) for row in branches):
# Branch identity includes the source Evidence identity. Compare its
# reported meaning here and verify its actual source reference below.
facts['unexplored_branches'] = [{key: row.get(key) for key in [
'reason', 'detail', 'next_probe']} for row in branches]
return facts
if task == 'firmware':
result = value.get('result')
if not isinstance(result, dict):
result = {}
engine = result.get('engine', result.get('provider'))
if isinstance(engine, dict):
engine = {'version': engine.get('version'),
'executable_sha256': engine.get('executable_sha256',
engine.get('engine_artifact_sha256'))}
regions = result.get('regions')
if isinstance(regions, list) and all(isinstance(row, dict) for row in regions):
# Binwalk's UUID is run-local; preserve it in the answer but do not
# compare it with the independent replay's different occurrence ID.
selected_regions = []
for row in regions:
selected = {key: row.get(key) for key in [
'offset', 'signature', 'description', 'reported_confidence']}
if 'reported_size' in row:
size = row['reported_size']
elif 'reported_extent_bytes' in row:
size = row['reported_extent_bytes']
else:
extent = row.get('reported_extent')
size = None
if isinstance(extent, dict) and extent.get('offset') == row.get('offset'):
length = extent.get('length')
offset = row.get('offset')
if isinstance(length, int) and isinstance(offset, int) and \
extent.get('end_exclusive') == offset + length:
size = length
selected['reported_size'] = size
selected_regions.append(selected)
regions = selected_regions
return {'artifact_sha256': value.get('artifact_sha256'),
'engine': engine, 'input_size': result.get('input_size', result.get('input_size_bytes')),
'regions': regions, 'coverage': result.get('coverage', result.get('examined_coverage'))}
if task == 'managed':
def selected(field, keys):
row = value.get(field)
return {key: required_field(row, key) for key in keys} if isinstance(row, dict) else row
coverage = value.get('coverage')
if isinstance(coverage, dict):
coverage = {'managed_artifact': coverage.get('managed_artifact'),
'native_boundaries': coverage.get('native_boundaries',
coverage.get('managed_native_boundaries'))}
coverage = {key: {field: row.get(field) for field in ['state', 'issues']}
if isinstance(row, dict) else row
for key, row in coverage.items() if key in ['managed_artifact', 'native_boundaries']}
return {
'artifact_sha256': value.get('artifact_sha256'),
'assembly': selected('assembly', ['name', 'version']),
'module': selected('module', ['name', 'mvid']),
'classification': value.get('classification'),
'target_frameworks': value.get('target_frameworks'),
'native_boundaries': selected('native_boundaries', ['module_refs', 'pinvoke_imports']),
'coverage': coverage,
}
facts = {field: value.get(field) for field in [
'artifact_sha256', 'package_name', 'version_name', 'version_code', 'matches', 'onCreate']}
methods = facts['onCreate']
if isinstance(methods, list) and all(isinstance(row, dict) for row in methods):
facts['onCreate'] = [{key: required_field(row, key) for key in [
'reported_signature', 'overload_index', 'dex_descriptor']} for row in methods]
return facts
def producer_ids(reply):
if not isinstance(reply, dict):
return set(), 0
ids = evidence_ids(reply.get('evidence_id'))
ids |= evidence_ids(reply.get('evidence_links'))
for call in reply.get('calls', []):
ids |= evidence_ids(call.get('evidence_id'))
ids |= evidence_ids(call.get('evidence_links'))
ids |= evidence_ids(call.get('partial_evidence_id'))
unreadable = 0
# SDK envelopes and Codex JSONL use distinct declared field spellings.
for field in ['structuredContent', 'structured_content']:
nested_ids, nested_unreadable = producer_ids(reply.get(field))
ids |= nested_ids
unreadable += nested_unreadable
for content in reply.get('content', []):
if content.get('type') != 'text':
continue
try:
value = json.loads(content['text'])
except ValueError:
unreadable += 1
continue
nested_ids, nested_unreadable = producer_ids(value)
ids |= nested_ids
unreadable += nested_unreadable
return ids, unreadable
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--trial', type=Path, required=True)
parser.add_argument('--oracle', type=Path, required=True)
parser.add_argument('--task', choices=[
'android', 'managed', 'native', 'firmware', 'electron-inventory'], default='android')
args = parser.parse_args()
oracle = json.loads(args.oracle.read_text())
if oracle.get('failure') or not oracle.get('projection'):
raise SystemExit('A failed replay is not a factual oracle')
projection = oracle['projection']
if args.task == 'managed':
projection = {**projection, 'coverage': {
'managed_artifact': projection['coverage'],
'native_boundaries': projection['native_boundaries']['coverage'],
}}
expected = requested_facts(projection, args.task)
# A task explicitly chooses these assertions; do not grade unrequested fields.
if any(value is None for value in expected.values()):
raise SystemExit('Replay does not contain the selected task facts')
failures = []
answer_format = None
answer_path = args.trial / 'answer.txt'
try:
answer, answer_format = read_answer(answer_path)
except (FileNotFoundError, ValueError) as error:
answer = {}
failures.append({'kind': 'answer_parse_failure', 'reason': str(error)})
actual_facts = requested_facts(answer, args.task)
for field, wanted in expected.items():
actual = actual_facts[field]
if not same_fact(actual, wanted):
failures.append({'kind': 'fact_mismatch', 'field': field,
'expected': wanted, 'actual': actual})
observed = set()
unreadable = 0
for line in (args.trial / 'events.jsonl').read_text().splitlines():
event = json.loads(line)
item = event.get('item', {})
if event.get('type') != 'item.completed' or item.get('type') != 'mcp_tool_call':
continue
# Only producer envelopes and bridge audit IDs establish retention.
ids, skipped = producer_ids(item.get('result') or {})
observed |= ids
unreadable += skipped
cited = evidence_ids(answer.get('evidence_ids'))
if args.task == 'electron-inventory':
for branch in answer.get('unexplored_branches', []):
if isinstance(branch, dict):
cited |= evidence_ids(branch.get('evidence_id'))
if not cited:
failures.append({'kind': 'missing_evidence_references'})
if cited - observed:
failures.append({'kind': 'unobserved_evidence_ids', 'ids': sorted(cited - observed)})
result = {'task': args.task,
'grader_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'scope': 'producer_fact_parity_and_reference_existence_not_runtime_accuracy',
'passed': not failures, 'fact_assertions': len(expected),
'answer_format': answer_format,
'unreadable_result_texts': unreadable,
'cited_evidence_ids': sorted(cited), 'failures': failures}
(args.trial / 'grade.json').write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result))
raise SystemExit(0 if result['passed'] else 1)
if __name__ == '__main__':
main()
+518
View File
@@ -0,0 +1,518 @@
{
"name": "rea-codemode-experiment",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rea-codemode-experiment",
"version": "0.0.0",
"dependencies": {
"@modelcontextprotocol/client": "2.3.1",
"@modelcontextprotocol/server": "2.3.1",
"js-tiktoken": "1.0.21",
"json-schema-to-typescript": "16.0.0",
"quickjs-emscripten": "0.32.0",
"zod": "4.3.6"
},
"devDependencies": {
"ajv": "8.20.0"
},
"engines": {
"node": "24.18.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "11.9.3",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz",
"integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==",
"license": "MIT",
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.15",
"js-yaml": "^4.1.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/philsturgeon"
}
},
"node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@jitl/quickjs-ffi-types": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.32.0.tgz",
"integrity": "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==",
"license": "MIT"
},
"node_modules/@jitl/quickjs-wasmfile-debug-asyncify": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.32.0.tgz",
"integrity": "sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-ffi-types": "0.32.0"
}
},
"node_modules/@jitl/quickjs-wasmfile-debug-sync": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.32.0.tgz",
"integrity": "sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-ffi-types": "0.32.0"
}
},
"node_modules/@jitl/quickjs-wasmfile-release-asyncify": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.32.0.tgz",
"integrity": "sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-ffi-types": "0.32.0"
}
},
"node_modules/@jitl/quickjs-wasmfile-release-sync": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.32.0.tgz",
"integrity": "sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-ffi-types": "0.32.0"
}
},
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"license": "MIT"
},
"node_modules/@modelcontextprotocol/client": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.3.1.tgz",
"integrity": "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.3.1",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.8",
"jose": "^6.1.3",
"pkce-challenge": "^5.0.0",
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@modelcontextprotocol/core": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@modelcontextprotocol/server": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.3.1",
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.26",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.26.tgz",
"integrity": "sha512-48NWfiY6oNcKOh47Z4h+sIvwcQR91h6KPhaYKgeMIrkT2jV82Gy90SErj60vW8jQbeMSOSbhIv3bZ7JoqPPVhA==",
"license": "MIT"
},
"node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jose": {
"version": "6.2.12",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
"integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tiktoken": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz",
"integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.5.1"
}
},
"node_modules/js-yaml": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz",
"integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/json-schema-to-typescript": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-16.0.0.tgz",
"integrity": "sha512-Ah6kK4q6SjlMzWBH1eNOQkdRh5Qhr5T/RCFwfjQqWZA7UDW+N47A8wfyoiN0L884s4L4bAEI54IjIuN3/u0B/A==",
"license": "MIT",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.9.3",
"@types/json-schema": "^7.0.15",
"@types/lodash": "^4.17.25",
"js-yaml": "^5.2.3",
"lodash": "^4.18.1",
"minimist": "^1.2.8",
"prettier": "^3.9.6",
"tinyglobby": "^0.2.17"
},
"bin": {
"json2ts": "dist/src/cli.js"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pkce-challenge": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
"integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/prettier": {
"version": "3.9.10",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
"integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/quickjs-emscripten": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.32.0.tgz",
"integrity": "sha512-So0Sqw869y/S2oE3Nuc0uT3Dhqgvsj8FSrwBdsuTosVsG8ME5/OcudU1GxsrIFdFABgy17GHnTVO9TYV/bLQcA==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-wasmfile-debug-asyncify": "0.32.0",
"@jitl/quickjs-wasmfile-debug-sync": "0.32.0",
"@jitl/quickjs-wasmfile-release-asyncify": "0.32.0",
"@jitl/quickjs-wasmfile-release-sync": "0.32.0",
"quickjs-emscripten-core": "0.32.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/quickjs-emscripten-core": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.32.0.tgz",
"integrity": "sha512-QFnPfjFey8EqknSrSxe1hZrf1/8z7/6s1QzGOmKo6++02r7QRRX7ZoyNaZh7JuVjWsVW87KnQrbZqnHkOAzUyg==",
"license": "MIT",
"dependencies": {
"@jitl/quickjs-ffi-types": "0.32.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"name": "rea-codemode-experiment",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "24.18.0"
},
"packageManager": "npm@11.16.0",
"scripts": {
"test": "node --test bridge.test.mjs",
"start": "node bridge.mjs",
"bench": "node benchmark.mjs"
},
"dependencies": {
"@modelcontextprotocol/client": "2.3.1",
"@modelcontextprotocol/server": "2.3.1",
"js-tiktoken": "1.0.21",
"json-schema-to-typescript": "16.0.0",
"quickjs-emscripten": "0.32.0",
"zod": "4.3.6"
},
"devDependencies": {
"ajv": "8.20.0"
}
}
@@ -0,0 +1,3 @@
Statically investigate the actual shipped APK at {{artifact_path}} using only the registered REA tools. Report the exact artifact SHA-256, package name, version name/code, every class-name match for the case-sensitive substring MainActivity, and all onCreate method descriptors reported for org.schabi.newpipe.MainActivity. Preserve reported signatures and explicit unknowns. For each onCreate entry include reported_signature, overload_index and dex_descriptor exactly as reported, preserving null. Final answer must be JSON with artifact_sha256, package_name, version_name, version_code, matches, onCreate, evidence_ids, limitations. Include real Evidence IDs supporting each result. Do not execute the APK or use shell, host file tools, network or other agents. Stop once these facts are supported.
When a client JavaScript executor is available, compose the required REA calls in one execution and return only selected facts from normalized_result, real Evidence IDs, target identity from subject.digest.sha256 and limitations. Parse original MCP text content if necessary. Do not return raw_result, whole Evidence or unrelated inventories. All matching classes and onCreate overloads must still be included.
@@ -0,0 +1 @@
Statically inventory {{artifact_path}} using only the registered REA tools. Open this ASAR as an archive, inspect its artifact inventory, and close the session. Report exact target SHA-256, the complete inventory manifest, counts of every entry_kind across all occurrences, the original occurrence records for root paths package.json, main.js and preload.js if present, all integrity contradictions, inspection coverage, unexplored branches, real Evidence IDs and limitations. This question asks about package bytes and inventory, not AST interpretation or runtime behavior. Do not execute target code, analyze all JavaScript, extract files, launch a GUI, use shell/file tools/network or other agents. When a client JavaScript executor is available, compose original calls in dependency order and return only requested fields from normalized_result; the full inventory is the Evidence nested in the inspect_artifact inventory_artifact substep. Use Evidence.subject.digest.sha256 for the target identity and retain both inspection and inventory Evidence IDs. Final JSON fields: artifact_sha256, manifest, entry_kind_counts, requested_paths, integrity_contradictions, coverage, unexplored_branches, evidence_ids, limitations. Stop when these facts and cleanup are supported. Use the original open_binary, inspect_artifact and close_binary operations, with integrity_policy=fail. The inventory Evidence has normalized_result.manifest and normalized_result.occurrences; each occurrence has entry_kind and logical_path. Count the complete occurrences array and filter those three exact root logical_path values. Keep the complete inventory inside the executor when one is available; return the requested projection.
@@ -0,0 +1 @@
Statically inventory {{artifact_path}} using only the registered REA tools. Open this ASAR as an archive, inspect its artifact inventory, and close the session. Report exact target SHA-256, the complete inventory manifest, counts of every entry_kind across all occurrences, the original occurrence records for root paths package.json, main.js and preload.js if present, all integrity contradictions, inspection coverage, unexplored branches, real Evidence IDs and limitations. This question asks about package bytes and inventory, not AST interpretation or runtime behavior. Do not execute target code, analyze all JavaScript, extract files, launch a GUI, use shell/file tools/network or other agents. When a client JavaScript executor is available, compose original calls in dependency order and return only requested fields from normalized_result; the full inventory is the Evidence nested in the inspect_artifact inventory_artifact substep. Use Evidence.subject.digest.sha256 for the target identity and retain both inspection and inventory Evidence IDs. Final JSON fields: artifact_sha256, manifest, entry_kind_counts, requested_paths, integrity_contradictions, coverage, unexplored_branches, evidence_ids, limitations. Stop when these facts and cleanup are supported.
@@ -0,0 +1 @@
Statically inspect {{artifact_path}} using only the registered REA tools. Report exact target SHA-256 and every reported firmware region with offsets, signatures, reported extents, provider provenance and examined coverage. Preserve unknowns and limitations; a signature scan does not prove partition boundaries or boot/runtime validity. Do not execute target code, extract files, launch a GUI, use shell/file tools/network or other agents. When a client JavaScript executor is available, select the requested normalized_result fields, Evidence.subject.digest.sha256 and real Evidence IDs before returning output. Final JSON fields: artifact_sha256, result, evidence_ids, limitations. Stop when the requested facts are supported.
@@ -0,0 +1 @@
Statically inspect {{artifact_path}} using only the registered REA tools. Report exact target SHA-256, assembly name/version, module name/MVID, managed classification and architecture with its evidence, every reported target framework, and all declared native ModuleRef/PInvoke boundaries with coverage and limitations. Do not load or execute the assembly, launch a GUI, use shell/file tools/network or other agents. When a client JavaScript executor is available, compose the required original MCP calls and return only requested normalized_result fields, target identity from subject.digest.sha256 and real Evidence IDs. Do not return whole Evidence, raw_result or unrelated resources/custom-attribute inventories. Preserve dependency order for operations sharing the selected managed target. Final JSON fields: artifact_sha256, assembly, module, classification, target_frameworks, native_boundaries, coverage, evidence_ids, limitations. Stop when these facts are supported.
@@ -0,0 +1 @@
Statically inspect {{artifact_path}} using only the registered REA tools and Ghidra. The pinned ELF header was independently observed to report entry address 0x403a50; treat that address as the analyst's seed, not as a function-name assumption. Keep default auto-analysis enabled and allow at least 600 seconds for native analysis (including the script deadline if using a bridge). Open the target with provider_id ghidra, resolve the containing procedure for that address, analyze it, and close the ephemeral session in a finally block or a subsequent cleanup call. Report exact target SHA-256, the entry_address, containing_procedure, procedure metadata, the first 512 UTF-16 units of pseudocode, caller_count, callee_count, real Evidence IDs and limitations. Do not execute target code, launch a GUI, use shell/file tools/network or other agents. When a client JavaScript executor is available, compose the original calls in dependency order and select requested fields before returning output; use Evidence.subject.digest.sha256 for the target identity. Final JSON fields: artifact_sha256, entry_address, containing_procedure, procedure, pseudocode_prefix, caller_count, callee_count, evidence_ids, limitations. The address seed applies only to the pinned jq artifact in corpus.json. Stop when these facts and cleanup are supported.
+127
View File
@@ -0,0 +1,127 @@
"""Run one owned experiment in a bounded, transient Linux systemd scope."""
import json
import os
from pathlib import Path
import subprocess
import sys
import time
import uuid
def available_bytes():
for line in Path('/proc/meminfo').read_text().splitlines():
if line.startswith('MemAvailable:'):
return int(line.split()[1]) * 1024
raise RuntimeError('MemAvailable unavailable')
def scope_has_processes(group):
if group is None:
return False
try:
return bool((group / 'cgroup.procs').read_text().strip())
except FileNotFoundError:
return False
def stop_owned_scope(unit, process, group):
subprocess.run(['systemctl', '--user', 'stop', '--no-block', unit],
check=False, timeout=5)
for attempt in range(2):
deadline = time.monotonic() + 10
while process.poll() is None or scope_has_processes(group):
if time.monotonic() >= deadline:
break
time.sleep(0.1)
else:
return
if attempt == 1:
raise RuntimeError('Cleanup unverified; retain owned scope ' + unit)
subprocess.run(['systemctl', '--user', 'kill', '--kill-who=all',
'--signal=SIGKILL', unit], check=False, timeout=5)
def main():
args = sys.argv[1:]
if args and args[0] == '--':
args = args[1:]
if not args:
raise SystemExit('Usage: resource-run.py -- COMMAND ARGS...')
gib = 1024 ** 3
if available_bytes() < 8 * gib:
raise SystemExit('Refusing experiment: less than 8 GiB available')
unit = 'rea-codemode-' + uuid.uuid4().hex + '.scope'
cpu = max(os.sched_getaffinity(0))
command = [
'systemd-run', '--user', '--scope', '--unit=' + unit,
'-p', 'MemoryMax=4G', '-p', 'MemoryHigh=3G', '-p', 'MemorySwapMax=0',
'-p', 'CPUQuota=100%', '-p', 'TasksMax=192',
'taskset', '-c', str(cpu), *args,
]
process = subprocess.Popen(command)
peak = 0
peak_tasks = 0
events = {}
cpu_stats = {}
abort = None
started = time.monotonic()
try:
group = None
while process.poll() is None:
if group is None:
reply = subprocess.run(
['systemctl', '--user', 'show', unit, '--value',
'--property=ControlGroup'],
capture_output=True, text=True, check=False,
)
path = reply.stdout.strip()
if path:
group = Path('/sys/fs/cgroup') / path.lstrip('/')
current = group / 'memory.current' if group else None
if current and current.exists():
try:
peak = max(peak, int(current.read_text()))
peak_tasks = max(peak_tasks, int((group / 'pids.current').read_text()))
cpu_stats = dict((key, int(value)) for key, value in
(line.split() for line in (group / 'cpu.stat').read_text().splitlines()))
for event_file in ['memory.events', 'pids.events']:
events[event_file] = dict(
(key, int(value)) for key, value in
(line.split() for line in (group / event_file).read_text().splitlines())
)
except FileNotFoundError:
# A completed transient scope can disappear between reads.
pass
if peak > 3 * gib:
abort = 'scope_memory_early_stop'
elif available_bytes() < 4 * gib:
abort = 'host_available_memory_early_stop'
elif events.get('pids.events', {}).get('max', 0):
abort = 'scope_task_limit_observed'
if abort:
stop_owned_scope(unit, process, group)
break
time.sleep(0.1)
code = process.wait()
except BaseException:
stop_owned_scope(unit, process, group)
raise
finally:
if scope_has_processes(group):
stop_owned_scope(unit, process, group)
print(json.dumps({
'resource_scope': unit, 'peak_cgroup_bytes_sampled': peak,
'elapsed_seconds': round(time.monotonic() - started, 3),
'abort': abort, 'hard_memory_bytes': 4 * gib,
'early_stop_bytes': 3 * gib, 'cpu_quota_percent': 100,
'cpu_affinity': [cpu],
'task_ceiling': 192, 'peak_tasks_sampled': peak_tasks,
'resource_events_last_observed': events,
'cpu_stat_last_observed': cpu_stats,
}), file=sys.stderr)
raise SystemExit(125 if abort else code)
if __name__ == '__main__':
main()
+367
View File
@@ -0,0 +1,367 @@
# Preliminary measurements
These are real Codex CLI 0.161.0 trials on the frozen checkout, using
`gpt-5.6-terra`, high reasoning, one CPU and a guarded memory scope. The task
called `binary_session` once and requested open state and server identity. This
is a health probe, not an investigation benchmark. API input tokens are cumulative
across model steps; cached tokens are a subset, not an additional cost.
| Profile | Seconds | Input tokens | Cached input | Output tokens | Outcome |
| ---------------------------------- | ------: | -----------: | -----------: | ------------: | --------------------------------------------------------------- |
| Direct MCP | 47.635 | 103154 | 70656 | 940 | Correct closed state; identity omitted after preview truncation |
| Client Code Mode flag | 67.345 | 197315 | 157696 | 1458 | Correct closed state; extra failed resource lookup |
| Client Code Mode only flag | 54.276 | 126609 | 84992 | 939 | Correct closed state; identity omitted |
| Bridge, original prompt | 300.173 | unavailable | unavailable | unavailable | Timeout before any completed tool call |
| Bridge, explicit projection prompt | 84.790 | 209248 | 175616 | 2125 | Correct closed state/version; conflated catalog identities |
These prompts and flags do not establish a controlled comparison. The projection
trial used a more explicit prompt. The client trials expose nested original MCP
calls in their transcripts; the flags alone do not establish code composition or
intermediate-result filtering. The timeout is retained as a failed trial without
an attributed root cause.
The direct tool delivered the original session result, including package version
6.4.0 and REA's catalog digests. In the bridge projection trial the script selected
the nonexistent `server_identity.catalog.digest`, which JavaScript omitted. The
answer instead used the bridge's SDK catalog hash. That is not the requested REA
identity and must fail that factual check. No unsupported digest is used as an
oracle for subsequent trials.
Discovery was unnecessarily returning entire output schemas. For example, the
live `binary_session` output schema occupies 18039 compact JSON bytes, compared
with 859 for its input schema. `analyze_javascript_application` uses 41662 and
2863 bytes respectively. The complete SDK catalog contains 172 tools and 1986857
compact JSON bytes. The bridge now defaults to input schemas and makes unchanged
output schemas available explicitly. Actual model savings from this change remain
unmeasured.
The upstream/downstream stdio boundary suite passed all nine tests. The repository
typecheck and lint passed under a one-CPU resource scope with a sampled peak of
2934038528 bytes. A 2 GiB soft limit caused heavy reclaim during native TypeScript
checking; the wrapper's soft limit is now aligned with its 3 GiB early-stop
threshold. Its 4 GiB hard ceiling and host-available-memory checks remain in force.
An earlier cached-build attempt was killed inside its 2 GiB scope; that is a
failed build attempt, not evidence of a host OOM or a benchmark result.
## Real shipped artifacts
All acquired bytes match `corpus.json`; ShareX's archive also matches the GitHub
asset digest and SQLite's npm archive matches its published SHA-512 integrity.
No target was executed. These measurements use real REA providers and SDK stdio,
with the same analysis arguments in each compared profile.
NewPipe's direct, discovery-only replay and bridge replay produce equal selected
facts, including exact target digest, package/version, two MainActivity matches,
reported `onCreate` signature, overload index and unavailable DEX descriptor. The
bridge retains all three real producer IDs. Discovery-only here is a replay cost
decomposition, not an implemented new MCP client protocol.
| Replay delivery | Discovery estimate | Intermediate/final result estimate |
| -------------------------------------------------- | -----------------: | ---------------------------------: |
| Direct: input definitions only, text content once | 107076 | 94945 |
| On-demand input definitions, original text content | 877 | 94945 |
| On-demand definitions, one composed projection | 877 | 1057 |
The bridge also sends 290 estimated tokens of code. Estimates use `cl100k_base`;
they are not Terra API tokens, latency or model task success. The direct baseline
uses input definitions and single text content, avoiding an inflated comparison
against duplicated MCP envelopes or unused output schemas. The full SDK envelope
figures, where useful for local transport, are recorded separately.
ShareX's real PE/CLI metadata and native-boundary workflows agree across direct
and bridge: assembly ShareX 19.0.2.0, modern .NET, x86_64, framework
`.NETCoreApp,Version=v9.0`, declared native boundaries and explicit coverage.
Single text-content estimates are 204766 tokens for the two original results and
4148 for the bridge projection. No .NET runtime behavior was verified.
jq's original trial failed because the stripped shipped executable has no
`jq_compile` symbol. The corrected direct trial uses the observed ELF entry
`0x403a50`, resolves it to Ghidra's `entry` function and analyzes that function.
It succeeds with retained Evidence and a closed ephemeral provider session.
The corrected bridge returns identical selected facts and limitations. Its final
result estimate is 1544 tokens versus 10189 for direct single-text results, plus
1610 for discovery and 267 for code. This small entry function
does not establish large-function or complete native-analysis coverage.
OpenWrt's original direct trial returned `unreadable_output` without the expected
report. Twelve independent bounded Binwalk invocations produced reports. An
observer launcher and a subsequent unchanged real Binwalk bridge trial succeeded,
reporting uImage at offset 0 and SquashFS at 2741985 with `signature_scan` coverage.
The observer is a diagnostic with a different launcher identity, not a benchmark
baseline. The original failure remains unexplained. Subsequent direct and bridge
workflows agree on all region facts and coverage; random upstream region IDs vary
and are preserved. Single-text estimates are 1387 tokens direct and 827 projected,
so this one-operation task provides much less composition benefit than Android.
SQLite's actual WASM fails the frozen REA WABT output parser with `Unrecognized
WABT section row`. WABT 1.0.42 prints the nine-character DataCount label without
leading padding; the owning parser currently requires leading whitespace. No
downstream shim is used, and no successful WASM provider claim is made.
Obsidian's initial real-model analysis ended without Evidence after Codex's host
closed stdout. Its standalone SDK replay returns a real provider timeout with a
retained partial Evidence ID. This was observed before automatic partial-summary
recovery was added, so the transcript retains that reference but does not export
its payload. A source-fixture production-boundary test now verifies that provider
timeout IDs survive auditing and resolve through the retained-evidence view.
Neither failure is a Code Mode speedup.
## Real Android agents
After raising the task ceiling, a direct Terra trial completes in 107.478 seconds
with 348556 API input tokens (292608 cached), 1815 output tokens and five completed
MCP calls. All six strict producer-fact assertions and cited-ID checks pass. Its
sampled peak is 1349251072 bytes and 127 tasks, with zero observed limit events.
A client Code Mode trial passes the same assertions in 183.361 seconds with
414869 input tokens (372480 cached), 3618 output tokens. The transcript must still
be inspected before attributing native composition or filtering to that flag.
The bridge trial before explicit projection guidance takes 84.754 seconds,
292063 input tokens (244992 cached) and 1661 output tokens. It returns whole
Evidence instead of filtering most intermediates. Requested facts and IDs are
supported, but its stronger exact-projection check fails because overload index
was omitted. A clarified question explicitly requires that index in later trials.
The next bridge trial composes three calls and filters their results, but selects
`normalized_result.engine.artifact_sha256` as the target hash. This is the Jadx
bridge JAR identity, not the APK identity. The strict hash check fails. It takes
166.411 seconds and 324417 input tokens (279040 cached), 2362 output tokens.
This confirms a real provenance risk in model-written projections. Trial
instructions now identify `Evidence.subject.digest.sha256` explicitly. This
change has not yet established a reliable repeated model efficiency gain.
The following bridge trial passes all six assertions and cited-ID checks, using
one execution script for all three original operations. It takes 137.966 seconds,
190313 input tokens (156672 cached) and 1695 output tokens. Four discovery calls
precede the script. This is an encouraging token result, but its revised prompt,
instructions and concurrent host workload prevent a controlled speed comparison.
Instructions and prompt digests are now retained without authentication or an
ambient environment snapshot, so future equal-task repetitions can be audited.
The 6.1 sol diagnostic touched the 96-task limit; it is invalid as a comparison
and was interrupted, with its copied profile removed. The wrapper now allows 192
tasks, records task/memory limit events and rejects capacity-limited trials. A
real thread-creation probe hit exactly 192 tasks, was stopped with exit 125 and
retained its limit reason; it used about 12 MiB and caused no memory-limit event.
The nine actual stdio boundary tests pass after discovery changes.
Wall times above include contention on the same pinned CPU during exploratory
work and differing prompts/instructions. They cannot support a controlled speed
ratio. A reproducible efficiency conclusion requires successful equal-task trials
and serial repetitions with the strongest existing-client baseline.
The stronger client trial uses an explicit instruction to compose and project the
existing operations, and passes all six checks: 72.341 seconds, 161370 input
tokens (120576 cached), 1664 output tokens and three completed MCP calls. An
equivalent question with the bridge passes in 115.962 seconds, 321295 input tokens
(283648 cached), 2274 output tokens, five discovery calls and one execution. The
native transcript does not expose executor source, so exact internal composition
is unverified. Nevertheless, this measured client profile is a materially stronger
baseline than the original repeated-call trial. Total input, uncached input,
output and wall time must be considered separately; cached-token differences make
total input an incomplete cost metric.
This reverses the preliminary temptation to equate replay compression with the
best Codex experience. Existing client support with explicit Evidence-aware
recipes may be enough. An optional bridge needs to justify discovery overhead and
remains useful to test for clients without equivalent execution support.
Eleven actual SDK/stdio tests pass, including a controlled delivery delay after a
real upstream result to exercise queued cancellation. Both the delayed call and
its queued successor release deferred guest handles before runtime disposal.
This test does not simulate or establish new provider behavior.
The bridge remains an experiment and has not changed any published REA contract.
## Existing-client and managed follow-up
The same Android v3 question with normal MCP, without the experimental Code Mode
feature flags, passes all six fact assertions and reference checks: 75.117 seconds,
103471 API input tokens (73728 cached), 1538 output tokens and three original MCP
calls. This is the strongest measured Android baseline so far. Its wall time also
includes a short boundary-test overlap, so it is not a controlled latency ratio.
The installed model catalog requests code-mode-only tool delivery; flag settings
and nested MCP events alone cannot establish the internal executor's behavior.
The first real ShareX managed-agent comparison uses the same question with Terra
high and identical provider settings. Normal MCP takes 81.578 seconds, 217573 input
tokens (186368 cached), 2647 output tokens and two original calls. The bridge takes
96.930 seconds, 218089 input tokens (155904 cached), 3247 output tokens, four
searches and two executions. Its first script uses an expression instead of the
required async-body `return`, yielding no projection; it repeats both operations.
Query discovery now defaults to lightweight names/descriptions, while exact-name
discovery supplies input schemas. This run still requested full output schemas
individually and therefore does not establish an efficiency gain from that change.
Both answers match all seven requested managed fact groups, including exact target
SHA-256, assembly/module identities, classification evidence, framework, declared
ModuleRef/PInvoke arrays and coverage. Those empty arrays concern ShareX.dll only.
The bridge passes cited-reference verification. Codex truncated the original
managed-artifact response in its normal-MCP JSONL at roughly one MiB, leaving an
invalid JSON fragment; the automated grader cannot validate that one reference
from a complete producer envelope. Its reference-failure result is retained,
despite the matching ID being visible in the transcript prefix and replay. This
is a verification limitation, not a demonstrated fabricated ID.
The runner now handles SIGTERM so copied trial credentials are removed during a
resource abort. A controlled non-model launcher received owned-scope SIGTERM,
exited 143 and left no trial profile while preserving the dummy source auth file.
This verifies credential cleanup only. The eleven actual stdio tests pass after
the adaptive discovery change, with a sampled peak of 1365106688 bytes, 42 tasks
and zero observed memory/task limit events.
## Serial comparison and package inventory
A serial Android v3 pair, without concurrent provider/build work, passes every
fact and cited-reference check. Normal MCP uses 56.962 seconds, 105861 input
(75776 cached, 30085 uncached), 1106 output tokens and three original calls. The
adaptive-discovery bridge uses 106.134 seconds, 200537 input (175872 cached,
24665 uncached), 2177 output tokens, four searches and one execution. Its smaller
uncached input does not imply lower overall cost: output, cache rates and latency
also matter. It still requests original output schemas and mistakenly searches
for the already exposed bridge executor. Sampled memory peaks are 1333710848 and
1656098816 bytes respectively; neither run touches memory/task limits. One pair
is insufficient for a statistical latency or reliability claim.
A separate Obsidian package-inventory replay succeeds with existing REA tools.
Direct and bridge return byte-for-byte equal selected facts: 397 graph nodes and
occurrences, 369 files, 28 directories, root main.js/package.json occurrences,
no root preload.js, no observed integrity contradiction, complete-within-substeps
coverage and an explicit unexplored JavaScript analysis branch. Both original
Evidence IDs are preserved. Direct/bridge operations take 6.267/6.618 seconds;
original single-text results total 178958 estimated tokens versus a 1404-token
bridge result, 1305 for discovery and 322 for code. These are replay estimates,
not model latency or success. Inventory does not complete the timed-out AST task.
An output-type discovery probe compiles seven real SDK schemas successfully.
Derived TypeScript estimates are 784/719/751 tokens for Android package/search/
class, 1558/1420 for managed artifact/native boundaries, 1264 for artifact inventory
and 2289 for function analysis. Their original JSON schemas use 1666/1480/1576,
3524/2932, 2812 and 5091 respectively. These declarations describe code values;
they do not preserve every JSON Schema validation rule. Exact-name discovery now
includes them without replacing explicitly requested original JSON schemas. Real
model benefit from this addition remains to be tested. The audit also carries
original Evidence links; a real retained-view readback verifies linked IDs rather
than trusting references invented by a projection.
The output-declaration boundary suite first failed its type-name assertion because
the compiler preferred the schema's URN identity as a display name. The derived
compilation now supplies a descriptive title while returning the original schema
unchanged. All eleven tests then pass, including original-link readback, at a
sampled peak of 1399996416 bytes and zero observed limit events. Declarations are
cached per exact tool name for this immutable live-catalog connection; repeated
discovery does not regenerate them. No provider implementation or published
REA API has changed.
## Typed-discovery serial agent checkpoint
All rows below use real Codex CLI 0.161.0, Terra high, the same rendered question
within each pair, and the same provider settings. The bridge/runtime/runner source
fingerprints match commit `22326e819`; model-written original arguments can differ.
Comparisons include required mode-specific instructions and are not a controlled
causal decomposition of inference, caching and provider time.
| Case | Profile | Wall seconds | Input | Cached | Uncached | Output | MCP calls | Fact/reference check |
| ------------------ | ------- | -----------: | -----: | -----: | -------: | -----: | --------: | --------------------------------- |
| android | direct | 67.329 | 107007 | 83712 | 23295 | 1670 | 3 | pass |
| android | bridge | 48.231 | 113256 | 91648 | 21608 | 1456 | 3 | pass |
| managed | bridge | 73.145 | 116974 | 92672 | 24302 | 2065 | 3 | classification field omitted |
| managed | direct | 33.15 | 108096 | 75776 | 32320 | 2315 | 2 | facts match; reference unverified |
| electron-inventory | direct | 89.494 | 266121 | 238848 | 27273 | 3215 | 3 | pass |
| electron-inventory | bridge | 145.577 | 462809 | 407040 | 55769 | 5624 | 8 | pass |
| firmware | bridge | 81.944 | 152886 | 126976 | 25910 | 2192 | 3 | pass |
| firmware | direct | 30.161 | 103224 | 74752 | 28472 | 1761 | 1 | pass |
The managed bridge omits the producer classification container field (`pe`) while
preserving status/runtime/implementation/architecture and all classification
evidence. Its strict complete-classification assertion fails; this is an omission,
not an incorrect architecture claim. Normal MCP matches requested facts but its
large-result JSONL truncation again prevents automated verification of one ID.
The initial grader treated grouped path records, extra known coverage counts and
an empty per-source contradiction object as mismatches. Task-specific comparison
now accepts those lossless report forms. Firmware compares observed offset/length
facts, checks any supplied derived end arithmetically, and verifies engine version
and executable digest. Run-local UUIDs and differently named display fields are
not compared as if they were stable source identity. Original strict grades and
pre-equivalence grades remain outside Git, and new grades include a grader digest.
These report-format comparisons do not add aliases to any REA tool contract.
Obsidian bridge discovery takes six searches and two executions. The first returns
the entire nested inventory; the second repeats it and computes a focused answer.
The live nested Evidence schema describes normalized_result as generic JSON, so
derived types alone cannot teach its concrete manifest/occurrences layout. Its
recursive record walk happens to match this corpus, but format-specific field
selection is the stronger recipe. Both model profiles choose record-and-continue
for integrity; deterministic inventory replays used fail. No contradictions were
observed, and the exact caller-selected arguments are retained.
## Completed serial cohort
The final sixteen-row cohort, raw-event digests, exact runner/bridge/lockfile
fingerprints, prompt/instruction/provider digests and sampled resource stats are
in [agent-results.json](agent-results.json). The requested model configuration is
Terra high. Two managed bridge answers omit the classification container field
under the stronger full-object check; two normal-MCP managed logs have a truncated
producer envelope. The twelve other rows pass requested fact and observed-reference
checks. This is producer parity, not independent runtime truth or a per-fact
citation-association proof. Earlier stricter report-form grades remain retained.
| Trial | Wall seconds | Input | Cached | Uncached | Output | MCP calls | Check |
| ---------------------------------- | -----------: | -----: | -----: | -------: | -----: | --------: | --------------------------------- |
| android-direct-5 | 67.329 | 107007 | 83712 | 23295 | 1670 | 3 | pass |
| android-bridge-7 | 48.231 | 113256 | 91648 | 21608 | 1456 | 3 | pass |
| managed-bridge-2 | 73.145 | 116974 | 92672 | 24302 | 2065 | 3 | classification container omitted |
| managed-direct-2 | 33.150 | 108096 | 75776 | 32320 | 2315 | 2 | facts match; reference unverified |
| electron-inventory-direct-1 | 89.494 | 266121 | 238848 | 27273 | 3215 | 3 | pass |
| electron-inventory-bridge-1 | 145.577 | 462809 | 407040 | 55769 | 5624 | 8 | pass |
| firmware-bridge-1 | 81.944 | 152886 | 126976 | 25910 | 2192 | 3 | pass |
| firmware-direct-1 | 30.161 | 103224 | 74752 | 28472 | 1761 | 1 | pass |
| native-direct-1 | 327.150 | 151343 | 122368 | 28975 | 2458 | 4 | pass |
| native-bridge-1 | 334.642 | 285961 | 245504 | 40457 | 2129 | 4 | pass |
| android-bridge-8 | 134.555 | 159370 | 134400 | 24970 | 3250 | 7 | pass |
| android-direct-6 | 71.624 | 153031 | 117504 | 35527 | 1501 | 3 | pass |
| managed-direct-3 | 75.229 | 108593 | 75776 | 32817 | 2774 | 2 | facts match; reference unverified |
| managed-bridge-3 | 40.609 | 125519 | 96000 | 29519 | 2274 | 3 | classification container omitted |
| electron-inventory-guided-bridge-1 | 85.909 | 101994 | 61440 | 40554 | 2563 | 3 | pass |
| electron-inventory-guided-direct-1 | 67.540 | 263484 | 221952 | 41532 | 2499 | 4 | pass |
Ghidra's aggregate sampled CPU time is 263.738 seconds directly and 271.096 with
the bridge. The bridge uses three original operations versus four directly,
obtaining containing identity from the analyzed procedure. Both close the session
successfully. The native comparison uses the same default analysis settings;
these data do not establish a meaningful native speedup.
The second Android bridge repetition completes seven original calls across five
scripts, without tool errors, before its final composed answer. Its 134.555-second
result reverses the first repetition's latency advantage. The model did not
reliably choose one composed execution even with explicit guidance. Uncached
input is lower in both bridge repetitions, while output and latency vary.
The guided Electron question explicitly names the existing operations and the
nested inventory's manifest/occurrences layout for both profiles. Both pass, with
integrity_policy=fail. The bridge composes open/inspect/close once; the normal-MCP
profile repeats inspect_artifact. The total-input reduction from 263484 to 101994
is large, but uncached input differs by only 978 tokens and bridge latency is
higher. Earlier non-guided profiles are a different question and cannot support
a causal prompt-improvement ratio. Unknown nested payload types remain an observed
contract-discovery problem; caller-selected values and identities are preserved.
All sixteen runs stay below memory/task ceilings, with maximum sampled memory
1659744256 bytes and 126 tasks; no observed memory/task limit event or resource
abort occurs. Copied profile count is zero at completion. Runtime measurements
belong to the recorded source fingerprint at 22326e819; subsequent timer-range
validation and test-only Ajv installation are not new model measurements.
Final boundary verification passes twelve actual SDK/stdio tests. A 2 MiB guest
budget rejects allocation pressure and the next execution succeeds. This is a
guest-budget failure, not an OS OOM or provider behavior claim. A local pinned-Node
probe shows a 2147483648-ms timeout becomes 1 ms; the owning named execution
contract now rejects that range before allocation. After SDK conversion, both
advertised bridge input schemas validate under JSON Schema 2020-12, and the actual
stdio client rejects invalid code, oversized memory and timer values. Sampled
suite memory peaks at 1387433984 bytes, with zero observed memory/task limit events.
See [decision.md](decision.md) for the recommendation: keep current REA contracts,
improve focused Evidence-aware workflow guidance, and retain the bridge as an
opt-in candidate. A broad default switch is not justified by these samples.
+88 -104
View File
@@ -1,12 +1,4 @@
#!/bin/bash
{
# Truncation guard: this brace group opens on line 2 and closes on the last
# line, so bash must read the entire file before it runs any command. On the
# `curl ... | bash` path, a download cut off anywhere before the final `}` is
# an unterminated compound command, which bash reports as a syntax error at
# EOF without running anything. Without the guard, a cut right after `set`
# would run the bare builtin and print every shell variable, and a cut right
# after `main` would run the installer without the caller's flags.
set -euo pipefail
PACKAGE="rea-agents"
@@ -29,112 +21,104 @@ usage() {
cat <<'EOF'
Usage: install.sh [--version VERSION] [--dry-run] [--no-setup] [--no-prompt] [--verbose]
Installs the REA CLI, then starts `rea setup` interactively to configure Hopper
and agents when a terminal is available. Pass --no-setup (or --no-prompt) to
skip setup and run `rea setup` later.
Installs only the REA CLI. Run `rea setup` separately to configure Hopper and agents.
EOF
}
main() {
while (($# > 0)); do
case "$1" in
--version)
(($# >= 2)) || fail "--version requires a value."
version="$2"
shift 2
;;
--dry-run)
dry_run=true
shift
;;
--no-setup|--no-prompt)
start_setup=false
shift
;;
--verbose)
verbose=true
shift
;;
--help|-h)
usage
exit 0
;;
*) fail "unknown option: $1" ;;
esac
done
while (($# > 0)); do
case "$1" in
--version)
(($# >= 2)) || fail "--version requires a value."
version="$2"
shift 2
;;
--dry-run)
dry_run=true
shift
;;
--no-setup|--no-prompt)
start_setup=false
shift
;;
--verbose)
verbose=true
shift
;;
--help|-h)
usage
exit 0
;;
*) fail "unknown option: $1" ;;
esac
done
if [[ "$verbose" == true ]]; then
set -x
fi
if [[ "$verbose" == true ]]; then
set -x
fi
platform="$(uname -s)"
[[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from https://nodejs.org and retry."
command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
platform="$(uname -s)"
[[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from https://nodejs.org and retry."
command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
[[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] ||
! ((node_major == 22 && node_minor >= 19 || node_major == 24 && node_minor >= 11 || node_major >= 26)); then
fail "Node.js $node_version is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+."
fi
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
[[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] ||
! ((node_major == 22 && node_minor >= 19 || node_major == 24 && node_minor >= 11 || node_major >= 26)); then
fail "Node.js $node_version is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+."
fi
if [[ -n "$version" ]]; then
[[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version."
else
release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
[[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
version="${BASH_REMATCH[1]}"
fi
if [[ -n "$version" ]]; then
[[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version."
else
release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
[[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
version="${BASH_REMATCH[1]}"
fi
# npm's exact-version resolver cleans build metadata from the requested version,
# so it may install the base release and make the post-install identity check fail.
if [[ "$version" =~ \+ ]]; then
fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry."
fi
# npm's exact-version resolver cleans build metadata from the requested version,
# so it may install the base release and make the post-install identity check fail.
if [[ "$version" =~ \+ ]]; then
fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry."
fi
install_args=(install --global)
if [[ "$platform" == "Linux" ]]; then
install_args+=(--prefix "$HOME/.local")
install_bin="$HOME/.local/bin/rea"
else
npm_prefix="$(npm prefix --global)" || fail "the npm global prefix could not be read. Repair the npm configuration, then retry."
install_bin="$npm_prefix/bin/rea"
fi
install_args+=("$PACKAGE@$version")
install_args=(install --global)
if [[ "$platform" == "Linux" ]]; then
install_args+=(--prefix "$HOME/.local")
install_bin="$HOME/.local/bin/rea"
else
npm_prefix="$(npm prefix --global)" || fail "the npm global prefix could not be read. Repair the npm configuration, then retry."
install_bin="$npm_prefix/bin/rea"
fi
install_args+=("$PACKAGE@$version")
printf 'REA install plan\n'
printf ' Version: %s\n' "$version"
printf ' Runtime: Node.js %s\n' "$node_version"
printf ' Command:'
printf ' %q' npm "${install_args[@]}"
printf '\n'
printf ' Binary: %s\n' "$install_bin"
printf ' Setup: %s\n' "$([[ "$start_setup" == true ]] && printf 'start when a terminal is available' || printf 'skipped')"
printf 'REA install plan\n'
printf ' Version: %s\n' "$version"
printf ' Runtime: Node.js %s\n' "$node_version"
printf ' Command:'
printf ' %q' npm "${install_args[@]}"
printf '\n'
printf ' Binary: %s\n' "$install_bin"
printf ' Setup: %s\n' "$([[ "$start_setup" == true ]] && printf 'start when a terminal is available' || printf 'skipped')"
if [[ "$dry_run" == true ]]; then
printf 'Dry run complete; no changes made.\n'
exit 0
fi
if [[ "$dry_run" == true ]]; then
printf 'Dry run complete; no changes made.\n'
exit 0
fi
printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
npm "${install_args[@]}" || fail "npm could not install REA. Check registry access and npm permissions, then retry."
[[ -x "$install_bin" ]] || fail "npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry."
installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "the installed REA version could not be read. Reinstall the requested version, then retry."
[[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version. Reinstall the requested version, then retry."
printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
npm "${install_args[@]}" || fail "npm could not install REA. Check registry access and npm permissions, then retry."
[[ -x "$install_bin" ]] || fail "npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry."
installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "the installed REA version could not be read. Reinstall the requested version, then retry."
[[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version. Reinstall the requested version, then retry."
printf 'REA %s is installed.\n' "$version"
if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
printf 'Starting REA setup...\n'
"$install_bin" setup </dev/tty >/dev/tty
else
printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
fi
}
main "$@"
exit $?
}
printf 'REA %s is installed.\n' "$version"
if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
printf 'Starting REA setup...\n'
"$install_bin" setup </dev/tty >/dev/tty
else
printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
fi
+1 -1
View File
@@ -6707,7 +6707,7 @@
},
"node_modules/rolldown": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz",
"resolved": "https://mirrors.cloud.tencent.com/npm/rolldown/-/rolldown-1.2.12.tgz",
"integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==",
"dev": true,
"license": "MIT",
+2 -10
View File
@@ -51,14 +51,10 @@
"bridge/process/ProcessRunTokenReader.swift",
"bridge/process/ProcessRunTokenReaderMain.swift",
"bridge/ghidra/ReaGhidraBridge.java",
"bridge/ghidra/ReaGhidraBundleActivator.java",
"bridge/ghidra/ReaGhidraExtensions.java",
"bridge/ghidra/ReaGhidraPrepareCom.java",
"bridge/ghidra/ReaGhidraApplySeeds.java",
"bridge/ghidra/ReaGhidraAnalysisOptions.java",
"bridge/ghidra/ReaGhidraNoReturnFix.java",
"bridge/ghidra/ReaGhidraMipsSoftFloat.java",
"bridge/ghidra/ReaGhidraPrepareRaw.java",
"bridge/android/ReaJadxBridge.java",
"bridge/mitmproxy/capture.py",
"scripts/rea.mjs",
@@ -165,14 +161,11 @@
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
"verify:ghidra:seeds": "npm run build:cached && node scripts/verify/ghidra/seeds.mjs",
"verify:ghidra:mips": "npm run build:cached && node scripts/verify-real-ghidra-mips.mjs",
"verify:ghidra:mips-shared": "npm run build:cached && node scripts/verify-real-ghidra-mips-shared.mjs",
"verify:ghidra:mips-relocatable": "npm run build:cached && node scripts/verify-real-ghidra-mips-relocatable.mjs",
"verify:ghidra:swift": "npm run build:cached && node scripts/verify-real-ghidra-swift.mjs",
"verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs",
"verify:ghidra:switch": "npm run build:cached && node scripts/verify-real-ghidra-switch.mjs",
"verify:ghidra:dos": "npm run build:cached && node scripts/verify-real-ghidra-dos.mjs",
"verify:ghidra:com": "npm run build:cached && node scripts/verify-real-ghidra-com.mjs",
"verify:ghidra:raw": "npm run build:cached && node scripts/verify-real-ghidra-raw.mjs",
"verify:ghidra:aarch64-jump-table": "npm run build:cached && node scripts/verify-real-ghidra.mjs --aarch64-jump-table",
"verify:interface-builder": "npm run build:cached && node scripts/verify/apple/interface-builder.mjs",
"verify:ghidra:cross-format": "npm run build:cached && node scripts/verify-real-ghidra.mjs --cross-format",
@@ -251,14 +244,13 @@
"verify:browser:runtime": "npm run build:cached && node scripts/verify-browser-runtime.mjs",
"verify:web:network-captures": "npm run build:cached && node scripts/verify-web-network-captures.mjs",
"verify:binary:layout": "npm run build:cached && node scripts/verify-binary-layout.mjs",
"verify:go:binary": "npm run build:cached && node scripts/verify-go-binary.mjs",
"verify:wasm:package": "npm run build:cached && node scripts/verify-packaged-wasm-artifact.mjs",
"verify:wasm:artifact": "npm run build:cached && node scripts/verify-wasm-artifact.mjs",
"verify:evm:interface": "npm run build:cached && node scripts/verify-evm-interface.mjs",
"verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs",
"mcp-catalog:check:built": "node scripts/generate-mcp-tool-catalog.mjs --check",
"compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/bundle-runtime.mjs",
"compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/bundle-runtime.mjs",
"compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs",
"compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs",
"artifacts:mcp-catalog": "node scripts/generate-mcp-tool-catalog.mjs",
"artifacts:skills": "node scripts/generate-skill-metadata.mjs",
"artifacts:product-catalog": "node scripts/generate-product-catalog.mjs",
+1
View File
@@ -0,0 +1 @@
export const bundleMcpRuntime: (root: string) => Promise<void>;
+104
View File
@@ -0,0 +1,104 @@
import { parse } from "@babel/parser";
import { readFile } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
/**
* Reduce filesystem resolution during MCP startup. Keep dependencies external,
* dynamic imports lazy, and the original compiled files for CLI/worker entrypoints.
*/
export const bundleMcpRuntime = async (root) => {
const compiledRoot = join(root, "dist");
const { rolldown, RUNTIME_MODULE_ID } = await import("rolldown");
const { imports = {} } = JSON.parse(
await readFile(join(root, "package.json"), "utf8"),
);
const bundle = await rolldown({
input: { main: join(compiledRoot, "main.js") },
platform: "node",
treeshake: false,
external: (source) => !source.startsWith(".") && !isAbsolute(source),
plugins: [
{
name: "preserve-compiled-module-locations",
transform(code, id) {
if (id === RUNTIME_MODULE_ID) return null;
if (!code.includes("import.meta")) return null;
const sourcePath = relative(compiledRoot, id);
if (
isAbsolute(sourcePath) ||
sourcePath === ".." ||
sourcePath.startsWith(`..${sep}`)
)
throw new Error(`MCP bundle input is outside dist: ${id}`);
// All chunks are directly under dist/mcp. Preserve the original
// module URL, including URL escaping, for bridges and createRequire.
const moduleUrl = `new URL(${JSON.stringify(`../${sourcePath.split(sep).map(encodeURIComponent).join("/")}`)}, import.meta.url).href`;
const edits = [];
const visit = (node) => {
if (node === null || typeof node !== "object") return;
const isMeta = (value) =>
value?.type === "MetaProperty" &&
value.meta.name === "import" &&
value.property.name === "meta";
if (
node.type === "CallExpression" &&
node.callee.type === "MemberExpression" &&
!node.callee.computed &&
isMeta(node.callee.object) &&
node.callee.property.name === "resolve" &&
node.arguments.length === 1 &&
node.arguments[0].type === "StringLiteral" &&
Object.hasOwn(imports, node.arguments[0].value)
)
// Package imports resolve in the same package scope after bundling.
return;
if (
node.type === "MemberExpression" &&
!node.computed &&
isMeta(node.object) &&
node.property.name === "url"
) {
edits.push({ start: node.start, end: node.end });
return;
}
if (isMeta(node))
throw new Error(
`Unsupported import.meta use in MCP bundle: ${id}`,
);
for (const value of Object.values(node)) {
if (Array.isArray(value)) value.forEach(visit);
else visit(value);
}
};
visit(parse(code, { sourceType: "module" }).program);
for (const { start, end } of edits.sort((a, b) => b.start - a.start))
code = code.slice(0, start) + moduleUrl + code.slice(end);
return { code, map: null };
},
},
],
});
try {
await bundle.write({
dir: join(compiledRoot, "mcp"),
format: "esm",
entryFileNames: "[name].js",
chunkFileNames: "[name]-[hash].js",
codeSplitting: true,
strictExecutionOrder: true,
minify: false,
sourcemap: false,
});
} finally {
await bundle.close();
}
};
if (
process.argv[1] !== undefined &&
pathToFileURL(resolve(process.argv[1])).href === import.meta.url
)
await bundleMcpRuntime(
resolve(dirname(fileURLToPath(import.meta.url)), ".."),
);
-1
View File
@@ -1 +0,0 @@
export const bundleRuntime: (root: string) => Promise<void>;
-140
View File
@@ -1,140 +0,0 @@
import { parse } from "@babel/parser";
import { readFile } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
/**
* Reduce filesystem resolution during CLI and MCP startup. Keep dependencies
* external, dynamic imports lazy, and original compiled worker entrypoints.
*/
export const bundleRuntime = async (root) => {
const compiledRoot = resolve(root, "dist");
// CLI, MCP, workers and updater fixtures share one release-identity owner.
const metadataModule = join(compiledRoot, "generatedPackageMetadata.js");
const isMetadataModule = (id) =>
isAbsolute(id) && resolve(id) === metadataModule;
const { rolldown, RUNTIME_MODULE_ID } = await import("rolldown");
const { imports = {} } = JSON.parse(
await readFile(join(root, "package.json"), "utf8"),
);
for (const [directory, entries] of [
["mcp", { main: "main.js" }],
[
"cli-runtime",
{
cli: "cli.js",
cliOutput: "cliOutput.js",
streamedJsonOutput: "cli/streamedJsonOutput.js",
mcpDoctor: "mcpDoctor.js",
},
],
]) {
const bundle = await rolldown({
input: Object.fromEntries(
Object.entries(entries).map(([name, path]) => [
name,
join(compiledRoot, path),
]),
),
platform: "node",
treeshake: false,
external: (source) =>
isMetadataModule(source) ||
(!source.startsWith(".") && !isAbsolute(source)),
plugins: [
{
name: "preserve-compiled-module-locations",
transform(code, id) {
if (id === RUNTIME_MODULE_ID) return null;
if (!code.includes("import.meta")) return null;
const sourcePath = relative(compiledRoot, id);
if (
isAbsolute(sourcePath) ||
sourcePath === ".." ||
sourcePath.startsWith(`..${sep}`)
)
throw new Error(`Runtime bundle input is outside dist: ${id}`);
// Each output directory is directly under dist. Preserve the original
// module URL, including URL escaping, for bridges and createRequire.
const moduleUrl = `new URL(${JSON.stringify(`../${sourcePath.split(sep).map(encodeURIComponent).join("/")}`)}, import.meta.url).href`;
const edits = [];
const visit = (node) => {
if (node === null || typeof node !== "object") return;
const isMeta = (value) =>
value?.type === "MetaProperty" &&
value.meta.name === "import" &&
value.property.name === "meta";
if (
node.type === "CallExpression" &&
node.callee.type === "MemberExpression" &&
!node.callee.computed &&
isMeta(node.callee.object) &&
node.callee.property.name === "resolve" &&
node.arguments.length === 1 &&
node.arguments[0].type === "StringLiteral" &&
Object.hasOwn(imports, node.arguments[0].value)
)
// Package imports resolve in the same package scope after bundling.
return;
if (
node.type === "MemberExpression" &&
!node.computed &&
isMeta(node.object) &&
node.property.name === "url"
) {
edits.push({ start: node.start, end: node.end });
return;
}
if (isMeta(node))
throw new Error(
`Unsupported import.meta use in runtime bundle: ${id}`,
);
for (const value of Object.values(node)) {
if (Array.isArray(value)) value.forEach(visit);
else visit(value);
}
};
visit(parse(code, { sourceType: "module" }).program);
for (const { start, end } of edits.sort(
(a, b) => b.start - a.start,
))
code = code.slice(0, start) + moduleUrl + code.slice(end);
return { code, map: null };
},
},
],
});
try {
await bundle.write({
dir: join(compiledRoot, directory),
format: "esm",
entryFileNames: "[name].js",
chunkFileNames:
directory === "mcp"
? "[name]-[hash].js"
: (chunk) => {
// Keep lazy facades distinguishable when source basenames repeat.
if (typeof chunk.facadeModuleId !== "string")
return "[name]-[hash].js";
const path = relative(compiledRoot, chunk.facadeModuleId);
return `${path.replace(/\.js$/u, "").split(sep).join("__")}-[hash].js`;
},
paths: (id) =>
isMetadataModule(id) ? "../generatedPackageMetadata.js" : id,
minifyInternalExports: directory === "mcp",
codeSplitting: true,
strictExecutionOrder: true,
minify: false,
sourcemap: false,
});
} finally {
await bundle.close();
}
}
};
if (
process.argv[1] !== undefined &&
pathToFileURL(resolve(process.argv[1])).href === import.meta.url
)
await bundleRuntime(resolve(dirname(fileURLToPath(import.meta.url)), ".."));
+1 -10
View File
@@ -150,16 +150,7 @@ async function verifyCase(executable, entrypoint, scenario) {
phase = "public-map-assertions";
observed = item?.mappings;
assert.equal(item.status, "included");
assert.equal(item.mappings.length, expected.length);
for (const [index, coordinates] of expected.entries()) {
const mapping = item.mappings[index];
for (const [key, value] of Object.entries(coordinates))
assert.deepEqual(mapping[key], value);
// This compiler fixture has one source declaration in one wrapped leaf.
assert.equal(mapping.source_index, 0);
assert.equal(mapping.declaration_index, 0);
}
assert.equal(item.original_sources.length, 1);
assert.deepEqual(item.mappings, expected);
assert.equal(item.artifact.sha256, sha(site.map));
assert.equal(item.artifact.bytes, Buffer.byteLength(site.map));
assert.equal(item.original_sources[0].artifact.sha256, sha(site.original));
-5
View File
@@ -27,7 +27,6 @@ export const toolFamilyCatalog = (sources) => {
contracts: [
...sources.artifactContracts.ARTIFACT_TOOL_CONTRACTS,
...sources.evmContracts.EVM_TOOL_CONTRACTS,
...sources.goContracts.GO_TOOL_CONTRACTS,
...sources.wasmContracts.WASM_TOOL_CONTRACTS,
],
},
@@ -189,10 +188,6 @@ export const providerCatalog = (sources) => {
identity: sources.evmProvider.EVMOLE_PROVIDER_IDENTITY,
contracts: sources.evmContracts.EVM_TOOL_CONTRACTS,
},
{
identity: sources.goProvider.GO_BINARY_PROVIDER_IDENTITY,
contracts: sources.goContracts.GO_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.ARTIFACT_GRAPH_PROVIDER,
contracts: sources.artifactContracts.ARTIFACT_TOOL_CONTRACTS,
-2
View File
@@ -49,8 +49,6 @@ const SOURCE_PATHS = {
wasmProvider: "dist/wasm/WabtRelease.js",
evmContracts: "dist/contracts/evm/evmToolContracts.js",
evmProvider: "dist/evm/EvmoleRelease.js",
goContracts: "dist/contracts/go/goToolContracts.js",
goProvider: "dist/go/GoBinaryProvider.js",
artifactContracts: "dist/contracts/artifactToolContracts.js",
managedContracts: "dist/contracts/managed/managedToolContracts.js",
firmwareContracts: "dist/contracts/firmware/firmwareToolContracts.js",
+2 -56
View File
@@ -1,11 +1,5 @@
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
import { toolContract } from "../../dist/contracts/toolContracts.js";
import {
expandProviderCapabilities,
expandToolAvailability,
providerCapabilitySet,
} from "../../dist/contracts/toolOutputSchemaPrimitives.js";
import { compareUnicodeCodePoints } from "../../dist/domain/unicodeCodePointOrder.js";
/** Read the textual JSON projection from one MCP tool call. */
export const mcpTextValue = (result) => {
@@ -16,10 +10,6 @@ export const mcpTextValue = (result) => {
const jsonValue = (result) => JSON.parse(mcpTextValue(result));
/** Sort canonical contract names using REA's code-point ordering. */
export const sortVerifierNames = (names) =>
[...names].sort(compareUnicodeCodePoints);
/** Read the complete canonical diagnostic from an MCP tool failure. */
export const requireMcpToolError = (result) => {
if (result.isError !== true) throw new Error("Expected an MCP tool failure");
@@ -45,44 +35,6 @@ const operationResultKey = (contract) => {
return evidence ? "normalized_result" : "result";
};
/** Expand compact session metadata once at the verifier boundary. */
export const expandMcpSessionStatus = (status) => ({
...status,
capabilities: expandProviderCapabilities(status.capabilities),
analysis_provider_candidates: expandProviderCandidates(
status.analysis_provider_candidates,
),
tool_availability: expandToolAvailability(status.tool_availability),
});
/** Expand provider capability groups while retaining candidate identity/state. */
export const expandProviderCandidates = (candidates) =>
candidates.map((candidate) => ({
...candidate,
capabilities: expandProviderCapabilities(
providerCapabilitySet.array().parse(candidate.capabilities),
),
}));
/** Expand CLI provider capability groups at the same owning boundary. */
export const expandProviderOutput = (providers) => ({
...providers,
capabilities: expandProviderCapabilities(
providerCapabilitySet.array().parse(providers.capabilities),
),
analysis_provider_candidates: expandProviderCandidates(
providers.analysis_provider_candidates,
),
});
/** Expand the CLI capabilities command's compact provider descriptor groups. */
export const expandCapabilitiesOutput = (capabilities) => ({
...capabilities,
capabilities: expandProviderCapabilities(
providerCapabilitySet.array().parse(capabilities.capabilities),
),
});
/** Validate an Evidence-producing tool output and return its normalized result. */
export const requireMcpEvidenceResult = (result, operation) => {
const contract = toolContract(operation);
@@ -96,10 +48,7 @@ export const requireMcpLifecycleResult = (result, operation) => {
const contract = toolContract(operation);
if (operationResultKey(contract) !== "result")
throw new Error(`${operation} requires the Evidence result extractor`);
const projected = requireOutput(result, operation, contract).result;
return operation === "binary_session"
? expandMcpSessionStatus(projected)
: projected;
return requireOutput(result, operation, contract).result;
};
/** Project a mixed verifier call using its exact named canonical output schema. */
@@ -109,10 +58,7 @@ export const requireMcpOperationResult = (
contract = toolContract(operation),
) => {
const key = operationResultKey(contract);
const projected = requireOutput(result, operation, contract)[key];
return operation === "binary_session"
? expandMcpSessionStatus(projected)
: projected;
return requireOutput(result, operation, contract)[key];
};
/** Verify provider identity directly from the tool result. */
-1
View File
@@ -6,7 +6,6 @@ import { fileURLToPath, pathToFileURL } from "node:url";
const PURE_LAYERS = new Set(["domain", "contracts"]);
const MIGRATED_PROVIDER_ROOTS = new Set([
"evm",
"go",
"wasm",
"android",
"firmware",
+5 -12
View File
@@ -11,7 +11,6 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../../dist/domain/evidence.js";
import { analysisProfileSchema } from "../../dist/domain/analysisProfile.js";
import { nativeValueTraceSchema } from "../../dist/domain/native/nativeValueTrace.js";
import { requireMcpLifecycleResult } from "./mcp-verifier-results.mjs";
import { verifyGhidraBoundaries } from "./real-ghidra-boundaries.mjs";
const defaultEntrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url));
@@ -173,16 +172,13 @@ async function verifyNativeValueAdapters(
{ timeout: 180000 },
);
assert.notEqual(opened.isError, true, JSON.stringify(opened));
const inventoryReply = await client.callTool({
const inventory = await client.callTool({
name: "binary_session",
arguments: {},
});
const inventory = requireMcpLifecycleResult(
inventoryReply,
"binary_session",
);
assert.notEqual(inventory.isError, true, JSON.stringify(inventory));
assert.ok(
inventory.tool_availability.some(
inventory.structuredContent?.result?.tool_availability.some(
(tool) => tool.name === "trace_native_values" && tool.available,
),
"Native value tracing is unavailable in real provider discovery",
@@ -271,14 +267,11 @@ async function verifyNativeValueAdapters(
arguments: {},
});
assert.notEqual(closed.isError, true, JSON.stringify(closed));
const statusReply = await client.callTool({
const status = await client.callTool({
name: "binary_session",
arguments: {},
});
assert.equal(
requireMcpLifecycleResult(statusReply, "binary_session").open,
false,
);
assert.equal(status.structuredContent?.result?.open, false);
assert.deepEqual(
(await readdir(env.TMPDIR)).filter((name) =>
name.startsWith("rea-ghidra-"),
+2
View File
@@ -13,6 +13,8 @@ import { promisify } from "node:util";
import Ajv from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
import { verifyLegacyGhidraReferenceSnapshot } from "./ghidra-reference-snapshot-e2e.mjs";
import { verifyGhidraSnapshotLifecycle } from "./real-ghidra-snapshot-lifecycle.mjs";
import { verifyGhidraTargetAdmission } from "./real-ghidra-target-admission.mjs";
+1 -1
View File
@@ -37,7 +37,7 @@ export async function verifyHopperWorkflows(call, invalid, procedure) {
);
assert.equal(cycle.length, 2, "fixture omitted its source-owned call cycle");
const cycleGraph = await call("get_call_graph", { address: cycle[0].value });
const cycleNodes = Object.values(cycleGraph.graph).flat();
const cycleNodes = Object.values(cycleGraph).flat();
assert.equal(cycleNodes.length, 2);
assert.deepEqual(
cycleNodes.map((node) => node.address).sort(),
+5 -8
View File
@@ -2,10 +2,6 @@ import { execFile } from "node:child_process";
import { lstat } from "node:fs/promises";
import { promisify } from "node:util";
import { Ajv2020 } from "ajv/dist/2020.js";
import {
requireMcpLifecycleResult,
sortVerifierNames,
} from "./mcp-verifier-results.mjs";
/** Promisified child_process execution helper. */
export const exec = promisify(execFile);
@@ -50,10 +46,11 @@ export const verifyCompleteToolCatalog = async (client, options) => {
{ name: "binary_session", arguments: {} },
options,
);
const expanded = requireMcpLifecycleResult(status, "binary_session");
const availability = expanded.tool_availability;
const expected = sortVerifierNames(availability.map(({ name }) => name));
const observed = sortVerifierNames(listed.tools.map(({ name }) => name));
const availability = status.structuredContent?.result?.tool_availability;
if (!Array.isArray(availability))
throw new Error("packaged MCP omitted tool availability");
const expected = availability.map(({ name }) => name).sort();
const observed = listed.tools.map(({ name }) => name).sort();
if (JSON.stringify(observed) !== JSON.stringify(expected))
throw new Error(
"packaged MCP tool inventory diverged from session metadata",
-4
View File
@@ -47,10 +47,6 @@ if (await pathExists(join(root, "src"))) {
for (const path of [
"dist/main.js",
"dist/mcp/main.js",
"dist/cli-runtime/cli.js",
"dist/cli-runtime/cliOutput.js",
"dist/cli-runtime/streamedJsonOutput.js",
"dist/cli-runtime/mcpDoctor.js",
"dist/mcpDoctor.js",
"dist/cli.js",
"dist/cliOutput.js",
+6 -11
View File
@@ -33,12 +33,8 @@ if (
const runtimeFiles = isMcpMode
? ["dist/mcp/main.js"]
: isMcpDoctorMode
? ["dist/mcp/main.js", "dist/cli-runtime/mcpDoctor.js"]
: [
"dist/cli-runtime/cli.js",
"dist/cli-runtime/cliOutput.js",
"dist/cli-runtime/streamedJsonOutput.js",
];
? ["dist/mcp/main.js", "dist/mcpDoctor.js"]
: ["dist/cli.js", "dist/cliOutput.js", "dist/cli/streamedJsonOutput.js"];
if (!(await compiledRuntimeExists(runtimeFiles))) {
process.stderr.write(
@@ -49,8 +45,7 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
const { runEntrypoint } = await import("../dist/mcp/main.js");
await runEntrypoint();
} else if (isMcpDoctorMode) {
const { runProductionMcpDoctorCli } =
await import("../dist/cli-runtime/mcpDoctor.js");
const { runProductionMcpDoctorCli } = await import("../dist/mcpDoctor.js");
const result = await runProductionMcpDoctorCli(args.slice(2), {
dispatcherPath: fileURLToPath(import.meta.url),
packageRoot,
@@ -58,14 +53,14 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
process.stdout.write(result.output);
process.exitCode = result.exitCode;
} else {
const { createCli } = await import("../dist/cli-runtime/cli.js");
const { createCli } = await import("../dist/cli.js");
const { createStreamedCliJsonOutput } =
await import("../dist/cli-runtime/streamedJsonOutput.js");
await import("../dist/cli/streamedJsonOutput.js");
const {
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
validateCliOutputArguments,
} = await import("../dist/cli-runtime/cliOutput.js");
} = await import("../dist/cliOutput.js");
const outputArguments = validateCliOutputArguments(args);
if (!outputArguments.ok) {
process.stdout.write(renderCliOutputArgumentError(outputArguments));
-16
View File
@@ -401,22 +401,6 @@ class WebsiteTranslationChecks(unittest.TestCase):
server.shutdown()
worker.join()
def test_every_reading_page_offers_light_and_dark_theme_colors(self):
site = ROOT / "website/_site"
english_pages = reading_pages(ROOT / "website/public")
self.assertTrue(english_pages)
for language in languages(ROOT):
code = language["code"]
for original in english_pages:
relative = original.relative_to(ROOT / "website/public")
for path in {original, site / relative if code == "en" else site / code / relative}:
with self.subTest(language=code, page=str(path.relative_to(ROOT))):
doc = Document(path.read_text())
media = sorted(node.attrs.get("media") for node in doc.nodes if node.tag == "meta" and node.attrs.get("name") == "theme-color")
self.assertEqual(media, ["(prefers-color-scheme: dark)", "(prefers-color-scheme: light)"])
styles = (ROOT / "website/public/assets/styles.css").read_text()
self.assertTrue("@media (prefers-color-scheme: dark)" in styles, "styles.css has no dark color scheme block")
if __name__ == "__main__":
unittest.main()
+67 -80
View File
@@ -1,5 +1,7 @@
import { createHash } from "node:crypto";
import { readFile, stat } from "node:fs/promises";
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { ApktoolResourceAnalysisService } from "../dist/application/apktool/ApktoolResourceAnalysisService.js";
import { createApktoolResourceAnalysisProvider } from "../dist/composition/apktool.js";
@@ -9,8 +11,8 @@ if (options.help) {
process.stdout.write(
"Usage: npm run verify:apktool [-- --apk PATH]\n" +
"Requires a caller-selected apktool launcher (REA_APKTOOL_COMMAND or\n" +
"PATH) and a Java runtime. Without --apk, only client availability is\n" +
"checked. Supply a local APK to verify decoding; nothing is downloaded or built.\n",
"PATH) and a Java runtime. Without --apk, a small signed probe APK is\n" +
"expected at the path given by --apk; nothing is downloaded or built.\n",
);
process.exit(0);
}
@@ -36,80 +38,70 @@ const run = async (operation, input) => {
return result.value.normalized_result;
};
try {
const client = await run("inspect_apktool_client", {});
if (client !== null) {
check(
"apktool launcher reports a version",
typeof client.client.apktool_version === "string" &&
client.client.apktool_version !== "",
`command=${client.client.command} version=${client.client.apktool_version}`,
);
}
const client = await run("inspect_apktool_client", {});
if (client !== null) {
check(
"apktool launcher reports a version",
typeof client.client.apktool_version === "string" &&
client.client.apktool_version !== "",
`command=${client.client.command} version=${client.client.apktool_version}`,
);
}
if (options.apk !== null) {
const decoded = await run("decode_android_resources", {
path: options.apk,
include_strings: true,
});
if (decoded !== null) {
const bytes = await stat(options.apk).then((info) => info.size);
const digest = createHash("sha256")
.update(await readFile(options.apk))
.digest("hex");
check(
"decoded target identity matches the file on disk",
decoded.target.bytes === bytes && decoded.target.sha256 === digest,
`${bytes} bytes`,
);
check(
"metadata carries version and SDK facts",
typeof decoded.metadata.version_name === "string" &&
decoded.metadata.min_sdk_version !== null &&
decoded.metadata.target_sdk_version !== null,
`version=${String(decoded.metadata.version_name)} sdk=${String(decoded.metadata.min_sdk_version)}..${String(decoded.metadata.target_sdk_version)}`,
);
check(
"decoded manifest names the package",
typeof decoded.metadata.package_name === "string" &&
decoded.manifest !== null &&
decoded.manifest.includes(decoded.metadata.package_name),
String(decoded.metadata.package_name),
);
check(
"string resources projected",
decoded.strings.length > 0,
`${decoded.strings.length} string(s)`,
);
if (decoded.locales.length > 0) {
const locale = await run("decode_android_resources", {
path: options.apk,
include_strings: true,
locale: decoded.locales[0],
});
if (locale !== null)
check(
`locale table projected for ${String(decoded.locales[0])}`,
Array.isArray(locale.strings),
`${locale.strings.length} string(s)`,
);
}
if (options.apk !== null) {
const decoded = await run("decode_android_resources", {
path: options.apk,
include_strings: true,
});
if (decoded !== null) {
const bytes = await stat(options.apk).then((info) => info.size);
const digest = createHash("sha256")
.update(await readFile(options.apk))
.digest("hex");
check(
"decoded target identity matches the file on disk",
decoded.target.bytes === bytes && decoded.target.sha256 === digest,
`${bytes} bytes`,
);
check(
"metadata carries version and SDK facts",
typeof decoded.metadata.version_name === "string" &&
decoded.metadata.min_sdk_version !== null &&
decoded.metadata.target_sdk_version !== null,
`version=${String(decoded.metadata.version_name)} sdk=${String(decoded.metadata.min_sdk_version)}..${String(decoded.metadata.target_sdk_version)}`,
);
check(
"decoded manifest names the package",
typeof decoded.metadata.package_name === "string" &&
decoded.manifest !== null &&
decoded.manifest.includes(decoded.metadata.package_name),
String(decoded.metadata.package_name),
);
check(
"string resources projected",
decoded.strings.length > 0,
`${decoded.strings.length} string(s)`,
);
if (decoded.locales.length > 0) {
const locale = await run("decode_android_resources", {
path: options.apk,
include_strings: true,
locale: decoded.locales[0],
});
if (locale !== null)
check(
`locale table projected for ${String(decoded.locales[0])}`,
Array.isArray(locale.strings),
`${locale.strings.length} string(s)`,
);
}
}
} catch (cause) {
failures += 1;
process.stdout.write(
`[FAIL] verifier execution — ${cause instanceof Error ? cause.message : String(cause)}\n`,
);
} finally {
try {
await provider.close();
} catch (cause) {
failures += 1;
process.stdout.write(
`[FAIL] provider cleanup — ${cause instanceof Error ? cause.message : String(cause)}\n`,
);
}
}
try {
await provider.close();
} catch {
// The provider owns per-call workspaces only; closing is best-effort.
}
if (failures > 0) {
@@ -124,12 +116,7 @@ function parseArguments(argv) {
const argument = argv[index];
if (argument === "--help" || argument === "-h") parsed.help = true;
else if (argument === "--apk") {
const path = argv[index + 1];
if (path === undefined) {
process.stderr.write("--apk requires a local APK path\n");
process.exit(2);
}
parsed.apk = path;
parsed.apk = argv[index + 1] ?? null;
index += 1;
} else {
process.stderr.write(`unknown argument: ${argument}\n`);
-23
View File
@@ -662,16 +662,6 @@ try {
bytes.writeBigUInt64LE(8n, header + 56);
return [`undersized-entry-${type}`, bytes, "invalid_input"];
});
const nonintegralSymbolEntries = ["SHT_SYMTAB", "SHT_DYNSYM"].map((type) => {
const section = protectedReport.sections.find((item) => item.type === type);
assert.notEqual(section, undefined);
const bytes = Buffer.from(sectionBearingBytes);
const header = Number(BigInt(section.header_location.offset));
const size = bytes.readBigUInt64LE(header + 32);
assert.ok(size + 1n <= BigInt(bytes.length) - BigInt(section.offset));
bytes.writeBigUInt64LE(size + 1n, header + 32);
return [`nonintegral-entry-count-${type}`, bytes, "invalid_input"];
});
const relocationSection = relocatable.sections.find(
(item) => item.type === "SHT_RELA",
);
@@ -687,17 +677,6 @@ try {
bytes.writeBigUInt64LE(8n, header + 56);
return [`undersized-entry-${type}`, bytes, "invalid_input"];
});
const nonintegralRelocationEntries = [
["RELA", 4, 24],
["REL", 9, 16],
].map(([type, sectionType, entrySize]) => {
const bytes = Buffer.from(object);
const header = Number(BigInt(relocationSection.header_location.offset));
bytes.writeUInt32LE(sectionType, header + 4);
bytes.writeBigUInt64LE(BigInt(entrySize + 1), header + 32);
bytes.writeBigUInt64LE(BigInt(entrySize), header + 56);
return [`nonintegral-entry-count-${type}`, bytes, "invalid_input"];
});
const invalidRelocationReferences = [
["missing-symbol-table", relocatable.sections.length + 100],
["wrong-symbol-table-kind", 1],
@@ -1087,9 +1066,7 @@ try {
],
...zeroSymbolEntries,
...undersizedSymbolEntries,
...nonintegralSymbolEntries,
...undersizedRelocationEntries,
...nonintegralRelocationEntries,
...invalidRelocationReferences,
["unterminated-dynamic-segment", shortDynamic, "invalid_input"],
["unterminated-interpreter", unterminatedInterpreter, "invalid_input"],
+19 -77
View File
@@ -5,19 +5,18 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import Ajv2020 from "ajv/dist/2020.js";
import { CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
import {
TOOL_CONTRACTS,
toolContract,
} from "../dist/contracts/toolContracts.js";
import { exec } from "./lib/verify-package-core.mjs";
import {
expandMcpSessionStatus,
sortVerifierNames,
} from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { createAnthropicModelFixture } from "./verify/clients/anthropic-model-fixture.mjs";
assert(
process.platform !== "win32",
"Native Claude Code verifier requires POSIX; Windows is unverified",
);
const repo = fileURLToPath(new URL("..", import.meta.url));
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
const command = process.env.REA_VERIFY_CLAUDE_COMMAND ?? "claude";
@@ -47,13 +46,8 @@ try {
await exec(command, ["--version"], { env: environment, timeout: 30_000 })
).stdout.trim();
} catch (cause) {
// npm installs put only a claude.cmd shim on PATH, which Node does not spawn without a shell.
const windowsHint =
process.platform === "win32"
? " On Windows, select claude.exe; npm installs provide it under node_modules/@anthropic-ai/claude-code/bin."
: "";
throw new Error(
`Claude Code preflight failed for ${command}; install Claude Code or set REA_VERIFY_CLAUDE_COMMAND.${windowsHint} ${String(cause)}`,
`Claude Code preflight failed for ${command}; install Claude Code or set REA_VERIFY_CLAUDE_COMMAND. ${String(cause)}`,
);
}
const target = join(lab, "fixture α测试");
@@ -63,8 +57,6 @@ await writeFile(
'export function result() { return { clientCompatibility: "REA_CLIENT_FIXTURE", count: 7 }; }\n',
);
let skillRequested = false;
let sessionRequested = false;
let sessionStatusSeen = false;
let skillLoaded = false;
let expectedSkillBody;
let evidenceSeen = false;
@@ -80,7 +72,7 @@ const containsText = (value, text) => {
return Object.values(value).some((nested) => containsText(nested, text));
return false;
};
const extractInlineResult = (value, matches) => {
const extractEvidence = (value) => {
if (typeof value === "string") {
try {
// Native Claude appends this resource hint to tool results. Original bodies remain intact in request artifacts.
@@ -88,16 +80,20 @@ const extractInlineResult = (value, matches) => {
/\n\n<system-reminder>\n<total_tokens>\d+ tokens left<\/total_tokens>\n<\/system-reminder>$/u,
"",
);
return extractInlineResult(JSON.parse(json), matches);
return extractEvidence(JSON.parse(json));
} catch {
return undefined;
}
}
if (value && typeof value === "object") {
if (matches(value)) return value;
if (
typeof value.evidence_id === "string" &&
value.normalized_result?.graph?.nodes?.length > 0
)
return value;
for (const nested of Object.values(value)) {
const result = extractInlineResult(nested, matches);
if (result) return result;
const evidence = extractEvidence(nested);
if (evidence) return evidence;
}
}
return undefined;
@@ -128,12 +124,10 @@ const fixture = await createAnthropicModelFixture({
0,
-"analyze_javascript_application".length,
);
const expected = sortVerifierNames(
TOOL_CONTRACTS.map((t) => prefix + t.name),
);
const expected = TOOL_CONTRACTS.map((t) => prefix + t.name).sort();
const reaTools = tools.filter((t) => t.name.startsWith(prefix));
assert.deepEqual(
sortVerifierNames(reaTools.map((t) => t.name)),
reaTools.map((t) => t.name).sort(),
expected,
"Claude must forward the complete REA catalog after connection",
);
@@ -151,61 +145,11 @@ const fixture = await createAnthropicModelFixture({
schemasChecked++;
}
}
if (mode === "call" && !sessionStatusSeen) {
const observedStatus = extractInlineResult(
body.messages,
(value) =>
Boolean(value.result?.server_identity) &&
Array.isArray(value.result?.tool_availability?.groups),
);
if (observedStatus) {
const parsedStatus =
toolContract("binary_session").outputSchema.parse(observedStatus);
const status = {
...parsedStatus,
result: expandMcpSessionStatus(parsedStatus.result),
};
assert.deepEqual(
status.result.server_identity.catalog,
CATALOG_IDENTITY,
"binary_session must return the exact compact catalog counts and digests",
);
assert.equal(
Object.hasOwn(status.result.server_identity.catalog, "tools"),
false,
"binary_session must not inline the duplicated canonical tool catalog",
);
assert.deepEqual(
sortVerifierNames(
status.result.tool_availability.map((entry) => entry.name),
),
sortVerifierNames(TOOL_CONTRACTS.map((contract) => contract.name)),
"binary_session must retain complete canonical tool availability",
);
sessionStatusSeen = true;
} else {
assert(
!sessionRequested,
"Claude must return the inline binary_session result before continuing",
);
const session = tools.find(
(tool) => tool.name === `${prefix}binary_session`,
);
assert(session, "Claude must expose the REA binary_session tool");
sessionRequested = true;
return call(session.name, {});
}
}
skillLoaded ||=
expectedSkillBody !== undefined &&
containsText(body.messages, expectedSkillBody) &&
containsText(body.messages, skillDirectory);
const observed = extractInlineResult(
body.messages,
(value) =>
typeof value.evidence_id === "string" &&
value.normalized_result?.graph?.nodes?.length > 0,
);
const observed = extractEvidence(body.messages);
if (observed) {
const evidence = toolContract(
"analyze_javascript_application",
@@ -239,7 +183,7 @@ const fixture = await createAnthropicModelFixture({
await writeFile(join(lab, "claude-evidence.json"), bytes);
evidenceSeen = true;
}
if (mode === "chat" || (sessionStatusSeen && evidenceSeen)) {
if (mode === "chat" || evidenceSeen) {
finalSeen = true;
return [{ type: "text", text: "REA_CLIENT_COMPATIBILITY_OK" }];
}
@@ -349,7 +293,6 @@ try {
"Skill",
"WaitForMcpServers",
"mcp__rea__analyze_javascript_application",
...(mode === "call" ? ["mcp__rea__binary_session"] : []),
],
},
}),
@@ -380,7 +323,7 @@ try {
assert.equal(parsed.result, "REA_CLIENT_COMPATIBILITY_OK");
assert.deepEqual(parsed.permission_denials, []);
assert(finalSeen);
if (mode === "call") assert(evidenceSeen && skillLoaded && sessionStatusSeen);
if (mode === "call") assert(evidenceSeen && skillLoaded);
} catch (cause) {
failure = cause;
if (typeof cause?.stdout === "string")
@@ -411,7 +354,6 @@ try {
schemasChecked,
pendingServerWaits,
evidenceSeen,
sessionStatusSeen,
skillLoaded,
artifactDigest,
finalSeen,
+29 -91
View File
@@ -5,12 +5,11 @@ import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { Ajv2020 } from "ajv/dist/2020.js";
import { parse as parseYaml } from "yaml";
import {
TOOL_CONTRACTS,
toolContract,
} from "../dist/contracts/toolContracts.js";
import { exec, pathExists } from "./lib/verify-package-core.mjs";
import { exec } from "./lib/verify-package-core.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs";
@@ -33,7 +32,6 @@ const skillPath = join(
"SKILL.md",
);
const cli = join(runtimeRoot, "scripts", "rea.mjs");
const configPath = join(profile, "cordis.patch.yml");
const verifier = createVerifierRun();
await Promise.all([
mkdir(account),
@@ -68,32 +66,32 @@ const clientVersion = (
timeout: 30_000,
})
).stdout.trim();
const lifecycle = [];
const runRea = async (args, artifact) => {
for (const args of [
["setup", "--skill", "--dry-run"],
["setup", "--skill", "--yes"],
]) {
const result = await exec(process.execPath, [cli, ...args, "--json"], {
env: environment,
cwd: workspace,
timeout: 60_000,
});
await writeFile(join(lab, `${artifact}.json`), result.stdout);
return JSON.parse(result.stdout);
};
const patches = (text) =>
parseYaml(text, {
customTags: [
{
tag: "tag:yaml.org,2002:js",
resolve: (source) => ({ __jsExpr: source }),
},
],
});
const withoutRea = (text) =>
patches(text).flatMap((op) => {
if (op.id === "rea-mcp") return [];
if (!Array.isArray(op.insert)) return [op];
const insert = op.insert.filter((entry) => entry.id !== "rea-mcp");
return insert.length === 0 ? [] : [{ ...op, insert }];
});
const parsed = JSON.parse(result.stdout);
assert.equal(parsed.status, args.includes("--dry-run") ? "planned" : "ready");
if (args.includes("--dry-run")) {
assert.equal(parsed.plannedActions.length, 1);
assert.equal(parsed.plannedActions[0].kind, "install_skill");
assert.equal(parsed.plannedActions[0].target, dirname(skillPath));
}
await writeFile(
join(lab, args.includes("--dry-run") ? "setup-plan.json" : "setup.json"),
result.stdout,
);
}
assert(
(await readFile(skillPath, "utf8")).includes(
"name: reverse-engineer-anything",
),
);
let catalog;
let patternCount = 0;
@@ -249,9 +247,10 @@ const fixture = await createOpenAiModelFixture({
});
let failure;
try {
// Seed model settings and a stale registration; the public setup workflow
// must produce the registration this native client actually boots.
const original = `
// Use the client's native Cordis patch DSL; generic `rea setup --client` does not edit it.
await writeFile(
join(profile, "cordis.patch.yml"),
`
- id: llm-pi-ai
config:
providers:
@@ -276,59 +275,13 @@ try {
config:
serverName: rea
transport: stdio
command: stale-launcher
args: [mcp]
command: ${JSON.stringify(process.execPath)}
args: ${JSON.stringify([cli, "mcp"])}
cwd: !!js process.cwd()
env:
REA_PROCESS_RUN_ID: ${JSON.stringify(verifier.run_id)}
`;
await writeFile(configPath, original);
const planned = await runRea(
["setup", "--client", "dsh", "--dry-run"],
"setup-plan",
`,
);
assert.equal(planned.status, "planned");
assert(planned.plannedActions.some((action) => action.target === configPath));
assert(
planned.plannedActions.some(
(action) => action.target === dirname(skillPath),
),
);
assert(
planned.plannedActions.every(
(action) =>
action.target === configPath || action.target === dirname(skillPath),
),
);
assert.equal(await readFile(configPath, "utf8"), original);
assert.equal(await pathExists(`${configPath}.rea.backup`), false);
lifecycle.push("dry-run");
const setup = await runRea(["setup", "--client", "dsh", "--yes"], "setup");
assert.equal(setup.status, "ready");
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
const configured = await readFile(configPath, "utf8");
assert.deepEqual(withoutRea(configured), withoutRea(original));
assert(configured.includes("!!js process.cwd()"));
assert(
(await readFile(skillPath, "utf8")).includes(
"name: reverse-engineer-anything",
),
);
lifecycle.push("setup");
const repeated = await runRea(
["setup", "--client", "dsh", "--yes"],
"setup-repeat",
);
assert.deepEqual(repeated.appliedActions, []);
assert.equal(await readFile(configPath, "utf8"), configured);
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
lifecycle.push("idempotent-setup");
const doctor = await runRea(
["doctor", "--client", "dsh", "--skill"],
"doctor",
);
assert.equal(doctor.healthy, true);
lifecycle.push("doctor");
const execution = exec(
command,
[
@@ -351,20 +304,6 @@ try {
assert.equal(fixture.failure, undefined);
assert(finalSeen && result.stdout.includes("REA_CLIENT_COMPATIBILITY_OK"));
if (mode === "call") assert(evidenceSeen && skillLoaded);
lifecycle.push(mode === "call" ? "native-tool-invocation" : "native-chat");
const uninstall = await runRea(["uninstall"], "uninstall");
assert.equal(uninstall.status, "complete");
assert(
uninstall.items.some(
(item) => item.name === "dsh" && item.status === "removed",
),
);
assert.deepEqual(
patches(await readFile(configPath, "utf8")),
withoutRea(original),
);
assert.equal(await pathExists(skillPath), false);
lifecycle.push("uninstall");
} catch (cause) {
failure = cause;
if (typeof cause?.stdout === "string")
@@ -377,7 +316,6 @@ try {
client: "deepseek-harness",
clientVersion,
mode,
lifecycle,
passed: failure === undefined,
artifacts: lab,
runtimeRoot,
+12 -19
View File
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve, sep } from "node:path";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import Ajv2020 from "ajv/dist/2020.js";
import {
@@ -13,18 +13,14 @@ import { exec } from "./lib/verify-package-core.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { createGeminiModelFixture } from "./verify/clients/gemini-model-fixture.mjs";
assert(
process.platform !== "win32",
"Native Gemini verifier requires POSIX; Windows is unverified",
);
const repo = fileURLToPath(new URL("..", import.meta.url));
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
const command = process.env.REA_VERIFY_GEMINI_COMMAND ?? "gemini";
const mode = process.argv[2] ?? "call";
// npm installs of Gemini CLI provide a JavaScript entry point; on Windows only a gemini.cmd shim reaches
// PATH, which Node does not spawn without a shell, so a selected .js entry runs under this Node. It is
// resolved once, because the preflight runs from the caller's directory and the client from the workspace.
const entryPoint = /\.(c|m)?js$/iu.test(command) ? resolve(command) : undefined;
const launch = (args) =>
entryPoint === undefined
? [command, args]
: [process.execPath, [entryPoint, ...args]];
assert(
["chat", "call"].includes(mode),
"Usage: verify-gemini-client.mjs [chat|call]",
@@ -64,15 +60,11 @@ const environment = {
let clientVersion;
try {
clientVersion = (
await exec(...launch(["--version"]), { env: environment, timeout: 30_000 })
await exec(command, ["--version"], { env: environment, timeout: 30_000 })
).stdout.trim();
} catch (cause) {
const windowsHint =
process.platform === "win32"
? " On Windows, select the JavaScript entry point; npm installs provide it as node_modules/@google/gemini-cli/bundle/gemini.js."
: "";
throw new Error(
`Gemini preflight failed for ${command}; install Gemini CLI or set REA_VERIFY_GEMINI_COMMAND.${windowsHint} ${String(cause)}`,
`Gemini preflight failed for ${command}; install Gemini CLI or set REA_VERIFY_GEMINI_COMMAND. ${String(cause)}`,
);
}
const original = JSON.stringify(
@@ -111,11 +103,11 @@ for (const args of [
assert(parsed.plannedActions.some((a) => a.target === configPath));
assert(
parsed.plannedActions.some((a) =>
a.target.startsWith(join(profile, ".agents", "skills") + sep),
a.target.startsWith(join(profile, ".agents", "skills") + "/"),
),
);
assert(
parsed.plannedActions.every((a) => a.target.startsWith(profile + sep)),
parsed.plannedActions.every((a) => a.target.startsWith(profile + "/")),
);
}
}
@@ -308,7 +300,8 @@ const fixture = await createGeminiModelFixture({
let failure;
try {
const response = await exec(
...launch([
command,
[
"--model",
"gemini-2.5-flash",
"--approval-mode",
@@ -321,7 +314,7 @@ try {
mode === "chat"
? "Say hello briefly."
: `Use REA to analyze the JavaScript directory ${target}. Report the result.`,
]),
],
{
env: {
...environment,
-518
View File
@@ -1,518 +0,0 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { isUtf8 } from "node:buffer";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../dist/domain/evidence.js";
import { goBinarySchema } from "../dist/domain/go/goBinary.js";
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
import { mcpTextValue } from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
const execute = promisify(execFile);
const compiler = process.env.REA_VERIFY_GO_COMMAND ?? "go";
if (process.env.REA_VERIFY_GO_COMMAND !== undefined && !isAbsolute(compiler))
throw new Error(
"REA_VERIFY_GO_COMMAND must be an absolute Go compiler path.",
);
let compilerVersion = null;
const run = createVerifierRun();
const root = await PrivateRuntimeRoot.create({ prefix: "rea-go-verifier-" });
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
const environment = Object.fromEntries(
Object.entries(process.env).filter(([, value]) => typeof value === "string"),
);
delete environment.HOPPER_TARGET_PATH;
const goEnvironment = {
...environment,
CGO_ENABLED: "0",
GOCACHEPROG: "",
GO111MODULE: "on",
GOENV: "off",
GOFLAGS: "",
GOMAXPROCS: "2",
GOTOOLCHAIN: "local",
GOWORK: "off",
GOPROXY: "off",
GOSUMDB: "off",
GOPATH: join(root.path, "gopath"),
GOCACHE: join(root.path, "cache"),
GOMODCACHE: join(root.path, "modules"),
GOTMPDIR: join(root.path, "tmp"),
// Go's own verifier seam isolates telemetry; no global configuration is changed.
TEST_TELEMETRY_DIR: join(root.path, "telemetry"),
};
const client = new Client({ name: "go-binary-verifier", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: environment,
stderr: "pipe",
});
const targets = [
["linux", "amd64", "elf", 64, "little"],
["linux", "386", "elf", 32, "little"],
["linux", "arm64", "elf", 64, "little"],
["linux", "ppc64", "elf", 64, "big"],
["linux", "mips", "elf", 32, "big"],
["windows", "amd64", "pe", 64, "little"],
["windows", "386", "pe", 32, "little"],
["darwin", "amd64", "macho", 64, "little"],
["darwin", "arm64", "macho", 64, "little"],
];
const verified = [];
const omittedCoverage = [];
const failures = [];
try {
await prepareFixture();
try {
compilerVersion = (
await execute(compiler, ["version"], {
cwd: root.path,
env: goEnvironment,
timeout: 10_000,
})
).stdout.trim();
} catch (cause) {
throw new Error(
"verify:go:binary requires Go on PATH or absolute REA_VERIFY_GO_COMMAND; Go is only a fixture producer, not a REA runtime dependency.",
{ cause },
);
}
const host = (await go(["env", "GOHOSTOS", "GOHOSTARCH"])).stdout
.trim()
.split("\n");
const oracle = join(
root.path,
host[0] === "windows" ? "buildinfo-oracle.exe" : "buildinfo-oracle",
);
await go(
["build", "-trimpath", "-buildvcs=false", "-o", oracle, "./oracle"],
{ GOOS: host[0], GOARCH: host[1] },
);
await client.connect(transport);
for (const target of targets) {
const [os, arch] = target;
const path = join(
root.path,
`${os}-${arch}${os === "windows" ? ".exe" : ""}`,
);
await go(
[
"build",
"-trimpath",
"-buildvcs=false",
"-ldflags=-s -w",
"-o",
path,
".",
],
{ GOOS: os, GOARCH: arch },
);
await verify(path, target, oracle);
if (os === "linux" && arch === "amd64") {
const sectionless = join(root.path, "linux-amd64-sectionless");
const bytes = Buffer.from(await readFile(path));
bytes.fill(0, 40, 48); // ELF64 e_shoff.
bytes.fill(0, 58, 64); // e_shentsize, e_shnum and e_shstrndx.
await writeFile(sectionless, bytes);
await verify(sectionless, target, oracle);
}
}
if (process.platform === "win32")
omittedCoverage.push(
"Compiler-produced non-UTF-8 argv strings were not verified on Windows; Windows argv cannot preserve arbitrary byte arguments.",
);
else {
const path = join(root.path, "linux-amd64-byte-strings");
const expected = JSON.parse(
(
await execute(oracle, ["--build-byte-fixture", compiler, path], {
cwd: root.path,
env: { ...goEnvironment, GOOS: "linux", GOARCH: "amd64" },
timeout: 120_000,
maxBuffer: 1024 * 1024,
})
).stdout,
);
await verify(path, targets[0], oracle, expected);
}
} catch (cause) {
failures.push(cause);
} finally {
for (const close of [
() => client.close(),
() => transport.close(),
() => root.close(),
]) {
try {
await close();
} catch (cause) {
failures.push(cause);
}
}
}
const verifier = await completeVerifierRun(run);
try {
await assert.rejects(readFile(join(root.path, "go.mod")), { code: "ENOENT" });
if (verifier.process_lineage.status === "verified")
assert.deepEqual(verifier.process_lineage.descendants, []);
} catch (cause) {
failures.push(cause);
}
if (failures.length > 0) {
console.error(
JSON.stringify(
{
status: "failed",
compiler: compilerVersion,
verified,
omitted_coverage: omittedCoverage,
verifier,
},
null,
2,
),
);
throw new AggregateError(
failures,
"Real Go metadata verification failed; cleanup failures are retained.",
);
}
console.log(
JSON.stringify(
{
status: "passed",
compiler: compilerVersion,
binaries: verified.length,
public_cases: verified.length * 2,
verified,
omitted_coverage: omittedCoverage,
fixture_execution:
"not-requested; only the Go fixture producer and test-owned buildinfo oracle/helper were executed",
verifier,
},
null,
2,
),
);
async function go(args, overrides = {}) {
return execute(compiler, args, {
cwd: root.path,
env: { ...goEnvironment, ...overrides },
timeout: 120_000,
maxBuffer: 1024 * 1024,
});
}
async function prepareFixture() {
await mkdir(join(root.path, "tmp"));
await mkdir(join(root.path, "telemetry"));
await writeFile(join(root.path, "telemetry", "mode"), "off\n");
await mkdir(join(root.path, "dependency"));
await mkdir(join(root.path, "oracle"));
await writeFile(
join(root.path, "go.mod"),
"module example.test/rea-go-fixture\n\ngo 1.20\n\nrequire example.test/dependency v1.2.3\nreplace example.test/dependency => ./dependency\n",
);
await writeFile(
join(root.path, "dependency", "go.mod"),
"module example.test/dependency\n\ngo 1.20\n",
);
await writeFile(
join(root.path, "dependency", "dependency.go"),
'package dependency\nfunc Value() string { return "REA fixture" }\n',
);
await writeFile(
join(root.path, "main.go"),
'package main\nimport ("fmt"; "example.test/dependency")\nfunc main() { fmt.Println(dependency.Value()) }\n',
);
await writeFile(
join(root.path, "oracle", "main.go"),
String.raw`package main
import (
"context"
"debug/buildinfo"
"encoding/base64"
"encoding/json"
"os"
"os/exec"
"time"
"unicode/utf8"
)
func main() {
byteFixture := len(os.Args) == 4 && os.Args[1] == "--build-byte-fixture"
path := os.Args[1]
if byteFixture {
path = os.Args[3]
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, os.Args[2], "build", "-buildvcs=false",
"-tags=" + string([]byte{0xff}),
"-ldflags=-X runtime.buildVersion=custom" + string([]byte{0xff}),
"-o", path, ".")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { panic(err) }
}
info, err := buildinfo.ReadFile(path)
if err != nil { panic(err) }
var result interface{} = info
if byteFixture {
settings := make([]map[string]string, 0)
byteSettings := make([]map[string]string, 0)
for _, setting := range info.Settings {
if utf8.ValidString(setting.Key) && utf8.ValidString(setting.Value) {
settings = append(settings, map[string]string{"Key": setting.Key, "Value": setting.Value})
} else {
byteSettings = append(byteSettings, map[string]string{
"KeyBytesBase64": base64.StdEncoding.EncodeToString([]byte(setting.Key)),
"ValueBytesBase64": base64.StdEncoding.EncodeToString([]byte(setting.Value)),
})
}
}
result = map[string]interface{}{
"GoVersion": nil,
"GoVersionBytesBase64": base64.StdEncoding.EncodeToString([]byte(info.GoVersion)),
"Path": info.Path, "Main": info.Main, "Deps": info.Deps,
"Settings": settings, "ByteSettings": byteSettings,
}
}
if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { panic(err) }
}
`,
);
}
async function inspect(mode, path) {
let envelope;
if (mode === "cli") {
const { stdout } = await execute(
process.execPath,
[entrypoint, "inspect-go-binary", path, "--json"],
{ env: environment, timeout: 30_000, maxBuffer: 8 * 1024 * 1024 },
);
envelope = JSON.parse(stdout);
} else {
const response = await client.callTool(
{ name: "inspect_go_binary", arguments: { path } },
{ timeout: 30_000 },
);
assert.notEqual(response.isError, true, mcpTextValue(response));
envelope = JSON.parse(mcpTextValue(response));
}
const evidence = parseEvidence(envelope);
assert.equal(evidence.subject.local_path, path);
const report = goBinarySchema.parse(evidence.normalized_result);
assert.equal(evidence.subject.digest.sha256, report.artifact.sha256);
return { evidence, report };
}
async function verify(path, target, oracle, byteExpected = null) {
const bytes = await readFile(path);
const expected =
byteExpected ??
JSON.parse(
(
await execute(oracle, [path], {
timeout: 10_000,
maxBuffer: 1024 * 1024,
})
).stdout,
);
const reports = [];
for (const mode of ["cli", "mcp"]) {
const { evidence, report } = await inspect(mode, path);
assert.equal(report.format, target[2]);
assert.equal(report.architecture, target[1]);
assert.equal(
evidence.subject.architecture,
{ amd64: "x86_64", 386: "x86", arm64: "arm64", mips: "mips" }[
target[1]
] ?? null,
);
assert.equal(report.bits, target[3]);
assert.equal(report.byte_order, target[4]);
assert.equal(report.artifact.path, path);
assert.equal(report.artifact.bytes, bytes.length);
assert.equal(
report.artifact.sha256,
createHash("sha256").update(bytes).digest("hex"),
);
const info = report.build_info;
assert.notEqual(info, null);
assert.equal(info.go_version, expected.GoVersion);
assert.equal(info.module.path, expected.Path);
assert.deepEqual(
normalizeObservedModule(info.module.main),
normalizeModule(expected.Main),
);
assert.deepEqual(
info.module.dependencies.map(normalizeObservedModule),
(expected.Deps ?? []).map(normalizeModule),
);
assert.deepEqual(
info.module.settings,
(expected.Settings ?? []).map(({ Key, Value }) => ({
key: Key,
value: Value,
})),
);
assert.equal(info.module.complete, byteExpected === null);
if (byteExpected === null) {
assert.deepEqual(info.module.unparsed_lines, []);
assert.deepEqual(info.module.unparsed_line_bytes_base64, []);
} else verifyByteStrings(info, expected);
verifySource(info, bytes);
reports.push(report);
assert.deepEqual(
await readFile(path),
bytes,
`${mode} changed source bytes`,
);
}
assert.deepEqual(
reports[0],
reports[1],
"Public CLI and MCP projections disagree",
);
verified.push({
target: `${target[0]}/${target[1]}`,
file:
byteExpected === null
? path.endsWith("sectionless")
? "sectionless"
: "stripped"
: "byte-valued-strings",
go_version: expected.GoVersion,
...(byteExpected === null
? {}
: { go_version_bytes_base64: expected.GoVersionBytesBase64 }),
});
}
function verifyByteStrings(info, expected) {
const rawByte = Buffer.from([0xff]);
const compilerBytes = Buffer.concat([Buffer.from("custom"), rawByte]);
const ldflagsBytes = Buffer.concat([
Buffer.from("-X runtime.buildVersion=custom"),
rawByte,
]);
assert.equal(info.go_version, null);
assert.equal(info.module_text, null);
assert.equal(info.go_version_bytes_base64, compilerBytes.toString("base64"));
assert.equal(info.go_version_bytes_base64, expected.GoVersionBytesBase64);
assert.deepEqual(expected.ByteSettings, [
{
KeyBytesBase64: Buffer.from("-ldflags").toString("base64"),
ValueBytesBase64: ldflagsBytes.toString("base64"),
},
{
KeyBytesBase64: Buffer.from("-tags").toString("base64"),
ValueBytesBase64: rawByte.toString("base64"),
},
]);
assert.deepEqual(info.module.unparsed_lines, [
'build\t-ldflags="-X runtime.buildVersion=custom\\xff"',
]);
assert.deepEqual(info.module.unparsed_line_bytes_base64, [
Buffer.concat([Buffer.from("build\t-tags="), rawByte]).toString("base64"),
]);
}
function normalizeModule(module) {
const identity = ({ Path, Version, Sum }) => ({
path: Path,
version: Version,
sum: Sum === "" || Sum === undefined ? null : Sum,
});
return {
...identity(module),
replacement: module.Replace == null ? null : identity(module.Replace),
};
}
function normalizeObservedModule(module) {
// debug/buildinfo JSON represents both absent and explicitly empty sums as "".
const identity = (value) => ({
...value,
sum: value.sum === "" ? null : value.sum,
});
return {
...identity(module),
replacement:
module.replacement === null ? null : identity(module.replacement),
};
}
function verifySource(info, bytes) {
assert.deepEqual(
bytes.subarray(info.header_offset, info.header_offset + 14),
Buffer.from("ff20476f206275696c64696e663a", "hex"),
);
assert.equal(
info.encoding,
"inline",
"Current compiler fixture must use inline string encoding",
);
const version = inlineString(bytes, info.header_offset + 32);
const module = inlineString(bytes, version.offset + version.bytes.length);
assert.deepEqual(info.version_location, {
offset: version.offset,
bytes: version.bytes.length,
});
assert.deepEqual(info.module_location, {
offset: module.offset,
bytes: module.bytes.length,
});
assert.equal(
isUtf8(version.bytes) ? version.bytes.toString("utf8") : null,
info.go_version,
);
assert.equal(version.bytes.toString("base64"), info.go_version_bytes_base64);
assert.equal(module.bytes.toString("base64"), info.module_bytes_base64);
assert.deepEqual(
module.bytes.subarray(0, 16),
Buffer.from("3077af0c9274080241e1c107e6d618e6", "hex"),
);
assert.deepEqual(
module.bytes.subarray(-16),
Buffer.from("f932433186182072008242104116d8f2", "hex"),
);
const moduleBody = module.bytes.subarray(16, -16);
assert.equal(
isUtf8(moduleBody) ? moduleBody.toString("utf8") : null,
info.module_text,
);
assert.equal(moduleBody.at(-1), 10);
}
function inlineString(bytes, start) {
let length = 0;
let offset = start;
let shift = 0;
for (;;) {
assert.ok(
offset < bytes.length && shift < 49,
"Fixture string length is invalid",
);
const byte = bytes[offset++];
length += (byte & 127) * 2 ** shift;
if ((byte & 128) === 0) break;
shift += 7;
}
assert.ok(
length <= bytes.length - offset,
"Fixture string contents are truncated",
);
return { offset, bytes: bytes.subarray(offset, offset + length) };
}
+3 -9
View File
@@ -1,19 +1,13 @@
import { json, run } from "./lib/verify-package-core.mjs";
import {
expandCapabilitiesOutput,
expandProviderOutput,
} from "./lib/mcp-verifier-results.mjs";
/** Verify packaged capabilities, providers, and non-Linux search results. */
export async function verifyPackageCapabilitiesAndSearch({ cli, environment }) {
const capabilities = expandCapabilitiesOutput(
json(await run(cli, ["capabilities", "--json"], environment)),
const capabilities = json(
await run(cli, ["capabilities", "--json"], environment),
);
if (!Array.isArray(capabilities.capabilities))
throw new Error("packaged capabilities CLI failed");
const providers = expandProviderOutput(
json(await run(cli, ["providers", "--json"], environment)),
);
const providers = json(await run(cli, ["providers", "--json"], environment));
const candidates = providers.analysis_provider_candidates;
if (
!Array.isArray(candidates) ||
+13 -14
View File
@@ -1,7 +1,4 @@
import {
requireMcpLifecycleResult,
requireMcpToolError,
} from "./lib/mcp-verifier-results.mjs";
import { requireMcpToolError } from "./lib/mcp-verifier-results.mjs";
import assert from "node:assert/strict";
import { join } from "node:path";
import { execFile } from "node:child_process";
@@ -24,14 +21,14 @@ const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
};
const verifyMcpTargetFree = async (client, mcpOptions) => {
const result = await client.callTool(
const status = await client.callTool(
{ name: "binary_session", arguments: {} },
mcpOptions,
);
const status = requireMcpLifecycleResult(result, "binary_session");
const currentDocument = status.tool_availability.find(
({ name }) => name === "current_document",
);
const currentDocument =
status.structuredContent?.result?.tool_availability?.find(
({ name }) => name === "current_document",
);
if (
currentDocument?.available !== false ||
currentDocument.reason !== "target_required"
@@ -70,13 +67,15 @@ const verifyMcpOpenAndBind = async (client, mcpOptions) => {
);
if (opened.isError === true)
throw new Error("packaged MCP could not open a binary");
const providerStatus = requireMcpLifecycleResult(
await client.callTool(
{ name: "binary_session", arguments: {} },
mcpOptions,
const providerStatusEnvelope = json(
prompts.mcpText(
await client.callTool(
{ name: "binary_session", arguments: {} },
mcpOptions,
),
),
"binary_session",
);
const providerStatus = providerStatusEnvelope.result;
if (
providerStatus.analysis_provider_binding?.provider?.id !== "hopper" ||
providerStatus.analysis_provider_binding?.selection_source !== "request"

Some files were not shown because too many files have changed in this diff Show More