Compare commits

..
Author SHA1 Message Date
Kaoru0822 84f66cc672 Merge remote-tracking branch 'origin/main' into fix/8284-zero-argument-parameter-bindings 2026-10-31 16:33:56 +00:00
Kaoru0822andClaude Opus 5.5 ff00a77731 fix(native): keep fixed zero-parameter calls out of missing-binding unknowns
trace_native_values reported "Parameter bindings ... are unavailable or
omitted" for every call whose callee had no recovered parameter nodes. A
Ghidra function with an authored int f(void) signature therefore looked as
unresolved as one whose decompiler recovered nothing, and no structured fact
distinguished the two or a variadic prototype.

The Ghidra bridge now reports native_api.parameter_list: basis "signature"
when Ghidra fixes the HighFunction inputs from a non-default signature (an
empty one as its void-input lock, otherwise every valid parameter
type-locked) and "recovered" otherwise, plus the prototype's varargs flag.
The Ghidra adapter requires the fact; the provider-neutral schema keeps it
optional so older retained Evidence stays readable with arity unknown.

The shared trace omits the unknown only for an analyzed callee whose
boundary and value flow have no parameters and whose list has a signature
basis and is not variadic. Recovered, variadic or unreported lists, and
callees that were not analyzed, keep it. A new Ghidra profile commitment
stops replay of snapshots recorded without the fact, and a bridge CI probe
checks the basis and varargs flag on real Ghidra decompilations.

Fixes #8284

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 16:33:56 +00:00
922 changed files with 7640 additions and 44694 deletions

No files matched your search

+1 -3
View File
@@ -13,9 +13,7 @@ documentation, and live catalogs may describe different versions.
For provider-backed work, exercise the analyst's task through REA and compare
relevant native provider and independent artifact observations where available.
Check applicable partial results, recovery and session boundaries; report what
was not exercised. For failure guidance, follow [design recovery around the
failure](../../../docs/tool-design.md#design-recovery-around-the-failure).
Use [workflow verification](../../../docs/testing.md#provider-backed-workflow-verification)
was not exercised. Use [workflow verification](../../../docs/testing.md#provider-backed-workflow-verification)
to select evidence for the proposed contract instead of seeking provider API
parity.
@@ -377,9 +377,8 @@ unless the group overrides them. The grouped name union is the complete tool
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
each provider candidate's availability and target-support status, code and
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
only when you need an unavailable candidate's remediation, provider
diagnostics, candidate capability sets, or tool surface, effect and annotation
flags; in those closed flag vocabularies, listed
only when you need provider diagnostics, candidate capability sets, or tool
surface, effect and annotation flags; in those closed flag vocabularies, listed
flags are true and omitted known flags are false. Provider candidate facts
describe possible providers, while top-level session capabilities describe the
current route and `analysis_provider_binding` names the selected provider. Do
@@ -432,10 +431,7 @@ output also streams without a single MCP frame.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
Evidence ID for the next view instead of requesting the complete bundle.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
@@ -518,8 +514,4 @@ as complete while required questions remain open.
Explain findings in plain language and tie them to returned evidence. When the
user asks to build something, use normal coding tools and separate observed
behavior from design choices. Close an opened native session with
`close_binary` when its engine is no longer needed. Evidence references and Unknown
history remain in memory on the same MCP connection. End the investigation with
`reset_investigation`; supply `save_path` to checkpoint the complete bundle before
clearing. Import the saved bundle to restore original references and Unknown
history in a new process. Retention is bounded and is not automatic persistence.
`close_binary` when the investigation is complete.
@@ -73,14 +73,6 @@ Parent Evidence ID plus source index preserves same-address
alternatives. Follow `coverage.next_offset` only as far as the question needs;
exhausting retained rows does not establish complete program discovery.
`batch_decompile` returns every body unless you select `detail: "summary"` when
the connected tool advertises it. Use its parent Evidence ID with
`view: {kind: "batch-decompile", collection: "items"}` for body-free rows and
exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
`include_pseudocode: true` for several chosen bodies, or use
`collection: "pseudocode"` with one row's `index` to page a large body. Batch
limitations aggregate every item, so do not attribute them to one successful row.
Use `record_unknown` to track unresolved questions in the user's investigation
and name the authority or environment still required. Supply supporting and
contradicting evidence IDs. It updates the session registry directly.
@@ -169,10 +169,7 @@ leaving it, and `both` when the question needs both. Trace traversal includes
containment relationships, so even a narrow seed can expand many nodes; a
bidirectional trace can reach unrelated sibling modules. A module item or page
is preferable when its observations already answer the question. Do not treat
all reached nodes as participants in the feature. For a broad trace, pass
`view: {"kind": "feature-trace", "collection": "paths"}` (or `summary`) to
retain the complete trace and read terminal paths first; page `nodes` or `edges`
with exact `where.node_ids` through `inspect_analysis_view` as needed.
all reached nodes as participants in the feature.
Reuse retained parent Evidence when the server advertises it, or supply complete
Evidence inline. Include Hopper or Ghidra Evidence only when its artifact digest
@@ -245,13 +242,9 @@ observation arrays and runtime reconciliation's `static_layers[].analysis` and
`runtime_observations[]` also accept exact references where advertised. Each
source can independently be inline or retained, preserving identity, coverage
and unknowns. CLI workflows advertising `--evidence-bundle` resolve these inputs
from the explicitly selected bundle. `close_binary` releases the engine while
retaining references and complete Unknown history on the same MCP connection.
`reset_investigation` explicitly clears them; optional `save_path` checkpoints the
complete bundle before clearing. Import that bundle in another connection to
restore its original references. Retention is bounded, in-memory and never silently
evicted; `binary_session.investigation` reports the budget. A missing reference
includes its ID and recovery guidance.
from the explicitly selected bundle. `close_binary` clears retained references;
export a bundle before closing, or supply portable inline Evidence in another
connection. A missing reference includes its ID and recovery guidance.
For exact file/native metadata or processing gaps, use
`view: {kind: "javascript-facts", where: {paths: ["package.json"]}, property_keys: ["name", "operation", "analysis_status"], limit: 32}`.
@@ -1,11 +1,8 @@
# Native, managed, and packaged artifacts
For release comparisons on one MCP connection, retain `list_procedures` Evidence
from each target and reuse its exact IDs after target switches or `close_binary`.
For a handoff or new process, export the complete bundle and import it there,
including its source closure. `reset_investigation` explicitly clears retained
records; optional `save_path` checkpoints them before clearing.
`match_native_functions` pairs unique exact names and, when supplied,
For release comparisons, export `list_procedures` Evidence from each target before
closing it. Close clears session records: import both bundles after collecting the
two targets, or supply portable inline Evidence. `match_native_functions` pairs unique exact names and, when supplied,
complete observed body bytes. Reuse optional same-context `list_names`,
`procedure_info`, `read_bytes` and existing `analyze_function` dossiers as
`left_observations`/`right_observations`; an MCP `analyze_function` summary
@@ -346,9 +343,8 @@ the complete tool inventory grouped by current status. Expand each group's
overrides them; availability, reasons, remediation, and modes remain inline.
Provider candidates keep availability and target-support status, code, and
reason. `omitted_fields` names what the summary leaves out; `{"detail":
"complete"}` adds each unavailable candidate's `remediation`, provider
diagnostics, candidate capability sets, and tool surface, `effects`, and
`annotations`. Those arrays name true flags from fixed
"complete"}` adds provider diagnostics, candidate capability sets, and tool
surface, `effects`, and `annotations`. Those 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.
-21
View File
@@ -1,21 +0,0 @@
name: REA production code
# Match the existing default-setup scan's local-input threat model.
threat-models: [local]
# Retain root traversal, including future production roots. Hidden directories
# need their own root; use a directory so Python can share this configuration.
paths:
- .
- docs/.vitepress
# Keep the default query suite and all production roots, including bridge/,
# install.sh, runtime scripts, release controllers, and GitHub Actions.
# Exclude test inputs at extraction time so they cannot taint production callers.
paths-ignore:
- tests
- scripts/fixtures
- "**/*.test.*"
- "**/*.fixture.*"
- "**/test_*.py"
- "**/*_test.py"
+1 -2
View File
@@ -106,8 +106,7 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
## Author readiness
<!-- The human owner completes these declarations before requesting review,
unless the PR author is exempt as listed in CONTRIBUTING.md.
<!-- The human owner completes these declarations before requesting review.
The PR author is the owner by default. A different owner must explicitly accept
ownership on this PR.
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
-2
View File
@@ -807,8 +807,6 @@ jobs:
tests/boundary/mcp/nativeDemangleMcp.test.ts
tests/boundary/mcp/nativeMachoSummaryMcp.test.ts
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts
tests/boundary/process/nativeUiVerifierLane.test.ts
tests/boundary/cli/nativeSignatureCli.test.ts
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
tests/boundary/cli/nativeMachoEncryptionCli.test.ts
-61
View File
@@ -1,61 +0,0 @@
name: CodeQL production
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "37 5 * * 1"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: codeql-production-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
analyze:
# A repository administrator must disable default setup before enabling
# this workflow; default setup rejects advanced CodeQL result uploads.
if: vars.REA_CODEQL_ADVANCED == 'true'
name: Analyze production (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
max-parallel: 1
matrix:
language: [javascript-typescript, python, actions]
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Initialize production analysis
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
with:
languages: ${{ matrix.language }}
build-mode: none
config-file: .github/codeql/production.yml
threads: 2
ram: 4096
- name: Analyze production code
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
with:
category: /language:${{ matrix.language }}
output: ${{ runner.temp }}/codeql-production-results
threads: 2
ram: 4096
- name: Retain complete analysis evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: codeql-production-${{ matrix.language }}
path: ${{ runner.temp }}/codeql-production-results
if-no-files-found: error
retention-days: 7
@@ -95,3 +95,30 @@ jobs:
decode cortex ARM:LE:32:Cortex mrs/4/msp bx/2/lr
decode v8m ARM:LE:32:v8-m mrs/4/msplim bx/2/lr
decode xtensa Xtensa:LE:32:default entry/3/a1 retw.n/2
# Authored void, variadic and one-parameter signatures fix Ghidra's
# HighFunction inputs; default signatures are recovered from uses. Only
# the signatures are authored. The source-owned x86-64 bytes are never
# executed.
- name: Distinguish fixed from recovered HighFunction parameter lists
shell: bash
run: |
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
export JAVA_HOME="${!java_home_variable}"
probe_root="${RUNNER_TEMP}/rea-parameter-list"
mkdir -p "${probe_root}/project"
{
printf '\270\001\000\000\000\303\314\314' # 0x1000: mov eax, 1; ret (default)
printf '\270\001\000\000\000\303\314\314' # 0x1008: int f(void)
printf '\270\001\000\000\000\303\314\314' # 0x1010: int f(...)
printf '\270\001\000\000\000\303\314\314' # 0x1018: int f(int x)
printf '\215\004\017\303\314\314\314\314' # 0x1020: lea eax, [rdi + rcx]; ret (default)
} > "${probe_root}/parameter-list.bin"
test "$(wc -c < "${probe_root}/parameter-list.bin")" -eq 40
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/project" ParameterList \
-import "${probe_root}/parameter-list.bin" -loader BinaryLoader -loader-baseAddr 0x1000 \
-processor x86:LE:64:default -noanalysis -readOnly -deleteProject \
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra;${GITHUB_WORKSPACE}/bridge/ghidra" \
-postScript ReaParameterListProbe.java "${GITHUB_WORKSPACE}/bridge/ghidra" \
> "${probe_root}/output.log" 2>&1 || { cat "${probe_root}/output.log"; exit 1; }
cat "${probe_root}/output.log"
grep -F 'REA_PARAMETER_LIST_OK default=recovered void=signature varargs=variadic one=signature used=recovered' "${probe_root}/output.log"
+3 -3
View File
@@ -55,12 +55,12 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install AArch64 and ELF32 cross toolchains for source-owned fixtures
- name: Install ELF32 cross toolchains for source-owned fixtures
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
gcc-aarch64-linux-gnu gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-arm64-cross libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
- name: Acquire exact upstream libraries in an isolated runner environment
shell: bash
run: |
-4
View File
@@ -21,10 +21,6 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
+8 -50
View File
@@ -54,10 +54,6 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
@@ -79,21 +75,10 @@ jobs:
PY
- name: Configure Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
# upload-pages-artifact excludes dot directories, including .well-known.
- name: Package the website including agent discovery
run: |
tar --dereference --hard-dereference --directory website/_site \
--exclude=.git --exclude=.github -cf "$RUNNER_TEMP/artifact.tar" .
tar -tf "$RUNNER_TEMP/artifact.tar" \
./.well-known/ard.json ./.well-known/ai-catalog.json \
./.well-known/agent-skills/index.json > /dev/null
- name: Upload the website
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
name: github-pages
path: ${{ runner.temp }}/artifact.tar
retention-days: 1
if-no-files-found: error
path: website/_site
deploy:
needs: prepare
@@ -145,24 +130,14 @@ jobs:
PUBLISH_TARGET: ${{ inputs.target }}
run: |
python3 -u - <<'PY'
import hashlib
import json
import os
import sys
import time
from urllib.error import URLError
from urllib.parse import urljoin, urlsplit
from urllib.parse import urljoin
from urllib.request import Request, urlopen
def open_public(url, accept=None):
headers = {
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
}
if accept:
headers["Accept"] = accept
return urlopen(Request(url, headers=headers), timeout=10)
expected = os.environ["GITHUB_SHA"]
pages_url = os.environ["PAGES_URL"]
if not pages_url:
@@ -178,7 +153,11 @@ jobs:
for name, base in list(pending.items()):
url = urljoin(base, "deployment.json") + "?commit=" + expected
try:
with open_public(url) as response:
request = Request(url, headers={
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
})
with urlopen(request, timeout=10) as response:
actual = json.load(response).get("commit")
if actual == expected:
print(f"{name} serves {expected}: {base}")
@@ -193,27 +172,6 @@ jobs:
time.sleep(5)
if pending:
sys.exit("Could not verify published revision on: " + ", ".join(pending))
for name, base in hosts.items():
with open_public(urljoin(base, ".well-known/agent-skills/index.json")) as response:
skill, = json.load(response)["skills"]
# Fetch from each selected host, including the /rea/ mirror.
artifact_url = urljoin(base, urlsplit(skill["url"]).path.lstrip("/"))
with open_public(artifact_url) as response:
digest = "sha256:" + hashlib.sha256(response.read()).hexdigest()
if digest != skill["digest"]:
sys.exit(name + " serves a skill archive with the wrong digest")
for manifest in ("ard.json", "ai-catalog.json"):
with open_public(urljoin(base, ".well-known/" + manifest)) as response:
if not json.load(response).get("entries"):
sys.exit(name + " serves an empty discovery manifest")
if name == "Cloudflare":
with open_public(base, accept="text/markdown") as response:
vary = {value.strip().lower() for value in response.headers.get("Vary", "").split(",")}
if response.headers.get_content_type() != "text/markdown" or "accept" not in vary:
sys.exit("Cloudflare did not negotiate Markdown with Vary: Accept")
if 'rel="ard"' not in response.headers.get("Link", ""):
sys.exit("Cloudflare is missing its discovery Link header")
print(name + " serves the discovery manifests and verified skill archive")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
links = " and ".join(f"[{name}]({url})" for name, url in hosts.items())
summary.write(f"Published commit `{expected}` to {links}.\n")
+2 -15
View File
@@ -28,10 +28,6 @@ Assess brittleness in touched code during implementation, debugging, review, and
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.
After confirming a bug, search sibling adapters, consumers and similar workflows
for the same failure. Verify suspected matches, record working comparisons, and
group findings by root cause instead of filing duplicate reports.
When addressing a review thread, inspect the producing boundary and affected consumers for brittleness. Complete directly related refactoring in the same change. Before resolving the thread, explain the root cause, structural fix, validation, and any remaining cleanup.
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.
@@ -79,9 +75,6 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
When changing installers or setup recovery, follow
[Checking installers and publisher downloads](docs/testing.md#checking-installers-and-publisher-downloads).
Coordinate expensive checks across agents. If a test unexpectedly consumes heavy CPU, stop its owned process family and profile a bounded representative case before retrying. Inspect production work, fixture generation, subprocesses, and repeated setup; preserve regression coverage when optimizing. Follow the [test resource guidance](docs/testing.md#developer-commands).
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
@@ -90,12 +83,6 @@ Coordinate expensive checks across agents. If a test unexpectedly consumes heavy
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
For user- or agent-facing tool problems, prioritize fixing the owning behavior,
contract, and actionable result or recovery at the point of use. Document the
result in product docs when explanation is needed. Add skill guidance only for
workflow judgment or context that the tool cannot reasonably convey; do not use
a skill as the primary repair for a discoverability or contract defect.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
@@ -128,8 +115,8 @@ owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates the filled declaration or the author exemption
listed in CONTRIBUTING.md; CI and automated review results do not establish that
The readiness check validates
the filled declaration; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
<!-- BEGIN:turborepo-agent-rules -->
+8 -11
View File
@@ -16,9 +16,10 @@ import inventory. For current provider wiring, start with
[`src/composition/binary.ts`](src/composition/binary.ts) for deep candidates and
the other `src/composition/` factories for tool services, including
[`gpu.ts`](src/composition/gpu.ts) and [`model.ts`](src/composition/model.ts).
Live Frida, Cutter, GDB and Rizin construction also uses shared composition
factories. For service ownership, availability and catalog declarations, follow
the [capability composition guide](docs/capability-composition.md).
Live Frida and Cutter adapters are constructed directly in
[`src/cli/fridaCommands.ts`](src/cli/fridaCommands.ts),
[`src/cli/cutterCommands.ts`](src/cli/cutterCommands.ts), and
[`src/server/createServer.ts`](src/server/createServer.ts).
Production factories construct fresh providers without starting engines or
acquiring targets; optional adapter failures must preserve successful peers. Run
`npm run verify:test-discovery` after adding or moving tests. Resolved import
@@ -93,8 +94,7 @@ Before requesting maintainer review, the human owner must:
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
for required checks and real-provider coverage.
- Complete the **Author readiness** declaration in the PR template, explicitly
stating that the PR is ready for initial review, unless the PR author is
exempt from the declaration as listed below.
stating that the PR is ready for initial review.
Keep the PR description concise and current. Explain the problem, observable
outcome, approach, and useful evidence; link long logs or put them in optional
@@ -115,12 +115,9 @@ validation current as work changes. AI agents must leave human declarations to
the human owner and must not attest to a human review on their behalf.
The `PR readiness` check reports pending for drafts and requires a named human
owner and all three template declarations for non-drafts. PRs authored by
@morluto, @kaoru0822-kitauji, @N0zoM1z0, or @gensokyo-dev are exempt from
the declaration; their human ownership, review, and evidence responsibilities
above still apply. The check identifies authors by GitHub account ID. It checks
that the declaration is filled, not whether the code or evidence is correct.
Existing PRs can copy the **Author readiness** section from the current template.
owner and all three template declarations for non-drafts. It checks that the
declaration is filled, not whether the code or evidence is correct. Existing PRs
can copy the **Author readiness** section from the current template.
Open PRs must have distinct head commits because GitHub shares commit statuses.
Maintainers enable merge enforcement after the workflow is merged: run
-2
View File
@@ -20,8 +20,6 @@
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Join the REA community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [Build RL environments](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
+1 -1
View File
@@ -60,7 +60,7 @@ pin inspection sources; they are not included in the npm package.
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.990/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.990/NOTICE) | Bring your own `kuna`; REA verified 1.990 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.986/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.986/NOTICE) | Bring your own `kuna`; REA verified 1.986 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
+44 -60
View File
@@ -693,7 +693,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
row.addProperty("status", relocation.getStatus().toString());
row.addProperty("type", relocation.getType());
row.add("symbol", GSON.toJsonTree(relocation.getSymbolName()));
row.add("values", GSON.toJsonTree(relocation.getValues() == null ? new long[0] : relocation.getValues()));
byte[] original = relocation.getBytes();
row.add("original_bytes_hex", original == null ? JsonNull.INSTANCE : GSON.toJsonTree(bytesHex(original)));
@@ -2275,6 +2274,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
throw new RequestFailure("invalid_request", "mode must be literal or regex");
}
boolean caseSensitive = requireBoolean(params, "case_sensitive");
List<InventoryItem> inventory = procedureSearch
? procedures().stream().map(FunctionEntry::item).toList()
: strings();
ValueMatcher matcher;
if (mode.equals("literal") && !caseSensitive) {
if (!params.has("literal_ascii_candidate"))
@@ -2288,26 +2290,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
: regexMatcher(expression, caseSensitive);
}
JsonArray items = new JsonArray();
if (procedureSearch) {
// Search the names exact lookup accepts, keeping one row per
// procedure under its primary name.
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
ProcedureNameMatch match = matchProcedureNames(entry, matcher);
if (!match.matched()) {
continue;
}
JsonObject item = resultItem(entry.item, null);
if (!match.aliases().isEmpty()) {
JsonArray aliases = new JsonArray();
for (String alias : match.aliases()) aliases.add(alias);
item.add("matched_aliases", aliases);
}
items.add(item);
}
return items;
}
for (InventoryItem item : strings()) {
for (InventoryItem item : inventory) {
monitor.checkCancelled();
if (!matcher.matches(item.value)) {
continue;
@@ -2317,36 +2300,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
return items;
}
/**
* The one definition of the names that select a procedure, shared by exact
* lookup and search: its primary name and every other symbol at its exact
* entry, each in leaf or namespace-qualified spelling. A label inside the
* body never names the procedure. Matching secondary symbols are reported
* by their qualified names, sorted.
*/
private ProcedureNameMatch matchProcedureNames(FunctionEntry entry, ValueMatcher matcher) throws Exception {
Function function = entry.function;
boolean primary = spellingMatches(function.getName(), entry.item.value, matcher);
Set<String> aliases = null;
for (Symbol symbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
String qualified = symbol.getName(true);
if (!spellingMatches(symbol.getName(), qualified, matcher)) continue;
if (qualified.equals(entry.item.value)) {
primary = true;
continue;
}
if (aliases == null) aliases = new TreeSet<>();
aliases.add(qualified);
}
return new ProcedureNameMatch(primary, aliases == null ? List.of() : List.copyOf(aliases));
}
/** A leaf is the suffix of its qualified spelling, so a literal found in it is found in both. */
private static boolean spellingMatches(String leaf, String qualified, ValueMatcher matcher) {
return matcher.matches(qualified) || (!leaf.equals(qualified) && matcher.matches(leaf));
}
private List<InventoryItem> names() throws Exception {
if (nameInventory != null) {
return nameInventory;
@@ -2449,13 +2402,26 @@ public final class ReaGhidraBridge extends HeadlessScript {
// exact symbol name, even when every character is a hexadecimal digit.
boolean explicitAddress = isExplicitReaAddress(value);
if (!explicitAddress) {
// Imported labels (for example Mach-O _main) can share a function
// entry without becoming its primary symbol; search accepts the same.
ValueMatcher selector = value::equals;
List<Function> matches = new ArrayList<>();
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
if (matchProcedureNames(entry, selector).matched()) matches.add(entry.function);
Function function = entry.function;
Symbol symbol = function.getSymbol();
String qualified = symbol == null ? function.getName() : symbol.getName(true);
boolean matchesName = function.getName().equals(value) || qualified.equals(value);
if (!matchesName) {
// Imported labels (for example Mach-O _main) can share a
// function entry without becoming its primary symbol. Only
// exact entries qualify; an interior label is not a selector.
for (Symbol entrySymbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
if (entrySymbol.getName().equals(value) || entrySymbol.getName(true).equals(value)) {
matchesName = true;
break;
}
}
}
if (matchesName) matches.add(function);
}
if (matches.size() > 1)
throw new RequestFailure("ambiguous", "Ghidra procedure name is ambiguous: " + value +
@@ -2529,6 +2495,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
JsonArray parameters = new JsonArray();
result.add("parameters", parameters);
result.add("parameter_list", parameterList(function, prototype));
JsonArray jumpTables = new JsonArray();
result.add("jump_tables", jumpTables);
@@ -2586,6 +2553,28 @@ public final class ReaGhidraBridge extends HeadlessScript {
return result;
}
/**
* Ghidra fixes prototype inputs from a non-default signature: an empty one
* reaches the decompiler as a void-input lock (FunctionPrototype), and each
* valid parameter is type-locked (LocalSymbolMap). Any other list was
* recovered from observed uses and may omit parameters.
*/
private static JsonObject parameterList(Function function, FunctionPrototype prototype) {
int count = prototype.getNumParams();
boolean signature = count > 0 || (
function.getSignatureSource() != SourceType.DEFAULT &&
function.getParameterCount() == 0
);
for (int index = 0; signature && index < count; index += 1) {
HighSymbol parameter = prototype.getParam(index);
signature = parameter != null && parameter.isTypeLocked();
}
JsonObject result = new JsonObject();
result.addProperty("basis", signature ? "signature" : "recovered");
result.addProperty("variadic", prototype.isVarArg());
return result;
}
private static JsonObject unavailableNativeValueFlow(String reason) {
JsonObject unavailable = new JsonObject();
unavailable.addProperty("available", false);
@@ -5172,11 +5161,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
private record AddressNameConflict(Namespace namespace, String leaf, List<Address> requesters, List<Address> holders) {}
private record AddressNameFailure(String reason, AddressNameConflict conflict) {}
private record FunctionEntry(Function function, InventoryItem item) {}
private record ProcedureNameMatch(boolean primary, List<String> aliases) {
boolean matched() {
return primary || !aliases.isEmpty();
}
}
private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations, JsonObject relationshipCoverage, boolean decompilerFailed) {}
private record CallBoundaryObservation(List<String> limitations, Integer unrepresentedCalleeCalls, JsonArray unresolvedCalls) {}
private record SwiftStringLiteralDecode(JsonArray literals, JsonArray omissions) {}
+1 -4
View File
@@ -65,10 +65,7 @@ func observe(_ request: Request) async throws -> [String: Any] {
guard SHA256.hash(data: executableBytes).map({ String(format: "%02x", $0) }).joined() == request.sha256 else {
try fail("target-mismatch", "Running executable bytes differ from the active target digest")
}
// .optionIncludingWindow omits a selected window while it is off screen, which
// is not absence: select from every window, and let capture report what it can.
guard let windows = (CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as? [[String: Any]])?
.filter({ ($0[kCGWindowNumber as String] as? NSNumber)?.uint32Value == request.window_id }),
guard let windows = CGWindowListCopyWindowInfo([.optionIncludingWindow], request.window_id) as? [[String: Any]],
windows.count == 1, let window = windows.first,
(window[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == request.pid,
(window[kCGWindowLayer as String] as? NSNumber)?.intValue == 0,
+23 -38
View File
@@ -15,8 +15,6 @@ struct ProcessIdentityObservation: Encodable {
let effective_uid: UInt32?
let lifecycle: String?
let relation: ProcessRelationObservation?
/** Display-only kernel accounting name (p_comm, at most MAXCOMLEN bytes). */
let name: String?
let reason: String?
}
@@ -33,7 +31,6 @@ struct ProcessMetadata {
let parentPid: Int32
let processGroupId: Int32
let pointerSize: Int
let name: String
}
struct ProcessSnapshot {
@@ -95,8 +92,7 @@ func readProcessSnapshot(
lifecycle: process.kp_proc.p_stat == SZOMB ? "zombie" : "non-zombie",
parentPid: process.kp_eproc.e_ppid,
processGroupId: process.kp_eproc.e_pgid,
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4,
name: processName(process)
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4
)
)
},
@@ -107,13 +103,6 @@ func readProcessSnapshot(
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)")
}
/** Decode the NUL-terminated accounting name; invalid UTF-8 is replaced for display. */
private func processName(_ process: kinfo_proc) -> String {
withUnsafeBytes(of: process.kp_proc.p_comm) { bytes in
String(decoding: bytes.prefix { $0 != 0 }, as: UTF8.self)
}
}
func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] {
readProcessIdentities(pids: pids, snapshot: readProcessSnapshot())
}
@@ -141,7 +130,6 @@ func readProcessIdentities(
effective_uid: nil,
lifecycle: nil,
relation: nil,
name: nil,
reason: "not-in-snapshot"
)
}
@@ -156,7 +144,6 @@ func readProcessIdentities(
parent_pid: metadata.parentPid,
process_group_id: metadata.processGroupId
),
name: metadata.name,
reason: nil
)
}
@@ -170,7 +157,6 @@ private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityO
effective_uid: nil,
lifecycle: nil,
relation: nil,
name: nil,
reason: reason
)
}
@@ -228,22 +214,6 @@ func processRunTokenObservation(
parse: Result<ParsedRunToken, RunTokenReadError>,
codeSigningFlags: () -> UInt32?
) -> RunTokenObservation {
// Native inspection restrictions apply even when argv is incomplete or
// overwritten, before parsing reaches the environment. Keep this fact at
// the native boundary; cleanup still decides whether the process is related.
func withheldEnvironment() -> RunTokenObservation? {
guard let flags = codeSigningFlags() else { return nil }
if (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return nil
}
func unavailableEnvironment(_ reason: String) -> RunTokenObservation {
withheldEnvironment() ?? unavailable(pid: pid, reason: reason)
}
switch parse {
case .success(let parsed):
if let runId = parsed.runId {
@@ -254,25 +224,40 @@ func processRunTokenObservation(
if parsed.environmentEntryCount > 0 {
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
}
if let withheld = withheldEnvironment() { return withheld }
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:
return unavailableEnvironment("environment_unavailable")
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:
return unavailableEnvironment("ambiguous_environment_boundary")
// These failures already establish that token bytes were available.
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 unavailableEnvironment("apple_vector_unavailable")
return unavailable(pid: pid, reason: "apple_vector_unavailable")
case .invalidBuffer:
return unavailableEnvironment("malformed_procargs")
return unavailable(pid: pid, reason: "malformed_procargs")
case .invalidBufferAt(let stage):
return unavailableEnvironment("malformed_procargs_\(stage.rawValue)")
return unavailable(pid: pid, reason: "malformed_procargs_\(stage.rawValue)")
}
}
}
+4 -4
View File
@@ -16,10 +16,10 @@ PROFILE = "[email protected];[email protected];[email protected]"
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
OUTPUT_BYTES = 64 * 1024 * 1024
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
PROFILES = {(62, 64, "little"): "amd64", (183, 64, "little"): "aarch64", (3, 32, "little"): "i386",
(40, 32, "little"): "arm", (8, 32, "little"): "mips", (8, 32, "big"): "mips"}
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF64 AArch64 little-endian, ELF32 i386, "
"ELF32 ARM little-endian or ELF32 MIPS little- or big-endian objects.")
PROFILES = {(62, 64, "little"): "amd64", (3, 32, "little"): "i386", (40, 32, "little"): "arm",
(8, 32, "little"): "mips", (8, 32, "big"): "mips"}
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF32 i386, ELF32 ARM little-endian "
"or ELF32 MIPS little- or big-endian objects.")
class LayoutFailure(Exception):
+4 -4
View File
@@ -257,12 +257,12 @@ beyond already-connected devices (`adb connect`) are not exposed yet.
## Real verification
`npm run verify:adb` exercises observation and transfer operations against a live device. It
`npm run verify:adb` exercises every operation against a live device. It
requires `adb` (or `REA_ADB_PATH`) and any attached emulator or phone;
`--serial` selects one when several are attached, `--pull PACKAGE` pulls a
real package and verifies the returned digests against the files on disk.
The optional `--install-apk` lifecycle is unavailable until the selected APK
identity and device cleanup ownership can be established safely.
real package and verifies the returned digests against the files on disk,
and `--install-apk PATH` drives a full install → resolve → uninstall
lifecycle with a lane-owned probe.
Record: verified against adb 34.0.5 on Linux against an Android 14
(API 34) x86_64 emulator — all operations above, a 224-package inventory,
+1 -6
View File
@@ -1149,12 +1149,7 @@ global conflict involving an equal pair is not used to normalize either pair; th
A step agrees when the statuses and the normalised bodies are equal. The report gives agreement overall, per probe and
per tool, `agreement_ignoring_order` for steps that differ only in item order, and each step's differences, at most 20,
with a JSON pointer and both values cut short. Each step reports `differences_total` before that cap,
`differences_omitted`, and `differences_truncated` (true when entries were omitted). Agreement and categories use the
complete comparison. Omitted entries are not retained, so `--full-output` cannot recover them; inspect the original
capture and probe Evidence to investigate beyond the returned pointers.
The difference categories are:
with a JSON pointer and both values cut short:
| Category | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
+5 -5
View File
@@ -8,7 +8,7 @@ graph TD
end
subgraph "Production Composition"
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>typed capability bindings: construction, availability and retryable lifetimes<br/>pure capability declarations feed provider catalogs"]
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>static WASM/EVM descriptors: named contracts, factories, retryable lifetimes"]
end
subgraph "Adapters"
@@ -23,7 +23,6 @@ graph TD
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback<br/>ProviderInspectionScope: prerequisite observation lifetime"]
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
LIFETIMES["CapabilityScope<br/>host-owned service close capabilities<br/>released peers removed; failed cleanup retained for retry"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots and session file ownership<br/>record coordination and post-commit notifications"]
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
end
@@ -100,7 +99,6 @@ graph TD
MAIN --> COMPOSITION
CLI --> COMPOSITION
SERVER --> COMPOSITION
COMPOSITION --> LIFETIMES
COMPOSITION --> SESSION
APP --> FIRMWARE
FIRMWARE --> PROCESS
@@ -151,7 +149,8 @@ graph TD
OBJDUMP --> DOMAIN
COMPOSITION --> RIZIN
RIZIN --> DOMAIN
COMPOSITION --> GDB
CLI --> GDB
SERVER --> GDB
GDB --> PROCESS
GDB --> DOMAIN
HOPPER --> HOPPER_BRIDGE
@@ -207,7 +206,8 @@ graph TD
NATIVE_READERS --> DOMAIN
COMPOSITION --> FORMAT_READERS
FORMAT_READERS --> DOMAIN
COMPOSITION --> RUNTIME
SERVER --> RUNTIME
CLI --> RUNTIME
RUNTIME --> DOMAIN
PROCESS --> FILESYSTEM
AUX --> FILESYSTEM
+5 -9
View File
@@ -9,11 +9,9 @@ ordinary binary inspection use the same modular capability.
## Bring your own engine
Real verification covers **Linux x64** hosts and **EXEC/DYN/REL** objects in
these profiles: ELF64 x86-64, ELF64 little-endian AArch64, ELF32 i386, ELF32
little-endian ARM and ELF32 little- or big-endian MIPS. Other machines, classes
and byte orders, including big-endian ARM, return `unsupported_target`. Other
hosts, including Linux arm64, return `capability_unavailable` naming the
detected host before any target is read. Provide an absolute
these profiles: ELF64 x86-64, ELF32 i386, ELF32 little-endian ARM and ELF32
little- or big-endian MIPS. Other machines, classes and byte orders, including
AArch64 and big-endian ARM, return `unsupported_target`. Provide an absolute
Python executable whose environment already contains unchanged pwntools 4.15.0,
pyelftools 0.33 and Unicorn 2.1.2:
@@ -426,10 +424,8 @@ rea inspect-native-exports ./libexample.so --detail summary --evidence_output ./
without an export directory is a complete empty inventory.
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
binding and default or protected visibility. Each row keeps `value`
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`,
`binding` and `visibility` (`"default"` or `"protected"`; `"unknown"` in older
historical Evidence where visibility was not recorded). ELF string tables do
not declare a text encoding. Names that
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
and `binding`. ELF string tables do not declare a text encoding. Names that
contain non-ASCII bytes are decoded as UTF-8 when those bytes form valid
UTF-8; `name_utf8_decodable` records whether the non-ASCII name bytes are
valid UTF-8 and `name_bytes_base64` retains those exact bytes (`null` for
+1 -1
View File
@@ -500,7 +500,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
connection/frame inventories; matching direction/opcode sequences alone do
not prove alignment after drops. Redirect resource types use only shared known metadata:
an omitted type leaves equality unknown rather than proving a modification.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. Source locations are CDP's zero-based lines and UTF-16 columns. A negative column, which Chrome reports on the first line of some inline scripts, is kept as reported and does not locate a column in the source URL; an invalid line or a non-integer column is `null` (WebMCP `registration_source` is `null`) and is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- WebSocket observations retain direction, opcode, and payload byte length. A missing or malformed CDP opcode is returned as `null`, with opcode-dependent payload byte length and shape also `null`, and marks `websocket_frames` incomplete. Missing or wrong-typed payload data is not treated as empty; its byte length is `null`, and a text payload shape is unknown. Numeric opcode `0` and an observed empty payload remain distinct from missing data. Opcode is nullable in the public inspection schema, so clients must handle unknown protocol values. When shape capture is selected, text messages are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw payloads are never retained.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache fingerprints hash all decoded response bytes, including bodies above 64 KiB, subject to the [64 MiB per-message CDP limit](#request-boundary). Missing or undecodable cache bodies and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
-107
View File
@@ -1,107 +0,0 @@
# Capability composition and ownership
REA keeps domain contracts and shared workflows separate from provider adapters
and CLI/MCP translation. A capability can have several provider identities; a
provider can contribute operations to several capabilities. Those associations
are source facts, while host availability is an observation.
## Source declarations and runtime bindings
`src/composition/capabilityDeclarations.ts` contains static provider identities,
named contract-group references and precise operation subsets. Complete groups
refer to `TOOL_CONTRACT_GROUPS` in the existing contract owner; family definitions
and provider catalogs read the same arrays, so a new group operation is included
without updating a copied provider list. Metadata imports only the group ID type
and pure metadata leaves; it must not load factories, schemas, engines or
generated catalogs. Catalog generation resolves groups and subsets against the
canonical contracts and rejects unknown groups/operations and conflicting
identities. New provider provenance must come from the implementation
that actually produces the Evidence, including format-specific capture adapters
and derived workflows.
`src/composition/capabilityServices.ts` binds typed service factories to those
capability keys. Each runtime entry owns construction, cleanup and any available
host/configuration facts or live probes. A service entry requires a close
capability; entries adapting managers retain their existing `closeAll` operation.
The host maps entries without erasing their individual service/port types. There
is no generic JSON operation dispatcher.
These two declarations have different loading needs. Static facts can be read
without loading a schema or provider graph. The full runtime composition is
materialized only when the MCP runtime needs it. One-shot CLI adapters import the
selected factory rather than constructing a complete capability host. CLI and MCP
reuse the same provider/service factories and application workflows, while
keeping their respective argument translation and lifetime.
## Resource lifetime
`CapabilityScope` registers a module's actual close capability when its instance
is composed. There is no independent server provider-close list to update.
Concurrent shutdown calls join the same cleanup attempt. A successful peer is
removed from pending cleanup; failed peers remain owned and are retried on the
next close. Synchronous throws and rejections without a reason remain failures,
and cannot prevent other peers from attempting cleanup. Shutdown seals further
ownership registration.
The host distinguishes these lifetimes:
- Host-owned service instances are released by that host.
- Caller-owned optional observations remain borrowed by SDK server instances;
their process-level owner closes them, including discarded discovery probes.
- CLI invocations use their existing finalizers to join cleanup and preserve the
original observation, cancellation and cleanup errors.
- Binary engines, bundle readers and investigation records retain their existing
owners. The `BinarySession` transition/drain barrier still coordinates target
changes, reset and disposal. A module refactor must not create independent
barriers that allow late work to repopulate reset records.
An application workflow may borrow a shared resource scope, such as byte entropy
using the host's artifact scope. Its wrapper does not acquire a second ownership
claim over the borrowed scope.
## Availability and registration
Module bindings provide host support, selected configuration and precisely typed
live availability probes through `CapabilityAvailabilityFacts`. Constructors do
not run these probes or launch engines. Session discovery invokes the existing
provider-owned probes only for advertised families; live failures remain fresh
observations. Caller policy, selected environments and injected providers retain
their existing precedence.
MCP registration remains in the MCP adapter. It consumes composed services,
record ports, admission and logging; it must not add fallback constructors that
can escape the host owner. Named contracts bind handlers and schemas. The complete
tool-family registrar map is checked by TypeScript, and catalog order never
selects a handler or schema.
## Extending a capability
1. Add or reuse the domain contract and application workflow, with explicit
Evidence provenance, coverage, unknowns and caller choices.
2. Put concrete construction in `src/composition/` and keep its default factory
lazy. Reference adapter-owned pure identity metadata from both the provider
and its static capability declaration.
3. Add the runtime binding if a host-owned service is needed. Bind cleanup and
availability/probes there, preserving borrowed versus owned resources.
4. Add the CLI/MCP argument adapters and their named registrations. A one-shot
command uses the shared selected factory and existing finalization helpers.
5. Run source-boundary, discovery, SDK-schema and relevant public workflow checks.
Exercise partial cleanup, retry, cancellation and caller-owned peers at the
applicable production seam; use actual applications/providers for routing and
complete Evidence verification. Recording ports do not prove real providers.
The constructor boundary guard covers the centralized Frida, Cutter, GDB and
Rizin constructors without rejecting legitimate type-only injected interfaces.
Other settled pure/provider boundaries continue checking type imports too. The
shared parser records module-edge erasure once. Whole-declaration type imports
are erased; inline type bindings retain their runtime module edge under
`verbatimModuleSyntax`. Static dynamic imports remain runtime edges too.
Tests belong with their semantic owner and must be
found exactly once; provider roots need the corresponding adapter discovery lane.
This is an internal composition contract. External plugin distribution, automatic
provider installation and a universal provider interface are separate concerns.
The [initial verification report](verification/capability-composition-2026-11-01.md)
records the executed public workflows, cleanup boundaries, resource measurements
and remaining provider qualification gaps for this composition increment.
-58
View File
@@ -561,25 +561,6 @@ be verified. See [MCP contracts](mcp-contracts.md) for session lifecycle and
[Ghidra first-query deadlines](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
for import, client timeouts and recovery.
A one-shot command releases its provider session and REA runtime before it
prints a failure, so it omits the same-session reuse and
`update_analysis_settings` guidance an MCP connection receives. Its
`details.limit` reports `scope: "rea-invocation"` and
`applies_at: "next_invocation"`; set the named `environment` (or the per-call
`parameter`) for a new invocation and rerun. For example, a Ghidra decompile
deadline names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS`.
`rea debug-with-gdb` and `rea debug-with-rizin` hold their debugger only for the
command's own process. If cleanup cannot confirm the debugger stopped, they print
`cleanup_incomplete` and exit nonzero without stopping it. `remediation.action`
names the process, as does `retained_debugger_pids` in `details.diagnostics`, or
in `details.diagnostics.cleanup_error.details.diagnostics` when the command also
failed or was interrupted. Exiting closes the debugger's command and output pipes.
REA does not establish what the debugger or its target does next, so check that
process. An MCP server that shuts down likewise exits with status 1 instead of
waiting on a debugger it could not stop. It names the process on stderr and as
`retained_debugger_pids` in its error log.
If one-shot analysis fails and closing its session also fails, the original
error details remain inline and `details.cleanup_error` describes the later
close failure. Each diagnostic retains its own reported resources; the later
@@ -612,9 +593,6 @@ Saved snapshots include only cache entries whose subject identifies the selected
target. Unbound observations and observations of child artifacts remain available
in the open session and its Evidence bundle; they are excluded from the target's
saved replay entries without changing their subjects.
The snapshot also retains available Evidence linked by selected records, including
transitive sources with an unbound or different subject. Those sources are not
replay entries for the selected target. Unrelated records remain excluded.
Snapshots retain eligible target-scoped question histories in full, including
mutation Evidence recorded while another target was active. Observations and
@@ -637,30 +615,6 @@ cached result. `close_binary` accepts `snapshot_path` and optional
`overwrite: true` to save before releasing provider resources. A failed save
leaves the session open so the caller can resolve the output failure.
When a validated snapshot does not match the selected binding, the error's
`details.binding_mismatch` reports confirmed target identities, provider
identities, or profile digests that differ. `snapshot` is the saved value and
`selected` is the requested value. Multiple differences can be reported. A
`profile_comparison: "not_compared"` means the profile was not available at
that boundary; it does not establish a match. A profileless snapshot can report
`workflow_scope: "incompatible"` even when its target and profile match.
Choose a matching target, provider, and profile, or analyze without the snapshot
and save a new one for the intended binding.
For a profile mismatch, the saved snapshot contains
`binding.analysis_profile.parameters`. If `binary_session` reports an active
profile whose digest matches `binding_mismatch.profile_digest.selected`, its
`analysis_provider_binding.analysis_profile.parameters` can be compared with
those saved parameters. A rejected switch leaves the previous session active,
so its profile may be different from the attempted selection; check the digest
before using it for this comparison. A configuration difference does not by
itself establish why analysis results differ.
If no target is open and you intend to inspect the selected binding, open that
target without the snapshot, then check its profile digest in `binary_session`
before comparing parameters. This may start the selected provider; the saved
snapshot remains a separate file.
If a selected snapshot's file descriptor cannot close, `open_binary` reports
`cleanup_incomplete`. `binary_session` reports `cleanup_pending` even when no
target opened. Retry `close_binary` to release the retained descriptor before
@@ -1308,18 +1262,6 @@ new file. A publication that has committed can win a later cancellation; the
after commit, the `cleanup_incomplete` error reports the same file as
`details.published_path` and `details.bytes`.
When a failure retains a completed observation and no bundle committed,
`details.source_closure` states what that observation cites. `publication` is
`not_published`. `records` holds authentic source Evidence this invocation
captured, within 64 KiB. `omitted` lists up to 64 other identities and
`omitted_count` the total: `not_captured` means the invocation holds no such
record, so nothing is shown for it or its ancestors; `not_inlined` means it was
captured but is not in this response. `complete` is true only when every cited
record is in `records` or embedded in the observation. If examining the closure
itself fails, `collection_failure` names the error type and the closure state is
unknown; the original failure is still returned. Nothing is retained after exit,
and running the command again is a new observation.
When the reader of piped output closes early, as `rea ... | head` does, the
CLI stops writing, prints no error and exits `141` (`128 + 13`, the status of a
SIGPIPE-terminated writer). The operation itself may have completed; only its
-4
View File
@@ -45,10 +45,6 @@ the client's configured command or reproduce the client's environment. If doctor
passes but the client disconnects, inspect the client's startup error and registered
launcher; see [verification scope](client-verification.md#launch-environment-verification).
`rea doctor` has the same limit for provider checks, and reports a provider setting
the registration's file does not carry as a
[non-blocking unknown](installation.md#check-readiness-for-your-task).
## Runtime settings and client deadlines
`update_analysis_settings` changes curated defaults inside the running REA
+3 -28
View File
@@ -222,25 +222,6 @@ Error copying, attribution, redaction, and delivery must preserve these facts.
Human-readable remediation explains the same failure; clients should not need
to parse that sentence to identify an editable setting.
`scope` and `applies_at` describe a continuing caller, such as an MCP
connection. A one-shot invocation, such as a CLI command, has released its
provider session and exited its runtime before its caller reads the failure,
so its adapter projects with an explicit `"invocation"` caller lifetime.
`details.limit` then keeps every other field and reports
`scope: "rea-invocation"`, `applies_at: "next_invocation"` and the launch
`environment` seed when one exists. `agent_editable: true` still holds, because
the caller chooses that environment value or per-call `parameter` for the new
invocation; `rea_restart_required: false` means no running REA process needs a
restart. A producer keeps guidance that holds only while its caller retains the
session, such as "the Ghidra session remains usable" or a same-session runtime
update, in `continuingCallerGuidance`; the shared presentation adds it only for
a continuing caller. The producer's own message and remediation must stay true
after the session is released. A composed error records each embedded child
failure through `childFailureDiagnostics`, which snapshots it at composition
for both lifetimes when they differ, so cleanup history keeps its state and
the final projection selects the caller's form. Failures retained inside
Evidence keep the continuing form, so CLI and MCP Evidence stay identical.
Cache policy is part of enforcement. Successful reusable analysis can survive
a budget update. Transient Ghidra decompile failures remain uncached so a raised
deadline can recover on the same Program. Admission checks still apply to reused
@@ -305,9 +286,7 @@ whether live repair is possible.
## What agents do
Use `details.limit` for a direct runtime update when it names an editable
setting with `scope: "rea-runtime"`. For `scope: "rea-invocation"`, set its
`environment` or `parameter` for a new invocation instead.
Follow `remediation.action` for per-call, fixed and external controls.
setting. Follow `remediation.action` for per-call, fixed and external controls.
Inspect settings only when bounds or application details are missing. Restart only when it says the value is
read at startup. A sentence that a control does not raise the limit is not a
request to change it.
@@ -337,12 +316,8 @@ request to change it.
`runtime-configuration:` check. Re-run `rea setup` for that client when the
setting is on the registration allowlist, then reconnect so the new process
sees it.
- `invalid_request`: follow the specific recovery and reported issue. Inspect
the schema or CLI help when the input violates the contract or the response
asks for `inspect_input_contract`. A valid selector can instead require a
different choice, such as selecting a reported address; an inventory miss
does not establish that the target lacks the item. Do not repeat the
unchanged call when the response says it cannot succeed unchanged.
- `invalid_request`: correct the reported fields from the current schema or
CLI help. Do not repeat the unchanged call.
A restart is required to adopt a changed launch environment or registration.
The curated runtime settings above require no REA restart. It is not the first step for a transient
-11
View File
@@ -147,14 +147,6 @@ window-targeted renderer reload/crash actions, and synthetic `open-url` or
WebContents identities, process metrics, and IPC channel and value-shape metadata.
Payload values are not retained.
For the exact Playwright launch sequence, the active hook removes only REA's
bootstrap arguments from the application-visible `process.argv`. The vector keeps
the selected entry point and every caller argument, in order, including repeated
hook paths, `.asar` filenames, and debug-like options. The private launch handoff
is consumed before application code runs. If the observed sequence cannot be
matched exactly, argv stays unchanged and capture coverage reports `hook_conflict`;
REA does not guess an entry point or remove arguments by filename patterns.
`window_index` selects from Playwright's current Page list at the start of each action. REA resolves that Page to its corresponding BrowserWindow once; the action's `target` uses the BrowserWindow's capture-scoped `window_id`, matching the window inventory. Reordering either enumeration after selection does not retarget the action. Handle cleanup failures retain the completed action records and remain owned for provider cleanup retry.
Run the real fixture verifier with an operator-provided Electron runtime:
@@ -201,9 +193,6 @@ neither result means the requested host operation occurred. The existing
`openExternal`, `openPath`, and `trashItem` blocks reject their promises.
The `trashItem` block prevents that call from reaching the OS trash operation;
it does not intercept other ways the application can remove files.
Process rejection and uncaught-exception events are observed at dispatch without
adding exception handlers. The runtime and application retain their warning,
handler, and fatal-exit policies, including for rejected shell-block promises.
CLI SIGINT/SIGTERM requests cancellation and awaits owned-process cleanup.
Window IDs, URLs, and cached titles come from one main-process Electron snapshot,
so a crashed renderer does not require a successful renderer-side title query.
+1 -7
View File
@@ -38,12 +38,6 @@ were examined; an unavailable section name does not erase those note facts.
For GNU notes, `gnu.build_id_hex`, `gnu.abi_tag`, or `gnu.gold_version` are
filled when the descriptor matches that type. Other owners keep `gnu` null.
For Linux `CORE` notes, `core.prpsinfo`, `core.file`, `core.siginfo`, or
`core.auxv` are decoded for process arguments and status (`NT_PRPSINFO`), mapped
file memory regions and paths (`NT_FILE`), fault signal and code (`NT_SIGINFO`), and
auxiliary vectors (`NT_AUXV`) across 32-bit and 64-bit images. Other owners or unparsed
notes keep `core` null.
`PT_NOTE` and `SHT_NOTE` are examined independently. The same file range may
appear twice when both tables declare it.
@@ -54,7 +48,7 @@ the image never carried notes in another container or overlay.
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
32 note entries are examined by default across every table. Retained note
names, section names, GNU descriptor text, and core dump string representations share a scan budget based on
names, section names, and GNU descriptor text share a scan budget based on
the source bytes plus any decoded section-name table, capped at
`max_file_bytes`. The raw source and decoded table must also fit together
within that ceiling. If admission fails, the error reports the required
-180
View File
@@ -52,15 +52,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -124,15 +115,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -196,15 +178,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -268,15 +241,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -340,15 +304,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -412,15 +367,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -484,15 +430,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -556,15 +493,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -628,15 +556,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -700,15 +619,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -772,15 +682,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -918,15 +819,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -990,15 +882,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1062,15 +945,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1134,15 +1008,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1206,15 +1071,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1278,15 +1134,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1350,15 +1197,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1422,15 +1260,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1494,15 +1323,6 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
-9
View File
@@ -28,15 +28,6 @@ The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
no system tools and changes no user configuration. [Upstream provenance and
license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md) are packaged.
Limiter discovery validates the selected path without launching it. A relative
path or directory is reported as `not_configured`, a missing file as
`executable_missing`, and denied access or another filesystem-probe failure as
`runtime_unavailable`. Recovery names the selected path and
`REA_EVM_PRLIMIT_COMMAND`; denied access asks for execute permission and
filesystem access. The original admission or system code stays in diagnostics.
An available filesystem probe does not verify the selected tool's version or
util-linux compatibility, and REA does not substitute another command.
## Evidence semantics
- `encoding` is required: raw bytes or UTF-8 hex, with optional `0x` prefix and
-3
View File
@@ -26,9 +26,6 @@ REA does not install these tools, their dependencies or system packages. It
checks the tool's `--version` banner when executing a request. Binwalk must
print `binwalk <version>` and Unblob must print the version alone. Inspection and
extraction have independent prerequisites; one does not require the other.
Capability discovery recommends `REA_BINWALK_COMMAND` for missing inspection
and `REA_UNBLOB_COMMAND` for missing extraction. Configuring the other engine
does not enable the unavailable operation.
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed. `rea doctor` does
not currently inspect firmware readiness. Engine selection/version checks run
+7 -14
View File
@@ -109,13 +109,9 @@ server; older servers keep the complete request after schema discovery.
header check does not establish valid table contents or exported symbols.
- **`libflutter` facts** are per-ABI observations: byte size, SHA-256
digest, GNU build IDs from ELF-declared `PT_NOTE` / `SHT_NOTE` ranges,
and Dart/toolchain strings the build carries. `dart_version` accepts the
labeled `Dart VM version:` form and the unlabeled Dart
`Version::String()` form (`<version> (<channel>) (<commit time>)` with an
optional ` on "<os>_<arch>"` suffix) that release engines embed. An
unlabeled match is a candidate based on its shape; it is not bound to a
Dart symbol and another string can match. `null` means no supported pattern
was found, not proof that no version exists. Android clang/LLVM toolchain
and any _labeled_ strings the build
carries. Release engine builds often strip the `Dart VM version` string —
absence is reported as `null`, not guessed. Android clang/LLVM toolchain
lines are reported verbatim as build provenance.
`build_id_candidates` preserves distinct IDs. `build_id` is populated only
when all declared note ranges were examined and one ID was found;
@@ -126,13 +122,10 @@ server; older servers keep the complete request after schema discovery.
Compressed `SHT_NOTE` sections are skipped with an unsupported-compression
limitation and partial coverage; IDs from other note ranges remain candidates.
Encoded note storage is never interpreted as note headers or inflated.
- Version and toolchain strings must fit their complete grammar.
`dart_version_capped` and `toolchain_capped` independently report labels
omitted by grammar or projection bounds; either makes identification
coverage partial. Values split across read chunks are retained without
publishing truncated prefixes. Unlabeled runs that miss the
`Version::String()` grammar are ignored and do not set
`dart_version_capped`.
- Labeled strings must fit their complete grammar. `dart_version_capped`
and `toolchain_capped` independently report labels omitted by grammar or
projection bounds; either makes identification coverage partial. Values
split across read chunks are retained without publishing truncated prefixes.
- **`coverage: "partial"`** also appears when an ABI carries `libapp.so`
without `libflutter.so` (or vice versa).
- The APK itself is digested before parsing, so `target.sha256` binds the
+1 -5
View File
@@ -172,11 +172,6 @@ have no file backing. ELF mappings are observed
without independent format-specific load-image verification, as the result's
status and limitations state.
Relocation rows retain Ghidra's `symbol` value, including an empty string or
null when returned by Ghidra. It is a provider observation, not proof of the
runtime-resolved address. Older Evidence may not have recorded this field;
a relocation page requesting it reports that uncertainty.
For large relocation inventories, select MCP `inspect_native_load_image` with
`detail: "summary"`. It returns verification state, observed identity and counts,
while retaining complete Evidence. Use `normalized_result.parent_evidence_id`
@@ -250,6 +245,7 @@ profile stages before retrying their retained descriptors.
| CFG | Dossiers use `BasicBlockModel` and retain only non-call successors inside the function body. CFG topology is address-normalized for comparison; provider-specific block construction remains a declared difference. |
| P-code value flow | Function dossiers include up to 3,000 high-p-code operations, at most 64 inputs per operation, and 12,000 def-use edges. Results identify memory reads/writes, branches, and calls by p-code opcode, and mark decompiler-dead operations. Large results report truncation, an omitted-operation lower bound, and known omitted-input/edge counts. The dossier is an intra-function graph; `trace_native_values` composes derived parameter/argument and return/output dependencies across resolved calls. It does not resolve memory aliasing, call side effects, runtime behavior, persistent state, or business meaning. |
| Result extent | Function instruction scans and native API boundary observations remain complete. P-code flow is the explicit bounded exception; its count fields distinguish exact retained counts from lower-bound or known-omitted counts. Caller cancellation and provider failures remain distinct. |
| Parameter lists | `native_api.parameter_list.basis` is `signature` when Ghidra fixes HighFunction inputs from a non-default signature: an empty one as a void-input lock, or every valid parameter type-locked. Otherwise it is `recovered` and the decompiler may have omitted parameters. `variadic` is the HighFunction prototype's varargs flag. `signature_source` names the signature's origin, which may be analysis or an import; a signature basis is not source-level proof. Ghidra snapshots recorded without this fact are not replayed. |
Runtime updates apply to newly accepted operations on the existing Program.
They preserve accepted budgets and do not clear cleanup blockers or change the
+1 -2
View File
@@ -43,8 +43,7 @@ rea decompile /absolute/path/to/program main --json
If the registration cannot be read or validated, provider diagnostics retain its
selected `registration_path` and the configuration owner's setting/constraint
facts in `configuration_details.settings`. The availability's `remediation`, and
each operation it makes unavailable, carry the configuration owner's repair.
facts in `configuration_details.settings`, plus `configuration_remediation`.
`configured` means a path was selected, not that it is valid or an IDA connection
was established; failed registrations remain unavailable. Correct
`REA_IDA_MCP_CONFIG` in the next CLI invocation's environment or the MCP server's
+1
View File
@@ -51,6 +51,7 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
+7 -71
View File
@@ -390,20 +390,6 @@ A scoped report has `scope.mode: "explicit"`. Its `scope_checks` determine
`environment_healthy` summarizes the full audit. `--target` adds a target check,
but the report remains audit-wide unless a scope option is also supplied.
Provider checks read the terminal running doctor. A client-started server may
receive a different environment, and doctor cannot observe it. When an available
provider relies on a setting that a registration's file does not record, or
records with other text, doctor adds a non-blocking
`registration_environment:<client>` check with `classification: "unknown"`. The
setting names appear in that registration's `provider_settings`. Selecting the
client or an affected provider places the check in `scope_checks`; it never
changes `healthy`, because the client may still pass the setting on. Preview
`rea setup --client <client-id> --skill=false --dry-run` from that terminal and
approve it to record the settings, then restart the client. To see what the
running server can use, call `binary_session` in the client and read
`result.tool_availability`. For Hopper, only an explicit `HOPPER_LAUNCHER_PATH`
is compared.
An invalid `REA_LOG_LEVEL` fails every scope, including one client or the
installed skill, because the executable rejects it before any command. A
selected client also fails for every other setting that MCP startup rejects,
@@ -680,8 +666,7 @@ Hopper is separate commercial software with its own license. Its free demo has
vendor-defined limits, and a paid license is optional. REA reuses any detected
installation and preserves Hopper during uninstall.
The automated Hopper installer supports macOS 12+, subject to the minimum
macOS version of the current Hopper release, and x86_64 hosts running
The automated Hopper installer supports macOS 12+ and x86_64 hosts running
Ubuntu 24.04+, Fedora 41+, Nobara 44+, Arch Linux, CachyOS, or Omarchy. The
official Linux packages are x86_64-only, so setup does not offer Hopper on other
Linux architectures; configure [Ghidra](#ghidra) there instead. REA itself runs on any
@@ -690,33 +675,12 @@ an existing Hopper launcher is selected through `HOPPER_LAUNCHER_PATH`. Ghidra
and IDA have their own provider-specific host requirements; Windows Ghidra uses
the [experimental P0 boundary](windows-ghidra-p0.md).
Bazzite uses RPM packages, but REA does not install Hopper automatically on it.
Setup skips the Hopper offer because Bazzite is outside the supported host list,
regardless of whether Xvfb is installed. Bazzite documents
[containers for installing Linux packages](https://docs.bazzite.gg/Installing_and_Managing_Software/Containers/).
If using a container, check its distribution and architecture against the list
above. Install REA and Hopper in that container, give it access to the files you
want to analyze, and configure your agent to launch REA there. Verify installation
and analysis before relying on that workflow.
`rea doctor` checks health; it does not offer to install Hopper. Run `rea setup`
for installation. Setup cannot prompt when its output is redirected or during a
container image build. To approve Hopper installation without prompts, run
`rea setup --yes --install-hopper`.
If a Hopper request fails on macOS or Linux, the setup result's `download`
field names the request (`release_feed`, `package` or `public_key`), its `url`,
and the observed outcome `kind`: `http_status` with `httpStatus`, `timeout`,
`network`, `response_too_large`, or `url_refused`. The remediation names the
same URL and outcome. HTTP 404 or 410 means the file is missing, so an
immediate retry is unlikely to help; 5xx, 408 and 429 indicate a temporary
service failure. The status does not establish the underlying cause or guarantee
that retrying will succeed. Doctor and Linux launches report the same request
and status when publisher verification cannot download the current package.
If the Linux package download receives an HTTP failure, setup includes the
observed HTTP status in its `download_failed` diagnostic. The status does not
establish the underlying cause or guarantee that retrying will succeed.
Hopper metadata and package downloads require HTTPS throughout the redirect
chain; a redirect to HTTP or to a URL carrying credentials is refused before
that destination is requested, and those credentials are not reported.
chain; a redirect to HTTP is refused before that destination is requested.
On macOS, approved setup downloads the official DMG, checks its published size
and digest, validates the application bundle, and atomically installs it to
@@ -724,15 +688,6 @@ and digest, validates the application bundle, and atomically installs it to
operator can choose its demo mode or activate an existing license. Homebrew and
administrator access are not used.
Each Hopper release declares its own minimum macOS version, so the setup plan
cannot confirm support before approval. Setup compares this Mac's version with
the release metadata's `deployment_target` before downloading the DMG, and with
the signed application's `LSMinimumSystemVersion` before copying it. If this Mac
is older, setup fails with `incompatible_host_version`, names both versions, and
installs nothing. Update macOS, or install a Hopper release that supports this
macOS version yourself and set `HOPPER_LAUNCHER_PATH`. An existing Hopper
installation is not checked against the current release's minimum.
The Hopper launcher action used by REA creates a document from an executable;
its supported command-line interface does not attach to an already-open
document. While another live REA session owns the same target and loader
@@ -773,12 +728,6 @@ Rotating signing subkeys remain bound to that primary identity.
The feed row itself is not PGP-signed; doctor labels its display version as
`reported_release_version`, separately from the authenticated launcher identity.
Even a correctly signed package can contain a launcher whose hash disagrees with
the feed. REA stops before installing that package. When reporting this error,
include the release version, package format and both hashes from the diagnostic.
Each format needs its own check: a matching Ubuntu DEB does not establish that
the Fedora RPM matches.
The approval plan includes the caller-selected cache location. Approved setup
retains the signed archive, public key and release metadata under
`${XDG_CACHE_HOME:-$HOME/.cache}/rea/hopper-releases/<launcher-sha256>`.
@@ -812,16 +761,6 @@ host mount and the rest of `/tmp` remain unchanged; this fallback never invokes
`sudo`. `rea doctor --provider hopper --json` reports the selected
strategy and both host and effective mount facts.
You do not need to start a separate `Xvfb :99` server. REA starts and manages its
own display. After installation, run `rea doctor --provider hopper --json` and
check `hopper-demo-runtime` for display and Python problems. Setup also runs these
health checks: installing the package is only one step toward a working provider.
If an image build ends with `exit status 1`, read the setup output immediately
before that summary to find the failing step. On minimal Ubuntu images, run
`apt-get update` before setup if an earlier layer deleted the package indexes.
REA's package installation commands do not refresh those indexes.
Hopper also embeds Python; the Python executable and standard library alone
are insufficient on Ubuntu. Approved setup checks the system interpreter
at `/usr/bin/python3` and, when necessary, installs its matching
@@ -1032,9 +971,7 @@ including while a shutdown request is in flight. A helper exit alone is
insufficient, and an application that never authenticated has no verified
process identity. If no matching bridge becomes available, inspect and close
the reported document in Hopper before ending the owning REA connection.
`close_binary` preserves Evidence and complete Unknown history in memory on the
same connection. Export the bundle before ending the REA process to resume later;
`reset_investigation(save_path)` checkpoints and explicitly clears those records.
Export the Evidence bundle before `close_binary` clears session records.
`binary_session.cleanup_pending` remains true while provider cleanup is retained,
and provider health remains visible even with `open: false`; that flag alone
does not establish successful cleanup.
@@ -1345,8 +1282,7 @@ fails with `provider_timeout` and that message; other Ghidra tools stay
available. If the caller also cancels while the timeout settles, the session
still closes and the message says so. Session reuse concerns an active MCP or
library session; a standalone CLI command still closes its own session when it
finishes, so its timeout names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` for a new
invocation instead. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
finishes. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
value for newly accepted operations on the existing Program; explicit `null`
removes the deadline. A transient timeout remains uncached so the same function
can be retried after an update. See the
+1 -8
View File
@@ -173,14 +173,7 @@ Results follow retained node order and default to offset 0 and limit 64. Supply
to examine relationships from that node. Other typed seeds do not search labels.
The result contains the matching basis, complete reachable subgraph, terminal
paths, authority summaries, and native handoffs. A bidirectional trace of a
large application can exceed one MCP response. Over MCP, pass
`view: {"kind":"summary"}` or a `feature-trace` page of `paths`, `nodes`,
`edges`, `seed_matches` or `native_handoffs` to retain the complete trace in
the session and return only selected rows with the trace's identity and
coverage. Page the retained trace with `inspect_analysis_view`; an oversized
complete reply offers its first `paths` page as a ready-to-call continuation.
See [MCP contracts](mcp-contracts.md) for filters. A handoff binds
paths, authority summaries, and native handoffs. A handoff binds
the exact native artifact digest and requested exports from the application
graph. Trace direction selects the reachable addons; requested
exports for each selected addon come from the complete supplied graph, so an
+3 -22
View File
@@ -876,22 +876,8 @@ and Evidence. If that expansion does not fit, the file node retains
`static_analysis` and any admitted `semantic_module_analysis` observations,
including their exact values and source locations. Use the public modules item
view to read them. The graph marks those unexpanded relationships as unknown.
The parent charges everything it retains against the heap that was free when
analysis started, less a transfer reserve. Semantic facts and expanded
relationships stop at half of that capacity, and a source refused there keeps
its static facts with the refusal reported. Static facts can continue to three
quarters of it while the live heap also has room for another copy of what it
holds, so results charged for earlier sources do not cost later sources their
static facts when the heap can still hold them. When that share runs short,
relationships that were reserved but not yet expanded are given up before any
static facts, and each affected file reports the omission.
If static facts themselves exhaust that share, or the parent's live heap cannot
hold the next checkpoint, analysis stops; inventoried but unexamined files remain
present with explicit unknown scopes. The failure reports the limit that applied
in bytes, what was still available, the live heap in use and what the checkpoint
needed.
If cumulative result capacity is exhausted, analysis stops; inventoried but
unexamined files remain present with explicit unknown scopes.
### Large local JSON inputs
@@ -1016,12 +1002,7 @@ reason in the renderer observations.
HTML script source ranges follow the HTML parser across LF, CRLF, and bare CR
line endings, preserving UTF-16 columns.
Inline JavaScript in local HTML, meaning classic or module `<script>` bodies
without a `src` attribute, is not analyzed. Embedded SVG scripts use `href`
(or `xlink:href` when `href` is absent) instead: an existing reference, even
an empty one, suppresses the inline body. SVG's `src` and HTML-only `language`
attributes do not suppress its JavaScript body. SVG references and inline
bodies share the same inert parsing and source coordinates as HTML scripts;
template content stays inert. Each HTML file that has an inline body gets an
without a `src` attribute, is not analyzed. Each HTML file that has one gets an
unknown `parse-html-inline-scripts` scope with the exact body count and the
script type and source range of the first 100 bodies; beyond that, coverage is
truncated under the `html_inline_script_locations_per_file` limit, and the
+2 -3
View File
@@ -65,9 +65,8 @@ confidence and basis, and does not prove a backend endpoint or reachability.
Pages obey the shared 1 MiB projection budget; MCP transport applies its own
wire-size guard. A byte-limited page returns a continuation; an indivisible
oversized record or metadata returns a typed resource error. Select fewer
fields or retrieve the complete retained parent through `get_evidence_bundle`
(`detail: "records"` with its `evidence_id`), `export_evidence_bundle`, or the
CLI's exported bundle. Full Evidence is never
fields or retrieve the complete retained parent through `get_evidence_bundle`,
`export_evidence_bundle`, or the CLI's exported bundle. Full Evidence is never
silently truncated.
Collections are `summary`, `routes`, `occurrences`, `sources`, and `unknowns`.
@@ -93,10 +93,6 @@ coverage. It does not print captured source text.
Runtime targets, frames, scripts, and workers become capture-scoped graph
nodes. REA evaluates compatible static candidates conservatively:
V8 Inspector targets whose protocol type is `node` use JavaScript asset/module
candidates for their verified entry location. A target-only correspondence does
not mark that script loaded; load-state evidence requires a script observation.
1. A captured-source SHA-256 plus one matching mapped location yields
a content-and-location match.
2. When no static candidate exists at a mapped location, one unique exact
+4 -39
View File
@@ -1,27 +1,9 @@
# Kuna provider
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.990, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases; `rea doctor` and analysis Evidence report them as unverified. Verification compares the reported version; it does not authenticate the executable. Analysis profiles retain the observed version and executable digest, independently of REA's current verified-release policy. The 1.990 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.986, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases, which it reports as unverified in `rea doctor` and in each analysis profile. The 1.986 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
Kuna is static: it reads the selected image and never executes it.
This profile change makes earlier Kuna analysis snapshots and resumable directory
exports incompatible, even with the same Kuna executable. Keep the old files:
their recorded Evidence remains historical. To continue analysis, open the
binary without the old snapshot and save a new one, or choose a new export
directory. Existing snapshot Evidence can be extracted from its
`evidence_bundle` JSON member and imported as a separate Evidence bundle; that
does not restore cached workflow entries or resume the old directory export.
For example, using `jq` and a new output path:
```bash
jq -e '.evidence_bundle | select(type == "object")' old-snapshot.json > history.json
rea evidence-import history.json --json
```
The CLI command validates the bundle in a one-shot session. To use the records
in an MCP session, call `import_evidence_bundle` with the extracted file path.
This validates the embedded bundle, not the rest of the snapshot container.
## Configure
```bash
@@ -48,10 +30,6 @@ completes. Cleanup retries keep both the observations and the exact file owner.
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile, and for the inventory run REA makes when the export ends without one. |
| `REA_KUNA_JOBS` | 1 | `decompile-project --jobs`. 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
Pseudocode read from a parallel project export includes a prototype-context
limitation in its Evidence, including in function dossiers and composed
workflows. Early single-function results have their own source limitation.
A single-function decompile, inventory, string, graph or xref run that exceeds its deadline fails as `provider_timeout`, naming the setting that owns the deadline and that raising it takes a restart. A project export that exceeds `REA_KUNA_PROJECT_TIMEOUT_MS` is an export failure instead: requests that need the export report `capability_unavailable` with the export's own retry guidance.
Windows is not supported yet. Supported targets are ELF and thin Mach-O executables for x86-64, AArch64 and ARM; PE and universal Mach-O are rejected with a typed reason.
@@ -60,7 +38,7 @@ Windows is not supported yet. Supported targets are ELF and thin Mach-O executab
| Tool | Source |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `list_procedures`, `search_procedures`, `procedure_address` | Kuna's callable inventory (`kuna functions --json`), including import slots but not [Mach-O data](#mach-o-data-entries) |
| `list_procedures`, `search_procedures`, `procedure_address` | Kuna's callable inventory (`kuna functions --json`), including import slots it does not decompile |
| `address_name` | Function entries, then the data labels Kuna's export assigns |
| `procedure_pseudo_code`, `batch_decompile` | The project export, or a single-function run while the export builds |
| `procedure_assembly` | The project export's labeled listing |
@@ -69,8 +47,6 @@ Windows is not supported yet. Supported targets are ELF and thin Mach-O executab
| `xrefs` | Exact-address incoming reference sites, with forwarding aliases filtered explicitly |
| `analyze_function` | Pseudocode and assembly from the export, plus call relationships; typed references, comments, strings and CFG remain unavailable |
Kuna's inventory gives some entries aliases, such as ELF versioned-symbol names and generated `sub_` names. Name selectors accept an alias that names one entry and list every entry a shared alias names. `search_procedures` matches aliases too: each entry appears once under its primary name, with the matching aliases in `matched_aliases`. `list_procedures` shows primary names only.
Typed procedure references, basic blocks, raw-memory tools, annotations and type recovery are not available from this provider. Use another provider for those operations.
## How answers are produced
@@ -86,7 +62,7 @@ The first text or assembly request in a session looks for this target's export i
- Assembly and data names wait for the export; progress notifications report the wait every ten seconds. A session does not rebuild a failed export: requests that need it fail with `retryable: false`, the export's failure, its captured Kuna output and what to change before reopening the target. A setting such as `REA_KUNA_PROJECT_TIMEOUT_MS` or `REA_KUNA_JOBS` is read when REA starts, so changing it also needs a restart. Pseudocode keeps using single-function runs.
- A single-function answer says so in its limitations. It lacks the export's whole-program prototypes, so a later request can return different text for the same function.
Pseudocode, assembly, `analyze_function` and data-name answers record where their text came from in `raw_result.source`: `single-function`, or `project-export` with the cache directory. `procedure_pseudo_code` and `procedure_assembly` answers from the export also name the file, byte span and the file's SHA-256. Combined `analyze_function` answers retain both facet records in `raw_result.source.files`, each with its file, digest and byte span. Both texts come from one export; losing that project retries the complete report. Listings and procedure lookups carry no `raw_result`; omitted [Mach-O data entries](#mach-o-data-entries) are reported in their limitations. Agents can open or search the export's `.c` and `.asm` there directly when that is cheaper than calling a tool per function. Text read that way is target-derived, like any decompiler output, but it bypasses the untrusted-content marker that REA's tool results carry.
Pseudocode, assembly, `analyze_function` and data-name answers record where their text came from in `raw_result.source`: `single-function`, or `project-export` with the cache directory. `procedure_pseudo_code` and `procedure_assembly` answers from the export also name the file, byte span and the file's SHA-256. Combined `analyze_function` answers retain both facet records in `raw_result.source.files`, each with its file, digest and byte span. Both texts come from one export; losing that project retries the complete report. Listings and procedure lookups carry no `raw_result`. Agents can open or search the export's `.c` and `.asm` there directly when that is cheaper than calling a tool per function. Text read that way is target-derived, like any decompiler output, but it bypasses the untrusted-content marker that REA's tool results carry.
The background export belongs to the session. `close_binary`, or the end of a CLI command, cancels it unless another session still waits for it. A one-shot CLI decompile therefore answers from a single-function run; any request that needs the export, such as `procedure_assembly` or `analyze_function`, keeps it.
@@ -96,17 +72,6 @@ Regex procedure and string search uses ECMAScript Unicode matching in a cancella
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger text remains in the local export and the error identifies the file and range. `analyze_function` with `detail: "complete"` (the CLI default) returns the complete dossier inline, which can still be large; later retained views do not remove that first transfer. The MCP default retains the dossier and returns its summary, so page the facets you need through `inspect_analysis_view`, or prefer the individual text tools for a focused question.
## Mach-O data entries
Kuna's inventory also lists Mach-O exports that are data, such as globals in `__DATA,__data`, constants in `__TEXT,__const` and `__mh_execute_header`. REA classifies each entry with the target's own section table, the same load-command facts `inspect_macho` reports, read from the leading bytes kept while the session's private copy is hashed:
- A section with `S_ATTR_PURE_INSTRUCTIONS` or `S_ATTR_SOME_INSTRUCTIONS`, or of type `S_SYMBOL_STUBS`, holds code; the entry stays.
- A lazy or non-lazy symbol-pointer section (`__got`, `__la_symbol_ptr`) holds import slots; the entry stays.
- Any other section, or the header and load commands before the first section, holds data. Listings and searches omit the entry and say how many they omitted in each region. Pseudocode, assembly, `analyze_function` and `procedure_address` refuse it by name or address, and call queries refuse its name, naming its region; call queries by exact address stay validated by Kuna's graph. `address_name` and `xrefs` still accept it.
- An address outside every section and the header stays, with a limitation saying REA cannot classify it. If the section table cannot be read, every entry stays and listings say why. Answers about one such entry, including pseudocode, assembly, `analyze_function`, `procedure_address`, call queries by name and `address_name`, carry the same limitation; code and import slots carry none.
A section's attributes are the format's declaration, not proof of what runs there, and a kept entry is not proven to be a function. ELF inventories are kept as Kuna reports them. On a Mach-O target, the first listing or lookup establishes the private copy that Kuna commands also use, once per session, even when a warm export answers it.
## Strings
`list_strings` and `search_strings` share one lazy `kuna strings --json --encoding all --min-length 5 --termination any --no-xrefs` scan of the digest-checked private input. Skipping references avoids program lifting, a call graph and a C export. REA validates the input, scan policy, every row and the total before caching any facts. Encoding, character count, byte length and terminator facts must agree; supplementary UTF-8 characters count as Unicode scalars. Malformed output is an error, not an empty search. REA parses the JSON as Kuna writes it instead of buffering it, so the scan has no fixed size: Kuna writes about 290 bytes per row, 31 MB for a 20 MB Ghostty binary and 179 MB (611k rows) for an arm64 Firefox `XUL`. Rows are validated one at a time, and the first contradiction stops Kuna. Text REA does not keep is dropped unassembled; kept text and retained rows are admitted against the REA process's live heap headroom as they arrive; a refusal is a `memory` resource constraint with `retryable: false`, and closing other targets or starting REA with a larger Node.js heap makes room. String scans use the whole-program `REA_KUNA_PROJECT_TIMEOUT_MS` deadline; `REA_KUNA_TIMEOUT_MS` applies to single-function decompilation. The parsed inventory belongs to one selected target/executable profile and expires on session close; Kuna diagnostics beyond their 8 MiB buffer are retained as a failure until close, while other failures are not cached.
@@ -172,7 +137,7 @@ the manifest's file identity as well as the export files. These refusal and
migration cases are verified with controlled Kuna-format exports; they do not
establish behavior of an independently installed Kuna release.
Builds publish by one rename, so a reader sees a complete export or none. If later staging retirement fails, the typed cleanup error retains the completed export's key, directory and manifest in `published_export`. The session adopts that verified project; later queries reuse its immutable files while `project_export.cleanup_incomplete` reports the retained owner's live state. `close_binary` retries only the remaining cleanup, and a successful retry resolves prior cleanup errors. A failed export followed by failed staging cleanup retains both failures; it does not invent a publication receipt. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. REA retains the file state verified with each digest, checks it before warm-cache reuse, and checks open descriptors before and after reading text. Assembly indexes are checked against the same state; a changed file makes the whole report retry rather than mixing facets from different exports. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven; that MCP error names the same-connection retry as `recovery.kind=retry_cleanup` ([retained cleanup retry](mcp-contracts.md#retained-cleanup-retry)). Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
Builds publish by one rename, so a reader sees a complete export or none. If later staging retirement fails, the typed cleanup error retains the completed export's key, directory and manifest in `published_export`. The session adopts that verified project; later queries reuse its immutable files while `project_export.cleanup_incomplete` reports the retained owner's live state. `close_binary` retries only the remaining cleanup, and a successful retry resolves prior cleanup errors. A failed export followed by failed staging cleanup retains both failures; it does not invent a publication receipt. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. REA retains the file state verified with each digest, checks it before warm-cache reuse, and checks open descriptors before and after reading text. Assembly indexes are checked against the same state; a changed file makes the whole report retry rather than mixing facets from different exports. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven. Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
## Verify with a real Kuna
+16 -208
View File
@@ -173,9 +173,6 @@ recommendation to change it. When it names no setting, reduce supported work;
do not invent one. Raising only the MCP
client's deadline cannot extend this budget. These hints never include the
complete catalog or caller arguments.
`recovery.kind=retry_cleanup` appears only on a `close_binary`
`cleanup_incomplete` error whose retained resources the session will retry; see
[retained cleanup](#retained-cleanup-retry).
Failures omit `structuredContent`, since each advertised `outputSchema`
describes successful results. Successful replies retain their schema-validated
structured data and matching text projection. Oversized errors are retained
@@ -210,19 +207,8 @@ unresolved relationship reads. Cancellation remains a tool error.
`trace_feature` validates each reference's containing-procedure resolution using
the same contract as navigation context. Explicit absence remains a successful
negative observation. If the provider reports a non-retryable capability refusal
attributed to the owner lookup without incomplete cleanup, the reference remains visible
with `containing_procedure.status: "unavailable"` and a residual unknown. That
unknown does not imply references were truncated.
Malformed provider replies return `unreadable_output` for
`resolve_containing_procedure` before successful-result projection;
cancellation and incomplete cleanup remain errors.
For a procedure match, `procedure_references` with `direction: "incoming"` can
provide the selected provider's reference kinds when supported. Its scope covers
the procedure body, so compare target addresses when selecting references to the
exact entry. It is a separate observation and does not establish runtime use or
complete reference discovery.
negative observation; malformed provider replies return `unreadable_output`
for `resolve_containing_procedure` before successful-result projection.
REA prepares complete MCP results within the pinned stdio client's 10 MiB
receive-buffer budget, including both text and structured representations and
@@ -298,12 +284,6 @@ limitation and reason IDs index that page's `items.messages` dictionary. Expand
those references to recover exact text, repeated messages, and absent versus
empty optional fields. Failed nodes retain their original error.
Retained `xrefs` Evidence also supports `native-observation` address pages and
`native-context` source caveats. `xrefs` still returns complete Evidence by
default and does not accept `detail`; when its MCP response exceeds the budget,
follow the returned continuation to page the retained result. These addresses
remain untyped; paging does not add reference kinds or establish completeness.
These views reference complete source context through `source_context.digest`
and caveat counts. Read it using the same parent with
`view: {"kind":"native-context","facet":"overview"}`. The other facets are
@@ -330,10 +310,6 @@ offered for completed `analyze_function` results only after session recording is
acknowledged; the complete result and its retention reference remain intact.
Oversized native inventories likewise include a ready-to-call first
`native-inventory` page. Reduce its limit or select fields when a row is large.
An oversized `trace_application_feature` result includes a first `feature-trace`
page of its retained terminal `paths`; page `nodes` or `edges` from there.
An oversized `batch_decompile` result includes a first body-free
`batch-decompile` page of its retained `items`; read bodies from there.
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.
@@ -347,26 +323,7 @@ a provider. `retention: "complete-record"` describes stored Evidence, while
`native_dossier.value_flow` independently reports truncation, unavailable or
unknown metadata. Missing omission counts remain null. Summary output carries
every matching record; actual transport admission still applies. Omitted
`detail` preserves complete canonical bundle delivery; filters require summary
or records.
To read a discovered record without transferring the whole bundle, call
`get_evidence_bundle` with `detail: "records"` and the same exact `filters`.
The result (`kind: "evidence-record-selection"`) pages the matching complete
records in canonical bundle (Evidence ID) order with `offset` (default 0) and
`limit` (default 8), and reports `matching_records`, `total_retained_records`
and `next_offset`. Each record is the retained Evidence itself, with an
unchanged ID, subject digest, provider, profile and readback, so it works for
any operation, including records that `inspect_analysis_view` does not support
such as annotations. `missing_evidence_links` lists direct `evidence_links` of
the returned records that this session or bundle does not retain; links are not
traversed. A selection is not an Evidence bundle: it omits manifests and
residual unknowns, cannot be imported, and describes historical observations
rather than current provider or database state. `offset` and `limit` require
`detail: "records"`. If one page exceeds the response budget, select fewer
records or one `evidence_id`; a single record larger than one response still
needs `export_evidence_bundle`, or `inspect_analysis_view` pages where its parent
supports views.
`detail` preserves complete canonical bundle delivery; filters require summary.
Residual-unknown mutations reject missing Evidence or unknown references and
unproven verified resolutions with nonretryable `invalid_request` diagnostics.
@@ -383,12 +340,6 @@ is null. `total` counts all matching heads; each page preserves complete records
in stable unknown ID order. Filters apply before paging. Pages reflect current
session state, so keep the unknown ledger unchanged while traversing it.
`list_unknowns` only lists registered heads. Import restores the Unknown records
present in a bundle; it does not turn residual question text embedded in Evidence
into registry entries. An empty registry therefore does not establish that
retained Evidence has no open questions. Inspect the relevant Evidence or its
analysis view for its residual questions.
Automatically retained workflow questions include their immutable result
Evidence ID. Distinct results retain independent questions and source links;
repeating the same result does not create another unknown. Workflow Evidence
@@ -408,26 +359,8 @@ that revision. If a list response overflows, retry on the same connection with
the same filters and offset and a smaller limit; export if one head still cannot
fit.
To find missing sources before a downstream analysis refuses them, read
`missing_direct_links` from the summary, or call `get_evidence_bundle` with
`detail: "links"`. The result (`kind: "evidence-link-inspection"`) transfers no
record payload. `filters` select the starting records, while membership is
checked against every record in the session or file, so a filtered-out source is
`retained`, not missing. Rows (`evidence_id`, `linked_evidence_id`, `status`) are
in canonical order, default to `link_status: "missing"` and page with `offset` and
`limit` (default 64); `links` counts every inspected link regardless of that
selection. `traversal: "direct"` (the default) reads the selected records' own
links and sets `retained_closure` to null, because it makes no closure claim.
`traversal: "retained-closure"` also follows retained links, as composed workflows
such as `build_call_path` do, and reports `complete` or `incomplete`; anything
beyond a missing link remains unknown. Missing means absent from this session or
file, not nonexistent, and neither status describes analysis coverage. Importing
a missing source changes this membership without changing any Evidence ID.
For a portable exported bundle, the CLI shares this projection:
`rea inspect-evidence-bundle '{"path":"/tmp/evidence.json","detail":"summary","filters":{"operation":"analyze_function"}}' --format json`.
`"detail":"records"` with an `evidence_id` filter returns that complete record,
and `"detail":"links"` reads link membership.
The CLI validates and materializes that local bundle under its existing memory
budget; summary delivery does not make arbitrary large files cheap to import.
@@ -588,11 +521,7 @@ names, the native API record, and high-pcode facets without starting a provider.
and limit default to 0 and 64; pseudocode uses UTF-16 code units and never
splits surrogate pairs, while other pages count rows. Unavailable facts and
provider limitations remain explicit. Source is
an exact same-session retained reference, portable inline Evidence, or an
Evidence record or bundle file. Native function, inventory, and observation
views require the `rea.analysis` predicate; function-matching views require
`rea.native-function-matching`. A mismatched predicate returns an
`invalid_request` identifying `source.predicate_type`. Views are
an exact same-session retained reference or portable inline Evidence. Views are
a summary, a layout mitigations or linkage facet, one section/symbol/module, or
a stable page with a caller-selected positive `limit`. Module pages include
JavaScript assets, bundled modules, and source modules. Select an exact
@@ -631,69 +560,6 @@ the working directory). Save its complete `--json` Evidence, then use
`inspect-analysis-view` with an `evidence-file` source and the same view. Neither
adapter executes the target or repeats analysis for retained queries.
`trace_application_feature` follows the same delivery contract. Omitting `view`
returns the complete trace. `view: {"kind":"summary"}` or a `feature-trace` page
retains the complete trace in the session first and returns only the selected
facts; query further pages with `inspect_analysis_view` and the returned
`parent_evidence_id`. The summary carries the trace identity (`trace_id`,
`source_evidence_id`, `source_graph_id`, seed and direction), the producer's
`summary` counts, `trace_coverage`, the traced subgraph's `graph_coverage`,
`evidence_links` and any no-match `semantic_discovery`. A `feature-trace` page
selects one `collection` in retained order and repeats that context:
- `paths`: exact terminal paths; `where.node_ids` selects paths visiting any of
those nodes and `where.end_kinds` selects their terminal node kinds.
- `nodes`: compact rows with identity, `seed`, `observation_count` and distinct
authority/state/confidence `basis`; `where.node_ids` and `where.kinds` filter
exactly, and `include_observations: true` adds each node's complete retained
observations.
- `edges`: exact relationships; `where.node_ids` matches either endpoint and
`where.relations` the relation.
- `seed_matches` and `native_handoffs`: the producer's exact rows.
Omitted filters select all; empty arrays select none. `offset` and `limit`
default to 0 and 64. Page `coverage` counts the selected collection only; it
never upgrades `trace_coverage`, and graph connectivity does not prove runtime
reachability. The CLI keeps complete trace JSON; save it and use
`inspect-analysis-view` with an `evidence-file` source.
### Selected batch decompile results
`batch_decompile` returns every pseudocode body by default. `detail: "summary"`
retains the complete batch in the session first and returns its producer counts,
`source_provider` (the composing workflow), `source_analysis_profile_digest`,
`decompiling_provider`, procedure identity counts, total and largest
`pseudocode` size, and `limitation_count`. A server that retains no Evidence
refuses the summary before analysis. Further views use the returned
`parent_evidence_id` through `inspect_analysis_view`, or a saved Evidence file or
portable bundle, and never repeat decompilation.
A `batch-decompile` view selects one `collection` and repeats the batch counts
and provider identities:
- `items`: rows in retained order with their original `index`, so duplicate
selectors stay distinct. A row keeps the selector in `address`, its `procedure`
identity and `status`, plus `pseudocode_code_units` or its exact `error` and
`limitations`. `where.indices`, `where.addresses`, `where.procedure_addresses`
and `where.statuses` filter exactly and combine; an item with unknown identity
never matches `procedure_addresses`. Omitted filters select all, and empty
arrays select none. `offset` and `limit` default to 0 and 64.
`include_pseudocode: true` adds each selected body.
- `pseudocode`: one `index`'s text in UTF-16 code-unit pages. `offset` and `limit`
default to 0 and 16,384, and `coverage` measures that body. An offset inside a
surrogate pair is refused, and a limit ending inside one stops before it. An
error item has no text and is refused with a pointer to `items`.
Page `coverage` counts the selection only: exhausting it establishes nothing
about functions outside the batch. Evidence `limitations` aggregate every item's
provider observations and are not attributed to one successful item; only error
items keep their own. A page over the response budget offers a smaller selection
from the same `source`: fewer rows, or one body's `pseudocode` pages when a single
row does not fit. An inline source offers none, because the record cannot be
repeated in that reply; resend it with a smaller selection.
`export_evidence_bundle` remains the complete alternative. The CLI has no batch command; save the Evidence and use
`inspect-analysis-view` with an `evidence-file` source or `--evidence-bundle`.
Completed `inspect_artifact` and atomic `inventory_artifact` Evidence also
support summary and inventory views. A summary reports the manifest's expected
counts separately from available record counts, observed entry kinds, inventory
@@ -809,24 +675,13 @@ advertises a file selector; a retained ID does not authorize a file read.
References belong to the current connection's Evidence ledger. Opening another
target or restarting the current target preserves retained records, including
when provider cleanup fails; records remain available for export and retry.
`close_binary` releases the engine and replay cache while preserving these records,
even when no binary is active or cleanup fails. `reset_investigation` explicitly
clears all records and Unknown revisions after draining earlier work; a healthy
active engine remains open. Its optional absolute `save_path` saves a complete
canonical bundle before clearing. Failed save, cancellation observed before clearing or incomplete cleanup
preserves records, and a published file is reported even if later cleanup fails.
A fresh connection has its own in-memory ledger; import the saved bundle to restore
original Evidence IDs and complete Unknown history. A missing reference reports its exact ID and
`close_binary` clears them, even when no binary is active. A fresh connection
has its own ledger. A missing reference reports its exact ID and
`details.reason: "missing"`; the server cannot infer
whether it was never recorded, cleared, or retained by another connection.
Supply complete inline Evidence, repeat its producer, or import an exported
Evidence bundle before referencing that imported record. Export a bundle or use
`reset_investigation(save_path)` before ending the process if the investigation
needs it later. Retention has no automatic persistence or silent eviction.
`binary_session.investigation` reports record/revision/head counts, estimated
retained representation bytes and the fixed allowance derived from initial V8
heap headroom. A capacity refusal preserves the existing investigation; checkpoint
and reset it, or start with a larger Node heap. These byte estimates are not RSS.
Evidence bundle before referencing that imported record. Export a bundle before
closing if the investigation needs it later.
CLI workflows that advertise `--evidence-bundle` can resolve the same reference
JSON from one explicitly selected local bundle, including an MCP export. They
@@ -864,27 +719,13 @@ 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.
Availability also reflects the active provider client's dispatched requests. A
refusal or provider failure observed for an operation keeps that operation
unavailable until a request for it dispatched later succeeds. A provider adapter
or process failure also marks that provider's other declared-available
operations unavailable until any request to that provider dispatched later
succeeds; their remediation states that the status was inferred rather than
observed for them. Recovery is not proof that each operation works, and failures
observed on other operations or providers remain. Outcomes are ordered by
dispatch to the provider client rather than reply completion, so a late reply
from an older request does not override a newer outcome. Request deadlines and
cancellations neither mark nor clear availability. A new provider client starts
without these outcomes.
`detail` selects the projection and the result echoes it. The default
`summary` keeps open state, target identity, the binding, current-route
`capabilities`, run, activity and health status, and each provider candidate's
identity, selection, and availability and target-support `status`, `code`, and
`reason`. It lists the fields it leaves out in `omitted_fields`: an unavailable
candidate's `remediation`, provider `diagnostics`, candidate `capabilities`, and
each tool group's static `surface`, `effects`, and `annotations`. Omitted fields
are not empty or false.
`reason`. It lists the fields it leaves out in `omitted_fields`: provider
`diagnostics`, candidate `capabilities`, and each tool group's static `surface`,
`effects`, and `annotations`. Omitted fields are not empty or false.
`detail: "complete"` returns them inline, groups tools by those flags too, and
has no `omitted_fields`. The output schema keys each result's candidate,
tool-group and omission fields by its `detail`. CLI `capabilities` and
@@ -901,13 +742,6 @@ finish without reading relationships, even when neither traversal mode is
available. Discovery does not block that direct invocation or replace its typed
partial/error outcomes.
`trace_native_ui_action` reports authored-UI and native-seed modes separately.
Apple dispatch metadata requires a Mach-O target; another provider may supply
the symbol-only alternative on other formats. If neither mode is available,
read each mode's missing operations and remediation: a remedy for an authored
app-bundle route does not by itself describe the native-seed route. Availability
does not guarantee that every selector or provider operation will succeed.
With complete detail, 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,
@@ -937,9 +771,7 @@ set, `shared_limitations` is the ordered common prefix; append a group's ordered
`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. An unavailable group carries `remediation` when its readiness
owner supplied a repair instruction; `tool_availability` uses that instruction
rather than the failure description.
limitations.
`binary_session.analysis_provider_candidates` is authoritative for deep-engine
discovery. Each candidate's capability sets, returned with complete detail,
@@ -1014,14 +846,6 @@ is `unavailable` with a reason when ownership could not be revalidated, or
at that bounded check. These are historical snapshots, not live process
inventories, and do not claim that no short-lived descendant existed.
After a failed close retires the target, `binary_session.cleanup_retry` names
`close_binary` only while that session still retains the cleanup bound to the
close error. Target-dependent availability then gives the same conditional
cleanup guidance before suggesting `open_binary`. The hint disappears after
cleanup succeeds. A pending flag alone does not establish this retry: active
targets, an in-flight initial close, and external-mutation blockers keep their
own recovery and do not receive this hint.
`binary_session.cleanup_pending` reports whether the session retains a provider
client, session file resource, or provider-candidate resource whose cleanup
has not succeeded. It can be true with
@@ -1037,20 +861,6 @@ inspection fails or is cancelled before a provider client exists. If no client
remains, no provider run or health observation is available; follow the cleanup
error's reported resources.
### Retained cleanup retry
A `close_binary` that retires the target clears engine replay state while
retaining investigation Evidence and Unknown history, including when cleanup fails. When the session keeps every reported owner for the next
`close_binary` on the same connection, its `cleanup_incomplete` error carries
`recovery: {"kind": "retry_cleanup", "operation": "close_binary"}`.
Keep the connection open, let the reported condition settle or resolve it, call
`close_binary` without `snapshot_path`, and check the result. The retry does not
rerun the failed analysis and can report `cleanup_incomplete` again.
`retryable` still describes the failed operation and stays `false`. A failed
`snapshot_path` save returns before retirement, keeps the target open and has
no `retry_cleanup` hint. `open_binary`, analysis tools and one-shot CLI commands
do not report this hint either, even when their cleanup remains pending.
## Initial target at startup
MCP normally starts without a target; use `open_binary` to select one during the
@@ -1442,11 +1252,11 @@ If the client timed out or cancelled during startup, the pending query may have
been interrupted. Reissuing `open_binary` for the same active target can reuse
the current client, so it is not a fresh-start recovery. On the same connection:
1. Keep useful inline Evidence or use the retained investigation references;
export a bundle before ending the process to resume later. `binary_session` can show the selected binding and its
1. Keep any useful inline Evidence, or export a bundle before closing if retained
records are needed. `binary_session` can show the selected binding and its
recorded state; target selection alone does not prove engine readiness.
2. Call `close_binary` and check its result. It drains owned work, closes the
provider and engine replay state while preserving investigation Evidence and Unknown history. A `cleanup_incomplete` result
provider, and clears retained session records. A `cleanup_incomplete` result
must be addressed according to its reported owned resources before retrying.
3. Call `open_binary` with the same caller-selected path and
`provider_id: "ghidra"`, then retry the first query with an appropriate client
@@ -1497,9 +1307,7 @@ cursor outside a procedure is represented as
procedure resolution returns typed unreadable output; provider errors never
become absence from their message text. `inspect_address_context` requires an explicit address and
returns name, procedure, comment, inline-comment, and bookmark facets;
unsupported facets are local `unavailable` outcomes. A facet whose input is
rejected includes its canonical `error` projection, including any structured
issues, alongside the other facet outcomes. Use `current_document`,
unsupported facets are local `unavailable` outcomes. Use `current_document`,
`current_address`, and `current_procedure` for direct single-field lookups; use
the aggregate tools when you need related context together.
+2 -14
View File
@@ -24,25 +24,13 @@ CLI equivalent is `rea guidance --query "exact JavaScript module"`, followed
by `rea guidance --action read --locator LOCATOR`. Search is limited to the
packaged managed skill files, works without providers or a target, and returns
deterministically paged matches. Every non-generic query word must occur in the
section or its subsections; generic-only queries return no matches. Results are documentation; locators
section; generic-only queries return no matches. Results are documentation; locators
are bound to the installed bundle and report stale or unavailable guidance without
returning cached section content. A no-match result says nothing about analysis
capability availability.
Each match reports its `match_scope`. A `section` match has every term in its
own text, meaning its heading and body before its first subsection. A
`subsections` match has the terms only spread across subsections, with no
single subsection holding them all. Its `own_text_terms` lists the terms in its
own text, and `subsections` lists up to 8 subsection locators, chosen greedily,
whose own text holds the rest; `omitted_subsections` counts any more that were
needed. Section matches rank first. A parent is not listed when one subsection
holds every term, because that subsection is already a match, unless the
parent's own text also holds them all. `content_length`
is the size of the section that read returns, so callers can decide before
reading a broad parent.
Search returns at most 20 matches per page with 500-code-unit context excerpts.
Read retrieves the complete selected section, including its subsections. Admission permits 1 MiB per file,
Read retrieves the complete selected section. Admission permits 1 MiB per file,
4 MiB per bundle, 4,096 sections, and 25,165,824 expanded UTF-16 code units before
section text is materialized. Unreadable or oversized bundles return
`bundle_unavailable`, with no partial index or inferred absence. Cleanup failures
+14 -40
View File
@@ -68,10 +68,7 @@ function renamed to `dead` remains selectable by name. Use the names returned
by the inventory, including their namespaces and any platform symbol prefix.
Ghidra also accepts secondary symbols at a function entry, such as an imported
`_main` label. A label inside a function is not a procedure-name selector; use
its address when selecting the containing function. `search_procedures` matches
the same names, in leaf or qualified spelling: the function appears once with
its primary name as `value`, and `matched_aliases` lists the qualified names of
the secondary symbols that matched.
its address when selecting the containing function.
Overloads can share a fully qualified name; ambiguity errors return every
matching entry address so the caller can select the intended function directly.
@@ -201,11 +198,6 @@ remains valid. This tool
has no dedicated CLI command; the existing `decompile` command remains the
single-procedure CLI operation.
`detail: "summary"` retains the complete batch and returns only its counts,
identities and sizes. `inspect_analysis_view` then reads rows or one body from
the retained record, an Evidence file or a portable bundle without repeating
decompilation: see [selected batch results](mcp-contracts.md#selected-batch-decompile-results).
Each batch schedules one provider read at a time, but has no shared input-count
or aggregate retained-output allowance. It retains every requested row and full
pseudocode body through Evidence construction. The MCP response budget checks
@@ -300,20 +292,6 @@ relationships but reports their completeness as unknown. In particular, a resolv
tail call observed in one direction does not prove that the reverse inventory is
complete. An empty Hopper inventory cannot establish that no such call exists.
No whole-binary reverse scan is performed.
For large native traces, `trace_feature` accepts `detail: "summary"`. It retains
the complete trace and returns counts and its parent Evidence ID. Use
`inspect_analysis_view` with that ID and `view: {kind: "native-feature-trace",
collection: "references", offset: 0, limit: 64}` to page the original rows;
`matches` and `unknowns` are separate collections. Matches accept an exact
`address`; references accept exact `source_address` and `target_address`
filters. Row indices refer to the retained collection, and page coverage does not establish provider discovery
completeness. `list_unknowns` pages the independently managed question heads.
In the one-shot CLI, `rea trace ... --detail summary --evidence_output PATH`
stores the trace and its linked source Evidence for later
`inspect-analysis-view` calls. This bundle does not include the CLI session's
Unknown registry; its original question texts remain available in the trace's
`unknowns` pages.
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
matching deadline and a worker old-generation heap. Deadline or cancellation
stops matching while leaving the Hopper API available. The deadline is
@@ -402,11 +380,6 @@ sites remain distinct. A static route does not establish runtime reachability.
Swift/closure dispatch requires resolved static references; unavailable ABI
forms remain unknown.
Encoded Objective-C method records retain their own locations in dispatch
Evidence. A handler node uses the implementation address with an unknown file
offset unless that function already has an independently located graph node;
the method-record offset does not locate the handler's code.
Incomplete reference kinds do not discard known typed calls or partial
provider call references. A `provider_call` with `observed` resolution retains
the provider-reported call site and endpoint without claiming generic direct,
@@ -448,16 +421,20 @@ nodes and edges. Node payloads are bounded to 8 MiB and serialized results to
32 MiB. Pagination returns edges whose source is on the node page and preserves
stable IDs for endpoints outside that page.
A call to an analyzed callee with no recovered parameter nodes gets the unknown
`Parameter bindings for <callee> are unavailable or omitted`, unless the
callee's `native_api` reports no parameters and a `parameter_list` with
`basis: "signature"` and `variadic: false`. That provider fact means the
callee's signature fixes zero parameters, so the call has nothing to bind.
Recovered, variadic or unreported parameter lists, and callees that were not
analyzed, keep the unknown. Evidence recorded before `parameter_list` existed
omits it, and its arity remains unknown.
Within one value trace, each exact procedure selector is attempted at most once,
including failed analyses. A failed seed returns the provider's original typed
failure and corrective guidance; it cannot establish a trace or target identity.
Failures while expanding a validated seed remain unknown observations and do not
establish canonical function identity. A cancelled expansion read returns the
cancellation. An expansion read whose provider cannot confirm cleanup of
resources it owns stops the trace: the result is that provider's own
`cleanup_incomplete` failure with its resources and any bound cleanup retry, and the work
admitted before the stop is retained as partial Evidence with a located unknown
at the stopping read. A successful seed without a native def-use
establish canonical function identity. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. A new trace
can retry failed selectors.
@@ -466,9 +443,8 @@ page omits retained nodes, and the edges they source, before `offset` or after
`next_offset`. Every page repeats `traversal`: `complete`, or `truncated` with
`reasons` naming each observed cause: the `max_depth`, `max_functions`,
`max_call_sites`, `max_nodes` or `max_edges` budget, the fixed
`node_payload_bytes` allowance, `provider_pcode_truncated`,
`ambiguous_call_candidates`, or, only in a stopped trace's partial Evidence,
`cleanup_incomplete`. Each cause also has a located unknown. A budget is
`node_payload_bytes` allowance, `provider_pcode_truncated`, or
`ambiguous_call_candidates`. Each cause also has a located unknown. A budget is
reported only after it rejects work, so an exactly filled budget is not
truncation; a depth or candidate limit is reported only when no other path
analyzes or queues that destination. `total_nodes` and `total_edges` count
@@ -1023,9 +999,7 @@ with a path-specific gap and incomplete capture coverage. Null alone does not
establish that an accessibility value was successfully read or absent.
An initial PID or window mismatch is `invalid_request` on `pid` or
`window_id` and leaves provider availability intact. A selected window that
exists off screen is not a mismatch: selection succeeds and the permission and
capture checks report their own outcome. An unavailable executable
`window_id` and leaves provider availability intact. An unavailable executable
URL or unresolved executable path leaves identity unknown and reports
`capability_unavailable`. Identity or window loss
after the initial capture remains an observation failure with collected facts.
-27
View File
@@ -652,33 +652,6 @@ that directory: it outlives each run, so the first compilation into a new
directory pays a cold-cache build and later runs reuse it. An absent or empty
value keeps the shared default.
With the default `xcrun` compiler and module cache, REA also keeps one verified
copy of the compiled helper in `~/Library/Caches/rea/process-helper/`, so later
REA processes skip both the compile and macOS's first-launch assessment of a
new executable. Each entry is keyed by the helper source, the `swiftc` and SDK
that `xcrun --find swiftc` and `xcrun --show-sdk-path` resolve (their real
paths, sizes and modification times, so `DEVELOPER_DIR`, `TOOLCHAINS`,
`xcode-select` and in-place toolchain updates all select a new entry), the CPU
architecture and the Darwin release. Before running an entry, REA requires the
cache directory, the entry and its files to be owned by the current user and
not writable by group or others, every real parent directory to be owned by
the current user or root and not writable by others (sticky root-owned
directories excepted), no ACL on any of them to allow another principal a
write-class right (the default `deny delete` entries are fine), the helper and
manifest to be regular files rather than
links or FIFOs, and the helper's SHA-256 to match the manifest. Anything else is
never executed and never removed, because another REA process may still be
verifying or running that path; REA then compiles a per-process helper as
before. A published entry is never replaced. If the cache is unavailable, for
example under a read-only home directory, the ownership operation proceeds
with the per-process compile. These checks reject corruption, loosened
permissions and other users, but they do not defend against another process
running as the same user, which could already modify REA itself. Setting
`REA_DARWIN_SWIFTC_PATH` or `REA_DARWIN_MODULE_CACHE_PATH` keeps the per-process
compile and writes nothing to this cache. REA does not prune old entries; each
is about 240 KiB. Delete the directory while no REA process is running, or run
`rea uninstall --purge-data`, to remove them.
`xcrun` also maintains a separate tool-lookup cache. Its `--no-cache` option
refreshes entries and does not guarantee a write-free lookup. To avoid that
lookup while compiling REA's helper, set `REA_DARWIN_SWIFTC_PATH` to the absolute
-2
View File
@@ -20,8 +20,6 @@
**[الموقع](https://rea.tools/ar/) · [الأدلة](https://rea.tools/ar/guides/) · [دراسات الحالة](https://rea.tools/ar/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="انضم إلى مجتمع REA على Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [إنشاء بيئات التعلم المعزز](#build-rl-environments) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [أدوات GNU objdump وRizin](../reverse-engineering-tools.md) · [التوثيق](#التوثيق)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Website](https://rea.tools/de/) · [Anleitungen](https://rea.tools/de/guides/) · [Fallstudien](https://rea.tools/de/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Der REA-Community auf Discord beitreten" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Schnellstart](#schnellstart) · [So funktioniert REA](#so-funktioniert-rea) · [Was du analysieren kannst](#was-du-analysieren-kannst) · [Fallstudien](#fallstudien) · [RL-Umgebungen erstellen](#build-rl-environments) · [Häufige Fragen](#häufige-fragen) · [Dokumentation](#dokumentation)
<code>npx rea-agents setup</code>
-2
View File
@@ -19,8 +19,6 @@
**[Ιστότοπος](https://rea.tools/) · [Οδηγοί](https://rea.tools/guides/) · [Μελέτες περίπτωσης](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Έλα στην κοινότητα REA στο Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Γρήγορη εκκίνηση](#γρήγορη-εκκίνηση) · [Πώς λειτουργεί το REA](#πώς-λειτουργεί-το-rea) · [Τι μπορείς να αναλύσεις](#τι-μπορείς-να-αναλύσεις) · [Μελέτες περίπτωσης](#μελέτες-περίπτωσης) · [Δημιουργία περιβαλλόντων RL](#build-rl-environments) · [Συχνές ερωτήσεις](#συχνές-ερωτήσεις) · [Τεκμηρίωση](#τεκμηρίωση)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Sitio web](https://rea.tools/es/) · [Guías](https://rea.tools/es/guides/) · [Casos prácticos](https://rea.tools/es/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Únete a la comunidad de REA en Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Inicio rápido](#inicio-rápido) · [Cómo funciona REA](#cómo-funciona-rea) · [Qué puedes analizar](#qué-puedes-analizar) · [Casos prácticos](#casos-prácticos) · [Crear entornos de RL](#build-rl-environments) · [Preguntas frecuentes](#preguntas-frecuentes) · [Documentación](#documentación)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[وب‌سایت](https://rea.tools/fa/) · [راهنماها](https://rea.tools/fa/guides/) · [نمونه‌های عملی](https://rea.tools/fa/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="به انجمن REA در دیسکورد بپیوندید" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[شروع سریع](#شروع-سریع) · [REA چگونه کار می‌کند؟](#rea-چگونه-کار-می‌کند) · [چه چیزهایی را میتوان تحلیل کرد؟](#چه-چیزهایی-را-می‌توان-تحلیل-کرد) · [نمونه‌های عملی](#نمونه‌های-عملی) · [ساخت محیط‌های RL](#build-rl-environments) · [پرسش‌های متداول](#پرسش‌های-متداول) · [مستندات](#مستندات)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Site web](https://rea.tools/fr/) · [Guides](https://rea.tools/fr/guides/) · [Études de cas](https://rea.tools/fr/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Rejoignez la communauté REA sur Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Démarrage rapide](#démarrage-rapide) · [Comment fonctionne REA](#comment-fonctionne-rea) · [Ce que vous pouvez analyser](#ce-que-vous-pouvez-analyser) · [Études de cas](#études-de-cas) · [Créer des environnements RL](#build-rl-environments) · [Questions fréquentes](#questions-fréquentes) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Situs Web](https://rea.tools/id/) · [Panduan](https://rea.tools/id/guides/) · [Contoh Penggunaan](https://rea.tools/id/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Bergabunglah dengan komunitas REA di Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Mulai Cepat](#mulai-cepat) · [Cara Kerja REA](#cara-kerja-rea) · [Hal yang Dapat Anda Analisis](#hal-yang-dapat-anda-analisis) · [Contoh Penggunaan](#contoh-penggunaan) · [Membangun lingkungan RL](#build-rl-environments) · [FAQ](#faq) · [Dokumentasi](#dokumentasi)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Sito web](https://rea.tools/it/) · [Guide](https://rea.tools/it/guides/) · [Showcase](https://rea.tools/it/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Unisciti alla community REA su Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Avvio rapido](#avvio-rapido) · [Come funziona REA](#come-funziona-rea) · [Cosa puoi analizzare](#cosa-puoi-analizzare) · [Showcase](#showcase) · [Creare ambienti RL](#build-rl-environments) · [FAQ](#faq) · [Documentazione](#documentazione)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[ウェブサイト](https://rea.tools/ja/) · [ガイド](https://rea.tools/ja/guides/) · [事例](https://rea.tools/ja/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="DiscordのREAコミュニティに参加" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[クイックスタート](#クイックスタート) · [REA の仕組み](#rea-の仕組み) · [分析できる対象](#分析できる対象) · [事例](#事例) · [RL 環境を構築する](#build-rl-environments) · [よくある質問](#よくある質問) · [ドキュメント](#ドキュメント)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[웹사이트](https://rea.tools/ko/) · [가이드](https://rea.tools/ko/guides/) · [사례](https://rea.tools/ko/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord에서 REA 커뮤니티에 참여하세요" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[빠른 시작](#빠른-시작) · [REA의 작동 방식](#rea의-작동-방식) · [분석할 수 있는 대상](#분석할-수-있는-대상) · [사례](#사례) · [RL 환경 만들기](#build-rl-environments) · [자주 묻는 질문](#자주-묻는-질문) · [문서](#문서)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[വെബ്സൈറ്റ്](https://rea.tools/) · [ഗൈഡുകൾ](https://rea.tools/guides/) · [ഉദാഹരണങ്ങൾ](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord-ൽ REA കമ്മ്യൂണിറ്റിയിൽ ചേരുക" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[ദ്രുതാരംഭം](#ദ്രുതാരംഭം) · [REA എങ്ങനെ പ്രവർത്തിക്കുന്നു](#rea-എങ്ങനെ-പ്രവർത്തിക്കുന്നു) · [എന്തൊക്കെ വിശകലനം ചെയ്യാം](#എന്തൊക്കെ-വിശകലനം-ചെയ്യാം) · [ഉദാഹരണങ്ങൾ](#ഉദാഹരണങ്ങൾ) · [RL പരിതസ്ഥിതികൾ നിർമ്മിക്കുക](#build-rl-environments) · [പതിവുചോദ്യങ്ങൾ](#പതിവുചോദ്യങ്ങൾ) · [ഡോക്യുമെന്റേഷൻ](#ഡോക്യുമെന്റേഷൻ)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Strona internetowa](https://rea.tools/pl/) · [Przewodniki](https://rea.tools/pl/guides/) · [Przykłady zastosowań](https://rea.tools/pl/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Dołącz do społeczności REA na Discordzie" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Szybki start](#szybki-start) · [Jak działa REA](#jak-działa-rea) · [Co możesz analizować](#co-możesz-analizować) · [Przykłady zastosowań](#przykłady-zastosowań) · [Tworzenie środowisk RL](#build-rl-environments) · [FAQ](#faq) · [Dokumentacja](#dokumentacja)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Site](https://rea.tools/pt-BR/) · [Guias](https://rea.tools/pt-BR/guides/) · [Casos de uso](https://rea.tools/pt-BR/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Participe da comunidade REA no Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Início rápido](#início-rápido) · [Como o REA funciona](#como-o-rea-funciona) · [O que você pode analisar](#o-que-você-pode-analisar) · [Casos de uso](#casos-de-uso) · [Criar ambientes de RL](#build-rl-environments) · [Perguntas frequentes](#perguntas-frequentes) · [Documentação](#documentação)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Site web](https://rea.tools/) · [Ghiduri](https://rea.tools/guides/) · [Studii de caz](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Alătură-te comunității REA pe Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Pornire rapidă](#pornire-rapidă) · [Cum funcționează REA](#cum-funcționează-rea) · [Ce poți analiza](#ce-poți-analiza) · [Studii de caz](#studii-de-caz) · [Construiește medii RL](#build-rl-environments) · [Întrebări frecvente](#întrebări-frecvente) · [Documentație](#documentație)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Сайт](https://rea.tools/ru/) · [Руководства](https://rea.tools/ru/guides/) · [Примеры](https://rea.tools/ru/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Присоединяйтесь к сообществу REA в Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Быстрый старт](#быстрый-старт) · [Как работает REA](#как-работает-rea) · [Что можно анализировать](#что-можно-анализировать) · [Примеры](#примеры) · [Создание RL-окружений](#build-rl-environments) · [Частые вопросы](#частые-вопросы) · [Документация](#документация)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[เว็บไซต์](https://rea.tools/th/) · [คู่มือ](https://rea.tools/th/guides/) · [ตัวอย่างการใช้งาน](https://rea.tools/th/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="เข้าร่วมชุมชน REA บน Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[เริ่มต้นอย่างรวดเร็ว](#เริ่มต้นอย่างรวดเร็ว) · [REA ทำงานอย่างไร](#rea-ทำงานอย่างไร) · [สิ่งที่วิเคราะห์ได้](#สิ่งที่วิเคราะห์ได้) · [ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน) · [สร้างสภาพแวดล้อม RL](#build-rl-environments) · [คำถามที่พบบ่อย](#คำถามที่พบบ่อย) · [เอกสาร](#เอกสาร)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Web sitesi](https://rea.tools/tr/) · [Rehberler](https://rea.tools/tr/guides/) · [Örnek çalışmalar](https://rea.tools/tr/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord'da REA topluluğuna katılın" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Hızlı başlangıç](#hızlı-başlangıç) · [REA nasıl çalışır](#rea-nasıl-çalışır) · [Neleri analiz edebilirsiniz](#neleri-analiz-edebilirsiniz) · [Örnek çalışmalar](#örnek-çalışmalar) · [RL ortamları oluşturun](#build-rl-environments) · [Sık sorulan sorular](#sık-sorulan-sorular) · [GNU objdump ve Rizin araçları](../reverse-engineering-tools.md) · [Belgeler](#belgeler)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Сайт](https://rea.tools/uk/) · [Посібники](https://rea.tools/uk/guides/) · [Приклади](https://rea.tools/uk/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Приєднуйтеся до спільноти REA в Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Швидкий старт](#швидкий-старт) · [Як працює REA](#як-працює-rea) · [Що можна аналізувати](#що-можна-аналізувати) · [Приклади](#приклади) · [Створення RL-середовищ](#build-rl-environments) · [Поширені запитання](#поширені-запитання) · [Документація](#документація)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Discord par REA community mein shamil hon" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Jaldi shuru karein](#jaldi-shuru-karein) · [REA kaise kaam karta hai](#rea-kaise-kaam-karta-hai) · [Kya kuch analyze kar sakte hain](#kya-kuch-analyze-kar-sakte-hain) · [Amli misaalein](#amli-misaalein) · [RL environments banayein](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[Trang web](https://rea.tools/vi/) · [Hướng dẫn](https://rea.tools/vi/guides/) · [Các nghiên cứu điển hình](https://rea.tools/vi/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Tham gia cộng đồng REA trên Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Bắt đầu nhanh](#bắt-đầu-nhanh) · [Cách REA hoạt động](#cách-rea-hoạt-động) · [Những gì bạn có thể phân tích](#những-gì-bạn-có-thể-phân-tích) · [Các nghiên cứu điển hình](#các-nghiên-cứu-điển-hình) · [Xây dựng môi trường RL](#build-rl-environments) · [Câu hỏi thường gặp](#câu-hỏi-thường-gặp) · [Tài liệu](#tài-liệu)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[網站](https://rea.tools/zh-TW/) · [指南](https://rea.tools/zh-TW/guides/) · [案例](https://rea.tools/zh-TW/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="加入 REA 的 Discord 社群" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[快速開始](#快速開始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什麼](#可以分析什麼) · [案例](#案例) · [建立 RL 環境](#build-rl-environments) · [常見問題](#常見問題) · [文件](#文件)
<code>npx rea-agents setup</code>
-2
View File
@@ -20,8 +20,6 @@
**[网站](https://rea.tools/zh-CN/) · [指南](https://rea.tools/zh-CN/guides/) · [案例](https://rea.tools/zh-CN/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="加入 REA 的 Discord 社区" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[快速开始](#快速开始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什么](#可以分析什么) · [案例](#案例) · [构建 RL 环境](#build-rl-environments) · [常见问题](#常见问题) · [GNU objdump 和 Rizin 工具](../reverse-engineering-tools.md) · [文档](#文档)
<code>npx rea-agents setup</code>
+2 -8
View File
@@ -14,13 +14,7 @@ can run.
## GNU objdump
REA sends GNU-style arguments to the selected command but does not verify its
implementation or version. Evidence identifies the command profile as
`objdump.command` and retains the selected executable; it does not claim that
the executable is GNU objdump. Other implementations may accept some commands
while differing in supported options and output.
`inspect_with_objdump` and `rea inspect-with-objdump` capture stdout and stderr as inline Evidence. Artifact inspection Evidence records SHA-256 digests before and after the provider call and whether those observed digests differ; this cannot prove the exact bytes consumed if the artifact changes during execution. Choose one artifact operation: `file_headers`, `section_headers`, `symbols`, `relocations`, `dwarf`, `disassemble`, or `disassemble_all`. `disassemble` maps to `-d`; `disassemble_all` maps to `-D` and has different symbol-boundary semantics. The pathless `inspect_with_objdump {"operation":"architectures"}` MCP call or `rea list-objdump-architectures` CLI command maps to `-i` and produces subjectless Evidence containing the selected command’s output, not a guarantee that any target is supported. GNU objdump uses this option to list its compiled-in BFD targets.
`inspect_with_objdump` and `rea inspect-with-objdump` capture stdout and stderr as inline Evidence. Artifact inspection Evidence records SHA-256 digests before and after the provider call and whether those observed digests differ; this cannot prove the exact bytes consumed if the artifact changes during execution. Choose one artifact operation: `file_headers`, `section_headers`, `symbols`, `relocations`, `dwarf`, `disassemble`, or `disassemble_all`. `disassemble` maps to `-d`; `disassemble_all` maps to `-D` and has different symbol-boundary semantics. The pathless `inspect_with_objdump {"operation":"architectures"}` MCP call or `rea list-objdump-architectures` CLI command maps to `-i` and produces subjectless provider-capability Evidence describing BFD targets compiled into the installed binary, not a guarantee that a target is supported.
Failure to launch the selected executable reports provider unavailability, the
configured command and the host error under `boundary: "provider-launch"`.
@@ -31,7 +25,7 @@ their rejected fields in input issues before artifact reads or provider launch.
For DWARF, REA does not follow local debug links by default. Set `follow_debug_links` to `true` to pass `-WK`; external debug-file identities are not bound to Evidence when following links is enabled. The CLI flag is `--follow-debug-links`; `--no-follow-debug-links` or `--follow-debug-links=false` keeps the default. A separate `true` or `false` word after the flag is not its value. REA clears `DEBUGINFOD_URLS` in the objdump child environment to disable debuginfod network access on GNU binutils versions that do not support `-WE`. The complete raw output is authoritative; REA does not normalize it into a version-independent disassembly schema.
For a known native symbol, use `operation: "disassemble", symbol: "my_function"` in MCP or `--symbol my_function` in CLI. REA passes `--disassemble=my_function` to objdump before collecting output, rather than producing the whole disassembly and filtering it afterward. Omit `symbol` to keep full code-section disassembly. Other operations reject this selector. Use a nonempty, NUL-free name with well-formed Unicode; unsupported values are rejected before reading the artifact or starting objdump. Symbol names are passed unchanged; matching, versioned symbols, aliases and the end of the disassembly follow the selected executable's semantics. Evidence records the selector and actual arguments. An empty match does not establish artifact-wide absence, and the selected observation does not contain the omitted disassembly; request the complete operation when needed.
For a known native symbol, use `operation: "disassemble", symbol: "my_function"` in MCP or `--symbol my_function` in CLI. REA passes `--disassemble=my_function` to objdump before collecting output, rather than producing the whole disassembly and filtering it afterward. Omit `symbol` to keep full code-section disassembly. Other operations reject this selector. Use a nonempty, NUL-free name with well-formed Unicode; unsupported values are rejected before reading the artifact or starting objdump. Symbol names are passed unchanged; matching, versioned symbols, aliases and the end of the disassembly follow the installed GNU objdump's semantics. Evidence records the selector and actual arguments. An empty match does not establish artifact-wide absence, and the selected observation does not contain the omitted disassembly; request the complete operation when needed.
```sh
rea inspect-with-objdump ./program disassemble
@@ -86,10 +86,7 @@ credentials and resets it between sequences.
environment refused the call), `not-served`, `no-tool` and `unmapped-id`, each with a JSON pointer and short values.
- The report gives agreement overall, per probe and per tool, agreement ignoring item order beside it, the counts per
category, the ids mapped and the values normalised, the records left uncompared, and each step's differences (at most
20 per step). Each step gives `differences_total` before that cap, `differences_omitted`, and
`differences_truncated`, true when entries were omitted. Equality and categories use all differences.
Omitted entries are not retained and `--full-output` cannot recover them; further investigation uses the original
capture and probe Evidence.
20 per step).
## Non-goals
File diff suppressed because it is too large. Load diff
+1186 -147
View File
File diff suppressed because it is too large. Load diff
-84
View File
@@ -99,49 +99,6 @@ include the smallest useful reproduction, impact and coverage limits. Do not
claim autonomous-agent selection from a scripted trace. Check related reports
before proposing another issue, and avoid treating lack of API parity as a bug.
## Design recovery around the failure
Distinguish a request that violates its input contract from a valid selection
that the current target, provider inventory, or project cannot resolve. The
same `invalid_request` code can represent either. Suggest schema inspection
only when the input contract is the problem; for a valid lookup, preserve the
caller's exact selector, the observed reason and any candidates, and give a
next step that addresses that outcome. An inventory miss does not prove that
the artifact lacks the requested item.
For prerequisite failures, identify what the requested operation actually needs.
A missing executable, a wrong path, an incompatible host or version, denied
permission, and a download timeout need different repairs. Preserve the observed
failure instead of calling all of them "not installed".
Derive the required engine from the operation's execution contract. For example,
`inspect_firmware_regions` needs Binwalk; `extract_firmware` needs Unblob.
Configuring the other tool cannot enable the requested operation. An installed
JDK outside the selected PATH may need `JAVA_HOME`, not another installation.
Say where a setting must change: a shell export does not change an already
running MCP server's environment.
Keep repair instructions with the contract that owns the failure, and preserve
them through provider readiness, capability discovery, doctor and CLI/MCP output.
An error description is not a repair instruction. Do not make each consumer
guess the fix from diagnostic text or invent another list of prerequisites.
Keep recovery within the selected target, provider, project and authority. If
another context could help, describe it as an explicit choice and state any
relevant loss of state; do not switch or select a candidate automatically.
Preserve uncertainty, partial observations, and session or cleanup state. A
suggested retry must be safe and explain any required change or wait.
Verify recovery through the public workflow: reproduce the failure, follow
the suggested next step when feasible, and check the result and session state.
Include a malformed-input control so schema guidance remains available when it
is actually needed. Compare CLI and MCP when both expose the operation.
For each confirmed bug, check sibling adapters and other consumers of the same
facts. Reproduce suspected matches and retain a working comparison where one
exists. Group reports and fixes around the shared cause; similar wording alone
does not establish another instance.
## Define the contract
- Use strict inputs with explicit target identity and required fields. Preserve
@@ -271,47 +228,6 @@ artifact can execute successfully.
## Own the lifecycle
The investigation owns authenticated Evidence and complete Unknown revision
history independently of an engine. `close_binary` drains earlier admitted work,
releases engine resources and clears replay/invalidation state; investigation
references survive, including target-free closes and cleanup failures. A target
switch does not make historical observations current for its new engine.
`reset_investigation` is the explicit destructive boundary. It drains earlier
analysis, imports, exports and Unknown operations, then clears investigation and
replay/source bindings while retaining a healthy active engine. Metadata mutation
invalidation remains in force until that engine is recreated. Later admitted work
waits for the transition; earlier late results cannot repopulate cleared history.
Runtime disposal seals new acquisitions and releases records after successful
owned engine/file cleanup; failed cleanup keeps the owner available for retry.
Records are retained in memory under a fixed per-owner representation allowance
derived from one quarter of initial available V8 heap. Admission accounts for
Evidence, Unknown history and indexes before atomic commit; metadata replacement
uses its size delta and duplicates do not accumulate. There is no silent eviction.
`binary_session.investigation` reports committed counts, estimated representation
bytes and the allowance; these estimates are not process RSS measurements. Live
authentication headroom is checked separately. On refusal, export a checkpoint,
reset, and import only the required investigation, or start a process with a
larger heap. Retention does not promise automatic persistence.
An MCP request retains its original session admission until its handler cleanup
and response send settle. A reset queued behind that request therefore cannot
invalidate Evidence before its reply is sent. Nested work borrows the same
admission. Cancellation before sending releases a suppressed reply after handler
cleanup; cancellation after sending starts still waits for transport settlement.
Lifecycle tools drain earlier admissions without acquiring one themselves.
`reset_investigation(save_path)` saves the complete cross-domain canonical bundle
once before clearing. It awaits file sync, atomic publication and staging cleanup;
failed save, incomplete cleanup or cancellation observed before reset commit
preserves all records. Publication
can precede cancellation or cleanup failure; the error retains its committed file
receipt. Import that bundle to restore original Evidence IDs and all Unknown
revisions in a new process. Engine replay snapshots remain a separate target and
profile-bound mechanism. This checkpoint contract does not promise recovery from
an uncheckpointed process exit or parent-directory loss after a host crash.
`SafeOutputTree` publishes only after file readback, nested-directory fsync
from children to parents, and root-parent fsync on hosts that support directory
syncing. Windows skips directory fsync. Failed writes retain a separate pending
+1 -2
View File
@@ -50,7 +50,6 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
36,src/main/reportMcpTransportError.ts:reportMcpTransportError,MCP error has no known validation or disconnection identity,MCP reported an error with unknown connection state. Check whether the client is still connected before following connection-loss recovery.,Connection state remains unknown,Keep current copy,implemented,injected unknown-error recovery regression
37,src/main.ts:serveStdio throw,MCP connection cannot be created,Failed to start MCP stdio transport,Exposes transport implementation and gives no recovery,"Retry once. If it fails again, tell the user REA could not start its MCP connection and ask them to restart the MCP client, then run `rea doctor`.",implemented,verified by injected transport startup-throw runtime test
38,src/main.ts:requestShutdown catch,Connection or analysis session close rejects,Failed to shut down cleanly,Does not explain duplicate-process risk or recovery,REA could not close cleanly. End the REA process before starting it again.,implemented,verified by injected handle-close rejection runtime test
38.5,src/domain/analysisErrorRecovery.ts:cleanupRetryRemediation,close_binary returns cleanup_incomplete while the session retains the failed cleanup owners,"Provider cleanup could not be fully confirmed. Review the reported local resources before opening another target. (retryable: false, no recovery)",Agent could abandon the connection that owns the only cleanup retry,"Append: This connection retains that cleanup. Keep it open; after the reported condition settles or is resolved, call close_binary without snapshot_path and check its result. That call retries the retained cleanup without rerunning the failed analysis, preserves investigation Evidence and Unknown history, and can report cleanup_incomplete again. Add recovery.kind=retry_cleanup naming close_binary; keep retryable false",implemented,MCP close_binary with the real KunaProvider and a fake Kuna whose export stop stays unproven; process-capture MCP retry; session tests for an unoffered one-shot close and for failed and completed snapshot saves; one-shot IDA CLI cleanup reports no hint; real Kuna not run
39,src/main.ts:entrypoint catch,Unhandled rejection escapes run(),Unexpected server startup failure,Vague and lacks diagnostic recovery,"Do not repeat this startup. Tell the user REA could not start and ask them to restart the MCP client, then run `rea doctor`.",implemented,verified by injected entrypoint rejection test
40,src/application/Update.ts:unknown-install-method,Running package is not owned by detectable global npm installation,Update manually with: npm install --global rea-agents@latest,Old global-npm advice does not identify the owning installation,"Update this installation with the package manager or source checkout that owns it. For a one-off latest invocation, use npx rea-agents@latest.",implemented,36 Update tests passed; public CLI unowned-installation refusal verified with isolated package-manager fixtures
41,src/application/Update.ts:install,npm global install subprocess exits unsuccessfully,npm install --global rea-agents@latest failed.,Old registry/global-install advice omits the actual version prefix and installer failure,"REA could not install {version} in {prefix}: {failure}. Repair this npm installation, then rerun rea update.",implemented,36 Update tests passed; public CLI owned npm fixture failure preserved version prefix and installer reason
@@ -119,7 +118,7 @@ Then rerun this installer.",implemented,verified by shell branch inspection
90,install.sh:version mismatch,Installed rea reports a different version,installed version ACTUAL does not match EXPECTED.,Low risk; gives exact integrity mismatch,Keep current copy,unchanged,verified by exact installer test
90.5,@modelcontextprotocol/server:tool input validation,Caller sends arguments that violate a declared tool schema,"Canonical invalid_request with the SDK diagnostic plus recovery.kind=inspect_input_contract and a bounded instruction to read the advertised schema or call tools/list",Keeps field/type correction while telling the agent how to recover; diagnostics over 4 KiB stay on the existing retained-error path,Normalize ordinary validation failures and retain oversized diagnostics,implemented,verified by real in-memory and stdio MCP client/server calls with wrong argument types
90.6,incur:unknown CLI command,Caller enters an unknown REA command,"'COMMAND' is not a command for 'rea'. Suggested command: rea --help",Low risk; identifies input and exact discovery action,Keep framework-generated copy,unchanged,verified by real packaged dispatcher invocation
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped. (MCP adds: The Ghidra session remains usable.)",Low risk; names the setting; only a continuing MCP caller is told the session remains usable,Keep current copy,implemented,"provider projection test, CLI/MCP fixture-bridge boundary test and real-provider lane"
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped; the Ghidra session remains usable.",Low risk; names the setting and keeps the session,Keep current copy,implemented,provider projection test and real-provider lane
90.8,src/ghidra/GhidraFileOffsetFailure.ts:ghidraFileOffsetUnavailable,"address_to_file_offset receives a well-formed address that is unmapped, uninitialized, without an authoritative target-file source, ambiguous or beyond the exact JSON integer range",{bridge reason}. REA establishes no single exact original-file offset for this well-formed address and does not derive or choose one.,Previous copy called the input invalid and directed schema repair for a valid address,"Keep the bridge reason as the headline, point to inspect_native_load_image, omit recovery.kind=inspect_input_contract and record details.issues[0].observed.file_mapping",implemented,provider projection tests; MCP and CLI analysis path through the real Ghidra client with a simulated bridge; real Ghidra not run
91,src/application/binaryDiagnostics/ByteEntropyService.ts (issues); src/domain/analysisErrorPresentation.ts (recovery),Invalid file or window,Expected a regular file.; Selected file could not be found.; Selected byte window starts beyond the end of the file.; Selected byte window extends beyond the end of the file.; File size exceeds exact numeric offset representation.,Invalid selection cannot produce complete entropy Evidence,"Read the declared input schema or CLI usage for inspect_byte_entropy; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call.",unchanged,Executed CLI/stdio MCP: path/offset/length refusals and tiny + system-binary controls; shared canonical recovery; service/MCP tests check classification and fields; remaining listed errors source-inspected
92,src/domain/analysisView/byteEntropyView.ts,Non-summary entropy view,Byte entropy Evidence supports the summary view; retrieve parent Evidence for the complete block series.,Unsupported view selection,Use the summary view or retrieve complete parent Evidence,implemented,typed input rejection
1 harm_rank location trigger current_copy user_risk proposed_replacement implementation_status verification_result
50 36 src/main/reportMcpTransportError.ts:reportMcpTransportError MCP error has no known validation or disconnection identity MCP reported an error with unknown connection state. Check whether the client is still connected before following connection-loss recovery. Connection state remains unknown Keep current copy implemented injected unknown-error recovery regression
51 37 src/main.ts:serveStdio throw MCP connection cannot be created Failed to start MCP stdio transport Exposes transport implementation and gives no recovery Retry once. If it fails again, tell the user REA could not start its MCP connection and ask them to restart the MCP client, then run `rea doctor`. implemented verified by injected transport startup-throw runtime test
52 38 src/main.ts:requestShutdown catch Connection or analysis session close rejects Failed to shut down cleanly Does not explain duplicate-process risk or recovery REA could not close cleanly. End the REA process before starting it again. implemented verified by injected handle-close rejection runtime test
38.5 src/domain/analysisErrorRecovery.ts:cleanupRetryRemediation close_binary returns cleanup_incomplete while the session retains the failed cleanup owners Provider cleanup could not be fully confirmed. Review the reported local resources before opening another target. (retryable: false, no recovery) Agent could abandon the connection that owns the only cleanup retry Append: This connection retains that cleanup. Keep it open; after the reported condition settles or is resolved, call close_binary without snapshot_path and check its result. That call retries the retained cleanup without rerunning the failed analysis, preserves investigation Evidence and Unknown history, and can report cleanup_incomplete again. Add recovery.kind=retry_cleanup naming close_binary; keep retryable false implemented MCP close_binary with the real KunaProvider and a fake Kuna whose export stop stays unproven; process-capture MCP retry; session tests for an unoffered one-shot close and for failed and completed snapshot saves; one-shot IDA CLI cleanup reports no hint; real Kuna not run
53 39 src/main.ts:entrypoint catch Unhandled rejection escapes run() Unexpected server startup failure Vague and lacks diagnostic recovery Do not repeat this startup. Tell the user REA could not start and ask them to restart the MCP client, then run `rea doctor`. implemented verified by injected entrypoint rejection test
54 40 src/application/Update.ts:unknown-install-method Running package is not owned by detectable global npm installation Update manually with: npm install --global rea-agents@latest Old global-npm advice does not identify the owning installation Update this installation with the package manager or source checkout that owns it. For a one-off latest invocation, use npx rea-agents@latest. implemented 36 Update tests passed; public CLI unowned-installation refusal verified with isolated package-manager fixtures
55 41 src/application/Update.ts:install npm global install subprocess exits unsuccessfully npm install --global rea-agents@latest failed. Old registry/global-install advice omits the actual version prefix and installer failure REA could not install {version} in {prefix}: {failure}. Repair this npm installation, then rerun rea update. implemented 36 Update tests passed; public CLI owned npm fixture failure preserved version prefix and installer reason
118
119
120
121
122
123
124
@@ -1,246 +0,0 @@
# Capability composition verification — 2026-11-01
Scope: issue #8429, the service composition/ownership increment of #740. Baseline
`6e9ee97a69cb16c7150d8b550e705cba055ee4f0`; implementation `bd9bf76d8`, followed
by type-only binding cleanup `161c3c146`. This changes existing composition,
metadata ownership and cleanup bookkeeping; no new provider/target authority.
`BinarySession` keeps its existing transition/admission barrier.
## Executed source and boundary checks
Full typecheck, module boundaries, architecture guards, test discovery,
production reachability, documentation consistency and repository formatting
passed. Changed-file typed lint passed with one worker and `GOMEMLIMIT=512MiB`:
39.622 s elapsed, 39.261 s process-family CPU, 2,945,368,064 B kernel peak.
Initial whole-repository typed and ordinary lint attempts hit the configured
3 GiB early stop; no OOM occurred. Whole-repository lint is not locally
qualified; changed-file checks and the final ownership-boundary typed checks
passed separately.
Relevant executed groups: 216 parser/boundary/metadata/provider-owner tests;
5 scope/factory tests; 27 MCP availability/composition tests; 28 deferred
startup/protocol/publication tests; 50 CLI factory/cleanup/target-input tests;
16 SDK schema tests; 4 compiled-verifier metadata link tests; and investigation
lifecycle, unknown-history and transport cleanup regressions. These groups
contain overlapping repeated checks and are not a unique total. The final
Android transport group passed all 4 cases, including construction failure and
failed cleanup retry. The moved Frida test body was preserved byte-for-byte;
its discovery owner changed to the adapter lane.
The old manual provider-close list was temporarily restored as a negative
control: the new successful-peer cleanup regression failed, then the current
source was restored. Synchronous close throws, undefined rejection reasons,
concurrent close joining, borrowed optional observations and failed-owner retry
are controlled tests, rather than claims of natural failures in real engines.
An initial new transport assertion omitted the existing outer
`OwnedMcpServers` AggregateError; its expected nesting was corrected. Other
preserved harness failures were missing SDK catalog preparation and two wrong
CLI test paths; corrected commands passed. One formatting issue was corrected.
Ordinary Knip reports one unused exported `DarwinHelperCache` type at
`src/process/DarwinHelperCache.ts:465`. An independent run on the clean pinned
baseline reports the identical sole issue; that file is unchanged here.
## Real applications and complete-result parity
Every case used actual public CLI/MCP routes. Inputs were hashed before/after,
with no source mutation. Both arms used pinned Node 24.22.1/npm 11.16.0 and
fresh isolated processes/configuration; OS page caches were uncontrolled.
| Domain | Genuine application/artifact | Independent fact oracle |
| ---------- | ------------------------------------------------------------------- | ----------------------------------------------- |
| Go | installed GitHub CLI `/usr/bin/gh`, Go 1.19.6 | ELF Go build-info bytes/module data |
| Managed | SDK 8.0.427 `NuGet.CommandLine.XPlat.dll`, selected Program members | independent dnfile metadata/CIL facts |
| JavaScript | installed agent-browser 0.27, selected entry source | original source declarations/imports |
| Android | NewPipe 0.29.1 APK inventory/application graph | complete ZIP member census and 9 member digests |
| WASM | actual CSS WASM artifact with WABT 1.0.42 | independent raw sections/types/exports |
All five full public CLI Evidence results matched across arms. Explicit Go/JS
per-arm bundle output paths were verified separately, and their complete saved
parent Evidence matched. MCP performed 117 calls per arm, including six fresh
producer calls, retained derivation, repeated close, checkpoint/reset, expected
missing references, same-process restore and actual fresh-process restore.
Complete producer bundles, full derived Evidence, exact requests/IDs and
complete checkpoints matched across arms. The analyst Unknown revision 1→2
history and every canonical mutation/history record remained authenticated;
fresh re-exported checkpoints were byte-identical. Fresh consumers ran no
producer calls. Ten independent domain fact assessments passed.
| Completed workflow | Baseline elapsed / CPU (s) | Feature elapsed / CPU (s) | Baseline / feature kernel peak (B) |
| ------------------------ | -------------------------- | ------------------------- | ---------------------------------- |
| Five CLI tasks | 17.675 / 16.268 | 22.192 / 20.667 | 393,936,896 / 390,504,448 |
| Five MCP lifecycle tasks | 68.270 / 65.500 | 61.201 / 58.041 | 731,680,768 / 776,331,264 |
Times are single observations including setup and all calls, not a causal
speedup/regression claim. Fresh-process tasks are reported separately from
same-process/restored consumers; neither is a controlled cold OS-cache run.
All tasks ran serially under a one-CPU, 4 GiB hard/3 GiB early-stop, no-swap,
192-task guard. CPU and peak memory are kernel process-family counters at
payload exit while the observer remained alive; all completed tasks verified
owned cleanup and no OOM. Sampling/RSS is not substituted for these counters.
Scripted checks do not incur model tokens; model usage is recorded separately.
## Native engine and Codex CLI
Real Ghidra 12.1.4/JDK 21 on `/usr/bin/jq` passed 37 public MCP calls per
arm. Two new light address queries before/after healthy investigation reset
returned identical Evidence. The same MCP client retained the same owned JVM
PID, start time, process group and public run identity. Three retained native
compositions used authenticated historical dossiers; these dossiers were not
regenerated. Full derived results, original bundles and Unknown histories
matched across arms and survived same/fresh-process restore.
A mixed-routing task passed 58 calls per arm, with explicit `jq → APK → managed`
metadata target switches and five new static producer families through the final
registry. These metadata switches do not imply launching every target engine.
Complete originals, derived results and checkpoint history matched across arms.
| Completed task | Baseline elapsed / CPU (s) | Feature elapsed / CPU (s) | Baseline / feature kernel peak (B) |
| --------------------------- | -------------------------- | ------------------------- | ---------------------------------- |
| Ghidra/native lifecycle | 38.196 / 36.804 | 41.741 / 40.369 | 1,111,789,568 / 1,098,362,880 |
| Mixed target/domain routing | 27.557 / 26.984 | 27.255 / 26.527 | 1,039,253,504 / 1,037,910,016 |
The native task includes a fresh engine start; its second query is a healthy
same-engine observation. Historical dossier reuse and fresh process consumers
are separate phases. These are single observations with uncontrolled OS caches.
Local `codex-cli 0.165.0` ran one actual `client-code` trial with the requested
`gpt-6.1-terra` alias, reasoning `high`, and 19 selected tools. The resolved
server-side model identity is not inferred. It completed 32 MCP calls across
JavaScript, managed, Android and WASM: five new producer calls, retained
compositions, four expected missing-reference controls, close/reset/save/import
and Unknown revision 1→2. There were no unexpected MCP errors or timeout.
The model's JS self-check remained explicitly **partial/unverified**: it used
`JSON.stringify`, whose property ordering changed after canonical import.
Independent full structural comparisons of all three actual results and the
baseline proved identical fields, values, array order and Evidence ID. The
first ordering difference was only the keys of
`graph.nodes[0].observations[0].properties`. This distinction is retained in the
assessment; the model's self-check is not relabeled a complete pass.
An actual new, target-free MCP process replayed the model's checkpoint in
9 calls. Complete Evidence and Unknown revision/mutation histories authenticated,
and its 18,968,472 B export was byte-identical (17 Evidence records,
2 Unknown revisions, 1 head), SHA-256
`165188d74298f0173c5730973ecb5354a1b966ca54ec46ee177efa049ebbc755`.
All four derived results and their complete producer records matched the
independent baseline. The copied authentication profile was removed; runtime,
production source and selected input hashes remained unchanged.
Actual client usage for the single attempt: **336,490 input tokens**, of which
**295,680 were cached**; **3,659 output tokens** and **250 reasoning output
tokens** were reported. Cached input is a subset, not an additional token total.
Baseline model usage was not measured; neither token savings nor a speedup is
claimed. Scripted source-fact and fresh-process verifiers incur no model API
tokens and are separate from this model usage.
| Completed verification | Elapsed / process-family CPU (s) | Kernel family peak (B) |
| ----------------------------------- | -------------------------------- | ---------------------- |
| Actual Codex CLI trial | 74.644 / 26.818 | 1,061,597,184 |
| Independent fresh checkpoint replay | 6.142 / 5.934 | 757,895,168 |
Manual final-answer review verified the reported IDs, counts, digests, coverage
and Unknown state against complete baseline values, all actual model calls and
the independent replay. The outcome is supported with the explicit model JS
self-check limitation resolved independently. Maximum observed in-flight MCP
calls was one; no target command, web or file-change tool items occurred.
Factual-assessment SHA-256:
`33451102e5faf57f11c627481899f16c4fc47fa5cba5f12cd2ed781a3de54ccf`.
Final immutable input/runtime audit SHA-256:
`73abce493182a7b433b4b78737720640ceb41d6df435cb0fec915468a0a304c8`.
## Catalog and build identity
Both compact/full SDK catalogs are byte-identical to baseline: 234 tools,
6 prompts, 180 CLI commands. The provider projection changes from 63 to 67 rows
only for source-proven web-source/module tracing, Harmony and QLOG provenance
corrections. All other product sections are unchanged. Its expected provider
SHA-256 is `4234ed15d7e53f34599b59dfba1f678369c0ae95b2135c7ba3414d944ab156b6`.
The 66 static declarations have 49 runtime keys and 17 explicit source-owned
workflow/engine groups; 70 bindings union to 67 identities. Static imports reach
44 pure modules without schema, engine or factory runtime imports.
The baseline runtime is `0e94962f64f3f532cc96b2432916f328f60058e97cd0e8e25f5c3e6ed3cdec10`
(2,670 files). Static pairs used clean `bd9bf76d8`, runtime
`62018a023b185dd9c6839799ed1fe4f085810161c723e508fc817d2fccdb59e3`
(2,713 files). Final type-only cleanup `161c3c146` rebuilt to
`d7be1d17dc6b5c23f33468398656ac4d3961380368be1f70dc767f0ba1fac6bf`.
The complete earlier manifest is cryptographically reconstructed by removing
one emitted trailing comma in `dist/composition/capabilityServices.js`'s
existing named import; every other runtime file is byte-identical. This is not
a claim that the two runtime hashes are identical. Native/mixed/model cases use
the final runtime.
Raw inputs, outputs, commands, hashes, recorded failed checks and resource receipts
remain outside Git under
`/home/yann/yann/.rea-worktree-artifacts/20261101/capability-module-ownership/`.
Static-pair audit receipt SHA-256:
`801707569577c7cec848334cf72bfab26a7ff4a95c2a87872d71d78eef7765a3`.
## Review corrections and final qualification
Two actionable review findings were fixed at their producing owners:
- `980991573`: import-edge facts now distinguish fully erased declarations from
inline type bindings, which still load their module under
`verbatimModuleSyntax`. Compiler-backed parser/boundary regressions passed;
the old parser fails the new expectations. A separate three-file probe through
the actually selected compiler emitted both inline runtime edges and erased
whole-declaration/type-query edges, without compiling the repository.
- `de6a61636`: the existing contract owner exposes 47 named canonical groups.
Thirty-seven full provider bindings reference those shared arrays, while
33 deliberate provider-subset/engine selections remain precise. The actual
ADB metadata now includes a new valid canonical ADB operation without an edit
to the provider declaration; reverting to the copied production lists fails
the regression. Unknown groups, unknown names and identity conflicts fail
catalog generation.
Final full typecheck, module boundaries, test discovery and changed-file typed
lint passed. Post-review source regressions passed 228 cases and SDK/MCP
regressions passed 34; the two strengthened boundary/metadata files passed
31 cases separately. These overlap earlier groups. Product catalog SHA-256
remains exactly `e942c72047db651939dd47a31042e6818caaca7470092d63645801954a72b523`;
both SDK catalogs remain byte-identical to the original baseline. Static metadata
retains its pure loading boundary; canonical group IDs are imported only as types.
The final implementation freeze is `de6a61636f3f84862cb4263c9ea219b7d2d4de36`,
with 2,713 runtime files, SHA-256
`be1d60c7dcca57eeb7ad09d682efd172c7f1679215e796a291b29eb52e29bda1`.
Earlier model/engine measurements remain attributed to their executed revisions;
they are not relabeled as runs on this later freeze.
A new actual mixed-routing task on this final freeze passed all 58 calls:
32.970 s elapsed, 32.180282 s process-family CPU, 1,042,538,496 B kernel peak.
Independent five-domain source facts, complete producer records and all five
full derived results/requests/IDs matched baseline and the prior qualified
implementation. Same-process and true fresh-process restore preserved the full
Unknown 1→2 mutation history and byte-identical 18,978,040 B checkpoint
(20 Evidence records, 2 Unknown revisions, 1 head), SHA-256
`0316bd6271c6d401a18c10d552b2005d39c98f3bbbc5945ae4823875af13fd5f`.
Owned cleanup, runtime/production-source/input hashes and no OOM were verified.
Append-only final-routing receipt SHA-256:
`9c410c2c31bf38d7edc569deb95b93b7f9a2cf68a7b9a7f84d3ec924f0ad75cb`.
This bounded requalification adds no model invocation or repeated native
analysis query; earlier token measurements remain unchanged.
CodeQL's six reported alert IDs (215, 490, 491, 493, 494, 495) all have open
instances on exact-base main `6e9ee97`. All 24 SARIF paths were reviewed and
independently checked: the new factory-default `process.env` sources retain the
existing command/directory selection and explicit local authority. The shared
filesystem/process sink files are byte-identical to base; Cutter's sink line
shift only removes its old identity constant. No source suppression or global
alert dismissal is included. The PR records the exact-base instance evidence
and the owner's authorized exception for this unrelated existing-alert check.
## Qualification limits
NewPipe coverage is static inventory/graph, not real JADX decompilation: JADX,
apktool and aapt2 were unavailable. No genuine local GGUF/safetensors target was
available. Real Hopper, IDA, JEB, Cutter GUI, GDB/Rizin debugger and Frida target
attachment are not qualified by these application cases; relevant factory,
protocol and cleanup tests use controlled seams. Historical native dossiers
are explicitly labeled reused evidence, not newly reproduced function analysis.
Retained records remain bounded by existing investigation policies; this does
not promise unlimited retention or a new durability backend. Further module
qualification and `BinarySession` simplification remain under parent #740.
@@ -1,298 +0,0 @@
# Investigation record lifetime verification
Verified on Linux with Node v24.22.1 and local Codex CLI 0.165.0 for
[#8347](https://github.com/morluto/rea/issues/8347). The final production revision is
`40ec9fe79b221b7550bf6a652f55389ea0b7e54f`, including main
`d54a4661da20382ad8456564a08569fdd24131c5`. Its runtime manifest SHA-256 is
`f4e55abdc5cb9fc2a86efcec5c410f2b5bc3eb5a7109a8d5ab88f07f8511c48b`.
Earlier measurements below identify their older revisions explicitly. Documentation
was being updated during final qualification; production sources and runtime
remained unchanged.
The [MCP contract](../mcp-contracts.md) separates engine cleanup, investigation
reset and explicit checkpointing. Automatic journaling remains tracked in
[#4809](https://github.com/morluto/rea/issues/4809).
## Response publication qualification
The request-publication correction is pinned to
`40ec9fe79b221b7550bf6a652f55389ea0b7e54f`. An MCP request now retains its
originating session admission through handler cleanup and physical send
settlement. Nested operations borrow that admission, avoiding reentry behind a
queued reset. Cancellation before a suppressed send and during an already-started
send have separate completion rules. Outgoing server requests cannot acknowledge
an incoming tool response merely because their IDs coincide.
All nine genuine saved-source workflows were repeated: 313 MCP calls and 19 CLI
checks, 114.210 s guarded elapsed, 109.411889 s CPU and 1,055,035,392 B maximum
family peak. Complete original public results and source identities matched
through close, checkpoint refusal, reset and same/fresh-process recovery; all
Unknown histories were preserved. No producer replay, source/runtime changes,
OOM or unresolved owned cleanup occurred. These runs have uncontrolled OS caches
and are not a paired timing comparison with the earlier cohorts below.
A new local Codex CLI task requested `gpt-6.1-terra`/`high` with the scoped
four-domain profile. It completed 25 MCP calls, including four expected
missing-reference controls, with no producer replay. Usage was 232,677 input
tokens including the 199,552 cached subset, 3,613 output tokens and 195 separately
reported reasoning tokens; elapsed was 61.466 s, local CPU 11.031356 s and peak
806,146,048 B. Requested aliases do not establish the server-resolved model
identity, and remote model compute is unmeasured.
An independent fresh stdio process authenticated that task's 21-record,
three-revision checkpoint: 16,138,657 B, SHA-256
`a4395246473397cdc9040cb643e3175fd6f8b24db40597f8b680f0e065643df5`.
Nine calls reproduced all four complete original results and source records and
reexported byte-identical bundle bytes. This took 6.353 s elapsed, 6.060161 s CPU
and 592,621,568 B peak, with verified cleanup and no OOM.
Independent full-result/source/history authentication and manual answer review
supported the model's stated conclusions. No task or model retry was needed in
this cohort; separate checker and report-aggregation CPU was not measured.
The actual Ghidra healthy-engine control also passed on this runtime: 49 calls,
51.115 s elapsed, 50.000976 s CPU and 1,124,110,336 B peak. The verifier-owned JVM
PID/start identity/process group, public analysis run and MCP client were the same
across reset. Two explicit `address_name` queries returned identical Evidence;
no dossier, inventory or decompile producer was replayed. Full checkpoint/history
recovery and owned cleanup passed, with no OOM. This observes retained-engine
behavior rather than a before/after speedup.
Across all three model cohorts, nine actual runs plus one prelaunch failure used
2,771,744 input tokens including the 2,468,608 cached subset, 23,334 output tokens
and 1,035 separately reported reasoning tokens. Guarded elapsed totals 480.694 s
and local CPU 87.103671 s; maximum family peak is 922,148,864 B. All earlier
failed/partial attempts remain included. These are execution costs, not a paired
model-token savings claim.
All nine controlled success/error publication cases passed. Reinstating only the
old admission factory made both successful-publication cases fail at the original
admission boundary; the corrected source was restored and all nine passed again.
The final boundary run used 12.872 s elapsed, 12.647482 s CPU and 941,723,648 B
peak. Existing lifecycle/deferred/shutdown tests passed 79/79; the full HTTP suite
passed 74/74. These controlled seams establish ordering, separately from real
application behavior.
CI also exposed the 1500-file JavaScript timeout fixture's retention headroom.
At a 512 MiB parent heap, the original timeout exceeded the 10 MiB wire budget;
fallback authentication projected 771,325,184 B against 411,666,312 B available
and correctly refused. The transport/timeout fixture now uses a finite 1536 MiB
parent heap while retaining all 1500 files and every recovery/query assertion.
Its worker remains at 128 MiB; the separate memory-pressure case retains its
512 MiB parent. All three tests passed in 23.556 s elapsed, 22.989758 s CPU and
1,064,308,736 B peak, including a public assertion that retained records fit the
fixed allowance. No production limit was raised for this fixture.
## Earlier review qualification
Review corrections were qualified at clean
`6d875b3ff73d40b365a713cf4ba69a10cca257b7`, with runtime manifest SHA-256
`328f05dc2d7b114c33dc0406e488c3046a43e1c12187e15ad9263be65df7cbaa`.
SDK error Evidence retention and reply publication now share session admission;
reset retries the MCP artifact scope alongside process cleanup before saving or
clearing. Controlled public MCP regressions cover both publication orderings,
queued cancellation, terminal shutdown, ordinary export cleanup failure, mixed
cleanup outcomes and scope reuse. Reinstating the former wiring made four of the
five new regression cases fail; all five pass with the correction. Controlled
waits also use bounded acknowledgements and release/join held work in `finally`.
The nine genuine saved-source cases below were repeated against this runtime:
**313 MCP calls and 19 CLI checks**, 118.510 s guarded elapsed, 114.859544 s CPU,
and 1,065,390,080 B maximum kernel-family peak. Every complete original result,
ID, source, coverage and Unknown revision chain matched through close, checkpoint
collision refusal, reset, same-process import and fresh-process recovery. No
producer or target open was replayed in this cohort. Input/checkpoint/runtime
immutability and owned cleanup were verified, with no OOM. These normal workflows
complement the controlled error/cleanup timing regressions; they do not reproduce
those timing faults themselves.
One further local Codex CLI task requested `gpt-6.1-terra`/`high` with the
scoped four-family code-mode profile. Its 25 MCP calls included four expected
missing-reference controls and no unexpected model errors or producer replay.
Independent full-result/source/history authentication and answer review supported
the answer within its stated scope; it did not claim to query every other
Unknown. The run used 58.269 s elapsed, 9.837083 s local CPU and 885,448,704 B peak,
with 239,931 input tokens including 215,168 cached, 3,721 output tokens and 277
separately reported reasoning tokens.
A real fresh stdio client restored that same new checkpoint: 21 records and
three Unknown revisions in 16,138,749 B, SHA-256
`760fc74951abc292a3ff2fe1771391ac0d4345416b2345dfa4e85cbb6ce96181`.
Nine calls reproduced all four complete original results and source records,
then reexported byte-identical bundle bytes. This took 5.443 s elapsed,
5.253212 s CPU and 595,218,432 B peak, with verified cleanup and no OOM.
Two external-checker failures were preserved and corrected against actual event
ordinals and query filter/paging semantics; neither required model or backend
replay. Separate checker CPU and remote model compute were not measured.
Across those two earlier cohorts, eight actual model runs plus one prelaunch failure used
2,539,067 input tokens including the 2,269,056 cached subset, 19,721 output tokens,
and 840 separately reported reasoning tokens. Guarded elapsed totals 419.228 s
and local CPU 76.072315 s; maximum family peak remains 922,148,864 B. Initial
failed/partial attempts below remain included. This is execution accounting,
not a paired model-token performance comparison.
That runtime also repeated the actual Ghidra healthy-engine control:
49 calls, 44.333 s elapsed, 43.348651 s CPU and 1,093,464,064 B peak. The same
verifier-owned JVM PID/start identity/process group, public analysis run and MCP
client survived reset; two `address_name` queries returned identical Evidence.
Startup-inclusive and warm requests took 24,953.573 ms and 6.762 ms respectively,
without dossier, inventory or decompile replay. These are startup/reuse
observations rather than a PR before/after speedup. Full checkpoint/history
recovery and owned cleanup passed, with no OOM.
## Initial main-integration workflows
Main was integrated in `4e20698b7b9883f659e445c059c011aa0e5de834`, with runtime
manifest SHA-256
`ee23cbaf0ecbf8d896fb5dc7a88317e2ab8eb77bd3a4467bf5793c3471ca9c7d`.
Nine genuine saved-source cases covered eight target families:
| Family | Actual source | Saved-source bundle SHA-256 |
| ---------- | ---------------------- | ------------------------------------------------------------------ |
| Native | jq observations | `0a3adad6ab39bfe29320e1f070a8e598382d2f17c4d6c19079bd6ff16dfe0937` |
| JavaScript | agent-browser 0.27.0 | `66232b6eb01b3dbf435949a1cd94cb2532b7f989b91cdf952a105b3cee231bda` |
| Managed | NuGet CLI, SDK 8.0.427 | `11ac63842299c0ac641a320ec9b2a5161b454a8a322d57724fd5f2ffb2fddfa4` |
| Android | NewPipe 0.29.1 | `fa499d0464a40ddc2f5159c893d68b8f50aa8e6d2b0080de22f2f3e5fc326825` |
| Harmony | Shark 1.0.8 APP | `3a526ab0113f80ada26ff5a0631fc95dac91485946f20584fc5a76f5ee8eb880` |
| Harmony | Shark inner HAP | `3431c0a347f331c3effacf7984ecb890f3fef46d8b00b03b50980023b065cd4a` |
| Apple | Rectangle 2.0.4 | `8daa6286b28aadf5ba0d41038046805e5c69874eaa0bc5daf0e265360fcae79d` |
| WASM | tree-sitter-css | `acb0cb6c3e0d520f83144e106e171c3ecebba70c5c0498bbb0113185a3e4bf95` |
| Process | Controlled jq captures | `55da0f130dc2265aae54261d49949471e5c929163c2b7314b42e95df3325b739` |
Each case imported authenticated observations, composed exact retained references,
closed without an active target, refused an existing checkpoint destination,
saved and reset, checked missing references, and restored into the same process
and a fresh public stdio MCP process. Complete original public results, source
links, coverage and Evidence IDs matched throughout. Every case included an
explicit analyst question with revisions 1→2; complete revision histories and
mutation Evidence authenticated after recovery. Bundle hashes above identify
the saved observations rather than release binaries. The actual jq target was
`/usr/bin/jq`, SHA-256
`90b1eef4498f31836342898fc6e97cfeb8a197c4366773a0fb120ae02a45ea52`.
That cohort completed **313 MCP calls and 19 public CLI checks**, without
source producer replay. Summed guarded elapsed was 119.445 s and CPU was
115.040362 s; maximum kernel cgroup-family peak was 1,047,826,432 B. All pinned
input/runtime hashes stayed unchanged, owned cleanup was verified, and no resource
stop or OOM occurred.
A separate actual Ghidra 12.1.4 control reset while its jq engine remained open.
The same verifier-owned JVM PID/start identity/process group, reported analysis
run and MCP client survived reset. Two identical `address_name` queries at
`0x104500` returned the same Evidence. The first request including startup took
21,023.963 ms; the post-reset request took 6.672 ms. This observes startup versus
reuse, rather than a PR before/after speedup. No function dossiers or procedure
inventories were regenerated. The complete close/checkpoint/import/fresh-process
control used 49 calls, 39.351 s elapsed, 38.222523 s CPU and 1,094,340,608 B peak;
reset acknowledged 15 records and three Unknown revisions with `retained_target`.
Earlier actual-provider controls also passed Ghidra→Rectangle provider-family
switching and Rectangle→NewPipe target switching, with reset retaining the healthy
second target. Those pre-sync runs preserved their complete original results and
histories; they are separate from the latest-runtime qualification above.
## Equivalent completed-task measurements
These pairs used exact baseline `60149282fcfc05ac32fbc94434c107710120da6f` and
implementation `3c9fb7f44a4496d18d893beb903c5f2ba68c06b4`, before the later main
integration. The baseline exported, closed and imported before reference
composition; the candidate closed and reused references directly. Both returned
the same complete original result for the same authentic inputs.
| Saved source | MCP calls, before→after | Elapsed s | CPU s | Kernel-family peak B |
| ------------ | ----------------------- | ----------- | ----------------- | ----------------------- |
| Native | 9→7 | 5.846→4.938 | 5.609106→4.760475 | 582,672,384→511,090,688 |
| JavaScript | 8→6 | 3.529→3.333 | 3.371675→3.163932 | 394,170,368→387,428,352 |
| WASM | 7→5 | 4.236→3.731 | 4.035871→3.520731 | 658,063,360→492,548,096 |
Each row is one sequential pair in fresh isolated processes. OS caches were
uncontrolled; builds and fingerprinting were measured separately. These samples
establish two fewer necessary calls, rather than statistical speedups or model
token savings.
## Initial Codex tasks
Local Codex requested `gpt-6.1-terra` with `high` reasoning. Native used the full
advertised surface without an enabled-tool filter or exact-name guide, both
before and after the latest main integration. JavaScript, packages and the mixed
managed/WASM/process/Harmony task used explicit scoped diagnostic profiles. The
mixed task used the existing experimental client code mode; its development
warning was retained.
All four task scopes passed independent canonical bundle/history authentication,
complete original-result comparison and final-answer review. These conclusions
were checked separately from process exit and token accounting. Native's final
12 calls preserved the original comparison, acknowledged a 14-record/three-revision
checkpoint, observed the expected missing-reference control, and restored both
Unknown heads. The mixed model used 25 calls; a separate latest-runtime fresh
stdio verifier used nine calls to restore its same 21-record/three-revision
checkpoint, reproduce all four complete originals, and reexport byte-identical
bundle bytes.
The initial cohort table includes **all attempts**, partial runs and the native task repeated
after main integration:
| Task/profile | Input tokens (cached subset) | Output tokens | Elapsed s / CPU s | Kernel-family peak B |
| ----------------------------------- | ---------------------------- | ------------- | ------------------- | -------------------- |
| Native, full discovery | 841,133 (769,536) | 4,326 | 101.845 / 18.383645 | 888,786,944 |
| JavaScript, scoped | 531,546 (452,224) | 3,421 | 88.236 / 14.456771 | 922,148,864 |
| Rectangle→NewPipe, scoped | 629,186 (567,424) | 4,384 | 108.062 / 22.589319 | 878,800,896 |
| Four-family mixed, scoped code mode | 297,271 (264,704) | 3,869 | 62.816 / 10.805497 | 914,604,032 |
Seven actual model runs plus one prelaunch failure consumed 2,299,136 input tokens
including the 2,053,888 cached subset, and 16,000 output tokens. Input already
includes cached input. Reported reasoning output was 563 tokens, recorded
separately. Canonical verification costs are separate from client/model costs.
The latest SDK catalog had 234 tools and 3,919,838 JSON bytes; those representation
bytes are not model tokens. No paired baseline model cohort ran, so these counters
do not establish causal whole-task token savings.
Three failed or partial attempts were preserved:
- Native's first attempt stopped before model/MCP launch because of a redundant
template-only harness argument. Its 0.718 s/0.473821 CPU s remains included.
- JavaScript saved its six-record/two-revision checkpoint before the isolated
client denied `binary_session`. The harness approval was corrected; a new
process restored the same checkpoint and identical trace without another
producer or reset. Joint accounting includes 16 client attempts, 15 served
calls and the client denial.
- The package model selected an unsupported generic view for a derived Apple
projection and stopped before checkpointing. Its continuation rehydrated
original authenticated saved inputs after matching current IDs and observed
summaries/projections, then used the correct projectors. Two inventory producers
ran once total; one additional NewPipe metadata reopen restored active state.
This qualifies explicit saved-source recovery. The first run's unsaved full
inventory body was not directly transported or checkpointed.
## Resource and regression boundaries
The genuine multi-domain/model workflows used `--max-old-space-size=512` for
MCP/CLI Node children; retention had no quota override. The controlled timeout
fixture's separate parent allowance is documented above. An earlier cumulative
default-policy control admitted all nine genuine
bundles: 34 records, 87,532,230 estimated representation bytes against its observed
150,244,580-byte allowance. Exact duplicates did not grow committed state. This
corpus did not reach default capacity; injected-policy regressions establish
atomic refusal, metadata replacement accounting and budget release on reset.
Local checks passed across 43 focused source/public-boundary files, 533 distinct
tests using each file's latest passing run, and 93 Python tests. The main
integration specifically reran eight lifecycle/recovery files with 61 passing
tests, including retained candidate cleanup failure/recovery and healthy-client
preservation. Typecheck, bounded lint, formatting, module/architecture guards,
documentation and production reachability passed. Controlled providers and
failure seams qualify cancellation, late work and cleanup orderings separately
from actual-provider observations.
Heavy tasks ran serially with one CPU, 4 GiB hard memory, 3 GiB early stop, zero
swap and 192 tasks maximum. Kernel CPU and memory.peak were observed at payload
exit while a cgroup observer remained alive. They include drivers/helpers and the
observer, exclude preflight outside the guard, and are distinct from process RSS
and retained-representation estimates. Failed model attempts, partial results,
assessments and raw receipts remain local; binaries and authentication data are
excluded from the repository.
Explicit acknowledged checkpoints qualify cross-process recovery. Uncheckpointed
process loss, automatic journaling, parent-directory power-loss recovery,
Windows, IDA/Hopper and a second actual deep engine were not qualified here.
+1 -2
View File
@@ -83,8 +83,7 @@ valid: partial results state whether WABT validation is unknown, rejected the
module, or succeeded before a later inspection failure.
CLI errors include this partial Evidence inline. MCP errors can return its
`retained-evidence` reference in `details.partial_observation`; use
`get_evidence_bundle` with `detail: "records"` and that `evidence_id` filter to
retrieve the matching record.
`get_evidence_bundle` with `detail: "complete"` to retrieve the matching record.
Core validation remains authoritative when an optional custom section cannot
be interpreted. WABT warning output is preserved in `custom_section_warnings`;
+2 -6
View File
@@ -97,13 +97,9 @@ it does not invent an empty string or body bytes.
The observed Playwright 1.58.2 profile can report a bracketed IPv6
`serverIPAddress`, forwarded from Chromium's `remoteIPAddress` through
[Playwright's HAR tracer](https://github.com/microsoft/playwright/blob/v1.58.2/packages/playwright-core/src/server/har/harTracer.ts).
Chromium DevTools exports (declared as `WebInspector` `537.36`, the frozen
AppleWebKit version in Chromium's user agent) report the same bracketed form:
[its HAR writer](https://github.com/ChromeDevTools/devtools-frontend/blob/63739e8ec45ca01b9e4be9b8b05946acdadd8f1f/front_end/models/har/Log.ts)
strips the port but not the brackets.
REA preserves the original spelling in reported Evidence and removes brackets
only from its validation copy, only for these declared producers and a valid
IPv6 literal. Invalid addresses, bracketed IPv4, ports and zone identifiers remain
only from its validation copy, only for this declared producer and a valid IPv6
literal. Invalid addresses, bracketed IPv4, ports and zone identifiers remain
validation failures. Producer declarations do not authenticate origin.
Native mitmproxy decoding requires a caller-supplied **mitmdump 12.2.3 on
+1 -1
View File
@@ -22,7 +22,7 @@ Results retain:
- Main-document script inventory, exact session script IDs, resource start coordinates, sourceURL/source-map declarations, producer hash and independently computed UTF-8 text digest.
- Armed-window requests from the selected main frame and selected origins, complete reported initiator objects (including stack hierarchy/descriptions), derived callsites, exact script-ID associations and unresolved async parent IDs. Source association does not establish UI causality. Callsite and listener `line_number`/`column_number` values are CDP's zero-based lines and UTF-16 columns in the resource named by `url`. With a `script_id`, that is the script's enclosing resource: the script file for an external script, or the HTML document for an inline script. `resource_start` is where the script text starts in that resource. A callsite with an empty `script_id` comes from the request's reported initiator rather than a script call frame, such as markup that the parser read in a document or a module import in a script file. It still locates a position in `url`, but has no session-script association, so it cannot be joined to captured source or `resource_start`. `trace_web_source_location` takes one-based, script-relative lines and zero-based UTF-16 columns.
For an external script, where `url` is the script file, use `line = line_number + 1` and keep `column = column_number`; this also applies to an empty-`script_id` initiator position in a script file. For an inline script, which requires a `script_id` and its `resource_start`, use `line = line_number - resource_start.line_number + 1`. Subtract `resource_start.column_number` from `column_number` only when `line_number === resource_start.line_number`; keep the reported column on later lines. Columns remain zero-based. This conversion requires known source-start and column coordinates; preserve unavailable coordinates as unknown. Chrome reports a negative start column for some inline scripts, such as one in a helper frame, and positions on that script's first line include it. REA keeps those columns as reported rather than failing the observation; a negative column does not locate a column in `url`, but the subtraction above still gives the script-relative column.
For an external script, where `url` is the script file, use `line = line_number + 1` and keep `column = column_number`; this also applies to an empty-`script_id` initiator position in a script file. For an inline script, which requires a `script_id` and its `resource_start`, use `line = line_number - resource_start.line_number + 1`. Subtract `resource_start.column_number` from `column_number` only when `line_number === resource_start.line_number`; keep the reported column on later lines. Columns remain zero-based. This conversion requires known source-start and column coordinates; preserve unavailable coordinates as unknown.
The coverage counter interval starts at backend acceptance before the armed receipt and ends at the resetting sample after the local timer. Counts may include execution during these command intervals; request events are restricted to the locally armed window. The result retains both backend timestamps and the distinct local clock.
+1 -2
View File
@@ -87,8 +87,7 @@ example under Smart App Control or an App Control policy, reports failure stage
invocation or invalid inspection uses `runtime_unavailable` and doctor
classification `runtime_failure`; it does not
mean Windows x64 itself is unsupported. Provider diagnostics retain the failure
stage under `windows_native_failure`; the availability's `remediation` carries
the corresponding repair.
stage under `windows_native_failure`, together with the corresponding remediation.
Self-test failures preserve the native operation and OS error text. Check the
process's writable local NTFS temporary directory and the reported filesystem,
DACL, Job Object or cleanup constraint, then rerun doctor after resolving it.
-45
View File
@@ -81,51 +81,6 @@
// In this case, there is no squashed merge commit. This could be a simple
// merge commit, a rebase merge commit, or a direct commit to the branch.
// Fallback to fetching the list of commits from the REST API. In the future
@@ -1154,15 +1154,40 @@
* @returns {string} HTML URL of the new file
*/
async createFileOnNewBranch(filename, contents, newBranchName, baseBranchName) {
- // create or update new branch to match base branch
- await this.forkBranch(newBranchName, baseBranchName);
- // use the single file upload API
+ // Preserve existing notes until the replacement file is committed.
+ // A failed write must not leave an open proposal pointing at a missing file.
+ if (!(await this.getBranchSha(newBranchName))) {
+ const baseSha = await this.getBranchSha(baseBranchName);
+ if (!baseSha) {
+ throw new Error(`Unable to find base branch: ${baseBranchName}`);
+ }
+ await this.createNewBranch(newBranchName, baseSha);
+ }
+ let existingSha;
+ try {
+ const { data } = await this.octokit.repos.getContent({
+ owner: this.repository.owner,
+ repo: this.repository.repo,
+ path: filename,
+ ref: newBranchName,
+ });
+ if (Array.isArray(data) || data.type !== 'file' || !data.sha) {
+ throw new Error(`Expected a file at ${filename} on branch: ${newBranchName}`);
+ }
+ existingSha = data.sha;
+ }
+ catch (error) {
+ if (!(error && error.status === 404)) {
+ throw error;
+ }
+ }
const { data: { content }, } = await this.octokit.repos.createOrUpdateFileContents({
owner: this.repository.owner,
repo: this.repository.repo,
path: filename,
// contents need to be base64 encoded
- content: Buffer.from(contents, 'binary').toString('base64'),
+ content: Buffer.from(contents, 'utf8').toString('base64'),
+ ...(existingSha ? { sha: existingSha } : {}),
message: 'Saving release notes',
branch: newBranchName,
});
--- a/build/src/github.d.ts
+++ b/build/src/github.d.ts
@@ -47,6 +47,8 @@
+1 -4
View File
@@ -12,7 +12,6 @@ import { planTestSelection } from "./test-selection.mjs";
// Actionlint validates workflow syntax only. The semantic suite asserts these
// workflows' release, publication and self-hosted runner safeguards.
const GUARDED_WORKFLOWS = new Set([
".github/workflows/codeql.yml",
".github/workflows/pr-title.yml",
".github/workflows/pr-readiness.yml",
".github/workflows/ci-full-pr.yml",
@@ -276,12 +275,10 @@ if (tier === "reused") {
for (const owner of owners) selected[owner] = true;
if (
file.startsWith(".github/workflows/") ||
file.startsWith(".github/codeql/") ||
file === ".github/actionlint.yaml"
) {
selected.workflows = true;
if (GUARDED_WORKFLOWS.has(file) || file.startsWith(".github/codeql/"))
selected.code = true;
if (GUARDED_WORKFLOWS.has(file)) selected.code = true;
if (file !== ".github/workflows/ci.yml") continue;
}
// The Notes example is also the source-owned agent evaluation fixture.
+1 -2
View File
@@ -3,7 +3,6 @@ export interface ReadinessPullRequest {
readonly state: "open" | "closed";
readonly draft: boolean;
readonly body: string | null;
readonly user: { readonly id: number } | null;
readonly head: { readonly sha: string };
readonly base: { readonly ref: string };
}
@@ -54,7 +53,7 @@ export interface ReadinessGitHub {
>;
}
export function evaluateReadiness(
pull: Pick<ReadinessPullRequest, "body" | "draft" | "user">,
pull: Pick<ReadinessPullRequest, "body" | "draft">,
template: string,
): ReadinessResult;
export function runReadinessCheck(input: {
+4 -18
View File
@@ -2,14 +2,6 @@ import { readFile } from "node:fs/promises";
const result = (state, description) => ({ state, description });
// GitHub account IDs remain stable when a login changes.
const EXEMPT_AUTHOR_IDS = new Set([
76467478, // morluto
208281322, // kaoru0822-kitauji
161784452, // N0zoM1z0
342580529, // gensokyo-dev
]);
const OPEN_HEADS_QUERY = `query ReadinessOpenHeads($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
pullRequests(first: 100, after: $after, states: OPEN, orderBy: {field: CREATED_AT, direction: ASC}) {
@@ -68,11 +60,11 @@ function readinessLines(markdown) {
let comment = false;
for (const source of lines) {
if (fence) {
const closing = /^ {0,3}(`+|~+)\s*$/u.exec(source);
if (
closing !== null &&
closing[1][0] === fence.character &&
closing[1].length >= fence.length
new RegExp(
`^ {0,3}${fence.character}{${fence.length},}\\s*$`,
"u",
).test(source)
)
fence = undefined;
continue;
@@ -113,11 +105,6 @@ export function evaluateReadiness(pull, template) {
"pending",
"Draft PR: complete Author readiness before requesting review.",
);
if (EXEMPT_AUTHOR_IDS.has(pull.user?.id))
return result(
"success",
"PR author is exempt from the readiness declaration.",
);
const expected = readinessLines(template).flatMap((line) => {
const match = /^- \[ \] (.+)$/u.exec(line);
return match ? [match[1]] : [];
@@ -223,7 +210,6 @@ export async function runReadinessCheck({ github, context, core }) {
latest.head.sha !== pull.head.sha ||
latest.body !== pull.body ||
latest.draft !== pull.draft ||
latest.user?.id !== pull.user?.id ||
latest.base.ref !== pull.base.ref
)
verdict = result(
+2 -8
View File
@@ -76,7 +76,6 @@ const PATH_SCOPES = {
"src/frida/frida-bindings.d.ts",
"src/objdump/**",
"src/cli/reverseEngineeringCommands.ts",
"src/process/ownedDebuggerCleanupError.ts",
"src/process/ownedDebuggerSession.ts",
"src/rizin/**",
"src/contracts/*ToolContracts.ts",
@@ -104,7 +103,6 @@ const PATH_SCOPES = {
"tests/boundary/providers/RizinInferiorTty.test.ts",
"tests/boundary/providers/RizinTestInferiorTty.ts",
"tests/boundary/process/ownedDebuggerSession.test.ts",
"tests/boundary/cli/debuggerOneShotExit.test.ts",
"tests/boundary/cli/gdbStartupCleanupCli.test.ts",
"tests/boundary/cli/reverseEngineeringCommands.test.ts",
"tests/boundary/mcp/cutter*.test.ts",
@@ -165,9 +163,6 @@ const PATH_SCOPES = {
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
"tests/boundary/mcp/nativeMachoSummaryMcp.test.ts",
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
"tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts",
"tests/boundary/process/nativeUiVerifierLane.test.ts",
"tests/conformance/native/ui-offscreen.swift",
"tests/boundary/cli/nativeSignatureCli.test.ts",
"tests/boundary/cli/nativeMachoArm64_32Cli.test.ts",
"tests/boundary/cli/nativeMachoEncryptionCli.test.ts",
@@ -632,6 +627,7 @@ const PATH_SCOPES = {
"scripts/verify-ghidra-bridge-sources.mjs",
"tests/conformance/ghidra/ReaArmContextProbe.java",
"tests/conformance/ghidra/ReaDecodeProbe.java",
"tests/conformance/ghidra/ReaParameterListProbe.java",
".github/workflows/ghidra-bridge-sources.yml",
],
website: [
@@ -704,9 +700,7 @@ export const CI_LANES = {
agentenv: sameJobs(PATH_SCOPES.agentenv, ["agentenv"]),
nativeaot: sameJobs(PATH_SCOPES.nativeaot, ["nativeaot"]),
ghidra_bridge: sameJobs(PATH_SCOPES.ghidra_bridge, ["ghidra_bridge"]),
website: sameJobs(PATH_SCOPES.website, ["website"], {
earlyInputs: [".agents/skills/reverse-engineer-anything/**"],
}),
website: sameJobs(PATH_SCOPES.website, ["website"]),
};
// An input owner omitted from the registry must never silently lose a lane.
for (const [name, paths] of Object.entries(PATH_SCOPES))
+16 -79
View File
@@ -28,7 +28,6 @@ const setAdd = bindBuiltinMethod(Set.prototype.add);
const hasOwnProperty = bindBuiltinMethod(Object.prototype.hasOwnProperty);
const { isProxy } = require("node:util").types;
const { createHash } = require("node:crypto");
const { EventEmitter: NodeEventEmitter } = require("node:events");
const nodeEventEmitterMethods = Object.create(null);
for (const method of [
@@ -78,6 +77,7 @@ let correlationSequence = 0;
const shape = (value, depth = 0) => {
if (depth > 2 || value === null) return value === null ? "null" : "nested";
if (arrayIsArray(value)) return "array";
switch (typeof value) {
case "string":
return "string";
@@ -90,12 +90,7 @@ const shape = (value, depth = 0) => {
case "function":
return "function";
case "object":
try {
return arrayIsArray(value) ? "array" : "object";
} catch {
// Revoked proxies retain their typeof identity but reject IsArray.
return "object";
}
return "object";
default:
return "unknown";
}
@@ -1246,59 +1241,6 @@ const boundaries = createElectronActiveBoundaryPatches({
shape,
});
// Admit only this provider's exact launch tail and Playwright's owned prefix.
// Caller arguments remain opaque, including repeated hook paths and debug flags.
const restoreApplicationArgv = () => {
const handoff = process.env.REA_ELECTRON_CAPTURE_ARGV;
if (handoff === undefined) return;
delete process.env.REA_ELECTRON_CAPTURE_ARGV;
const plan = JSON.parse(handoff);
if (
plan === null ||
typeof plan !== "object" ||
!Number.isSafeInteger(plan.length) ||
plan.length < 3 ||
typeof plan.sha256 !== "string" ||
!/^[a-f0-9]{64}$/u.test(plan.sha256)
)
throw new Error("Capture argument handoff is not this hook's launch plan");
const tailIndex = process.argv.length - plan.length;
const prefixIndex = process.argv[1] === "--no-sandbox" ? 2 : 1;
if (
tailIndex !== prefixIndex + 2 ||
process.argv[prefixIndex] !== "--inspect=0" ||
process.argv[prefixIndex + 1] !== "--remote-debugging-port=0" ||
process.argv[tailIndex] !== "-r" ||
process.argv[tailIndex + 1] !== __filename ||
typeof process.argv[tailIndex + 2] !== "string" ||
process.argv[tailIndex + 2].length === 0
)
throw new Error("Observed argv does not match Playwright's capture prefix");
const tail = sliceArguments(process.argv, tailIndex);
if (
createHash("sha256").update(JSON.stringify(tail)).digest("hex") !==
plan.sha256
)
throw new Error(
"Observed argv does not match the capture argument handoff",
);
for (let index = 2; index < tail.length; index++)
process.argv[index - 1] = tail[index];
process.argv.length = tail.length - 1;
};
try {
restoreApplicationArgv();
} catch (cause) {
globalThis.__reaElectronActiveHookError = true;
recordRuntime(
"error",
cause instanceof Error ? cause.message : String(cause),
"failed",
{ target: "active-hook-arguments", error: true },
);
}
try {
const electron = require("electron");
patchEmitter(electron.app, (event, args) =>
@@ -1325,25 +1267,20 @@ try {
boundaries.patchApplicationEffects(electron.app);
boundaries.patchNativeAddonLoading();
boundaries.patchChildProcess();
// Observe dispatch without adding listeners: their presence changes Node's
// rejection fallback and Electron's uncaught-exception listener-count policy.
const processEmit = process.emit;
process.emit = function observedProcessEmit(event, ...args) {
if (this === process) {
const kind =
event === "unhandledRejection"
? "unhandled-rejection"
: event === "uncaughtExceptionMonitor"
? "uncaught-exception"
: null;
if (kind !== null)
recordRuntime("process-lifecycle", kind, "observed", {
argument_shape_values: args,
error: true,
});
}
return applyFunction(processEmit, this, arguments);
};
process.once("uncaughtException", (cause, origin) => {
recordRuntime("process-lifecycle", "uncaught-exception", "observed", {
argument_shape_values: [cause, origin],
error: true,
});
throw cause;
});
process.once("unhandledRejection", (reason, promise) => {
recordRuntime("process-lifecycle", "unhandled-rejection", "observed", {
argument_shape_values: [reason, promise],
error: true,
});
throw reason;
});
} catch (cause) {
globalThis.__reaElectronActiveHookError = true;
recordRuntime(
@@ -3,19 +3,19 @@
"snapshot_hash": "65817c30a78bb44c3dc3771876b6010a",
"engines": {
"x86_64": {
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"dart_version": null,
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"armeabi-v7a": {
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"dart_version": null,
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"arm64-v8a": {
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"dart_version": null,
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
-26
View File
@@ -1,26 +0,0 @@
import { createServer } from "node:http";
/** Interpret request targets once, before a fixture routes or forwards them. */
export const createBrowserFixtureHttpServer = (handleRequest) =>
createServer((request, response) => {
let target;
try {
// A leading // is an origin-form path, not a replacement authority.
const requested = new URL(
request.url?.startsWith("/")
? `http://fixture.invalid${request.url}`
: request.url,
);
if (requested.protocol !== "http:" && requested.protocol !== "https:")
throw new TypeError("Unsupported fixture request protocol");
target = { pathname: requested.pathname, search: requested.search };
} catch {
response.writeHead(400, {
"Content-Type": "text/plain; charset=utf-8",
"X-Content-Type-Options": "nosniff",
});
response.end("Invalid HTTP request target");
return;
}
handleRequest(request, response, target);
});
@@ -1,12 +1,11 @@
import assert from "node:assert/strict";
import { once } from "node:events";
import { request } from "node:http";
import { createServer, request } from "node:http";
import { connect } from "node:net";
import { createBrowserScenarioProvider } from "../../dist/composition/browserScenario.js";
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
import { PlaywrightScenarioServiceWorkers } from "../../dist/browser/PlaywrightScenarioServiceWorkers.js";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const beforeDeadline = async (promise, label, timeoutMs = 2_000) => {
let timer;
@@ -27,7 +26,7 @@ const beforeDeadline = async (promise, label, timeoutMs = 2_000) => {
// Real loopback proxy: Playwright's first transport is forwarded to Chrome,
// then only the policy owner's discovery/handshake is deliberately withheld.
export const startProxy = async (endpoint, mode, ownerOnly = false) => {
const startProxy = async (endpoint, mode, ownerOnly = false) => {
const upstream = new URL(endpoint);
const sockets = new Set();
let versions = 0;
@@ -48,44 +47,38 @@ export const startProxy = async (endpoint, mode, ownerOnly = false) => {
socket.once("close", () => closed());
entered();
};
const server = createBrowserFixtureHttpServer(
(incoming, response, target) => {
const versionRequest =
target.pathname.replace(/\/$/, "") === "/json/version";
if (versionRequest) versions += 1;
if (
enabled &&
mode === "http-discovery" &&
versions === 2 &&
versionRequest
) {
stall(incoming.socket);
return;
}
const destination = new URL(upstream);
destination.pathname = target.pathname;
destination.search = target.search;
const outgoing = request(destination, (received) => {
const chunks = [];
received.on("data", (chunk) => chunks.push(chunk));
received.on("end", () => {
const content = Buffer.concat(chunks).toString("utf8");
response.writeHead(received.statusCode, {
"content-type": "application/json",
});
response.end(
content.replaceAll(
`127.0.0.1:${upstream.port}`,
`127.0.0.1:${server.address().port}`,
),
);
const server = createServer((incoming, response) => {
const versionRequest = incoming.url.replace(/\/$/, "") === "/json/version";
if (versionRequest) versions += 1;
if (
enabled &&
mode === "http-discovery" &&
versions === 2 &&
versionRequest
) {
stall(incoming.socket);
return;
}
const outgoing = request(new URL(incoming.url, upstream), (received) => {
const chunks = [];
received.on("data", (chunk) => chunks.push(chunk));
received.on("end", () => {
const content = Buffer.concat(chunks).toString("utf8");
response.writeHead(received.statusCode, {
"content-type": "application/json",
});
response.end(
content.replaceAll(
`127.0.0.1:${upstream.port}`,
`127.0.0.1:${server.address().port}`,
),
);
});
outgoing.on("error", () => response.destroy());
response.on("close", () => outgoing.destroy());
outgoing.end();
},
);
});
outgoing.on("error", () => response.destroy());
response.on("close", () => outgoing.destroy());
outgoing.end();
});
server.on("connection", (socket) => {
sockets.add(socket);
socket.on("close", () => sockets.delete(socket));
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { once } from "node:events";
import { createServer } from "node:http";
import { setTimeout as delay } from "node:timers/promises";
import { chromium } from "playwright-core";
@@ -10,7 +11,6 @@ import { createBrowserScenarioProvider } from "../../dist/composition/browserSce
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
import { runScenarioCli } from "./browser-scenario-verifier.mjs";
import { verifyScenarioPolicySetup } from "./browser-scenario-service-worker-setup-e2e.mjs";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const diagnosticError = (error) =>
error instanceof Error
@@ -26,38 +26,38 @@ const diagnosticError = (error) =>
}
: { kind: typeof error };
export const startSite = async () => {
const startSite = async () => {
let otherOrigin;
const server = createBrowserFixtureHttpServer(
(_request, response, { pathname: path }) => {
if (path === "/sw.js") {
response.setHeader("content-type", "application/javascript");
response.end(`self.addEventListener("install", () => self.skipWaiting());
const server = createServer((request, response) => {
const path = new URL(request.url, "http://localhost").pathname;
if (path === "/sw.js") {
response.setHeader("content-type", "application/javascript");
response.end(`self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", event => event.waitUntil(clients.claim()));
self.addEventListener("fetch", event => {
if (new URL(event.request.url).pathname === "/probe")
event.respondWith(new Response("worker"));
});`);
} else if (path === "/probe") {
response.end("network");
} else if (path === "/failed-navigation") {
response.destroy();
} else if (path === "/stalled-image.png") {
response.writeHead(200, { "content-type": "image/png" });
response.flushHeaders();
} else if (path === "/pending-image" || path === "/pending-child") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><img id="pending-image" src="/stalled-image.png">',
);
} else if (path === "/pending-frame") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><iframe id="pending-frame" src="/pending-child"></iframe>',
);
} else {
response.setHeader("content-type", "text/html");
response.end(`<!doctype html><p id="state">ready</p>
} else if (path === "/probe") {
response.end("network");
} else if (path === "/failed-navigation") {
response.destroy();
} else if (path === "/stalled-image.png") {
response.writeHead(200, { "content-type": "image/png" });
response.flushHeaders();
} else if (path === "/pending-image" || path === "/pending-child") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><img id="pending-image" src="/stalled-image.png">',
);
} else if (path === "/pending-frame") {
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><iframe id="pending-frame" src="/pending-child"></iframe>',
);
} else {
response.setHeader("content-type", "text/html");
response.end(`<!doctype html><p id="state">ready</p>
<a id="navigate" href="${otherOrigin}/">Navigate</a>
${path === "/frame" ? `<iframe src="${otherOrigin}/"></iframe>` : ""}
${
@@ -72,9 +72,8 @@ export const startSite = async () => {
</script>`
: ""
}`);
}
},
);
}
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
return {
+22 -23
View File
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { once } from "node:events";
import { setTimeout as delay } from "node:timers/promises";
@@ -10,7 +11,6 @@ import {
scenarioProfiles,
} from "./browser-scenario-verifier.mjs";
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
import { createBrowserFixtureHttpServer } from "./browser-fixture-http.mjs";
const scenarioFor = (browser, origin) =>
browserScenarioSchema.parse({
@@ -149,29 +149,28 @@ const storagePage = (kind, port) => {
</script></body></html>`;
};
export const startStorageSite = async () => {
const startStorageSite = async () => {
let port = 0;
const server = createBrowserFixtureHttpServer(
(_request, response, { pathname: path }) => {
const kind =
path === "/storage-main"
? "main"
: path === "/storage-frame"
? "frame"
: path === "/storage-debugger"
? "debugger"
: path === "/storage-top"
? "top"
: undefined;
if (kind === undefined) {
response.writeHead(404).end();
return;
}
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(storagePage(kind, port));
},
);
server.listen(0, "127.0.0.1");
const server = createServer((request, response) => {
const path = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
const kind =
path === "/storage-main"
? "main"
: path === "/storage-frame"
? "frame"
: path === "/storage-debugger"
? "debugger"
: path === "/storage-top"
? "top"
: undefined;
if (kind === undefined) {
response.writeHead(404).end();
return;
}
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(storagePage(kind, port));
});
server.listen(0, "::");
await once(server, "listening");
port = server.address().port;
return {
-57
View File
@@ -1,57 +0,0 @@
interface ProviderBinding {
readonly identity: {
readonly id: string;
readonly name: string;
readonly version: string | null;
};
readonly operations: readonly string[] | { readonly group: string };
}
interface ProviderCatalogSources {
readonly toolContracts: {
readonly TOOL_CONTRACTS: readonly { readonly name: string }[];
};
readonly toolFamilies: {
readonly TOOL_CONTRACT_GROUPS: Readonly<
Record<string, readonly { readonly name: string }[]>
>;
};
readonly capabilityDeclarations: {
readonly CAPABILITY_DECLARATIONS: Readonly<
Record<string, { readonly providers: readonly ProviderBinding[] }>
>;
};
}
export function providerCatalog(sources: ProviderCatalogSources): {
readonly id: string;
readonly name: string;
readonly version: string | null;
readonly capabilities: readonly string[];
}[];
export function toolFamilyCatalog(sources: {
readonly toolFamilies: {
readonly TOOL_FAMILY_DEFINITIONS: readonly {
readonly id: string;
readonly surface: string;
readonly contracts: readonly { readonly name: string }[];
}[];
};
readonly toolContracts: {
readonly TOOL_CONTRACTS: readonly unknown[];
};
readonly catalogIdentity: {
readonly FULL_CATALOG_IDENTITY: {
readonly counts: { readonly mcp_tools: number };
};
};
}): {
readonly total: number;
readonly families: readonly {
readonly id: string;
readonly surface: string;
readonly count: number;
readonly tools: readonly string[];
}[];
};
+403 -44
View File
@@ -23,55 +23,414 @@ export const toolFamilyCatalog = (sources) => {
return { total: familyTotal, families };
};
/** Derive provider capabilities from the same static declarations used by composition. */
/** Build provider identities with their sorted capability names. */
export const providerCatalog = (sources) => {
const contractNames = new Set(
sources.toolContracts.TOOL_CONTRACTS.map(({ name }) => name),
const contractsByName = new Map(
sources.toolContracts.TOOL_CONTRACTS.map((contract) => [
contract.name,
contract,
]),
);
const providers = new Map();
for (const declaration of Object.values(
sources.capabilityDeclarations.CAPABILITY_DECLARATIONS,
)) {
for (const { identity, operations } of declaration.providers) {
let provider = providers.get(identity.id);
if (provider === undefined) {
provider = { ...identity, capabilities: new Set() };
providers.set(identity.id, provider);
} else if (
provider.name !== identity.name ||
provider.version !== identity.version
) {
const declaredContracts = (providerId, operations) =>
operations.map((operation) => {
const contract = contractsByName.get(operation);
if (contract === undefined)
throw new TypeError(
`Provider ${identity.id} has conflicting source identities`,
`Provider ${providerId} declares an unknown analyst operation: ${operation}`,
);
}
let selectedOperations = operations;
if (!Array.isArray(operations)) {
const contracts =
sources.toolFamilies.TOOL_CONTRACT_GROUPS[operations.group];
if (contracts === undefined)
throw new TypeError(
`Provider ${identity.id} declares an unknown contract group: ${operations.group}`,
);
selectedOperations = contracts.map(({ name }) => name);
}
for (const operation of selectedOperations) {
if (!contractNames.has(operation))
throw new TypeError(
`Provider ${identity.id} declares an unknown analyst operation: ${operation}`,
);
provider.capabilities.add(operation);
}
}
}
return [...providers.values()]
.map(({ id, name, version, capabilities }) => ({
id,
name,
version,
capabilities: [...capabilities].sort((left, right) =>
left.localeCompare(right),
return contract;
});
const {
applicationContracts,
reconciliationContracts,
observationContracts,
activeContracts,
fuseContracts,
} = electronContractSlices(sources);
return [
{
identity: sources.cutterProvider.CUTTER_PROVIDER_IDENTITY,
contracts: sources.cutterContracts.CUTTER_TOOL_CONTRACTS,
},
{
identity: sources.objdumpProvider.OBJDUMP_PROVIDER_IDENTITY,
contracts:
sources.reverseEngineeringContracts.REVERSE_ENGINEERING_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_with_objdump",
),
},
{
identity: sources.rizinProvider.RIZIN_PROVIDER_IDENTITY,
contracts:
sources.reverseEngineeringContracts.REVERSE_ENGINEERING_TOOL_CONTRACTS.filter(
({ name }) => name === "execute_rizin_command",
),
},
{
identity: sources.gdbProvider.GDB_PROVIDER_IDENTITY,
contracts: sources.gdbContracts.GDB_TOOL_CONTRACTS,
},
{
identity: sources.rizinDebugProvider.RIZIN_DEBUGGER_PROVIDER_IDENTITY,
contracts: sources.rizinDebugContracts.RIZIN_DEBUG_TOOL_CONTRACTS,
},
{
identity: sources.fridaProvider.FRIDA_PROVIDER_IDENTITY,
contracts: sources.fridaContracts.FRIDA_TOOL_CONTRACTS,
},
{
identity: sources.hopperProvider.HOPPER_PROVIDER_IDENTITY,
contracts: declaredContracts(
"hopper",
sources.hopperProvider.HOPPER_OPERATIONS,
),
},
{
identity: sources.ghidraProvider.GHIDRA_PROVIDER_IDENTITY,
contracts: declaredContracts(
"ghidra",
sources.ghidraProvider.GHIDRA_OPERATIONS,
),
},
{
identity: sources.idaProvider.IDA_PROVIDER_IDENTITY,
contracts: declaredContracts("ida", sources.idaProvider.IDA_OPERATIONS),
},
{
identity: sources.kunaProvider.KUNA_PROVIDER_IDENTITY,
contracts: declaredContracts(
"kuna",
sources.kunaProvider.KUNA_OPERATIONS,
),
},
{
identity: sources.nativeProvider.NATIVE_MACOS_PROVIDER_IDENTITY,
contracts: sources.nativeContracts.NATIVE_TOOL_CONTRACTS,
},
{
identity: sources.pwntoolsProvider.PWNTOOLS_PROVIDER_IDENTITY,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) =>
name === "inspect_binary_layout" ||
name === "inspect_recorded_crash",
),
},
{
identity: sources.artifactProviders.PE_RESOURCES_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_pe_resources",
),
},
{
identity: sources.artifactProviders.PE_CERTIFICATES_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_pe_certificates",
),
},
{
identity: sources.artifactProviders.PE_IMPORTS_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_pe_imports",
),
},
{
identity: sources.artifactProviders.DEVICETREE_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_devicetree",
),
},
{
identity: sources.artifactProviders.PE_LOAD_CONFIG_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_pe_load_config",
),
},
{
identity: sources.artifactProviders.ELF_NOTES_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_elf_notes",
),
},
{
identity: sources.artifactProviders.APPLE_IPS_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_apple_ips",
),
},
{
identity: sources.artifactProviders.KERNEL_MODULE_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_kernel_module",
),
},
{
identity: sources.artifactProviders.BYTE_ENTROPY_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_byte_entropy",
),
},
{
identity: sources.artifactProviders.NATIVE_EXPORTS_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_native_exports",
),
},
{
identity: sources.captureProvider.TSHARK_PROVIDER_IDENTITY,
contracts: sources.captureContracts.CAPTURE_TOOL_CONTRACTS,
},
{
identity: sources.signalProvider.SIGROK_PROVIDER_IDENTITY,
contracts: sources.signalContracts.SIGNAL_TOOL_CONTRACTS,
},
{
identity: sources.wasmProvider.WABT_PROVIDER_IDENTITY,
contracts: sources.wasmContracts.WASM_TOOL_CONTRACTS.filter(
({ kind }) => kind === "artifact-provider",
),
},
{
identity: sources.apkSignatureProvider.APK_SIGNATURE_PROVIDER_IDENTITY,
contracts: sources.apkSignatureContracts.APK_SIGNATURE_TOOL_CONTRACTS,
},
{
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.gpuProvider.GPU_CODE_OBJECTS_PROVIDER_IDENTITY,
contracts: sources.gpuContracts.GPU_TOOL_CONTRACTS,
},
{
identity: sources.il2cppProvider.IL2CPP_METADATA_PROVIDER_IDENTITY,
contracts: sources.il2cppContracts.IL2CPP_TOOL_CONTRACTS,
},
{
identity: sources.modelProvider.MODEL_ARTIFACT_PROVIDER_IDENTITY,
contracts: sources.modelContracts.MODEL_TOOL_CONTRACTS,
},
{
identity: sources.rustProvider.RUST_BINARY_PROVIDER_IDENTITY,
contracts: sources.rustContracts.RUST_TOOL_CONTRACTS,
},
{
identity: sources.pythonPycProvider.PYTHON_PYC_PROVIDER_IDENTITY,
contracts: sources.pythonPycContracts.PYTHON_PYC_TOOL_CONTRACTS,
},
{
identity: sources.erlangProvider.ERLANG_BEAM_PROVIDER_IDENTITY,
contracts: sources.erlangContracts.ERLANG_BEAM_TOOL_CONTRACTS,
},
{
identity: sources.electronFuseProvider.ELECTRON_FUSE_PROVIDER_IDENTITY,
contracts: fuseContracts,
},
{
identity: sources.sqliteDomain.SQLITE_PROVIDER_IDENTITY,
contracts: sources.sqliteContracts.SQLITE_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.ARTIFACT_GRAPH_PROVIDER,
contracts: [
...sources.artifactContracts.ARTIFACT_TOOL_CONTRACTS,
...sources.desktopContracts.DESKTOP_APPLICATION_TOOL_CONTRACTS,
],
},
{
identity: sources.artifactProviders.MANAGED_STATIC_PROVIDER,
contracts: sources.managedContracts.MANAGED_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.MANAGED_WORKFLOW_PROVIDER,
contracts:
sources.managedWorkflowContracts.MANAGED_WORKFLOW_TOOL_CONTRACTS,
},
{
identity: sources.firmwareProvider.BINWALK_PROVIDER_IDENTITY,
contracts: sources.firmwareContracts.FIRMWARE_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_firmware_regions",
),
},
{
identity: sources.firmwareProvider.UNBLOB_PROVIDER_IDENTITY,
contracts: sources.firmwareContracts.FIRMWARE_TOOL_CONTRACTS.filter(
({ name }) => name === "extract_firmware",
),
},
{
identity: sources.androidProvider.JADX_PROVIDER_IDENTITY,
contracts: sources.androidContracts.ANDROID_TOOL_CONTRACTS,
},
{
identity: sources.pcapProvider.PCAP_PROVIDER_IDENTITY,
contracts: sources.pcapContracts.PCAP_TOOL_CONTRACTS,
},
{
identity: sources.flutterProvider.FLUTTER_PROVIDER_IDENTITY,
contracts: sources.flutterContracts.FLUTTER_TOOL_CONTRACTS,
},
{
identity: sources.apktoolProvider.APKTOOL_PROVIDER_IDENTITY,
contracts: sources.apktoolContracts.APKTOOL_TOOL_CONTRACTS,
},
{
identity: sources.jebProvider.JEB_PROVIDER_IDENTITY,
contracts: sources.jebContracts.JEB_TOOL_CONTRACTS,
},
{
identity: sources.adbProvider.ADB_PROVIDER_IDENTITY,
contracts: sources.adbContracts.ADB_TOOL_CONTRACTS,
},
{
identity: sources.webNetworkCaptureProvider.HAR_CAPTURE_PROVIDER_IDENTITY,
contracts:
sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS,
},
{
identity: sources.webNetworkCaptureProvider.SAZ_CAPTURE_PROVIDER_IDENTITY,
contracts:
sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS,
},
{
identity:
sources.webNetworkCaptureProvider.MITMPROXY_CAPTURE_PROVIDER_IDENTITY,
contracts:
sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS,
},
{
identity: sources.browserIdentities.CDP_BROWSER_PROVIDER_IDENTITY,
contracts: [
...sources.browserContracts.BROWSER_TOOL_CONTRACTS,
...sources.webRuntimeContracts.WEB_RUNTIME_TOOL_CONTRACTS,
],
},
{
identity:
sources.browserIdentities.PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY,
contracts:
sources.browserScenarioContracts.BROWSER_SCENARIO_TOOL_CONTRACTS,
},
{
identity: sources.browserIdentities.CDP_ELECTRON_PROVIDER_IDENTITY,
contracts: observationContracts,
},
{
identity:
sources.browserIdentities.PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY,
contracts: activeContracts,
},
{
identity: sources.v8InspectorProvider.V8_INSPECTOR_PROVIDER_IDENTITY,
contracts:
sources.javascriptRuntimeObservationContracts
.JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.JAVASCRIPT_APPLICATION_PROVIDER,
contracts: applicationContracts,
},
{
identity:
sources.artifactProviders.JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER,
contracts: reconciliationContracts,
},
{
identity:
sources.artifactProviders.JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER,
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
({ name }) =>
name !== "project_android_application_graph" &&
name !== "project_apple_application_graph",
),
},
{
identity: sources.artifactProviders.ANALYSIS_VIEW_PROVIDER,
contracts: sources.analysisViewContracts.ANALYSIS_VIEW_TOOL_CONTRACTS,
},
{
identity: sources.artifactProviders.ANDROID_APPLICATION_PROVIDER,
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
({ name }) => name === "project_android_application_graph",
),
},
{
identity: sources.artifactProviders.APPLE_APPLICATION_PROVIDER,
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
({ name }) => name === "project_apple_application_graph",
),
},
{
identity: sources.artifactProviders.WEB_SCRIPT_EXPORT_PROVIDER,
contracts: sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
},
{
identity: sources.javascriptRecoveryProvider.WAKARU_PROVIDER_IDENTITY,
contracts:
sources.javascriptRecoveryContracts.JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
},
]
.map(({ identity, contracts }) => ({
id: identity.id,
name: identity.name,
version: identity.version,
capabilities: names(contracts),
}))
.sort((left, right) => left.id.localeCompare(right.id));
};
const electronContractSlices = (sources) => {
const applicationContracts =
sources.electronContracts.ELECTRON_TOOL_CONTRACTS.filter(
({ name }) => name === "analyze_javascript_application",
);
const reconciliationContracts =
sources.electronContracts.ELECTRON_TOOL_CONTRACTS.filter(
({ name }) => name === "reconcile_javascript_runtime",
);
const observationContracts =
sources.electronContracts.ELECTRON_TOOL_CONTRACTS.filter(
({ name }) =>
name !== "analyze_javascript_application" &&
name !== "reconcile_javascript_runtime" &&
name !== "capture_electron_scenario" &&
name !== "inspect_electron_fuses",
);
const activeContracts =
sources.electronContracts.ELECTRON_TOOL_CONTRACTS.filter(
({ name }) => name === "capture_electron_scenario",
);
const fuseContracts =
sources.electronContracts.ELECTRON_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_electron_fuses",
);
if (
applicationContracts.length !== 1 ||
reconciliationContracts.length !== 1 ||
observationContracts.length !== 2 ||
activeContracts.length !== 1 ||
fuseContracts.length !== 1
)
throw new Error("Electron provider capability ownership drifted");
return {
applicationContracts,
reconciliationContracts,
observationContracts,
activeContracts,
fuseContracts,
};
};
+124 -1
View File
@@ -39,8 +39,131 @@ const SOURCE_PATHS = {
cli: "dist/cli.js",
toolContracts: "dist/contracts/toolContracts.js",
toolFamilies: "dist/contracts/toolFamilies.js",
capabilityDeclarations: "dist/composition/capabilityDeclarations.js",
binaryDiagnosticsContracts:
"dist/contracts/native/binaryDiagnosticsToolContracts.js",
cutterContracts: "dist/contracts/cutterToolContracts.js",
cutterProvider: "dist/cutter/CutterBridgeClient.js",
reverseEngineeringContracts:
"dist/contracts/reverseEngineeringToolContracts.js",
gdbContracts: "dist/contracts/gdbToolContracts.js",
gdbProvider: "dist/gdb/GdbSessionManager.js",
rizinDebugContracts: "dist/contracts/rizinDebugToolContracts.js",
objdumpProvider: "dist/objdump/ObjdumpCommand.js",
rizinProvider: "dist/rizin/RizinCommand.js",
rizinDebugProvider: "dist/rizin/RizinDebugSessionManager.js",
fridaContracts: "dist/contracts/fridaToolContracts.js",
fridaProvider: "dist/frida/FridaInstrumentationManager.js",
pwntoolsProvider: "dist/native/pwntools/PwntoolsRelease.js",
nativeContracts: "dist/contracts/native/nativeToolContracts.js",
captureContracts: "dist/contracts/captures/captureToolContracts.js",
captureProvider: "dist/captures/TsharkCaptureProvider.js",
signalContracts: "dist/contracts/signals/signalToolContracts.js",
signalProvider: "dist/signals/SigrokRecordingProvider.js",
wasmContracts: "dist/contracts/wasm/wasmToolContracts.js",
wasmProvider: "dist/wasm/WabtRelease.js",
apkSignatureContracts:
"dist/contracts/apkSignature/apkSignatureToolContracts.js",
apkSignatureProvider: "dist/apkSignature/ApkSignatureRelease.js",
evmContracts: "dist/contracts/evm/evmToolContracts.js",
sqliteContracts: "dist/contracts/sqlite/sqliteToolContracts.js",
sqliteDomain: "dist/domain/sqlite/sqliteDatabase.js",
evmProvider: "dist/evm/EvmoleRelease.js",
goContracts: "dist/contracts/go/goToolContracts.js",
goProvider: "dist/go/GoBinaryProvider.js",
gpuContracts: "dist/contracts/gpu/gpuToolContracts.js",
gpuProvider: "dist/gpu/GpuCodeObjectsProvider.js",
modelContracts: "dist/contracts/model/modelToolContracts.js",
modelProvider: "dist/model/ModelArtifactProvider.js",
il2cppContracts: "dist/contracts/il2cpp/il2cppToolContracts.js",
il2cppProvider: "dist/il2cpp/Il2cppMetadataProvider.js",
rustContracts: "dist/contracts/rust/rustToolContracts.js",
rustProvider: "dist/rust/RustBinaryProvider.js",
pythonPycContracts: "dist/contracts/python/pythonPycToolContracts.js",
pythonPycProvider: "dist/python/PythonPycProvider.js",
erlangContracts: "dist/contracts/erlang/beamToolContracts.js",
erlangProvider: "dist/erlang/BeamProvider.js",
artifactContracts: "dist/contracts/artifactToolContracts.js",
desktopContracts: "dist/contracts/desktopApplicationToolContracts.js",
managedContracts: "dist/contracts/managed/managedToolContracts.js",
firmwareContracts: "dist/contracts/firmware/firmwareToolContracts.js",
firmwareProvider: "dist/firmware/FirmwareRelease.js",
androidContracts: "dist/contracts/android/androidToolContracts.js",
flutterContracts: "dist/contracts/flutter/flutterToolContracts.js",
flutterProvider: "dist/flutter/FlutterBuildProvider.js",
androidProvider: "dist/android/JadxRelease.js",
apktoolContracts: "dist/contracts/apktool/apktoolToolContracts.js",
pcapContracts: "dist/contracts/pcap/pcapToolContracts.js",
pcapProvider: "dist/pcap/PcapProvider.js",
apktoolProvider: "dist/apktool/ApktoolProvider.js",
jebContracts: "dist/contracts/jeb/jebToolContracts.js",
jebProvider: "dist/jeb/JebProvider.js",
adbContracts: "dist/contracts/adb/adbToolContracts.js",
adbProvider: "dist/adb/AdbProvider.js",
managedWorkflowContracts:
"dist/contracts/managed/managedWorkflowToolContracts.js",
browserContracts: "dist/contracts/browserToolContracts.js",
webRuntimeContracts: "dist/contracts/webRuntimeToolContracts.js",
webNetworkCaptureContracts:
"dist/contracts/webNetworkCaptureToolContracts.js",
webNetworkCaptureProvider: "dist/browser/history/CaptureRelease.js",
browserScenarioContracts: "dist/contracts/browserScenarioToolContracts.js",
electronContracts: "dist/contracts/javascript/electronToolContracts.js",
electronFuseProvider: "dist/electron/ElectronFuseProvider.js",
javascriptRuntimeObservationContracts:
"dist/contracts/javascript/javascriptRuntimeObservationToolContracts.js",
applicationContracts: "dist/contracts/applicationToolContracts.js",
analysisViewContracts: "dist/contracts/analysisViewToolContracts.js",
webScriptContracts: "dist/contracts/webScriptToolContracts.js",
javascriptRecoveryContracts:
"dist/contracts/javascript/javascriptRecoveryToolContracts.js",
javascriptRecoveryProvider: "dist/javascript/recovery/WakaruRelease.js",
supportedClients: "dist/application/SupportedClients.js",
hopperProvider: "dist/hopper/HopperProviderCapabilities.js",
ghidraProvider: "dist/ghidra/GhidraProviderCapabilities.js",
idaProvider: "dist/ida/IdaProviderCapabilities.js",
kunaProvider: "dist/kuna/KunaProviderCapabilities.js",
nativeProvider: "dist/native/NativeMacOSProviderMetadata.js",
artifactProviders: "dist/domain/investigationProviders.js",
browserIdentities: "dist/browser/providerIdentities.js",
v8InspectorProvider: "dist/inspector/providerIdentity.js",
evidence: "dist/domain/evidence.js",
evidenceBundle: "dist/domain/evidenceBundle.js",
processCapture: "dist/domain/process/processCapture.js",
analysisSnapshot: "dist/domain/analysisSnapshot.js",
artifactGraph: "dist/domain/artifactGraph.js",
browserObservation: "dist/domain/browserObservation.js",
browserScenario: "dist/domain/browserScenario.js",
browserScenarioCapture: "dist/domain/browserScenarioCapture.js",
browserScenarioDiff: "dist/domain/browserScenarioDiff.js",
browserSession: "dist/domain/browserSession.js",
electronObservation: "dist/domain/javascript/electronObservation.js",
electronActiveObservation:
"dist/domain/javascript/electronActiveObservation.js",
javascriptRuntimeObservation:
"dist/domain/javascript/javascriptRuntimeObservation.js",
webBundleAnalysis: "dist/domain/webBundleAnalysis.js",
webCaptureDiff: "dist/domain/webCaptureDiff.js",
managedArtifact: "dist/domain/managed/managedArtifact.js",
managedComparison: "dist/domain/managed/managedMemberComparison.js",
managedNativeVerification: "dist/domain/managed/managedNativeVerification.js",
webMcpDiscovery: "dist/domain/webMcpDiscovery.js",
webScreenshot: "dist/domain/webScreenshot.js",
javascriptApplicationGraph:
"dist/domain/javascript/javascriptApplicationGraph.js",
javascriptApplicationAnalysis:
"dist/domain/javascript/javascriptApplicationAnalysis.js",
javascriptSemanticGraph: "dist/domain/javascript/javascriptSemanticGraph.js",
javascriptSemanticQuery:
"dist/domain/javascript/javascriptSemanticQuerySchemas.js",
javascriptSemanticTrace:
"dist/domain/javascript/javascriptSemanticTraceSchemas.js",
javascriptFeatureTrace:
"dist/domain/javascript/javascriptFeatureTraceSchemas.js",
javascriptVersionComparison:
"dist/domain/javascript/javascriptApplicationVersionComparisonSchemas.js",
reconstructionVerification:
"dist/domain/reconstructionVerificationSchemas.js",
residualUnknown: "dist/domain/residualUnknown.js",
};
/** Load every compiled source module referenced by the catalog. */
-2
View File
@@ -2,8 +2,6 @@
export interface ImportSpecifierFact {
readonly specifier: string;
readonly line: number;
/** True only when TypeScript erases this module edge under verbatimModuleSyntax. */
readonly typeOnly: boolean;
}
/** Static string specifier on one import or re-export node, when it has one. */
+1 -21
View File
@@ -26,22 +26,6 @@ export const importSpecifier = (node) => {
}
};
/** Whether TypeScript erases this module edge under verbatimModuleSyntax. */
const isTypeOnly = (node) => {
switch (node.type) {
case "TSImportType":
return true;
case "ImportDeclaration":
case "TSImportEqualsDeclaration":
return node.importKind === "type";
case "ExportNamedDeclaration":
case "ExportAllDeclaration":
return node.exportKind === "type";
default:
return false;
}
};
/**
* Static import and re-export specifiers in source order.
* Comments and string literals are not imports. Non-string dynamic imports
@@ -53,11 +37,7 @@ export const readImportSpecifiers = (source) => {
traverseFast(tree, (node) => {
const specifier = importSpecifier(node);
if (specifier === undefined) return;
specifiers.push({
specifier,
line: node.loc?.start.line ?? 1,
typeOnly: isTypeOnly(node),
});
specifiers.push({ specifier, line: node.loc?.start.line ?? 1 });
});
return specifiers;
};
+1 -80
View File
@@ -21,8 +21,6 @@ export const verifyKunaReferences = async ({
address,
expectedCaller,
nativeIncoming,
verifyNamedTrace = false,
minimumTraceReferences = 1,
}) => {
const before = await Promise.all([
entries(join(cacheRoot, "projects")),
@@ -82,84 +80,7 @@ export const verifyKunaReferences = async ({
direction: "forward",
});
assert.equal(path.value.goal_status, "reached");
let traceMs;
if (verifyNamedTrace) {
const trace = await call("trace_feature", { query: name });
traceMs = trace.ms;
assert.ok(
trace.value.matches.some(
(match) => match.type === "procedure" && match.address === address,
),
`${name} was not retained in its feature trace`,
);
assert.ok(
incoming.value.length >= minimumTraceReferences,
`${name} fixture did not produce the expected incoming references`,
);
const references = trace.value.references.filter(
(row) => row.target_address === address,
);
assert.deepEqual(
references.map((row) => row.source_address).sort(),
[...incoming.value].sort(),
`${name} lost or duplicated an observed incoming reference`,
);
for (const reference of references) {
assert.equal(reference.containing_procedure.status, "unavailable");
assert.equal(reference.containing_procedure.provider_id, "kuna");
assert.ok(
trace.value.residual_unknowns.some((unknown) =>
unknown.includes(reference.source_address),
),
);
}
assert.equal(trace.value.truncated, false);
const summary = await call("trace_feature", {
query: name,
detail: "summary",
});
assert.equal(
summary.value.parent_evidence_id,
trace.structured.evidence_id,
);
assert.equal(
summary.value.summary.references,
trace.value.references.length,
);
assert.equal(
summary.value.summary.unknowns,
trace.value.residual_unknowns.length,
);
for (const [collection, values] of [
["matches", trace.value.matches],
["references", trace.value.references],
["unknowns", trace.value.residual_unknowns],
]) {
const view = await call("inspect_analysis_view", {
source: {
kind: "retained-evidence",
evidence_id: trace.structured.evidence_id,
},
view: {
kind: "native-feature-trace",
collection,
offset: 0,
limit: 1,
},
});
assert.equal(view.value.coverage.total, values.length);
assert.deepEqual(
view.value.items.rows.map(({ value }) => value),
values.slice(0, 1),
);
assert.equal(view.value.items.collection, collection);
}
}
composed = {
graph_ms: graph.ms,
path_ms: path.ms,
...(traceMs === undefined ? {} : { feature_trace_ms: traceMs }),
};
composed = { graph_ms: graph.ms, path_ms: path.ms };
}
assert.deepEqual(
await Promise.all([
Loaded 100 of 922 files, more files were not shown because too many files have changed in this diff. Show more