Compare commits
149
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27dc42d102 | ||
|
|
b83b9caf4a | ||
|
|
bb212ffecb | ||
|
|
6b180783ed | ||
|
|
8cbaf6c702 | ||
|
|
2c4338db1f | ||
|
|
18aa9af2b2 | ||
|
|
1f23e977b5 | ||
|
|
08fbeb8bda | ||
|
|
47d3c6b1a1 | ||
|
|
b33c0df02e | ||
|
|
5bf4fffc1b | ||
|
|
847472acf5 | ||
|
|
270bc38f72 | ||
|
|
d5fc7e6ebf | ||
|
|
279c590ef6 | ||
|
|
2ae4e1608f | ||
|
|
ba98130049 | ||
|
|
1accac27c3 | ||
|
|
9df559b7e7 | ||
|
|
94bc7ed322 | ||
|
|
52b73ee857 | ||
|
|
a8170163b9 | ||
|
|
bbfe6edd60 | ||
|
|
2582c267af | ||
|
|
2183c9598c | ||
|
|
93196449ad | ||
|
|
fbfdb6abe9 | ||
|
|
e6c487799b | ||
|
|
7ca7e38717 | ||
|
|
f2b7bbb312 | ||
|
|
69324b0f86 | ||
|
|
6c98708793 | ||
|
|
d205c528e3 | ||
|
|
796faa0e4e | ||
|
|
f0b9ef34c4 | ||
|
|
f4456dc196 | ||
|
|
7d79a6a85b | ||
|
|
823521085a | ||
|
|
abb54ce374 | ||
|
|
d818c3d263 | ||
|
|
d0253c99d6 | ||
|
|
2fb5e204a3 | ||
|
|
7e4eed9dd4 | ||
|
|
19329e43bb | ||
|
|
01cdbbdc55 | ||
|
|
182e9a268c | ||
|
|
4f7b5fb602 | ||
|
|
c555caad65 | ||
|
|
9ac76d3d94 | ||
|
|
fc847ffe35 | ||
|
|
f5fcf5216f | ||
|
|
34a88d6c16 | ||
|
|
f1edf0da0c | ||
|
|
d37c346477 | ||
|
|
9e372c6bc9 | ||
|
|
fd6da70920 | ||
|
|
6668424343 | ||
|
|
3b5605ecd2 | ||
|
|
09de00b410 | ||
|
|
e103ae0ce5 | ||
|
|
c43bc0bf66 | ||
|
|
3460ceac3f | ||
|
|
16924e275e | ||
|
|
3fcba07962 | ||
|
|
daa641aa3c | ||
|
|
8a247fc70f | ||
|
|
147ae9718a | ||
|
|
300905c11b | ||
|
|
f1d18859eb | ||
|
|
662fad9072 | ||
|
|
ceeff5a417 | ||
|
|
964decd989 | ||
|
|
3ad1032b11 | ||
|
|
3ddb2e4cf7 | ||
|
|
7016951394 | ||
|
|
3fa972af7c | ||
|
|
9fa6d2b254 | ||
|
|
63c65a2243 | ||
|
|
742b6f587f | ||
|
|
3c1cce78ee | ||
|
|
b27e1a74c9 | ||
|
|
dc2e8117b6 | ||
|
|
bec88b9d77 | ||
|
|
6b0113a534 | ||
|
|
68c3c5b538 | ||
|
|
af67ad5047 | ||
|
|
c1fe0d653d | ||
|
|
d38f099692 | ||
|
|
63696d2773 | ||
|
|
6385ef4355 | ||
|
|
98886adddf | ||
|
|
cc038da218 | ||
|
|
4844ae812b | ||
|
|
11bcf44252 | ||
|
|
376eeca770 | ||
|
|
f4322b15ed | ||
|
|
926e18b3fc | ||
|
|
e7df00e807 | ||
|
|
f9de41371f | ||
|
|
2d84c800bc | ||
|
|
060eb43b54 | ||
|
|
bf54c46264 | ||
|
|
bd3210eb24 | ||
|
|
22a0746f90 | ||
|
|
7aa4b98c98 | ||
|
|
78cf6100f3 | ||
|
|
2cf9c28f27 | ||
|
|
5d6029ea62 | ||
|
|
fc935d0df3 | ||
|
|
a425b3e528 | ||
|
|
149a0fc06c | ||
|
|
c192c58815 | ||
|
|
e5ee3ac658 | ||
|
|
969aafcf28 | ||
|
|
34239d445b | ||
|
|
83d2780008 | ||
|
|
8589b7e778 | ||
|
|
63c530998e | ||
|
|
f03a55909d | ||
|
|
f1ab5959bd | ||
|
|
31c87b40ee | ||
|
|
6a407619fe | ||
|
|
95b561f2e9 | ||
|
|
2f9b9c48b8 | ||
|
|
3fc007534b | ||
|
|
9c56d09c05 | ||
|
|
a43ce8a98f | ||
|
|
ae029fe2f8 | ||
|
|
5120eb4412 | ||
|
|
f0d175dace | ||
|
|
73965f9431 | ||
|
|
5e13e0a876 | ||
|
|
4a076f368c | ||
|
|
d9099190ab | ||
|
|
04221e5f01 | ||
|
|
2092f90b7e | ||
|
|
0f7fd005db | ||
|
|
5a7e8a166a | ||
|
|
9534e42ec8 | ||
|
|
6b27d8f4e3 | ||
|
|
f85a932d71 | ||
|
|
69e670d268 | ||
|
|
9d5b931875 | ||
|
|
715c9c8a84 | ||
|
|
4f3b573b09 | ||
|
|
e09a503f44 | ||
|
|
26bb85c007 | ||
|
|
9744d07211 |
No files matched your search
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: rea-analysis-optimization
|
||||
description: Optimize REA providers and shared analysis workflows for agent context, latency, CPU, and memory while preserving correctness and Evidence. Use for analysis bottlenecks and cache or query design; CI/test-suite optimization belongs to rea-ci-tests-audit.
|
||||
---
|
||||
|
||||
# REA analysis optimization
|
||||
|
||||
Find a concrete source of unnecessary work, change it at its owner, and verify
|
||||
equivalent completed investigations. A useful outcome can also be a measured
|
||||
decision to leave an implementation alone.
|
||||
|
||||
Follow the checkout's [performance priorities](../../../AGENTS.md#performance),
|
||||
[tool design](../../../docs/tool-design.md), and
|
||||
[testing guide](../../../docs/testing.md). This skill supplies an optimization
|
||||
workflow, not additional permission to execute targets, publish, or merge.
|
||||
Use the current contracts, provider prerequisites, and user authorization.
|
||||
|
||||
Read [the Kuna case study](references/kuna-case-study.md) when selecting a
|
||||
reuse strategy or interpreting a benchmark. Its pinned observations illustrate
|
||||
tradeoffs; they are not current-provider guarantees or universal speedups.
|
||||
|
||||
## Pin the question and the correctness oracle
|
||||
|
||||
Describe the analyst's completed task, including the facts and relationships
|
||||
needed to answer it, requested detail, source access, and meaningful unknowns.
|
||||
Choose a representative real artifact and a small or empty case. Record the
|
||||
baseline revision, target digest, engine identity, options, host, and entrypoint.
|
||||
Use a separate checkout for changes without disturbing other work.
|
||||
|
||||
Establish an oracle independent of the changed implementation: source-built
|
||||
fixture expectations, independently mapped file bytes, or a native producer
|
||||
paired with checks of its interpretation. Comparing two wrappers around the
|
||||
same faulty decoder does not establish correctness. Preserve address widths,
|
||||
same-address alternatives, unresolved endpoints, coverage, and source linkage.
|
||||
An empty result proves absence only within the actually examined scope.
|
||||
|
||||
## Locate the earliest unnecessary work
|
||||
|
||||
Trace one public CLI or MCP task through shared application code to its producer.
|
||||
Measure relevant phases: admission and identity checks, native analysis,
|
||||
decoding, lookup, projection, serialization, Evidence retention, and cleanup.
|
||||
Include catalog discovery and extra calls when they affect the completed task.
|
||||
|
||||
Use the evidence to choose the smallest structural correction:
|
||||
|
||||
| Observed cost | Candidate correction | Condition to establish |
|
||||
| --------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| Repeated scans of validated facts | Reuse existing order or lookup structure | The owner establishes ordering and identity; every matching alternative survives |
|
||||
| Repeated equivalent engine work | Share a validated result or in-flight producer | Inputs, freshness, lifetime, and per-caller outcomes remain explicit |
|
||||
| Full reports for selected facts | Push selection or projection to the earliest capable owner | Required facts and evidence remain reachable; examined coverage is reported |
|
||||
| Large temporary transport buffers | Decode incrementally or use bounded source reads | Validate the whole admitted document; retained facts and copies still consume memory |
|
||||
| Many repeated homogeneous reads | Share admission, decoding, or batch work | Preserve item identity, order where required, and individual failures |
|
||||
|
||||
Do not introduce an index, generic cache, batch endpoint, or orchestration layer
|
||||
without identifying the work it removes and its maintenance/resource cost.
|
||||
Projection after materializing a full report may reduce output while leaving
|
||||
analysis time and peak memory unchanged. Fix missing or incorrect facts before
|
||||
benchmarking an alternative composition against a better-covered workflow.
|
||||
|
||||
## Keep one interpretation and a defined reuse lifetime
|
||||
|
||||
Let the producer boundary validate and normalize facts once. Consumers query
|
||||
that representation through the shared CLI/MCP implementation; adapters should
|
||||
not rebuild relationships or independently reinterpret raw reports. Keep lookup
|
||||
identity separate from display formatting. Reuse contracts when their intent
|
||||
matches, honoring requested detail and complete Evidence access.
|
||||
|
||||
For every cache or shared producer, state its key, owner, invalidation, retained
|
||||
size, and lifetime. Account for result-affecting target bytes, engine contents,
|
||||
analysis options, architecture/load mapping, and parser/schema versions where
|
||||
they affect reuse. Validate live state before calling a cached observation
|
||||
current. Cache a refusal only when it is deterministic for that validated key;
|
||||
malformed, partial, transient, or cancelled attempts need explicit retry policy.
|
||||
|
||||
One cancelled waiter must not invalidate a successful peer. Preserve producer
|
||||
failure context and each caller's cancellation reason. Retain ownership and
|
||||
capacity charges until shared work and cleanup settle; preserve completed
|
||||
observations when cleanup fails. Exercise invalidation, concurrent consumers,
|
||||
last-waiter cancellation, close, and cleanup retry when those paths change.
|
||||
|
||||
Bound the process family and retained state, not just a parent heap or worker
|
||||
count. Account for decoding and serialization expansion before allocation.
|
||||
Choose CPU, memory, concurrency, and disk limits from the host and workload;
|
||||
stop before OOM and verify owned cleanup. A guard-stopped task is incomplete.
|
||||
Do not replace a transport buffer cap with an unbounded retained inventory or
|
||||
hide partial results behind a success status.
|
||||
|
||||
## Compare completed tasks and limit the claim
|
||||
|
||||
Run the affected real public workflow before and after with equivalent inputs,
|
||||
results, coverage, and Evidence semantics. Include small inputs so setup costs
|
||||
and regressions are visible. Distinguish fresh sessions, warm runtime caches,
|
||||
and OS file caches. Alternate or repeat runs when variance affects the decision.
|
||||
Choose meaningful boundary regressions for cases the actual engine cannot
|
||||
reliably reproduce; a fake engine does not qualify a real provider.
|
||||
|
||||
Report task outcome, relevant phase timings, total elapsed and CPU time, peak
|
||||
process-family memory and its accounting method, resource stops, and cleanup.
|
||||
Check the payload exit status as well as the profiler's. Keep failed attempts
|
||||
and identify unmeasured costs; a passing retry does not explain a failure.
|
||||
|
||||
For token claims, count schemas/discovery, arguments, replies, retries, and
|
||||
continuations needed to finish the task. Label tokenizer estimates separately
|
||||
from actual model usage and record the client/catalog behavior. Smaller JSON
|
||||
alone establishes neither fewer model tokens nor better investigation quality.
|
||||
Do not claim a complex-software advantage without completed representative
|
||||
investigations and comparable correctness/coverage.
|
||||
|
||||
Finish with the bottleneck, owning change, preserved semantics, executed
|
||||
checks, before/after measurements, and remaining limits. Keep any integration
|
||||
record current with its tested revision and distinguish merged production work
|
||||
from experiment-only code. Stop when the demonstrated bottleneck is resolved
|
||||
or further benefit remains speculative.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "REA analysis optimization"
|
||||
short_description: "Optimize analysis with preserved Evidence and measured costs"
|
||||
default_prompt: "Use $rea-analysis-optimization to find and verify a concrete analysis bottleneck while preserving correctness and Evidence."
|
||||
@@ -0,0 +1,112 @@
|
||||
# Kuna: reuse before adding machinery
|
||||
|
||||
Read this reference for concrete examples of the skill's decisions. The records
|
||||
below concern static Linux analysis with caller-supplied Kuna. They do not
|
||||
establish parity with another provider or a general advantage for Code Mode,
|
||||
atomic tools, or any particular orchestration design.
|
||||
|
||||
## Exact-address lookup: a measured, narrow improvement
|
||||
|
||||
[PR #4695](https://github.com/morluto/rea/pull/4695) reused the validated string
|
||||
inventory's numeric address order. Previously, each `list_strings(address)`
|
||||
filtered all cached facts. The owner now performs a binary lower-bound lookup
|
||||
and returns every encoding at that address: O(log n + matches), without a
|
||||
second retained lookup index.
|
||||
|
||||
The owning implementation is
|
||||
[`KunaStringInventory.ts`](../../../../src/kuna/KunaStringInventory.ts), consumed
|
||||
by [`KunaStringQueries.ts`](../../../../src/kuna/KunaStringQueries.ts). Inspect
|
||||
their current versions before adapting the example. Numeric u64 addresses,
|
||||
same-address encodings, duplicate rejection, source digests, caller selection,
|
||||
and the existing session lifetime were part of the contract. Full listings and
|
||||
literal/regex searches kept their semantics. An attempted removal of the
|
||||
duplicate-detection set was dropped: no stable memory benefit was established,
|
||||
and it delayed malformed-input rejection.
|
||||
|
||||
### Recorded workload and results
|
||||
|
||||
The benchmark used unmodified Kuna 1.833 and LLVM 16 on Linux x86-64, with
|
||||
602,544 string facts. Baseline was `be0b2d138`; the final feature was
|
||||
`5f564962da0e1b17f00a68a74a312539f0209a01`, subsequently integrated in
|
||||
`c6b62754b095a134d6d67877508fa34311a2eda2`. These are historical observations,
|
||||
not a benchmark of every subsequent main revision.
|
||||
|
||||
The same production stdio MCP task discovered the catalog, opened the binary,
|
||||
made one cold selected read and 28 warm exact reads, checked retained Evidence,
|
||||
and closed the session. Five selected addresses and two absent selectors were
|
||||
read four times. A native filtered scan selected the strings; an independent
|
||||
ELF load-header mapping checked the source bytes and terminators. Returned
|
||||
facts, source linkage, target digests, and cleanup were checked.
|
||||
|
||||
Three alternating fresh sessions per implementation used one CPU, a 4 GiB
|
||||
process-family hard limit, a 3 GiB early stop, and no swap. OS file caches were
|
||||
warm. Whole-harness costs include the independent native oracle, hashing,
|
||||
catalog/client work, and correctness checks.
|
||||
|
||||
| Median | Before | After |
|
||||
| ---------------------------------------- | ---------: | ---------: |
|
||||
| 28 warm exact MCP reads | 495.2 ms | 90.8 ms |
|
||||
| Cold selected MCP read | 11.30 s | 11.01 s |
|
||||
| Whole harness elapsed | 28.24 s | 27.43 s |
|
||||
| Whole process-family CPU | 27.71 s | 26.89 s |
|
||||
| Kernel family peak, including file cache | 2237.6 MiB | 2240.6 MiB |
|
||||
|
||||
Warm totals ranged from 491.7–544.3 ms before to 84.8–99.5 ms after: about
|
||||
5.5 times faster at this phase. Cold/whole-task timing and memory overlapped;
|
||||
no stable improvement in those costs was established. One small SQLite pair
|
||||
(7,985 facts) measured 20 warm reads at 88.6 → 80.5 ms and the whole harness at
|
||||
13.68 → 13.94 s. The large-inventory lookup benefit does not generalize to a
|
||||
whole-task speedup on small inputs.
|
||||
|
||||
Catalog hashes, call counts, and normalized facts were unchanged. A
|
||||
`cl100k_base` estimate over one pass of raw catalog JSON, arguments, and replies
|
||||
was about 846,000 tokens in both lanes, with about 802,562 from the catalog.
|
||||
This was transport serialization, not rendered model context or actual usage;
|
||||
client discovery, prompts, replay, and protocol framing were unmeasured.
|
||||
**No token reduction or improved agent investigation outcome was established.**
|
||||
|
||||
Pinned target SHA-256:
|
||||
`f62d254b7f2bf42df8c8b07d46ee3bb4c2cafeca436b2e6bc6ccbe4581f58f40`.
|
||||
Pinned engine SHA-256:
|
||||
`d858918b0766435cd96b999a77f2d5a31a0cc4f07256421c883b5b9dc4dd58e4`.
|
||||
All six normalized-result sequence digests were
|
||||
`4d2f90269da8568984090b20274163c4a7c2a0538aab63530bd2fe44840a0bba`.
|
||||
Selected byte agreement does not prove native discovery completeness; missing
|
||||
strings and relationships can remain unknown.
|
||||
|
||||
## Other transferable changes and their limits
|
||||
|
||||
- [Cached string inventory and search, #4095](https://github.com/morluto/rea/pull/4095):
|
||||
share validated facts across queries in a defined session. Audit key inputs
|
||||
and lifetime before extending reuse across targets or engine revisions.
|
||||
- [Shared single-function work, #4045](https://github.com/morluto/rea/pull/4045)
|
||||
and [repeat-request reuse, #4169](https://github.com/morluto/rea/pull/4169):
|
||||
avoid duplicate native work while retaining per-caller cancellation and
|
||||
cleanup ownership. A query count alone cannot reveal duplicate subprocesses.
|
||||
- [Streamed strings, #4318](https://github.com/morluto/rea/pull/4318)
|
||||
and [streamed call graph, #4329](https://github.com/morluto/rea/pull/4329):
|
||||
remove fixed aggregate transport-buffer failures through incremental parsing.
|
||||
Streaming transport does not bound every retained object or final reply.
|
||||
These links establish changes; no numerical speedup is asserted here.
|
||||
|
||||
Typed reference facts, native string-query projection, and shared native byte
|
||||
reads remained experimental at the 2026-10-17 checkpoint
|
||||
`65f2f9a22bc892f61d2b00f9a4611124f57d4bff` on
|
||||
`experiment/atomic-codemode-20261016`. Some experiments used a patched native
|
||||
engine. Check current integration and bring-your-own engine capabilities before
|
||||
proposing them; a working experiment is not a stock-provider guarantee.
|
||||
|
||||
## Applying the reasoning elsewhere
|
||||
|
||||
For another provider or format, inspect its actual representation and native
|
||||
capabilities before borrowing the implementation. Existing numeric ordering
|
||||
may support range reads; an on-disk native query may avoid materializing an
|
||||
inventory; an immutable parse may support several views. Measure the relevant
|
||||
alternative rather than requiring every provider to use Kuna's cached array.
|
||||
|
||||
For live observations, immutable artifact caching rules are insufficient:
|
||||
validate the process/session identity and freshness required by the caller.
|
||||
For compact replies, preserve access to source Evidence and report examined
|
||||
coverage; projection must not silently turn an unresolved edge into no edge.
|
||||
For batching, test mixed successes and failures and measure the entire task,
|
||||
including discovery, extra calls, native work, and serialization.
|
||||
@@ -24,19 +24,7 @@ runs:
|
||||
BUILD_CACHE_PREFIX: rea-portable-build-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.npmrc', '.nvmrc', 'package-lock.json') }}
|
||||
run: |
|
||||
npm run --silent test:prepare -- --dry=json |
|
||||
node --input-type=module -e '
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
const { tasks } = JSON.parse(readFileSync(0, "utf8"));
|
||||
const hashes = tasks.map(({ taskId, hash }) => {
|
||||
if (typeof hash !== "string")
|
||||
throw new Error(`Build task ${taskId} must have an eager Turbo hash`);
|
||||
return `${taskId}:${hash}`;
|
||||
}).sort();
|
||||
const hash = createHash("sha256").update(hashes.join("\n")).digest("hex");
|
||||
console.log(`key=${process.env.BUILD_CACHE_PREFIX}-${hash}`);
|
||||
console.log(`prefix=${process.env.BUILD_CACHE_PREFIX}-`);
|
||||
' >> "$GITHUB_OUTPUT"
|
||||
node scripts/ci/portable-build-cache.mjs key >> "$GITHUB_OUTPUT"
|
||||
- name: Restore portable build outputs
|
||||
if: inputs.build_cache_artifact == '' && inputs.save != 'true'
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
|
||||
@@ -357,10 +357,15 @@ jobs:
|
||||
# test:prepare caches only portable JS, skill, and catalog outputs.
|
||||
# Native package jobs still run their build and prepack checks; Turbo
|
||||
# restores matching outputs or rebuilds when their inputs differ.
|
||||
- name: Stage current portable build task cache
|
||||
shell: bash
|
||||
run: |
|
||||
# A new Turbo dry run can evict the entries this build just used.
|
||||
node scripts/ci/portable-build-cache.mjs snapshot < .cache/ci-build-plan.json
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: rea-build-cache
|
||||
path: .turbo/cache/
|
||||
path: .cache/ci-build-cache/
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -648,6 +653,7 @@ jobs:
|
||||
tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts
|
||||
tests/boundary/cli/nativePlistSelectionFailureCli.test.ts
|
||||
tests/boundary/cli/nativeDemangleCli.test.ts
|
||||
tests/boundary/cli/nativeDispatchPropertyAttributes.test.ts
|
||||
tests/boundary/mcp/nativeDemangleMcp.test.ts
|
||||
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
|
||||
tests/boundary/cli/nativeSignatureCli.test.ts
|
||||
@@ -748,6 +754,7 @@ jobs:
|
||||
tests/boundary/filesystem/webScriptManifestPaths.test.ts
|
||||
tests/boundary/process/ProcessEvidence.host.test.ts
|
||||
tests/boundary/process/packageNpmInvocation.test.ts
|
||||
tests/boundary/process/packedBridgeVerification.test.ts
|
||||
tests/boundary/process/windowsOutputCapture.test.ts
|
||||
tests/acceptance/webModuleCli.test.ts
|
||||
tests/acceptance/webSourceLocationCli.test.ts
|
||||
|
||||
@@ -24,7 +24,19 @@ jobs:
|
||||
real-recorded-crash:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: ["ubuntu:24.04", "debian:trixie-slim"]
|
||||
container: ${{ matrix.image }}
|
||||
steps:
|
||||
- name: Install fixture and debugger prerequisites in the disposable container
|
||||
shell: bash
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install --yes --no-install-recommends \
|
||||
ca-certificates git python3-venv gcc libc6-dev strace gdb
|
||||
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -36,11 +48,6 @@ jobs:
|
||||
- uses: ./.github/actions/portable-build-cache
|
||||
with:
|
||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||
- name: Install GDB on the disposable CI runner
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install --yes --no-install-recommends gdb
|
||||
- name: Acquire exact unchanged upstreams in isolated runner storage
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "6.5.0"
|
||||
".": "6.7.0"
|
||||
}
|
||||
@@ -1,5 +1,93 @@
|
||||
# Changelog
|
||||
|
||||
## [6.7.0](https://github.com/morluto/rea/compare/rea-agents-6.6.0...rea-agents-6.7.0) (2026-10-18)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Client configuration edits follow the duplicate-member semantics used by MCP clients.
|
||||
|
||||
### Changes
|
||||
|
||||
- Includes the [6.6.0 changes and migration notes](https://github.com/morluto/rea/releases/tag/rea-agents-6.6.0). That version was tagged but was not published to npm.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Client configuration:** Setup edits the last repeated JSON/JSONC member, matching the value clients actually read. Setup and uninstall refuse a removal that would reactivate a shadowed duplicate, preserving the original configuration. ([#4957](https://github.com/morluto/rea/pull/4957), [morluto/rea#4952](https://github.com/morluto/rea/issues/4952)). Thanks @kaoru0822-kitauji, @xu91102.
|
||||
|
||||
Full contribution record: [4 commits and 1 merged PR](https://github.com/morluto/rea/blob/rea-agents-6.7.0/docs/releases/6.7.0.contributions.json).
|
||||
|
||||
## [6.6.0](https://github.com/morluto/rea/compare/rea-agents-6.5.0...rea-agents-6.6.0) (2026-10-18)
|
||||
|
||||
### Highlights
|
||||
|
||||
- Add Kuna as a caller-supplied static-analysis provider.
|
||||
- Query selected JavaScript facts and multiple APK locale tables without repeating the underlying analysis.
|
||||
- Extend Ghidra analysis to Windows ARM PE targets and explicit raw m68k images.
|
||||
- Preserve completed Evidence, cancellation reasons and retryable cleanup across provider and CLI finalization.
|
||||
|
||||
### Changes
|
||||
|
||||
- **Kuna analysis:** Use a caller-installed Kuna for procedure listings, pseudocode, assembly, call relationships, exact-address xrefs and cached string searches. Project exports retain source spans and digests; repeated requests reuse completed work. Recorded real-provider coverage uses Kuna 1.833 on Linux; Windows, PE and universal Mach-O are unsupported. See [the provider guide](docs/kuna-provider.md). ([#3832](https://github.com/morluto/rea/pull/3832), [#3854](https://github.com/morluto/rea/pull/3854), [#4071](https://github.com/morluto/rea/pull/4071), [#4095](https://github.com/morluto/rea/pull/4095)). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Retained JavaScript facts:** Query exact application paths, selected properties and filtered module references through `inspect_analysis_view`, with paging and complete row Evidence. These views reuse completed analysis and preserve source selection and unknown coverage. ([#4265](https://github.com/morluto/rea/pull/4265), [morluto/rea#4190](https://github.com/morluto/rea/issues/4190), [#4097](https://github.com/morluto/rea/pull/4097), [morluto/rea#4092](https://github.com/morluto/rea/issues/4092)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **APK locale tables:** Request several resource locales in one decode with repeatable CLI `--locales` or MCP `locales`. Each table reports its status and coverage; existing single-locale fields remain available. ([#4804](https://github.com/morluto/rea/pull/4804), [morluto/rea#4777](https://github.com/morluto/rea/issues/4777)). Thanks @abhay-2108, @N0zoM1z0.
|
||||
- **Windows ARM binaries:** Ghidra admits ARMNT/Thumb-2 and ARM64 PE applications and DLLs on the Windows x64 host lane. Recorded Ghidra 12.1.4 verification uses `WindowsResourceReference` disabled and raised startup budgets; default-analyzer completion, ARM64EC/ARM64X and Windows-on-Arm hosts remain unverified or unsupported. See [the exact scope](docs/windows-ghidra-p0.md#native-authority-and-target-identity). ([#3965](https://github.com/morluto/rea/pull/3965), [morluto/rea#2186](https://github.com/morluto/rea/issues/2186)). Thanks @N0zoM1z0, @WestLabsAI.
|
||||
- **Raw m68k images:** Analyze explicitly selected big-endian m68k raw binaries using Ghidra’s fixed 68000 profile and a caller-supplied aligned entry. Real CLI/MCP verification used Ghidra 12.1.4 on macOS arm64; Linux is unverified and Windows P0 does not admit m68k. See [mapping requirements](docs/raw-binary.md). ([#4018](https://github.com/morluto/rea/pull/4018)). Thanks @icedac.
|
||||
- **JADX deadlines:** Set `REA_JADX_OPERATION_TIMEOUT_MS` to extend APK loading and upstream operation deadlines together. Doctor reports the effective value and setup forwards an explicit selection. ([#4466](https://github.com/morluto/rea/pull/4466), [morluto/rea#3918](https://github.com/morluto/rea/issues/3918)). Thanks @jaychou813, @SandstromPL.
|
||||
- **Electron window options:** Resolve stable local constant `BrowserWindow` options while retaining source provenance. Relevant writes and escapes keep the result unknown. ([#4400](https://github.com/morluto/rea/pull/4400), [morluto/rea#4366](https://github.com/morluto/rea/issues/4366)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Language editions:** Add the Greek website edition and Malayalam documentation, with Korean typography and translation corrections across the site. ([#3520](https://github.com/morluto/rea/pull/3520), [#4792](https://github.com/morluto/rea/pull/4792), [#3821](https://github.com/morluto/rea/pull/3821), [morluto/rea#3820](https://github.com/morluto/rea/issues/3820), [#3799](https://github.com/morluto/rea/pull/3799), [morluto/rea#3798](https://github.com/morluto/rea/issues/3798), [#4344](https://github.com/morluto/rea/pull/4344), [#3856](https://github.com/morluto/rea/pull/3856), [#4166](https://github.com/morluto/rea/pull/4166)). Thanks @gensokyo-dev, @hi2242, @kalayciburak, @kaoru0822-kitauji, @kraken-503, @kukinghan, @N0zoM1z0, @yeseong0412.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Cancellation and cleanup:** Keep completed observations, exact cancellation reasons and the original physical failure through finalization. Failed cleanup retains its process or file owner for retry; recovered resources disappear from pending-cleanup reports without replacing the original error. ([#4238](https://github.com/morluto/rea/pull/4238), [morluto/rea#4232](https://github.com/morluto/rea/issues/4232), [#4274](https://github.com/morluto/rea/pull/4274), [morluto/rea#3923](https://github.com/morluto/rea/issues/3923), [3e82366](https://github.com/morluto/rea/commit/3e8236639abffa39deb5d5e939ec8baf6aaf3ca3), [16924e2](https://github.com/morluto/rea/commit/16924e275e939d0543ba01493324959a9e1d731c), [bbfe6ed](https://github.com/morluto/rea/commit/bbfe6edd60290f9bfbec68c8a0bcf605d8bbf5b3), [1f23e97](https://github.com/morluto/rea/commit/1f23e977b50301e91f02a4a17c9f8d48bb7b60f3), [08fbeb8](https://github.com/morluto/rea/commit/08fbeb8bdab27118b6c57039aef7c0fa8b6463bc)). Thanks @coygeek.
|
||||
- **Provider startup:** Join prerequisite probes and in-flight readiness work before shutdown, preserve typed timeout and JVM-memory failures, and retain startup diagnostics through cleanup. Ghidra recognizes keg-only Homebrew JDKs and names the setting responsible for a startup timeout. ([376eeca](https://github.com/morluto/rea/commit/376eeca770820b31f7f8d4c5fcc397315621643e), [ceeff5a](https://github.com/morluto/rea/commit/ceeff5a41788e47e5aee96a4baf191a6ce7fde4b), [3ad1032](https://github.com/morluto/rea/commit/3ad1032b11edea2dcb054e78f951cc43159ae83d), [964decd](https://github.com/morluto/rea/commit/964decd989acfa84c4bfc3295362c4c46877fc20), [3fa972a](https://github.com/morluto/rea/commit/3fa972af7c7cb6cf014ffc426bdf7130dc6f2fd2), [#4557](https://github.com/morluto/rea/pull/4557), [morluto/rea#4556](https://github.com/morluto/rea/issues/4556), [#4590](https://github.com/morluto/rea/pull/4590), [morluto/rea#4241](https://github.com/morluto/rea/issues/4241)). Thanks @DanielCvz1, @J0UH, @Rinkia, @RishTheFish21, @vadlachary2002.
|
||||
- **Client configuration:** Preserve the selected launcher, destination file, caller timeout choices and unresolved configuration evidence. Setup/uninstall recovery stays bound to the exact published file, keeps current verified backups, and transfers failed cleanup ownership for retry. ([d0253c9](https://github.com/morluto/rea/commit/d0253c99d65afbbd12732a2fdaf711640574e17f), [7e4eed9](https://github.com/morluto/rea/commit/7e4eed9dd48485b6a7100300aa98401811c2deb9), [2fb5e20](https://github.com/morluto/rea/commit/2fb5e204a35723f6f843d56ca47b0db94d8fcc44), [8235210](https://github.com/morluto/rea/commit/823521085a28f825e7fee1929d852d420042d1f7), [d818c3d](https://github.com/morluto/rea/commit/d818c3d2639d3115edb8a5269a754101aea6c3bd), [19329e4](https://github.com/morluto/rea/commit/19329e43bb38ca9d64512d801b25abcb188d2512), [dadf909](https://github.com/morluto/rea/commit/dadf909dce2e8b3d055541de69b4118d21276316), [333e0ab](https://github.com/morluto/rea/commit/333e0ab0dfc126a9915907d5079ed6195190e38e), [c15c206](https://github.com/morluto/rea/commit/c15c2062ad815cc9a3222ccfaeb7ef53cacd8b72)). Thanks @Adolanium.
|
||||
- **Configuration formats:** Read signed 64-bit TOML integers without rounding, handle large TOML documents without argument-spread failures, preserve JSONC trivia and Hermes YAML scalar text, and reject NUL bytes in registration environment values. ([#4937](https://github.com/morluto/rea/pull/4937), [morluto/rea#4917](https://github.com/morluto/rea/issues/4917), [c43bc0b](https://github.com/morluto/rea/commit/c43bc0bf66a4e245a0cafa40def0d0207bcbd73c), [4fd28fc](https://github.com/morluto/rea/commit/4fd28fc9e0c4f8358147af38c72ab9e814f52d61), [#3568](https://github.com/morluto/rea/pull/3568), [#4335](https://github.com/morluto/rea/pull/4335)). Thanks @alfredo1491, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @ShuaixinHuang, @xu91102.
|
||||
- **Claude Code configuration:** Setup, doctor and uninstall now accept only the strict JSON format Claude Code reads, including a leading UTF-8 BOM. Comments and trailing commas are refused before backup or writing; clients that support JSONC retain their existing behavior. ([#4944](https://github.com/morluto/rea/pull/4944), [morluto/rea#4940](https://github.com/morluto/rea/issues/4940)). Thanks @kaoru0822-kitauji, @xu91102.
|
||||
- **CLI output and dispatch:** Accept `--flag=value` and spaced global output values, keep array positions in filtered JSON, validate integer tokens before coercion, and exit quietly when the output reader closes. Cancellation remains effective through command finalization; app bundles and static native inspections select the intended workflow. ([#3811](https://github.com/morluto/rea/pull/3811), [#4823](https://github.com/morluto/rea/pull/4823), [2ae4e16](https://github.com/morluto/rea/commit/2ae4e1608f290c95ff7cff28c4d16a79b301d03d), [8d82579](https://github.com/morluto/rea/commit/8d8257928bbe8d43fedffc4db5fca26c8108820f), [#3781](https://github.com/morluto/rea/pull/3781), [279c590](https://github.com/morluto/rea/commit/279c590ef6bb737d4a48ea696a0ae5eb30010b3d), [#3511](https://github.com/morluto/rea/pull/3511), [morluto/rea#3321](https://github.com/morluto/rea/issues/3321), [f31b231](https://github.com/morluto/rea/commit/f31b231a92a61be6bd1800b16be0b3210ae82944)). Thanks @arjunprabhulal, @coygeek, @kaoru0822-kitauji, @ShuaixinHuang, @Sriram-PR, @Tapraj-Pathak.
|
||||
- **Updates:** Route `--update` through the verified updater and bound retained package-manager diagnostics. ([4f7b5fb](https://github.com/morluto/rea/commit/4f7b5fb6023ce2ec449561782d09e3c7e5ac7c7b), [01cdbbd](https://github.com/morluto/rea/commit/01cdbbdc556262c47d04a5b3391dd9c17946d3fc)).
|
||||
- **MCP contracts:** Advertise self-contained tool inputs while preserving tuple, array, Boolean and nested-JSON constraints across schema profiles. Schema-reference handling no longer rewrites literal metadata; catalog identity includes the output-schema profile. ([5e30969](https://github.com/morluto/rea/commit/5e30969cc5f1255d44ec7a1f1ca8dbac0af5e7c4), [10aa74d](https://github.com/morluto/rea/commit/10aa74dfedc3ae776f50d1fa1eb83b64ae82d3d6), [204b9f3](https://github.com/morluto/rea/commit/204b9f3ac140a993c3e458bb0e117de31818ab29), [b3389a5](https://github.com/morluto/rea/commit/b3389a5b0834265fe7a40391be315d810a53a97e), [a2e3016](https://github.com/morluto/rea/commit/a2e30168a046f3b79319eb7fbaf807b802104199), [ba98130](https://github.com/morluto/rea/commit/ba98130049fa40ade66b03611ff70c7ddc011a1d), [c5fb86d](https://github.com/morluto/rea/commit/c5fb86d1948af0a287a581b29b2b1d554743c607)). Thanks @akushonkamen, @HarsH-1877, @kaoru0822-kitauji, @xingzihai.
|
||||
- **Session ordering:** Keep snapshot admission ahead of later calls on the same connection, retain shutdown signals until cleanup succeeds, and preserve inspection authority through investigation publication. ([#3661](https://github.com/morluto/rea/pull/3661), [morluto/rea#3334](https://github.com/morluto/rea/issues/3334), [8873b39](https://github.com/morluto/rea/commit/8873b39770e89eae22b7693bc492d7e423b22bea), [663d07f](https://github.com/morluto/rea/commit/663d07f02cd62c99d81c59391d2c6d745c875335)). Thanks @coygeek, @rudycelekli, @Swatantra-66.
|
||||
- **JavaScript exports and resolution:** Retain CommonJS exports created with `Object.defineProperty` and track export receiver lifetimes. Resolve scoped packages, package `main`, JSON requires and `.mts`/`.cts` imports using the owning loader; overwritten `require` bindings and erased type-only imports no longer create false runtime links. ([#4725](https://github.com/morluto/rea/pull/4725), [morluto/rea#4724](https://github.com/morluto/rea/issues/4724), [#3567](https://github.com/morluto/rea/pull/3567), [morluto/rea#3541](https://github.com/morluto/rea/issues/3541), [#4838](https://github.com/morluto/rea/pull/4838), [morluto/rea#4632](https://github.com/morluto/rea/issues/4632), [#3844](https://github.com/morluto/rea/pull/3844), [morluto/rea#3383](https://github.com/morluto/rea/issues/3383), [#4164](https://github.com/morluto/rea/pull/4164), [morluto/rea#3915](https://github.com/morluto/rea/issues/3915), [#4223](https://github.com/morluto/rea/pull/4223), [#3801](https://github.com/morluto/rea/pull/3801), [morluto/rea#3393](https://github.com/morluto/rea/issues/3393), [#4165](https://github.com/morluto/rea/pull/4165), [morluto/rea#3932](https://github.com/morluto/rea/issues/3932)). Thanks @Abishek0070, @coygeek, @JNTMTMTM, @kaoru0822-kitauji, @prasad2512, @rodricksz4h5, @xu91102.
|
||||
- **JavaScript value flow:** Preserve values captured before later assignments, guarded factory fallthrough and nested destructuring. Track exact property keys and setter writes; direct `eval`, compound assignments and incoming callbacks preserve unresolved effects instead of inventing certainty. ([#4196](https://github.com/morluto/rea/pull/4196), [morluto/rea#3881](https://github.com/morluto/rea/issues/3881), [#4341](https://github.com/morluto/rea/pull/4341), [morluto/rea#3963](https://github.com/morluto/rea/issues/3963), [bfb7ed0](https://github.com/morluto/rea/commit/bfb7ed0bd43edc605e00fc52c6e21e735b285904), [#3480](https://github.com/morluto/rea/pull/3480), [#3831](https://github.com/morluto/rea/pull/3831), [morluto/rea#3312](https://github.com/morluto/rea/issues/3312), [9ac76d3](https://github.com/morluto/rea/commit/9ac76d3d9486856d25395b413d7ac5ffa744cad0), [#4342](https://github.com/morluto/rea/pull/4342), [morluto/rea#4206](https://github.com/morluto/rea/issues/4206), [#4243](https://github.com/morluto/rea/pull/4243), [morluto/rea#3880](https://github.com/morluto/rea/issues/3880)). Thanks @coygeek, @kaoru0822-kitauji, @maxazure, @N0zoM1z0, @ShuaixinHuang.
|
||||
- **JavaScript relationships:** Distinguish `Map` reads from network endpoints, associate response endpoints only with writes that reach the read, and preserve child-process receiver ownership and effective stdio choices. Version comparisons keep relocation and unknown relationships distinct from application changes. ([fbfdb6a](https://github.com/morluto/rea/commit/fbfdb6abe9d663be0be146a15a489e33ccba0f68), [#4759](https://github.com/morluto/rea/pull/4759), [morluto/rea#4606](https://github.com/morluto/rea/issues/4606), [#3819](https://github.com/morluto/rea/pull/3819), [morluto/rea#3362](https://github.com/morluto/rea/issues/3362), [#4336](https://github.com/morluto/rea/pull/4336), [morluto/rea#3355](https://github.com/morluto/rea/issues/3355), [556504e](https://github.com/morluto/rea/commit/556504edcbb0f8cd9eaf81a91b18ff891b8f5457), [4d980d0](https://github.com/morluto/rea/commit/4d980d017622d3175d86aaf3b0dcfbdf4ca3d091)). Thanks @Abdooo2235, @coygeek, @kaoru0822-kitauji, @Xinglan233.
|
||||
- **JavaScript source admission:** Parse JavaScript with its own grammar, refuse FIFO recovery inputs without blocking, and report oversized or resource-skipped files with their actual capacity and coverage state. Malformed runtime events stay excluded rather than becoming policy-filtered facts. ([#3782](https://github.com/morluto/rea/pull/3782), [morluto/rea#3780](https://github.com/morluto/rea/issues/3780), [#3788](https://github.com/morluto/rea/pull/3788), [morluto/rea#3332](https://github.com/morluto/rea/issues/3332), [#4757](https://github.com/morluto/rea/pull/4757), [morluto/rea#4629](https://github.com/morluto/rea/issues/4629), [#3852](https://github.com/morluto/rea/pull/3852), [morluto/rea#3670](https://github.com/morluto/rea/issues/3670), [#4755](https://github.com/morluto/rea/pull/4755), [morluto/rea#4685](https://github.com/morluto/rea/issues/4685)). Thanks @andresaragon, @coygeek, @kaoru0822-kitauji, @sx4im.
|
||||
- **Browser capture:** Bound CDP handshakes and command responses, preserve malformed inventory uncertainty and partial cleanup diagnostics, and report cancellation after content reads and final cleanup. Accessibility comparisons require observed coverage; attached emulation teardown limits remain explicit. ([#3555](https://github.com/morluto/rea/pull/3555), [morluto/rea#3553](https://github.com/morluto/rea/issues/3553), [#3901](https://github.com/morluto/rea/pull/3901), [morluto/rea#3379](https://github.com/morluto/rea/issues/3379), [4cc044b](https://github.com/morluto/rea/commit/4cc044be68f813e96907e6cedcf422d0a3170de3), [#4406](https://github.com/morluto/rea/pull/4406), [morluto/rea#4403](https://github.com/morluto/rea/issues/4403), [#2893](https://github.com/morluto/rea/pull/2893), [0123d59](https://github.com/morluto/rea/commit/0123d5976828bfe621bd51daa4cf39fee48a1349), [2ff0867](https://github.com/morluto/rea/commit/2ff086796ad7b8f60397956750584e170dbe2910)). Thanks @coygeek, @dakjdakd, @kaoru0822-kitauji, @maxazure, @N0zoM1z0, @YunzhouLi-hub.
|
||||
- **Browser page facts:** Preserve URL path bytes when removing credentials, repeated document `Link` headers and complete COOP/COEP structured items. WebMCP discovery reads the actual schema properties and exact receiver path. ([#4731](https://github.com/morluto/rea/pull/4731), [df2fc25](https://github.com/morluto/rea/commit/df2fc251e318a1255309dba232f9f73c700777b6), [c360cfc](https://github.com/morluto/rea/commit/c360cfcd6d7901514b16dd6113eba2e0b0fdf422), [ebd5a82](https://github.com/morluto/rea/commit/ebd5a82c439f6deb3a06062225ce009eb03257cf), [38f92d1](https://github.com/morluto/rea/commit/38f92d108e3148cdf47e4ffbfe3de8a76e795d51)). Thanks @Abdooo2235, @geekwolf, @ShuaixinHuang.
|
||||
- **Electron runtime capture:** Keep native IPC listener and one-shot dispatch behavior, block synchronous shell effects during capture, refuse active scenarios when the inspect fuse is disabled, and preserve fuse/transport cleanup ownership. ([#4389](https://github.com/morluto/rea/pull/4389), [morluto/rea#4390](https://github.com/morluto/rea/issues/4390), [#4605](https://github.com/morluto/rea/pull/4605), [morluto/rea#4593](https://github.com/morluto/rea/issues/4593), [#4562](https://github.com/morluto/rea/pull/4562), [morluto/rea#4393](https://github.com/morluto/rea/issues/4393), [#3605](https://github.com/morluto/rea/pull/3605), [morluto/rea#3453](https://github.com/morluto/rea/issues/3453), [f9de413](https://github.com/morluto/rea/commit/f9de41371f7536e2e22ce6e43c9314fa2f238a30), [e7df00e](https://github.com/morluto/rea/commit/e7df00e80777757b329bbe8aba6d421d8892b9d5)). Thanks @Adolanium, @gensokyo-dev, @gkobayashi0, @HarsH-1877, @kaoru0822-kitauji, @keeponlight, @N0zoM1z0, @Nitin13-git, @xu91102.
|
||||
- **APK resource accuracy:** Retain completed tables and decode facts after interrupted or failed reads. Derive locale limits from resource configuration fields, disclose unselectable qualified tables, distinguish document failures from omitted entries, and bind Evidence to the decoded snapshot. ([34a88d6](https://github.com/morluto/rea/commit/34a88d6c1674710953e2c94bd0a17332cd69ca6f), [f5fcf52](https://github.com/morluto/rea/commit/f5fcf5216ffb3a266b8c00f6d9a0943051084ab5), [6385ef4](https://github.com/morluto/rea/commit/6385ef4355d7a794b3154ee38529cbddee583336), [98886ad](https://github.com/morluto/rea/commit/98886adddfcbd7b4f4236b3a9825f91878c3cb11), [dc2e811](https://github.com/morluto/rea/commit/dc2e8117b60c4481372ed89b077e6c1cf81ee653), [#4771](https://github.com/morluto/rea/pull/4771), [morluto/rea#4768](https://github.com/morluto/rea/issues/4768)). Thanks @abhay-2108, @gensokyo-dev, @Iyanuoluwa007, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
- **Apktool guidance:** Retain the observed version and disclose the matching platform/release advisory windows without treating a version string as proof of decoder safety. Missing prerequisites and decode refusals identify the selected APK or launcher. ([d38f099](https://github.com/morluto/rea/commit/d38f099692394ec90c582f9f0a14b036b421b160), [63696d2](https://github.com/morluto/rea/commit/63696d277328f556c971d1fc9f81ae83128d468b), [cc038da](https://github.com/morluto/rea/commit/cc038da218cada68c87700477aba3db4739791ff)). Thanks @GovinduSasanka, @peter-research.
|
||||
- **Android method bodies:** Recover original bytecode bodies when Java source is omitted while preserving genuine native/abstract body absence and primary failures during retryable cleanup. ([63c65a2](https://github.com/morluto/rea/commit/63c65a22430a4bbc0c1807a0298edb867775001f), [742b6f5](https://github.com/morluto/rea/commit/742b6f587f3c4ef1b8212e7376d6f3a305b194dc), [9fa6d2b](https://github.com/morluto/rea/commit/9fa6d2b254cc390665974efb52463386a029e9fc)). Thanks @balkan16, @Chessing234.
|
||||
- **ADB observations and transfers:** Preserve display-scoped focus, distinguish missing declarations from absent focus, retain numeric physical density and mark unknown permission coverage partial. Transfers use consumed snapshot identity and refuse unknown destination existence; uninstall requests explicitly select user zero. ([7086768](https://github.com/morluto/rea/commit/7086768a27242a15daa93486aefbaacc3c891e78), [3d028db](https://github.com/morluto/rea/commit/3d028dbc74a465f6f8df1813a931fbb5362e7dc3), [#4074](https://github.com/morluto/rea/pull/4074), [morluto/rea#3942](https://github.com/morluto/rea/issues/3942), [#4663](https://github.com/morluto/rea/pull/4663), [#4665](https://github.com/morluto/rea/pull/4665), [1342127](https://github.com/morluto/rea/commit/1342127e91d4db8b4113a7a4e85be909bc96e1db), [61e6880](https://github.com/morluto/rea/commit/61e688070f48016b6d178cd04c02fcc2f33e19ad), [be0b2d1](https://github.com/morluto/rea/commit/be0b2d138c46bb951fc3c8ee57ef7b0d57fed312)). Thanks @Copilot, @coygeek, @gongfpp, @Tapraj-Pathak, @yankikucuk.
|
||||
- **Apple metadata:** Preserve exact plist/keyed-archive integers, negative zero, non-finite real identity and dictionary keys. Reject malformed XML before value decoding, retain undecodable dates as unknown and report unsupported OpenStep entries explicitly. ([#4324](https://github.com/morluto/rea/pull/4324), [morluto/rea#4292](https://github.com/morluto/rea/issues/4292), [2cf9c28](https://github.com/morluto/rea/commit/2cf9c28f27261b7e64af2ba7ecb4e23ea7714aee), [#4839](https://github.com/morluto/rea/pull/4839), [morluto/rea#4834](https://github.com/morluto/rea/issues/4834), [f2b7bbb](https://github.com/morluto/rea/commit/f2b7bbb31279a820f4cf32d439570f8e5381d010), [6c98708](https://github.com/morluto/rea/commit/6c98708793c23615de8053b2cd14d9f164b960c5), [78cf610](https://github.com/morluto/rea/commit/78cf6100f33ad2412c0a3d60b83f8a44a43fbbb5), [c7e9e09](https://github.com/morluto/rea/commit/c7e9e096d3e2444e4c251123b8e914e9c0941f4e), [#3793](https://github.com/morluto/rea/pull/3793), [#4105](https://github.com/morluto/rea/pull/4105), [morluto/rea#3892](https://github.com/morluto/rea/issues/3892)). Thanks @coygeek, @kaoru0822-kitauji, @mertkayacs, @pablodiazjorge, @pollychen-lab, @xu91102.
|
||||
- **Apple application structure:** Derive bundle components and launchd domains from their owning directories, retain unavailable components, and select only exact `Assets.car` names as asset catalogs. Interface Builder keeps unresolved classes and counts hierarchy records beyond the returned object page. ([149a0fc](https://github.com/morluto/rea/commit/149a0fc06c3c67ee45624e6da14dfb0754473758), [5d6029e](https://github.com/morluto/rea/commit/5d6029ea62a65ec34fd739ee40223c77c6fdb63b), [c192c58](https://github.com/morluto/rea/commit/c192c58815ddeaf5cf30159e7082c742c0bc36b7), [#4094](https://github.com/morluto/rea/pull/4094), [morluto/rea#3914](https://github.com/morluto/rea/issues/3914), [#4118](https://github.com/morluto/rea/pull/4118), [morluto/rea#3958](https://github.com/morluto/rea/issues/3958), [#4162](https://github.com/morluto/rea/pull/4162), [morluto/rea#3950](https://github.com/morluto/rea/issues/3950)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native names and layout:** Keep valid Objective-C records beside malformed neighbors, preserve leading U+FEFF and repeated load-command fields, and select exact arm64e.x1 slices. PE resource ownership and NativeAOT reads stay within declared image/header ranges. ([#3833](https://github.com/morluto/rea/pull/3833), [morluto/rea#3336](https://github.com/morluto/rea/issues/3336), [#4549](https://github.com/morluto/rea/pull/4549), [morluto/rea#4540](https://github.com/morluto/rea/issues/4540), [morluto/rea#4541](https://github.com/morluto/rea/issues/4541), [#4730](https://github.com/morluto/rea/pull/4730), [#4048](https://github.com/morluto/rea/pull/4048), [morluto/rea#4008](https://github.com/morluto/rea/issues/4008), [#3500](https://github.com/morluto/rea/pull/3500), [morluto/rea#3389](https://github.com/morluto/rea/issues/3389), [#4463](https://github.com/morluto/rea/pull/4463), [morluto/rea#4373](https://github.com/morluto/rea/issues/4373), [morluto/rea#4455](https://github.com/morluto/rea/issues/4455), [3fc0075](https://github.com/morluto/rea/commit/3fc007534bb2104d662b43cc712281f9d1d6a7d3), [4a076f3](https://github.com/morluto/rea/commit/4a076f368c62bdfb19415accad342e03a38d1005)). Thanks @coygeek, @Iyanuoluwa007, @jayinvers, @kaoru0822-kitauji, @kevinjosh10, @maxazure, @ShuaixinHuang.
|
||||
- **Native analysis providers:** Keep Ghidra byte matches within examined coverage, retain PE PDB/function-boundary limitations and preserve configured MIPS choices. Attached IDA refuses universal Mach-O rather than silently choosing a slice; request timeout identity and captured output survive adapter handling. ([#4531](https://github.com/morluto/rea/pull/4531), [#2933](https://github.com/morluto/rea/pull/2933), [morluto/rea#2259](https://github.com/morluto/rea/issues/2259), [01a1b3f](https://github.com/morluto/rea/commit/01a1b3fda6a7dacd24a0b10619d41d12153080d4), [50dd9fc](https://github.com/morluto/rea/commit/50dd9fcad1efb47cb404192c30bdf190dcdaca58), [#4765](https://github.com/morluto/rea/pull/4765), [morluto/rea#3871](https://github.com/morluto/rea/issues/3871), [c0cfccb](https://github.com/morluto/rea/commit/c0cfccb45fe43e7eeff18e56c64caa5b121988f9)). Thanks @BeLit-Development-2, @coygeek, @kaoru0822-kitauji, @N0zoM1z0, @ShuaixinHuang.
|
||||
- **Recorded crashes:** Prevent GDB from discovering an executable named by an untrusted core, reject core mapping paths that escape the isolated sysroot, and preserve pwntools worker ownership across launcher exec. Completed decode facts and cancellation reasons remain available through failed final cleanup. ([5bf4fff](https://github.com/morluto/rea/commit/5bf4fffc1b835130ddb16f96db9ce13b639591db), [b33c0df](https://github.com/morluto/rea/commit/b33c0df02e93fabca00a44f2de625b1a63637ee9), [#4435](https://github.com/morluto/rea/pull/4435), [morluto/rea#4011](https://github.com/morluto/rea/issues/4011)). Thanks @coygeek, @N0zoM1z0.
|
||||
- **Cutter and Rizin:** Read Cutter’s active document from core configuration and attribute its host version correctly. Keep interrupted Rizin frames and output whitespace before completion markers, with exact command and cleanup failure attribution. ([#4507](https://github.com/morluto/rea/pull/4507), [morluto/rea#4497](https://github.com/morluto/rea/issues/4497), [#4514](https://github.com/morluto/rea/pull/4514), [morluto/rea#4498](https://github.com/morluto/rea/issues/4498), [#4459](https://github.com/morluto/rea/pull/4459), [morluto/rea#4015](https://github.com/morluto/rea/issues/4015), [d5fc7e6](https://github.com/morluto/rea/commit/d5fc7e6ebf9af050081fc4c6e18b9079cfaa5cdc), [199396f](https://github.com/morluto/rea/commit/199396f61c8f61da326d059606a992304a760147)). Thanks @coygeek, @N0zoM1z0, @Xander-AJ.
|
||||
- **Windows cleanup:** Retain unpublished runtimes after failed removal, handle read-only attributes and sharing conflicts, preserve primary snapshot failures and honor caller environment overrides. ([f78d137](https://github.com/morluto/rea/commit/f78d137b42741a0ed5fb8ac161286f9a50220c4d), [d69acc3](https://github.com/morluto/rea/commit/d69acc3455c33f90a2fc4381db43d38b6af50622), [0679a79](https://github.com/morluto/rea/commit/0679a7972b4f2b02eee6a86a44f50f4c2967d3cb), [5f9ac2c](https://github.com/morluto/rea/commit/5f9ac2c98a6fe11988c41afb737df9b6d5c3a1b3), [1bbf4bd](https://github.com/morluto/rea/commit/1bbf4bd16ca134cdb2187eaf29291cae90d14a57)). Thanks @DeryFerd.
|
||||
- **Managed metadata:** Compare nominal signatures and declaring identities across builds, preserve empty names without display substitutions, verify ABI-prefixed Mach-O exports exactly, and charge cached metadata once. Metadata errors report artifact file offsets; mixed-mode assemblies retain their actual boundary classification. ([#3059](https://github.com/morluto/rea/pull/3059), [#3079](https://github.com/morluto/rea/pull/3079), [#3853](https://github.com/morluto/rea/pull/3853), [morluto/rea#3352](https://github.com/morluto/rea/issues/3352), [#3974](https://github.com/morluto/rea/pull/3974), [morluto/rea#3961](https://github.com/morluto/rea/issues/3961), [#3491](https://github.com/morluto/rea/pull/3491), [morluto/rea#3449](https://github.com/morluto/rea/issues/3449), [#4418](https://github.com/morluto/rea/pull/4418), [morluto/rea#4372](https://github.com/morluto/rea/issues/4372), [#3516](https://github.com/morluto/rea/pull/3516), [morluto/rea#3366](https://github.com/morluto/rea/issues/3366)). Thanks @8rulerstar, @coygeek, @duckhunt113, @Hahaa909, @JayOfTheKeyboard, @kaoru0822-kitauji, @keeponlight, @N0zoM1z0, @tomyrms.
|
||||
- **Archive and filesystem identity:** Reject colliding extraction paths before writing, classify ZIP offsets beyond EOF as malformed, normalize ZIP current-directory prefixes once, and preserve symlink and unreadable-path provenance. A directory named `*.map` is no longer mistaken for a source-map file. ([862a0b2](https://github.com/morluto/rea/commit/862a0b2631785492f8dc20c8acd396646c545fd1), [c209dcb](https://github.com/morluto/rea/commit/c209dcb209118069f123e789949eeba1ef78853e), [6b1c36e](https://github.com/morluto/rea/commit/6b1c36e336eeacbb7159b211e687b4b84f99b9f5), [25c2222](https://github.com/morluto/rea/commit/25c22222abe7a681491bdb4d7316250fdcf32973), [d6e4fe4](https://github.com/morluto/rea/commit/d6e4fe4a4dd8914ec3db50d2a7fc3d0802649262), [#4131](https://github.com/morluto/rea/pull/4131), [morluto/rea#3909](https://github.com/morluto/rea/issues/3909)). Thanks @Adkid-Zephyr, @coygeek, @jackie-cqz, @kaoru0822-kitauji, @maxazure, @XY-code1.
|
||||
- **Evidence and reconstruction:** Authenticate all retained inventory substeps, preserve source-attributed caveats in composed workflows, and require comparable cited Evidence before declaring reconstruction coverage or readiness. Missing Evidence and residual replay uncertainty stay explicit. ([9ac91e0](https://github.com/morluto/rea/commit/9ac91e0c1e13e91cd21341635ad5be89b0e8642d), [c4608a2](https://github.com/morluto/rea/commit/c4608a29620ea3e7bb8259b47357b942fe4e4709), [#4075](https://github.com/morluto/rea/pull/4075), [morluto/rea#3357](https://github.com/morluto/rea/issues/3357), [#3526](https://github.com/morluto/rea/pull/3526), [morluto/rea#3363](https://github.com/morluto/rea/issues/3363), [153017d](https://github.com/morluto/rea/commit/153017d750322188c1a112b2284c1b8065c67320)). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @rudycelekli.
|
||||
- **Offline data:** Preserve exact UTC packet-capture instants, SQLite column identity and snapshot admission failures, and cooked WASM directive literals. Cleanup cancellation retains completed EVM/WASM observations rather than losing them. ([796faa0](https://github.com/morluto/rea/commit/796faa0e4e6bf745bcdc33acef65af8ccfc3622d), [748ac7f](https://github.com/morluto/rea/commit/748ac7f0df340f0d0569eca2e803a59cd51ffdbd), [bc51d0e](https://github.com/morluto/rea/commit/bc51d0ea5df7dcee1fcc42dfae3ab0c62c9cca5e), [4f87f02](https://github.com/morluto/rea/commit/4f87f02bb19bf77349a683a5bcb2da2d1da427d0), [d694aef](https://github.com/morluto/rea/commit/d694aef227e90164e6654ea9e5ce6a854d099404), [321cacb](https://github.com/morluto/rea/commit/321cacb84a020f447e1110df5c58fa71d99b7bf1)). Thanks @himamaheswarreddy, @mertkayacs, @rollroyces, @viniciusdsandrade.
|
||||
- **Kuna recovery:** Share concurrent single-function requests and cache repeats, validate warm exports once, stream string inventories beyond the former fixed buffer ceiling, and keep unproven processes in their run slots until close confirms release. Failure messages distinguish final buffer refusals from configurable deadlines. ([#4045](https://github.com/morluto/rea/pull/4045), [#4169](https://github.com/morluto/rea/pull/4169), [#4049](https://github.com/morluto/rea/pull/4049), [#4318](https://github.com/morluto/rea/pull/4318), [#4052](https://github.com/morluto/rea/pull/4052), [#4098](https://github.com/morluto/rea/pull/4098), [#4343](https://github.com/morluto/rea/pull/4343)). Thanks @kaoru0822-kitauji.
|
||||
- **Flutter snapshots:** Check symbol-table ranges against loadable segments and retain completed inspection facts when cancellation occurs during snapshot cleanup. ([#3859](https://github.com/morluto/rea/pull/3859), [morluto/rea#3346](https://github.com/morluto/rea/issues/3346), [#4766](https://github.com/morluto/rea/pull/4766), [morluto/rea#4717](https://github.com/morluto/rea/issues/4717), [#4762](https://github.com/morluto/rea/pull/4762), [morluto/rea#4640](https://github.com/morluto/rea/issues/4640)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native metadata recovery:** Keep unknown Objective-C category classes, empty protocol names, undecoded Swift aliases and unfinished property types explicit instead of discarding neighboring valid records. Preserve unnamed segment identity and exclude a dylib’s own install name from dependencies. ([#4093](https://github.com/morluto/rea/pull/4093), [morluto/rea#4012](https://github.com/morluto/rea/issues/4012), [#4078](https://github.com/morluto/rea/pull/4078), [morluto/rea#3873](https://github.com/morluto/rea/issues/3873), [#4085](https://github.com/morluto/rea/pull/4085), [morluto/rea#3934](https://github.com/morluto/rea/issues/3934), [#3825](https://github.com/morluto/rea/pull/3825), [morluto/rea#3345](https://github.com/morluto/rea/issues/3345), [#4051](https://github.com/morluto/rea/pull/4051), [morluto/rea#3875](https://github.com/morluto/rea/issues/3875), [#4050](https://github.com/morluto/rea/pull/4050), [morluto/rea#3872](https://github.com/morluto/rea/issues/3872)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Document selection:** Preserve explicitly selected plist paths and bundle defaults, and only use document fallback after confirming its applicability. Finalization keeps completed selection facts and cancellation outcomes. ([e6c4877](https://github.com/morluto/rea/commit/e6c487799bb64f559e66b69be1863e6ec5187548), [#3774](https://github.com/morluto/rea/pull/3774), [9df559b](https://github.com/morluto/rea/commit/9df559b7e712298a761ff26e9caae8d659b7e54b)). Thanks @kaoru0822-kitauji.
|
||||
- **Capture and comparison accuracy:** Admit schema-valid HAR numeric values while retaining exact-publication limits, preserve empty URL path segments, and keep unrelated process traces and unnamed reference endpoints distinct or unknown. ([#3159](https://github.com/morluto/rea/pull/3159), [morluto/rea#3145](https://github.com/morluto/rea/issues/3145), [#3792](https://github.com/morluto/rea/pull/3792), [morluto/rea#3307](https://github.com/morluto/rea/issues/3307), [#4178](https://github.com/morluto/rea/pull/4178), [morluto/rea#3931](https://github.com/morluto/rea/issues/3931), [#3809](https://github.com/morluto/rea/pull/3809), [morluto/rea#3388](https://github.com/morluto/rea/issues/3388)). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0, @pablodiazjorge, @xu91102.
|
||||
- **Evidence publication:** Recheck staged output identity after its final write, classify missing review Evidence and duplicate ledger entries as caller input, and preserve the selected freshness requirement in comparison parameters. ([#4764](https://github.com/morluto/rea/pull/4764), [morluto/rea#4613](https://github.com/morluto/rea/issues/4613), [#4452](https://github.com/morluto/rea/pull/4452), [morluto/rea#4444](https://github.com/morluto/rea/issues/4444), [#4065](https://github.com/morluto/rea/pull/4065), [morluto/rea#4000](https://github.com/morluto/rea/issues/4000)). Thanks @amyanger, @coygeek, @kaoru0822-kitauji, @Rinkia, @Swatantra-66.
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **Saved Evidence:** Evidence and analysis snapshots now require explicit nullable `relationship_coverage` in their identity commitments. Artifact inventories require scoped coverage and explicit `integrity_contradictions`; obsolete `coverage_status` and prose-based coverage inference are removed. Regenerate old Evidence/snapshots and inventory observations with this release: saved files are not migrated and omitted facts are not filled with guessed defaults. ([db8128d](https://github.com/morluto/rea/commit/db8128dc0d1c4f731f3d19877fa00d25cf17f657), [0405c9d](https://github.com/morluto/rea/commit/0405c9d361de6d904dfa16cc0b6d8350407f2c9b), [a9588be](https://github.com/morluto/rea/commit/a9588be4ebdd20eeda975cb63ffcdb1cd868b340)).
|
||||
- **Browser capture results:** Consumers must retain resolved capture limits, event families and omission facts. Set desired budgets through the supported capture options; omitted caller inputs still select defaults, but stored observations must report the resolved values. Admitted content reads queue behind bounded concurrency. ([57a23f0](https://github.com/morluto/rea/commit/57a23f0a2da0fdc3835e203718ded8acea5a34e6)).
|
||||
- **Android version codes:** `inspect_adb_package.version_code` is a decimal string rather than a JSON number. Read it as a string, or use `BigInt` for numeric comparison. ([#3485](https://github.com/morluto/rea/pull/3485), [morluto/rea#3368](https://github.com/morluto/rea/issues/3368), [7666a48](https://github.com/morluto/rea/commit/7666a482b69dc7cabdbac3bbaf89a43e32300192)). Thanks @coygeek, @kaoru0822-kitauji.
|
||||
- **Native UI failures:** Failed or cancelled native UI scenarios now return typed errors with the collected report in `partial_observation`. Handle the error path and read that report instead of expecting a successful result containing failed steps. ([d909919](https://github.com/morluto/rea/commit/d9099190ab3a819bd3a9fd62a8714cef398016d5)).
|
||||
- **Provider status:** `binary_session` candidate availability can be unknown during owner finalization. Preserve that state rather than treating it as unavailable; internal consumers must await the asynchronous session status API. ([150e996](https://github.com/morluto/rea/commit/150e996a86f2bec4982ba87f37bf502a8d5e8214)).
|
||||
- **Swift symbol locations:** `swift_symbols` entries may have a null `address` when the provider supplies no location. Handle unknown addresses; optional `evidence.reported_address` retains the original provider value. ([8116dc0](https://github.com/morluto/rea/commit/8116dc06512b4609d8db63cebc9f2ff8cb964151)).
|
||||
- **Linux Hopper admission:** Older builds previously admitted by a fixed allowlist need retained publisher-signed package evidence or an approved update to the current public demo. Update explicit `HOPPER_LAUNCHER_PATH` selections when migrating; the live feed offers only its current release. The official RPM metadata mismatch remains unresolved and is refused. ([#3590](https://github.com/morluto/rea/pull/3590), [morluto/rea#3577](https://github.com/morluto/rea/issues/3577), [70cd4cb](https://github.com/morluto/rea/commit/70cd4cbfb849b61f05d97d2690372a9782831426)). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
|
||||
|
||||
Full contribution record: [571 commits and 236 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.6.0/docs/releases/6.6.0.contributions.json).
|
||||
|
||||
## [6.5.0](https://github.com/morluto/rea/compare/rea-agents-6.4.0...rea-agents-6.5.0) (2026-10-13)
|
||||
|
||||
Provider prerequisites and recorded verification scope are listed in [the testing guide](docs/testing.md).
|
||||
|
||||
@@ -21,6 +21,9 @@ For analysis workflows and agent interfaces, follow the
|
||||
[performance priorities](AGENTS.md#performance). Explain the selected output
|
||||
scope, reuse/cache decisions, and relevant before/after measurements when these
|
||||
costs change.
|
||||
Use the [analysis optimization skill](.agents/skills/rea-analysis-optimization/SKILL.md)
|
||||
to locate provider and shared-workflow bottlenecks, preserve Evidence, and
|
||||
compare equivalent real tasks before claiming an improvement.
|
||||
|
||||
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
|
||||
· [Ελληνικά](docs/readme/README_el.md)
|
||||
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -132,6 +132,14 @@ For a CLI installed with `npm install --global` or `bun add --global`:
|
||||
rea update
|
||||
```
|
||||
|
||||
`rea --update` is an alias for this verified workflow and accepts the same global
|
||||
output flags. Use `rea update` when combining it with other command controls.
|
||||
|
||||
Updater commands retain at most 4 MiB of stdout and stderr combined. Oversized
|
||||
metadata replies are rejected after the command exits; verbose installs continue
|
||||
with bounded diagnostics. Human progress output respects terminal backpressure
|
||||
and waits for submitted writes to finish.
|
||||
|
||||
Bun global updates require Bun 1.2.17 or later. With an older Bun release,
|
||||
upgrade Bun manually before running `rea update`.
|
||||
|
||||
@@ -345,7 +353,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Star history
|
||||
|
||||
🎉 **250,000 GitHub stars — thank you!**
|
||||
🎉 **300,000 GitHub stars — thank you!**
|
||||
|
||||
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
|
||||
|
||||
|
||||
@@ -15,11 +15,14 @@ import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
import kotlin.Unit;
|
||||
import kotlin.ResultKt;
|
||||
import kotlin.coroutines.Continuation;
|
||||
import kotlin.coroutines.CoroutineContext;
|
||||
import kotlin.coroutines.EmptyCoroutineContext;
|
||||
import kotlin.coroutines.intrinsics.IntrinsicsKt;
|
||||
import kotlinx.coroutines.BuildersKt;
|
||||
import kotlinx.io.CoreKt;
|
||||
import kotlinx.io.JvmCoreKt;
|
||||
import kotlinx.serialization.json.JsonObject;
|
||||
|
||||
/** REA-owned metadata boundary for the unmodified jadx-headless-mcp 0.7.1 JAR. */
|
||||
class ReaJadxBridge {
|
||||
@@ -28,7 +31,8 @@ class ReaJadxBridge {
|
||||
private JadxSession indexedSession;
|
||||
private Map<String, List<ClassSnapshot>> classes = Map.of();
|
||||
|
||||
private record MethodSnapshot(String name, String shortId, Map<String, Object> summary) {}
|
||||
private record MethodSnapshot(String name, String rawName, String shortId, List<String> bodyAbsenceMarkers,
|
||||
Map<String, Object> summary) {}
|
||||
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary) {}
|
||||
|
||||
private static final class InputFailure extends IllegalArgumentException {
|
||||
@@ -44,15 +48,93 @@ class ReaJadxBridge {
|
||||
|
||||
private static CallToolResult json(Object value) { return text(JSON.toJson(value), false); }
|
||||
|
||||
/** Translate the typed load outcome before upstream drops its exception identity. */
|
||||
static Object loadResult(Object result) {
|
||||
try { ResultKt.throwOnFailure(result); }
|
||||
catch (Throwable failure) {
|
||||
if (failure instanceof java.util.concurrent.CancellationException
|
||||
|| failure instanceof Error && !(failure instanceof OutOfMemoryError)) return result;
|
||||
if (failure instanceof OutOfMemoryError) {
|
||||
String reason = failure.getMessage();
|
||||
boolean heap = "Java heap space".equals(reason) || "GC overhead limit exceeded".equals(reason);
|
||||
Map<String, Object> reply = new LinkedHashMap<>();
|
||||
reply.put("kind", "jvm-memory-failure");
|
||||
reply.put("memory_area", heap ? "heap" : "unknown");
|
||||
reply.put("exception_type", "java.lang.OutOfMemoryError");
|
||||
if (reason != null) reply.put("message", reason);
|
||||
reply.put("max_heap_bytes", Runtime.getRuntime().maxMemory());
|
||||
return text(JSON.toJson(reply), true);
|
||||
}
|
||||
return text("load failed: " + failure.getMessage(), true);
|
||||
}
|
||||
SessionHolder.LoadResult loaded = (SessionHolder.LoadResult) result;
|
||||
Map<String, Object> reply = new LinkedHashMap<>();
|
||||
reply.put("state", "LOADED");
|
||||
reply.put("apk_path", loaded.getApkPath());
|
||||
reply.put("class_count", loaded.getClassCount());
|
||||
reply.put("raw_class_count", loaded.getRawClassCount());
|
||||
reply.put("resource_count", loaded.getResourceCount());
|
||||
reply.put("load_duration_ms", loaded.getLoadDurationMs());
|
||||
reply.put("decompile_timeout_ms", loaded.getDecompileTimeoutMs());
|
||||
reply.put("threads", loaded.getThreads());
|
||||
reply.put("code_cache_size", loaded.getCodeCacheSize());
|
||||
reply.put("resources", loaded.getResourceMode());
|
||||
if (!loaded.getIncludePackages().isEmpty()) reply.put("include_packages", loaded.getIncludePackages());
|
||||
if (!loaded.getExcludePackages().isEmpty()) reply.put("exclude_packages", loaded.getExcludePackages());
|
||||
return json(reply);
|
||||
}
|
||||
|
||||
private Object load(kotlinx.serialization.json.JsonObject arguments, SessionConfig defaults,
|
||||
Continuation<? super CallToolResult> continuation) {
|
||||
JsonObject args = JsonParser.parseString(arguments.toString()).getAsJsonObject();
|
||||
String path = string(args, "path");
|
||||
SessionConfig options = defaults.withLoadOverrides(
|
||||
optionalString(args, "include_packages"), optionalString(args, "exclude_packages"),
|
||||
args.has("threads") ? integer(args, "threads", 1) : null,
|
||||
args.has("code_cache_size") ? integer(args, "code_cache_size", 64) : null,
|
||||
optionalString(args, "resources"));
|
||||
return loadCall(next -> holder.load(path, options, next), continuation);
|
||||
}
|
||||
|
||||
static Object loadCall(kotlin.jvm.functions.Function1<Continuation<SessionHolder.LoadResult>, Object> load,
|
||||
Continuation<? super CallToolResult> continuation) {
|
||||
var forwarded = new java.util.concurrent.atomic.AtomicBoolean();
|
||||
Continuation<SessionHolder.LoadResult> resumed = new Continuation<>() {
|
||||
public CoroutineContext getContext() { return continuation.getContext(); }
|
||||
public void resumeWith(Object result) {
|
||||
if (!forwarded.compareAndSet(false, true)) throw new IllegalStateException("Load resumed twice");
|
||||
Object translated;
|
||||
try { translated = loadResult(result); }
|
||||
catch (Throwable failure) { translated = ResultKt.createFailure(failure); }
|
||||
// Keep the consumer outside the translation catch, including inline completion.
|
||||
continuation.resumeWith(translated);
|
||||
}
|
||||
};
|
||||
Object result;
|
||||
try { result = load.invoke(resumed); }
|
||||
catch (Throwable failure) {
|
||||
if (forwarded.get()) ResultKt.throwOnFailure(ResultKt.createFailure(failure));
|
||||
result = ResultKt.createFailure(failure);
|
||||
}
|
||||
if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) return result;
|
||||
Object translated = loadResult(result);
|
||||
ResultKt.throwOnFailure(translated);
|
||||
return translated;
|
||||
}
|
||||
|
||||
private static String optionalString(JsonObject args, String name) {
|
||||
return args.has(name) ? string(args, name) : null;
|
||||
}
|
||||
|
||||
private static String string(JsonObject args, String name) {
|
||||
JsonElement value = JsonParser.parseString(args.toString()).getAsJsonObject().get(name);
|
||||
JsonElement value = args.get(name);
|
||||
if (value == null || !value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString())
|
||||
throw new IllegalArgumentException(name + " must be a string");
|
||||
return value.getAsString();
|
||||
}
|
||||
|
||||
private static int integer(JsonObject args, String name, int fallback) {
|
||||
JsonElement value = JsonParser.parseString(args.toString()).getAsJsonObject().get(name);
|
||||
JsonElement value = args.get(name);
|
||||
if (value == null) return fallback;
|
||||
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isNumber())
|
||||
throw new IllegalArgumentException(name + " must be a nonnegative integer");
|
||||
@@ -90,10 +172,13 @@ class ReaJadxBridge {
|
||||
if (!shortId.startsWith(rawName + "("))
|
||||
throw new IllegalStateException("JADX method ID has no raw-name prefix: " + shortId);
|
||||
String descriptor = shortId.substring(rawName.length());
|
||||
List<String> bodyAbsenceMarkers = new ArrayList<>();
|
||||
if (method.getAccessFlags().isNative()) bodyAbsenceMarkers.add("native_method");
|
||||
if (method.getAccessFlags().isAbstract()) bodyAbsenceMarkers.add("abstract_method");
|
||||
String signature = name + "(" + method.getArgTypes().stream()
|
||||
.map(Object::toString).collect(Collectors.joining(", "))
|
||||
+ "): " + method.getReturnType().toString();
|
||||
return new MethodSnapshot(name, shortId, Map.of(
|
||||
return new MethodSnapshot(name, rawName, shortId, List.copyOf(bodyAbsenceMarkers), Map.of(
|
||||
"name", name, "signature", signature,
|
||||
"dex_descriptor", descriptor,
|
||||
"is_constructor", method.isConstructor(), "def_pos", 0));
|
||||
@@ -140,16 +225,52 @@ class ReaJadxBridge {
|
||||
|
||||
private JadxSession.SmartCode methodBody(JadxSession current, ClassSnapshot cls,
|
||||
MethodSnapshot selected, JavaMethod method) {
|
||||
if (!selected.shortId().equals("<clinit>()V"))
|
||||
return current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
|
||||
// Access flags belong to the parsed member, before code generation rewrites it.
|
||||
// These methods have no Java body; source slicing can include unrelated members.
|
||||
if (!selected.bodyAbsenceMarkers().isEmpty())
|
||||
return new JadxSession.SmartCode("", "java", false, selected.bodyAbsenceMarkers());
|
||||
// Java code generation can fold all or part of <clinit> into field declarations.
|
||||
// Its method text cannot establish coverage, even when a static block remains.
|
||||
JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), method.getName());
|
||||
if (!smali.getFound() || smali.getBlocks().size() != 1)
|
||||
throw new IllegalArgumentException("Static initializer disassembly requires one exact method: " + selected.shortId());
|
||||
if (selected.shortId().equals("<clinit>()V"))
|
||||
return exactMethodSmali(current, cls, selected, List.of("static_initializer_disassembly"));
|
||||
String java = current.getMethodSource(method);
|
||||
if (java.startsWith("// ERROR:")) throw new IllegalArgumentException(java);
|
||||
// The upstream placeholder means only that Java code was omitted, including
|
||||
// methods merged into synthetic bridges. Access flags established absence above.
|
||||
if (java.isEmpty() || java.equals(
|
||||
"// method exists but has no decompiled body (native/abstract)"))
|
||||
return exactMethodSmali(current, cls, selected, List.of("java_body_unavailable"));
|
||||
List<String> markers = DecompileFailureKt.detectDecompileFailure(java);
|
||||
if (markers.stream().anyMatch(DecompileFailureKt.getSTRONG_FAILURE_MARKERS()::contains))
|
||||
return exactMethodSmali(current, cls, selected, markers);
|
||||
return new JadxSession.SmartCode(
|
||||
TextUtilKt.truncateToBytes(smali.getBlocks().get(0), current.getMaxSourceBytes()),
|
||||
"smali", true, List.of("static_initializer_disassembly"));
|
||||
TextUtilKt.truncateToBytes(java, current.getMaxSourceBytes()), "java", false, markers);
|
||||
}
|
||||
|
||||
private JadxSession.SmartCode exactMethodSmali(JadxSession current, ClassSnapshot cls,
|
||||
MethodSnapshot selected, List<String> markers) {
|
||||
JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), selected.rawName());
|
||||
String block = selectMethodSmali(smali, selected.shortId());
|
||||
return new JadxSession.SmartCode(
|
||||
TextUtilKt.truncateToBytes(block, current.getMaxSourceBytes()), "smali", true, markers);
|
||||
}
|
||||
|
||||
private static String selectMethodSmali(JadxSession.MethodSmali smali, String shortId) {
|
||||
if (!smali.getFound())
|
||||
throw new IllegalArgumentException("JADX could not disassemble " + shortId
|
||||
+ (smali.getNote() == null ? "" : ": " + smali.getNote()));
|
||||
// Upstream returns every same-name overload, and can return an unterminated
|
||||
// trailing block. Match the full original identity before applying output limits.
|
||||
List<String> matches = smali.getBlocks().stream().filter(block -> {
|
||||
String header = block.lines().findFirst().orElse("").strip();
|
||||
return header.startsWith(".method ") && header.endsWith(" " + shortId);
|
||||
}).toList();
|
||||
if (matches.size() != 1)
|
||||
throw new IllegalArgumentException("Disassembly could not establish one selected method: " + shortId);
|
||||
String block = matches.get(0);
|
||||
if (!block.stripTrailing().endsWith("\n.end method"))
|
||||
throw new IllegalArgumentException("Selected method disassembly is incomplete: " + shortId);
|
||||
return block;
|
||||
}
|
||||
|
||||
// The local transport admits 8 MiB frames. Reserve 1 KiB for the fixed
|
||||
@@ -203,7 +324,8 @@ class ReaJadxBridge {
|
||||
return new DirectInvokes(sites, exhaustive[0]);
|
||||
}
|
||||
|
||||
private CallToolResult handle(String name, JsonObject args) {
|
||||
private CallToolResult handle(String name, kotlinx.serialization.json.JsonObject input) {
|
||||
JsonObject args = JsonParser.parseString(input.toString()).getAsJsonObject();
|
||||
if (name.equals("rea_jvm_status")) return json(Map.of(
|
||||
"engine_reported_version", BuildInfo.VERSION,
|
||||
"max_heap_bytes", Runtime.getRuntime().maxMemory(),
|
||||
@@ -213,12 +335,25 @@ class ReaJadxBridge {
|
||||
JadxSession current = session();
|
||||
if (name.equals("list_classes")) {
|
||||
List<String> names = current.getClassFqns();
|
||||
String query = args.has("query") ? string(args, "query") : "";
|
||||
int offset = integer(args, "offset", 0);
|
||||
int limit = integer(args, "limit", 200);
|
||||
int start = Math.min(offset, names.size());
|
||||
int end = (int) Math.min((long) start + limit, names.size());
|
||||
return json(Map.of("total", names.size(), "offset", offset, "limit", limit,
|
||||
"items", names.subList(start, end)));
|
||||
int end = start;
|
||||
List<String> items;
|
||||
if (query.isEmpty()) {
|
||||
end = (int) Math.min((long) start + limit, names.size());
|
||||
items = names.subList(start, end);
|
||||
} else {
|
||||
items = new ArrayList<>();
|
||||
while (end < names.size() && items.size() < limit) {
|
||||
String nameInInventory = names.get(end++);
|
||||
if (nameInInventory.contains(query)) items.add(nameInInventory);
|
||||
}
|
||||
}
|
||||
return json(Map.of("total_classes", names.size(), "next_offset", end,
|
||||
"scope", "top_level", "offset", offset, "limit", limit,
|
||||
"items", items));
|
||||
}
|
||||
ClassSnapshot cls = selected(args);
|
||||
if (name.equals("get_class_summary")) return json(cls.summary());
|
||||
@@ -230,6 +365,7 @@ class ReaJadxBridge {
|
||||
JadxSession.SmartCode code = methodBody(current, cls, overloads.get(index), method);
|
||||
return json(Map.of("class_name", cls.summary().get("full_name"),
|
||||
"method_name", overloads.get(index).name(), "full_name", method.getFullName(),
|
||||
"dex_descriptor", overloads.get(index).summary().get("dex_descriptor"),
|
||||
"overload_index", index, "overload_count", overloads.size(),
|
||||
"mode", code.getKind(), "fell_back", code.getFellBack(),
|
||||
"markers", code.getMarkers(), "body", code.getText()));
|
||||
@@ -241,7 +377,7 @@ class ReaJadxBridge {
|
||||
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
|
||||
else {
|
||||
List<MethodSnapshot> overloads = methods(cls, args);
|
||||
if (overloads.size() != 1 && !args.containsKey("overload_index"))
|
||||
if (overloads.size() != 1 && !args.has("overload_index"))
|
||||
throw new InputFailure("overload_index", "Select one of " + overloads.size() + " method overloads");
|
||||
overloadIndex = integer(args, "overload_index", 0);
|
||||
if (overloadIndex >= overloads.size())
|
||||
@@ -287,11 +423,12 @@ class ReaJadxBridge {
|
||||
public static void main(String[] args) throws Exception {
|
||||
PrintStream protocolOut = System.out;
|
||||
System.setOut(System.err);
|
||||
SessionHolder holder = new SessionHolder(new SessionConfig(
|
||||
1048576, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL));
|
||||
SessionConfig defaults = new SessionConfig(
|
||||
1048576, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL);
|
||||
SessionHolder holder = new SessionHolder(defaults);
|
||||
ServerCapabilities capabilities = new ServerCapabilities(
|
||||
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "1", null, null, null),
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "4", null, null, null),
|
||||
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
|
||||
SessionToolsKt.registerSessionTools(server, holder);
|
||||
ManifestToolsKt.registerManifestTools(server, holder);
|
||||
@@ -299,13 +436,17 @@ class ReaJadxBridge {
|
||||
MethodToolsKt.registerMethodTools(server, holder);
|
||||
XrefToolsKt.registerXrefTools(server, holder);
|
||||
ReaJadxBridge bridge = new ReaJadxBridge(holder);
|
||||
Tool loadTool = server.getTools().get("load_apk").getTool();
|
||||
server.removeTool("load_apk");
|
||||
server.addTool(loadTool, (connection, request, continuation) ->
|
||||
bridge.load(request.getArguments(), defaults, continuation));
|
||||
Set<String> consumed = Set.of("load_apk", "get_app_info", "get_android_manifest",
|
||||
"list_classes", "get_class_summary", "get_method_body",
|
||||
"get_xrefs_to_class", "get_xrefs_to_method");
|
||||
for (String name : new ArrayList<>(server.getTools().keySet()))
|
||||
if (!consumed.contains(name)) server.removeTool(name);
|
||||
Tool diagnostics = new Tool("rea_jvm_status",
|
||||
new ToolSchema(null, new JsonObject(Map.of()), List.of(), null),
|
||||
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(Map.of()), List.of(), null),
|
||||
"Observed worker JVM and metadata scope", null, null, null, null, null, null);
|
||||
server.addTool(diagnostics, (connection, request, continuation) -> bridge.handle("rea_jvm_status", request.getArguments()));
|
||||
for (String name : List.of("list_classes", "get_class_summary",
|
||||
@@ -320,13 +461,16 @@ class ReaJadxBridge {
|
||||
};
|
||||
Map<String, kotlinx.serialization.json.JsonElement> properties = new LinkedHashMap<>();
|
||||
for (String field : fields) properties.put(field, tool.getInputSchema().getProperties().get(field));
|
||||
if (name.equals("list_classes")) properties.put("query",
|
||||
new kotlinx.serialization.json.JsonObject(Map.of(
|
||||
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))));
|
||||
String description = switch (name) {
|
||||
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
|
||||
case "list_classes" -> "Pages of case-sensitive matching top-level class names; offset and next_offset are inventory positions, limit bounds matching rows. Omitted or empty query inventories all names.";
|
||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
|
||||
default -> "Complete static using-node inventory for an exact class or selected method overload; one node may contain multiple invoke instructions, and source lines are unresolved.";
|
||||
};
|
||||
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
|
||||
Tool narrowed = new Tool(name, new ToolSchema(null, new kotlinx.serialization.json.JsonObject(properties),
|
||||
tool.getInputSchema().getRequired(), null), description, null, null, null, null, null, null);
|
||||
server.addTool(narrowed, (connection, request, continuation) -> {
|
||||
try { return bridge.handle(name, request.getArguments()); }
|
||||
|
||||
@@ -140,6 +140,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
"params"
|
||||
);
|
||||
private static final String[] CAPABILITIES = {
|
||||
"read_binary_inventory_summary",
|
||||
"annotate_native_function",
|
||||
"set_address_name",
|
||||
"set_addresses_names",
|
||||
@@ -427,6 +428,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
);
|
||||
}
|
||||
return switch (request.method) {
|
||||
case "read_binary_inventory_summary" -> binaryInventorySummary(request.params);
|
||||
case "inspect_native_load_image" -> inspectNativeLoadImage(request.params);
|
||||
case "read_bytes" -> readMemoryBytes(request.params);
|
||||
case "search_bytes" -> searchMemoryBytes(request.params, descriptor);
|
||||
@@ -1974,6 +1976,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonObject binaryInventorySummary(JsonObject params) throws Exception {
|
||||
requireKeys(params, Set.of());
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("document", currentProgram.getName());
|
||||
result.add("segments", segments());
|
||||
// Count the owning inventories, preserving their exact discovery rules
|
||||
// without serializing function identities or string values for counts.
|
||||
result.addProperty("procedure_count", procedures().size());
|
||||
result.addProperty("string_count", strings().size());
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonArray listNames(JsonObject params) throws Exception {
|
||||
requireKeys(params, Set.of("document", "address"));
|
||||
requireDocument(params);
|
||||
@@ -2000,6 +2014,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
private JsonArray listSegments(JsonObject params) {
|
||||
requireKeys(params, Set.of("document"));
|
||||
requireDocument(params);
|
||||
return segments();
|
||||
}
|
||||
|
||||
private JsonArray segments() {
|
||||
JsonArray result = new JsonArray();
|
||||
String imageBase = canonicalAddress(currentProgram.getImageBase());
|
||||
MemoryBlock[] blocks = currentProgram.getMemory().getBlocks();
|
||||
|
||||
@@ -20,8 +20,9 @@ def inspect(request):
|
||||
root = Path(os.environ["REA_CORE_CONTEXT_REQUEST"]).parent
|
||||
empty = root / "no-libraries"
|
||||
empty.mkdir(mode=0o700)
|
||||
for command in ["set auto-load off", "set debuginfod enabled off", "set auto-solib-add off", "set history save off", "set pagination off",
|
||||
"set sysroot ./no-libraries", "set solib-search-path ./no-libraries"]:
|
||||
for command in ["set auto-load off", "set debuginfod enabled off", "set auto-solib-add off", "set history save off", "set pagination off", "set target-file-system-kind unix",
|
||||
"set sysroot ./no-libraries", "set solib-search-path ./no-libraries",
|
||||
"set debug-file-directory ./no-libraries"]:
|
||||
gdb.execute(command, to_string=True)
|
||||
entry = request["gdbinit"]
|
||||
try:
|
||||
@@ -38,7 +39,19 @@ def inspect(request):
|
||||
# relative filename in the owned cwd so no selected path becomes GDB syntax.
|
||||
if Path(request["snapshot_path"]) != root.parent / "object.snapshot":
|
||||
raise ContextFailure("decoder", "Core context request did not select the shared owned snapshot.")
|
||||
# GDB 16 searches AT_EXECFN/NT_FILE outside sysroot when no executable BFD
|
||||
# is selected. Occupy that slot with the already running, owned debugger;
|
||||
# exec-file loads no symbols and never runs it. Clear it before any query.
|
||||
# /proc/self/exe identifies this process, not the core's historical PID.
|
||||
architecture = {"x86_64-little-endian": "i386:x86-64", "aarch64-little-endian": "aarch64"}.get(request["architecture"])
|
||||
if architecture is None:
|
||||
raise ContextFailure("unsupported", "Unsupported recorded core architecture.")
|
||||
gdb.execute("exec-file /proc/self/exe", to_string=True)
|
||||
# The placeholder must not select the core's architecture. Reuse the
|
||||
# decoder's validated profile, including for cross-architecture GDB builds.
|
||||
gdb.execute("set architecture " + architecture, to_string=True)
|
||||
gdb.execute("core-file ../object.snapshot", to_string=True)
|
||||
gdb.execute("file", to_string=True)
|
||||
inferior = gdb.selected_inferior()
|
||||
if inferior.connection is None or inferior.connection.type != "core" or gdb.current_progspace().filename is not None:
|
||||
raise ContextFailure("decoder", "Requested core-only boundary was not established; no mapping query performed.")
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Producer regressions with controlled GDB state, not native debugger qualification."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
BRIDGE = Path(__file__).with_name("core_context.py")
|
||||
|
||||
|
||||
class CoreContextTests(unittest.TestCase):
|
||||
def inspect(self, failure=None, connection="core", symbols=None, architecture="x86_64-little-endian"):
|
||||
commands, reads, queries = [], [], []
|
||||
state = {"executable": None, "symbols": None, "connection": None}
|
||||
gdb = types.ModuleType("gdb")
|
||||
gdb.VERSION = "16.3"
|
||||
gdb.selected_inferior = lambda: types.SimpleNamespace(
|
||||
connection=state["connection"], pid=123)
|
||||
gdb.current_progspace = lambda: types.SimpleNamespace(filename=state["symbols"])
|
||||
|
||||
def execute(command, to_string):
|
||||
self.assertTrue(to_string)
|
||||
commands.append(command)
|
||||
if command == failure:
|
||||
raise RuntimeError("controlled command failure")
|
||||
if command == "exec-file /proc/self/exe":
|
||||
state["executable"] = "/proc/self/exe"
|
||||
elif command == "core-file ../object.snapshot":
|
||||
if state["executable"] is None:
|
||||
reads.append("recorded-executable")
|
||||
state["executable"] = state["symbols"] = "recorded-executable"
|
||||
state["connection"] = types.SimpleNamespace(type=connection)
|
||||
elif command == "file":
|
||||
state["executable"] = None
|
||||
state["symbols"] = symbols
|
||||
|
||||
gdb.execute = execute
|
||||
pwndbg = types.ModuleType("pwndbg")
|
||||
pwndbg.aglib = types.ModuleType("pwndbg.aglib")
|
||||
pwndbg.aglib.vmmap = types.ModuleType("pwndbg.aglib.vmmap")
|
||||
pwndbg.lib = types.ModuleType("pwndbg.lib")
|
||||
pwndbg.lib.version = types.ModuleType("pwndbg.lib.version")
|
||||
pwndbg.lib.version.__version__ = "2026.09.15"
|
||||
|
||||
def maps():
|
||||
self.assertIsNone(state["executable"], "Helper executable must be discarded before mapping queries")
|
||||
queries.append("maps")
|
||||
return [types.SimpleNamespace(start=4096, end=8192, offset=0, flags=5, objfile="recorded-executable")]
|
||||
|
||||
pwndbg.aglib.vmmap.get = maps
|
||||
modules = {module.__name__: module for module in [
|
||||
gdb, pwndbg, pwndbg.aglib, pwndbg.aglib.vmmap, pwndbg.lib, pwndbg.lib.version]}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
stage = root / "debugger"
|
||||
stage.mkdir()
|
||||
plugin = root / "gdbinit.py"
|
||||
plugin.write_text("")
|
||||
request = stage / "request.json"
|
||||
reply = stage / "reply.json"
|
||||
request.write_text(json.dumps({"gdbinit": str(plugin), "architecture": architecture, "snapshot_path": str(root / "object.snapshot"), "reply_path": str(reply)}))
|
||||
with patch.dict(sys.modules, modules), patch.dict(os.environ, REA_CORE_CONTEXT_REQUEST=str(request)):
|
||||
runpy.run_path(str(BRIDGE))
|
||||
result = json.loads(reply.read_text())
|
||||
return result, commands, reads, queries
|
||||
|
||||
def test_core_load_never_opens_recorded_executable_and_queries_only_after_discard(self):
|
||||
result, commands, reads, queries = self.inspect()
|
||||
self.assertTrue(result["ok"], result)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, ["maps"])
|
||||
self.assertEqual(commands[-4:], ["exec-file /proc/self/exe", "set architecture i386:x86-64", "core-file ../object.snapshot", "file"])
|
||||
self.assertLess(commands.index("set debug-file-directory ./no-libraries"), commands.index("core-file ../object.snapshot"))
|
||||
self.assertIsNone(result["value"]["executable"])
|
||||
self.assertEqual(result["value"]["maps"][0]["current_file_identity"], "unknown")
|
||||
|
||||
def test_failed_guard_core_load_or_discard_never_queries_mappings(self):
|
||||
for command in ["exec-file /proc/self/exe", "set architecture i386:x86-64", "core-file ../object.snapshot", "file"]:
|
||||
with self.subTest(command=command):
|
||||
result, commands, reads, queries = self.inspect(failure=command)
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
if command in ["exec-file /proc/self/exe", "set architecture i386:x86-64"]:
|
||||
self.assertNotIn("core-file ../object.snapshot", commands)
|
||||
|
||||
def test_cross_architecture_uses_the_validated_decoder_profile(self):
|
||||
result, commands, reads, queries = self.inspect(architecture="aarch64-little-endian")
|
||||
self.assertTrue(result["ok"], result)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, ["maps"])
|
||||
self.assertLess(commands.index("set architecture aarch64"), commands.index("core-file ../object.snapshot"))
|
||||
|
||||
def test_unknown_architecture_refuses_before_loading_core(self):
|
||||
result, commands, reads, queries = self.inspect(architecture="unknown")
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(result["reason"], "unsupported")
|
||||
self.assertNotIn("core-file ../object.snapshot", commands)
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
|
||||
def test_wrong_connection_or_remaining_symbols_never_queries_mappings(self):
|
||||
for options in [{"connection": "native"}, {"symbols": "unexpected-symbols"}]:
|
||||
with self.subTest(options=options):
|
||||
result, _, reads, queries = self.inspect(**options)
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(reads, [])
|
||||
self.assertEqual(queries, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+37
-11
@@ -103,9 +103,10 @@ that every class or engine operation will load successfully. Discovery limits
|
||||
each ZIP metadata read to 8 MiB before allocation; a larger directory reports
|
||||
the exhausted read budget and the selected JAR path.
|
||||
|
||||
The current metadata bridge is verified on macOS arm64 with OpenJDK 21 and the
|
||||
public Appium ApiDemos fixture. The POSIX adapter also supports Linux; the new
|
||||
bridge has not yet undergone real verification on Linux/JDK 25.
|
||||
The filtered class-page bridge (protocol 2) is verified on Linux x86-64 with
|
||||
Temurin 21, public Appium ApiDemos 6.0.18 and NewPipe 0.29.1 APKs. Earlier
|
||||
metadata-bridge records covered macOS arm64/OpenJDK 21 and Windows x64/JDK 21;
|
||||
this protocol revision has not been rerun on those hosts or Linux/JDK 25.
|
||||
Windows x64 uses an owned Job Object with bidirectional stdio, atomic job
|
||||
assignment and kill-on-owner-close. Real Windows verification uses JDK 21 and
|
||||
the pinned JADX 0.7.1 JAR; see the real verification lane below.
|
||||
@@ -146,10 +147,20 @@ queries accept the same `overload_index`; the bridge resolves the selected
|
||||
method node and the result reports its index and displayed signature. These
|
||||
indices are tied to the current artifact and engine, not stable across builds.
|
||||
|
||||
The engine's smali fallback also joins same-name overloads; REA rejects that
|
||||
fallback for an overloaded method instead of attributing all bodies to one
|
||||
selected overload. Native/abstract methods with no decompiled body report
|
||||
`body_status: "not_available"` without inferring which implementation flag caused it.
|
||||
Smali fallbacks select one complete block using the original method name and DEX
|
||||
descriptor, before applying the source byte limit. This preserves the selected
|
||||
overload even when Java code generation renames a method. If the selected Java
|
||||
body was omitted, including a method merged into a synthetic bridge, REA returns
|
||||
its original smali with the `java_body_unavailable` marker. The marker describes
|
||||
missing Java; it does not infer why the method was omitted or name a replacement.
|
||||
Unresolved or duplicate disassembly identities fail instead of attributing another
|
||||
method's instructions to the selection.
|
||||
|
||||
Native and abstract methods are identified from access flags
|
||||
captured before code generation. They report `body_status: "not_available"`, an
|
||||
empty source, and the `native_method` or `abstract_method` marker; their declarations
|
||||
are not passed through the engine's source slicer. Other missing decompiled bodies
|
||||
do not establish either access flag.
|
||||
|
||||
Static initializers (`<clinit>`) return smali with the
|
||||
`static_initializer_disassembly` marker. Java decompilation can move their
|
||||
@@ -163,7 +174,10 @@ the same source byte limit and explicit truncation reporting.
|
||||
permissions and class/resource counts. Summary fields are extracted by the
|
||||
upstream engine and should be checked against the retained XML when needed.
|
||||
Signature verification is explicitly `not_performed`.
|
||||
- Class search consumes all pages and checks their counts. An empty `query`
|
||||
- Class search filters the engine's cached top-level names before transferring
|
||||
pages, then follows contiguous inventory positions without rescanning earlier
|
||||
rows. It checks selection, scope, examined ranges and counts before reporting
|
||||
complete coverage. Complete producer replies stay in Evidence; an empty `query`
|
||||
inventories all class-name rows; class names are decompiler representations.
|
||||
Repeated names remain repeated in `matches` so the inventory row count and
|
||||
producer evidence stay visible. `duplicate_names` identifies matching names
|
||||
@@ -233,8 +247,12 @@ rea project-android-application-graph '{"inventory_evidence":[<inventory_artifac
|
||||
```
|
||||
|
||||
The projection reports exact component paths and hashes, runtime-family hints,
|
||||
and path-based bridge hypotheses. It does not decode DEX or claim observed
|
||||
runtime calls. Framework hints match complete native library names, such as
|
||||
and path-based bridge hypotheses. File and slice occurrences supply component
|
||||
endpoints; directory trees and symlinks retain their identities in the source
|
||||
inventory without being classified as code files. It does not decode DEX or
|
||||
claim observed runtime calls. DEX/JVM runtime hints summarize the classified
|
||||
bytecode component lists. Framework hints match complete native library names,
|
||||
such as
|
||||
`libreactnativejni.so`, `libreactnative.so`, `libhermes.so`,
|
||||
`libhermes_executor.so`, `libflutter.so`, and `libunity.so`; unrelated names
|
||||
such as `libcommunity.so` and `libreactivity.so` do not identify those families.
|
||||
@@ -299,7 +317,15 @@ settings. Evidence reports the worker's observed `Runtime.maxMemory()` rounded
|
||||
down to MiB and retains exact bytes, Java version and visible processors in its
|
||||
raw runtime response. The observed allocatable heap can differ from configured
|
||||
`-Xmx`, especially with Serial GC. Heap is not a total RSS cap: the JVM also
|
||||
uses native memory. Independent REA instances have independent queues. Whole
|
||||
uses native memory. A typed JVM memory allocation failure during APK loading
|
||||
reports `resource_constraint` with the observed maximum heap in bytes. Known
|
||||
heap failures name `REA_JADX_HEAP_MIB` in remediation; other memory regions
|
||||
remain unknown. A healthy doctor configuration check does not establish that
|
||||
any particular APK fits. Failures before the bridge can report a typed result
|
||||
and upstream errors from other operations retain their existing diagnostics;
|
||||
error text alone does not establish memory exhaustion.
|
||||
|
||||
Independent REA instances have independent queues. Whole
|
||||
operations have a **120-second** execution deadline by default, starting after
|
||||
they reach the queue's front. For a known slow cold load, such as a large
|
||||
multidex APK, select another finite deadline:
|
||||
|
||||
@@ -24,6 +24,30 @@ missing launcher reports `capability_unavailable` with the
|
||||
`binary_session` uses the same host probe for tool availability; it does not
|
||||
validate a particular APK or guarantee that the selected input can be decoded.
|
||||
|
||||
Client inspection and decode results disclose the following reported-version
|
||||
advisory windows without refusing otherwise compatible launchers:
|
||||
|
||||
| Advisory | Reported version window | Host scope |
|
||||
| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ---------- |
|
||||
| [CVE-2024-21633](https://github.com/iBotPeaches/Apktool/security/advisories/GHSA-2hqv-2xv4-5h5w) | Before 2.9.2 | All |
|
||||
| [GHSA-vgwr-4w3p-xmjv](https://github.com/iBotPeaches/Apktool/security/advisories/GHSA-vgwr-4w3p-xmjv) | Before 2.9.3 | Windows |
|
||||
| [CVE-2026-39973](https://github.com/iBotPeaches/Apktool/security/advisories/GHSA-m8mh-x359-vm8m) | 3.0.0 release line through versions before 3.0.2 | All |
|
||||
|
||||
The 2026 advisory names 3.0.1 and its 3.0.2 fix. Inclusion of 3.0.0 is
|
||||
source-derived: [tagged 3.0.0](https://github.com/iBotPeaches/Apktool/blob/f770b39e425af4fa6ac4ca1e9ea6c4824339c6d3/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/ResTypeSpec.java)
|
||||
passes type names through unchanged; the [3.0.2 patch](https://github.com/iBotPeaches/Apktool/commit/65dd8480dfcb63068562ffaa527f71bb0a9f772c)
|
||||
validates those names at their owner before they reach the output-path producer.
|
||||
Prereleases before the fixed release are conservatively disclosed; the exact
|
||||
`-dirty` downstream marker is compared as a release while preserving its spelling.
|
||||
This is version guidance, not verification of a particular prerelease or decoder.
|
||||
|
||||
A launcher version does not prove the decoder's contents, execution platform,
|
||||
downstream backports, or overall safety. Windows applicability uses the selected
|
||||
local host; an opaque wrapper may run another engine. A missing warning means
|
||||
only that none of these version/host windows matched. Verify the selected
|
||||
decoder's patches before decoding untrusted APKs; the compatibility floor above
|
||||
is not a security recommendation. REA does not fetch advisories at runtime.
|
||||
|
||||
On Windows, a `.bat` or `.cmd` launcher (such as Chocolatey's `apktool.bat`)
|
||||
runs through an explicit `cmd.exe /d /s /c` call. That call has no AutoRun,
|
||||
no delayed expansion, and quotes every argument. A launcher path that
|
||||
@@ -38,13 +62,15 @@ reports `capability_unavailable`.
|
||||
rea inspect-apktool-client
|
||||
rea decode-android-resources /targets/Example.apk
|
||||
rea decode-android-resources /targets/Example.apk --locale de
|
||||
rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR
|
||||
rea decode-android-resources /targets/Example.apk --no-strings
|
||||
```
|
||||
|
||||
Equivalent MCP methods are `inspect_apktool_client` and
|
||||
`decode_android_resources`. The decode request takes `path`, an optional
|
||||
`locale` (project `res/values-<locale>/strings.xml` instead of the default
|
||||
table), and `include_strings` (default true). Regional locales such as `pt-BR`
|
||||
primary `locale` (project `res/values-<locale>/strings.xml` instead of the default
|
||||
table), `locales` (repeatable array of additional localized string tables to project
|
||||
from the same single decode pass), and `include_strings` (default true). Regional locales such as `pt-BR`
|
||||
select Android's `values-pt-rBR`; Android qualifiers such as `pt-rBR` and
|
||||
`b+zh+Hans+CN` may also be supplied directly.
|
||||
|
||||
@@ -52,6 +78,8 @@ select Android's `values-pt-rBR`; Android qualifiers such as `pt-rBR` and
|
||||
|
||||
- **An owned APK snapshot is digested and decoded.** `target.sha256` and
|
||||
`target.bytes` bind the observation to the exact APK that was decoded.
|
||||
The Evidence subject carries the same snapshot digest and caller-selected
|
||||
path, including when the original file changes during decoding.
|
||||
- **Metadata comes from apktool.yml as apktool printed it.** Version codes
|
||||
keep their own spelling (`'7'` versus `7`); `package_name` is read from
|
||||
the decoded manifest, not inferred.
|
||||
@@ -59,9 +87,28 @@ select Android's `values-pt-rBR`; Android qualifiers such as `pt-rBR` and
|
||||
truncated when larger.
|
||||
- **Strings are projected verbatim.** Escaped entities stay escaped, and
|
||||
entries containing nested markup (`<b>`, `<xliff:g>`) are counted as
|
||||
unparsed rather than flattened — the limitation names the count. The
|
||||
default table is bounded to 2000 entries with partial coverage when more
|
||||
exist; `locales` lists every `values-<locale>` that has a strings table.
|
||||
unparsed rather than flattened — the limitation names the examined count.
|
||||
Malformed XML and a wrong document root are whole-document projection
|
||||
failures: the result is partial, the failure reason is disclosed, and entry
|
||||
counts remain unknown. They are not counted as one unsupported string.
|
||||
The default table is bounded to 2000 entries with partial coverage when more
|
||||
exist. `locales` lists observed regular `strings.xml` tables whose exact
|
||||
`values-<locale>` directory is selectable by the request's locale grammar.
|
||||
Locale suffixes allow up to 23 ASCII characters, derived from Apktool's
|
||||
language, script, region, and variant fields; for example,
|
||||
`b+sr+Latn+RS+REVISED` selects that exact table.
|
||||
Other table directories, including combined qualifiers such as
|
||||
`values-en-rUS-v21` and `values-mcc310-fr`, are named in limitations with
|
||||
their source spelling. They are not advertised as selectable locales;
|
||||
REA does not infer Android's runtime resource matching. Non-regular
|
||||
strings-table paths are disclosed with partial coverage. Discovery uses
|
||||
the same bounded traversal as the file count; filesystem failures remain
|
||||
failures rather than being interpreted as missing tables.
|
||||
When multiple tables are requested with `locales`, `string_tables` records
|
||||
each projected table with its locale, status (`present`, `missing`,
|
||||
`oversized`, `malformed`, or `skipped`), entry counts, and coverage,
|
||||
bounded at an aggregate budget of 10,000 strings and 32 MiB XML. When `locales`
|
||||
is omitted, legacy single-table payloads are preserved without adding `string_tables`.
|
||||
- **Smali is skipped by design** (`apktool d --no-src`): Java-source
|
||||
recovery belongs to the JADX family. Resource smali edits and rebuild are
|
||||
a follow-up lane with mutation authority.
|
||||
@@ -81,9 +128,11 @@ select Android's `values-pt-rBR`; Android qualifiers such as `pt-rBR` and
|
||||
coverage and an explicit limit, rather than reported as missing.
|
||||
- `decoded_file_count` is capped at 100,000 files; the cap is reported in
|
||||
the limitations when hit.
|
||||
- Apktool's own decode failures (`AndrolibException`, directory traversal
|
||||
refusals) surface as `unsupported_target` with apktool's reason preserved
|
||||
verbatim.
|
||||
- An ordinary nonzero decode exit surfaces as `unsupported_target` naming
|
||||
the caller-selected APK, with Apktool's diagnostic and captured output.
|
||||
Launcher startup, ownership preparation, signal termination and incomplete
|
||||
output collection remain provider failures; they do not establish that the
|
||||
selected APK is unsupported.
|
||||
|
||||
## Real verification
|
||||
|
||||
|
||||
@@ -60,6 +60,15 @@ states that absence is unknown. Component arrays remain complete when candidate
|
||||
hypotheses are omitted; component arrays still include every component from
|
||||
the supplied inventory pages, and overall coverage is `partial` in that case.
|
||||
|
||||
Inventoried files whose bytes were not read, such as encrypted archive members,
|
||||
are listed in `unavailable_components` with their original path, format,
|
||||
encryption flag, limitations, and null content identity. The same application-root
|
||||
and AppleDouble attribution rules apply. These files make overall coverage
|
||||
`partial`; component absence is unknown even when `inventory_complete` is true.
|
||||
Directories have no file bytes and symlinks remain in `symlinks`, so neither is
|
||||
an unavailable file component. The additive field participates in `projection_id`,
|
||||
including when empty, so Apple projection identifiers change.
|
||||
|
||||
## Application roots
|
||||
|
||||
| Inventory root | Application root |
|
||||
@@ -68,6 +77,15 @@ the supplied inventory pages, and overall coverage is `partial` in that case.
|
||||
| `.app` directory | `.`, the inventoried bundle itself |
|
||||
| Directory, ZIP, or DMG containing apps | Each outermost `<Name>.app` followed by `Contents/` |
|
||||
|
||||
File and slice occurrences supply component endpoints. Directory tree identities
|
||||
remain in source inventory and bundle anatomy, and links remain in `symlinks`.
|
||||
|
||||
Root and layout discovery use known directories and ancestors implied by deeper
|
||||
inventory paths. A leaf file or symlink named `*.app`, `Contents`, or `Versions`
|
||||
does not establish that directory. Directory and authenticated-file identity are
|
||||
selected before applying anatomy case preferences, so a file cannot shadow a
|
||||
folded directory name or a directory shadow a metadata/signature file.
|
||||
|
||||
ZIP archives made with `ditto -c -k --keepParent` report `<Name>.app`. A DMG
|
||||
reports `<image>.dmg/<Volume>/<Name>.app`; DMG child inventory needs the native
|
||||
macOS mounting adapter. When a DMG has only its root identity, the projection is
|
||||
|
||||
@@ -124,6 +124,10 @@ FileBytes digests, every source mapping and its memory digest, relocation record
|
||||
module partitions, relocation fixups and linear coordinates rather than relying
|
||||
on the imported executable SHA alone.
|
||||
|
||||
Expected DOS relocation records retain each fixup's pre-application bytes and
|
||||
applied value. Their memory-byte windows come from the final relocated image,
|
||||
including changes made by later overlapping fixups.
|
||||
|
||||
The result is `verified`, `mismatch`, or `unsupported`. Every check retains its
|
||||
expected and observed values. A memory-digest mismatch identifies the source
|
||||
range's file offset and analysis address; it does not locate the first differing
|
||||
@@ -134,6 +138,10 @@ their contents and DOS runtime semantics are not verified. Independent verificat
|
||||
also covers explicit [raw-binary imports](raw-binary.md); other formats return
|
||||
`unsupported` with measured observations.
|
||||
|
||||
MZ verification retains the initialized image described by the header's page
|
||||
count and streams appended overlay bytes into the complete original and modified
|
||||
source digests. Overlay size does not increase the retained image allocation.
|
||||
|
||||
`read_bytes` returns initialized provider memory, including loader fixups; an
|
||||
unmapped or uninitialized byte ends the read with an explicit returned length and
|
||||
`complete: false`. `address_to_file_offset` uses the provider's source mapping;
|
||||
|
||||
@@ -65,6 +65,14 @@ PDB, or change analyzer settings.
|
||||
|
||||
## Loaded addresses and file offsets
|
||||
|
||||
Ghidra computes overview procedure and string counts from the same inventories
|
||||
used by `list_procedures` and `list_strings`, without transporting those rows
|
||||
solely to count them. Counts include external functions and only Ghidra-defined
|
||||
strings; they do not establish discovery of unidentified functions or undefined
|
||||
string bytes. Complete inventories remain available through the list tools.
|
||||
Overview reuse follows the target/profile snapshot lifetime and is invalidated
|
||||
by session metadata edits.
|
||||
|
||||
`rea analyze <path> --provider ghidra --json` and MCP `binary_overview` report
|
||||
the observed loaded `image_base` in `normalized_result`. They retain each
|
||||
segment's reported `image_base` and `address_space`. A provider that does not
|
||||
|
||||
@@ -45,6 +45,12 @@ The MCP tool `project_harmony_application_graph` takes the same
|
||||
- path-based N-API bridge hypotheses between bytecode and `libs/<abi>` native
|
||||
libraries
|
||||
|
||||
File and slice occurrences supply component and child-package paths; directory
|
||||
trees and symlinks retain their identities in the source inventory without
|
||||
being classified as code files. Ark and JavaScript runtime hints summarize the
|
||||
classified bytecode and JavaScript lists; supported source extensions share the
|
||||
same classification rule as their component rows.
|
||||
|
||||
Every claim is path-presence only. Manifest semantics (module identity,
|
||||
abilities, entry points, permissions, dependencies), Ark bytecode contents and
|
||||
versions, resource resolution, signing verification, and observed native
|
||||
|
||||
@@ -41,7 +41,7 @@ rea function /absolute/path/to/program main --json
|
||||
rea decompile /absolute/path/to/program main --json
|
||||
```
|
||||
|
||||
Open the **original input binary** in REA, not an `.idb` or `.i64` database. Its SHA-256 must match IDA's recorded input hash. REA checks the attached identity and image base around each analysis request. A mismatch or target switch fails with recovery guidance. The original input must remain readable by REA; WSL can use its mounted alias while the GUI runs on Windows.
|
||||
Open the **original input binary** in REA, not an `.idb` or `.i64` database. Its SHA-256 must match IDA's recorded input hash. REA checks the attached identity and image base around each analysis request. A mismatch or target switch fails with recovery guidance. The original input must remain readable by REA; WSL can use its mounted alias while the GUI runs on Windows. Attached profiles, including native mode, refuse universal Mach-O inputs at admission: every slice shares the recorded whole-file hash, so REA cannot verify which slice the GUI loaded. Open a thin file extracted with `lipo -thin` in both IDA and REA, or use headless mode, which analyzes the selected slice.
|
||||
|
||||
REA's close operation releases its MCP connection or proxy. It never saves or closes the existing GUI database. External edits remain visible because results are live and are not replayed from snapshots. These checks do not lock the GUI or prove that the database is unpatched.
|
||||
|
||||
|
||||
+21
-5
@@ -57,14 +57,26 @@ Before applying changes, REA checks your current configuration. The plan lists:
|
||||
Malformed or unsafe existing configuration blocks the whole transaction before
|
||||
Hopper installation or any file write. Declining or pressing Ctrl-C makes no
|
||||
changes. Agent configuration writes preserve unrelated entries and comments,
|
||||
create backups, use atomic replacement, and verify their result. Setup and
|
||||
uninstall retain an existing `.rea.backup` rather than replacing the first snapshot.
|
||||
create backups, stage changes in a private directory, and verify their result.
|
||||
Before publication, they refuse detected changes to the selected configuration.
|
||||
Replacement uses an explicit destination under the selected canonical parent;
|
||||
it does not follow a newly inserted destination symlink. First-run setup uses
|
||||
an exclusive hard link and fails without overwriting a newly created destination;
|
||||
filesystems without hard-link support cannot publish a first-run configuration.
|
||||
Setup and uninstall retain an existing `.rea.backup` rather than replacing the
|
||||
first snapshot.
|
||||
If that snapshot differs from the current configuration, setup and uninstall
|
||||
preserve a verified `.rea.backup.<sha256>.recovery` copy of the current bytes
|
||||
before editing; setup also previews it. Both snapshots remain available.
|
||||
When an edit made a backup, a failed uninstall update or restore, and a failed
|
||||
setup readback or rollback, name the backup path that holds the bytes from just
|
||||
before that edit.
|
||||
If setup cannot verify a newly created configuration, it retains the file and
|
||||
reports manual recovery instead of deleting a pathname that may have changed.
|
||||
For an existing configuration, setup and uninstall recovery refuse a detected
|
||||
replacement or edit and name the recovery backup. Quit clients before editing their configuration;
|
||||
these checks do not lock out another writer modifying the same file in place,
|
||||
or exclude pathname changes between the final check and publication.
|
||||
Setup also retains valid environment overrides in REA's existing server entry,
|
||||
such as `REA_MCP_INPUT_SCHEMA_PROFILE`, `REA_MCP_OUTPUT_SCHEMA_PROFILE`, and
|
||||
`REA_MCP_MAX_RESPONSE_BYTES`.
|
||||
@@ -264,14 +276,14 @@ Register with the variable unset:
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```bash
|
||||
claude mcp add --scope local rea -- npx -y rea-agents@6.5.0 mcp
|
||||
claude mcp add --scope local rea -- npx -y rea-agents@6.7.0 mcp
|
||||
```
|
||||
|
||||
On native Windows, use the same project directory and wrap the package-runner
|
||||
shim so the stdio client can launch it:
|
||||
|
||||
```powershell
|
||||
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.5.0 mcp
|
||||
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.7.0 mcp
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
@@ -1125,6 +1137,10 @@ compiler remains distinguishable from a compiler that exited with an error.
|
||||
The helper compile deadline defaults to 120,000 ms; set
|
||||
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 when
|
||||
a busy host needs longer.
|
||||
On macOS, the Java prerequisite probe waits for this independently bounded
|
||||
preparation before starting its five-second command deadline. Caller cancellation
|
||||
still interrupts preparation, and failed helper cleanup remains owned for retry.
|
||||
Linux process-table preparation remains inside the command deadline.
|
||||
Other rejection values retain their primitive value or an explicit type.
|
||||
Shutdown warnings include the failure
|
||||
kind, message, and diagnostics, while process and temporary-project cleanup
|
||||
@@ -1171,7 +1187,7 @@ For a client that requires manual configuration, use:
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@6.5.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@6.7.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,6 +586,21 @@ not bypass worker heap, execution deadline or parent result admission. Truncated
|
||||
coverage reports both effective node ceilings; omitted relationships remain
|
||||
unknown.
|
||||
|
||||
Unknown records have independent defaults of 100000 across the application and
|
||||
20000 per source. Select `max_semantic_unknowns` and
|
||||
`max_semantic_unknowns_per_file` (CLI: `--max-semantic-unknowns` and
|
||||
`--max-semantic-unknowns-per-file`), each from 1 to 1000000. The parent offers the
|
||||
remaining capacity to the worker, and rejects excess records before adoption.
|
||||
Unknown admission happens before canonical identity and provenance construction.
|
||||
When capacity is exhausted, coverage is partial and truncated and names both
|
||||
effective unknown ceilings. `coverage.omitted_unknown_occurrences` counts examined
|
||||
emission occurrences by family and reason, including repeated emissions. It does
|
||||
not claim distinct discarded identities or count frontiers in unexamined source.
|
||||
Omitted unknown provenance and IDs are not retained. Existing source, node,
|
||||
execution and memory limits continue to determine examined coverage. Raising only
|
||||
a node capacity does not raise the unknown capacity. Graphs without unknown
|
||||
omissions omit this optional coverage field, preserving their existing identities.
|
||||
|
||||
While sources remain, the sequential worker reserves one next compact static
|
||||
checkpoint at its 8 MiB bound with the same 6x retention expansion used at
|
||||
admission. Earlier checkpoints are already charged to retained bytes; the
|
||||
|
||||
@@ -133,8 +133,11 @@ Each operation uses one worker, a 1 GiB address-space ceiling and a 120-second
|
||||
deadline; it accepts up to 64 MiB of total input across all selected files and
|
||||
128 MiB/10000 entries of staged
|
||||
output, with 8 MiB per diagnostic/report stream. Exceeding a limit is an explicit
|
||||
failure with rollback. These limits apply per operation and do not claim aggregate
|
||||
host containment across concurrent callers.
|
||||
failure with rollback. Selected inputs beyond the total input allowance return a
|
||||
non-retryable `resource_constraint` with `resource: "file-size"` and
|
||||
`reported_limits.maximum_input_bytes`/`remaining_input_bytes`; input that is not
|
||||
valid UTF-8 is an `invalid_format` request issue. These limits apply per
|
||||
operation and do not claim aggregate host containment across concurrent callers.
|
||||
|
||||
See [testing.md](testing.md#javascript-source-recovery) for the focused real
|
||||
Wakaru CLI/MCP lane and its source-owned compiler fixtures. Other host platforms
|
||||
|
||||
@@ -109,7 +109,9 @@ The direct observations are:
|
||||
- `Runtime.executionContextCreated`,
|
||||
`Runtime.executionContextDestroyed`, and
|
||||
`Runtime.executionContextsCleared`, establishing context lifecycle within
|
||||
the capture window.
|
||||
the capture window. A created or destroyed event whose context ID is
|
||||
missing or not a safe integer adds no context record; it is retained only
|
||||
as a count reported in `unknowns`.
|
||||
|
||||
The Inspector protocol does not directly expose require/import caller edges,
|
||||
EventEmitter emissions or listener invocation, Electron IPC messages or
|
||||
|
||||
@@ -268,6 +268,16 @@ markup, including UTF-8 and BOM-marked UTF-16 sources. Unidentified text and
|
||||
OpenStep plists, including a data-only value such as `<deadbeef>`, report
|
||||
`unknown` even when plutil decodes their values through an XML conversion.
|
||||
|
||||
A plist explicitly selected through the path input or as the active plist target
|
||||
reports an unreadable or malformed selection as `invalid_request`. A missing or
|
||||
unreadable default plist of another target reports `capability_unavailable`.
|
||||
Permission failures remain `access_denied`; output and cleanup failures retain
|
||||
their own error categories.
|
||||
|
||||
A plist decode refusal includes plutil's diagnostic from either output stream.
|
||||
`details.captured_output` retains bounded stdout/stderr excerpts, observed byte
|
||||
totals and exit status. `truncated: true` means those excerpts are incomplete.
|
||||
|
||||
`inspect_macho.selected_architecture` identifies the exact selected lipo slice
|
||||
and its `container_file_offset` in the input file. A thin Mach-O has a known
|
||||
offset of zero; an unknown universal-slice offset stays null. The Evidence
|
||||
@@ -324,12 +334,16 @@ complete observation.
|
||||
shared/cyclic references, class descriptors, raw serialized fields and
|
||||
malformed/unresolved/nil references. Missing fields stay absent; unknown
|
||||
classes are preserved. Select `root`, `offset` and `limit` explicitly.
|
||||
Object `kind` describes the decoded archive value: data, dates and numbers
|
||||
remain `scalar` when their JSON value uses a `$plist_type` wrapper. An actual
|
||||
dictionary with the same fields remains `dictionary`.
|
||||
UID 0 cannot distinguish conditional nil from ordinary nil. Limits are
|
||||
64 MiB of input, 200,000 objects/references and 128 nesting levels.
|
||||
XML dictionary entries decoded as `__proto__` are omitted with an explicit
|
||||
count because REA results cannot represent that key. XML tag whitespace and
|
||||
comments do not change that handling. Other values and the original archive
|
||||
digest are preserved; key interpretation follows the pinned plist decoder.
|
||||
comments do not change that handling. XML key names combine their text and
|
||||
CDATA segments without trimming or expanding literal CDATA entities. Other
|
||||
values and the original archive digest are preserved.
|
||||
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
||||
compiled storyboard and nib resources into objects, outlets, actions,
|
||||
connections and controller/class names. A full app graph is often several
|
||||
@@ -425,7 +439,11 @@ decrement scrolling and text-value entry, or bounded waits. No global event
|
||||
injection is used. Unsupported AX actions fail explicitly. The result preserves
|
||||
ordered before/after captures and gaps; an action may have occurred before a
|
||||
post-action capture fails. Application state is left as-is; REA does not attempt
|
||||
to restore it.
|
||||
to restore it. Failed or cancelled scenarios return a typed error with the
|
||||
collected report in `partial_observation`, including ordered step outcomes.
|
||||
Cancellation preserves the caller's reason and remains observable through helper
|
||||
cleanup. A later abort does not replace an earlier helper or output failure.
|
||||
Cleanup failures retain their runtime owner until a confirmed cleanup retry.
|
||||
|
||||
Scenarios accept caller-selected action lists and accessibility node counts.
|
||||
Individual waits cannot exceed the operation's 180-second deadline, and the
|
||||
@@ -579,6 +597,10 @@ digests identify the original serialized bytes.
|
||||
## Keyed archive integer precision
|
||||
|
||||
The archive reader observes XML number element types and binary number markers.
|
||||
XML values and their number facts are interpreted together from the validated
|
||||
DOM. Hexadecimal `<integer>` text, including an optional sign, retains its value;
|
||||
`<real>` text and CDATA segments are combined without inferring text from nested
|
||||
elements. The XML path does not serialize or reparse character references.
|
||||
Integers beyond the exact range of a JSON number are reported with the original
|
||||
decimal text as `{ "$plist_type": "integer", "decimal": "<exact digits>" }` when
|
||||
that source value can be associated unambiguously. Integral real values remain
|
||||
@@ -616,6 +638,30 @@ or a negative year, is reported as `{ "$plist_type": "date", "iso8601": null }`
|
||||
with a counted limitation; the rest of the plist or archive is still decoded.
|
||||
`inspect_plist` and Interface Builder decoding use the same projection.
|
||||
|
||||
A negative-zero real is reported as `{ "$plist_type": "real", "value": "-0" }`
|
||||
because a JSON number cannot carry the sign of zero. `inspect_plist` and
|
||||
`inspect_keyed_archive` count these values in a limitation, and Interface
|
||||
Builder node attributes use the same value. Positive zero and integer zero,
|
||||
including an `<integer>-0</integer>` literal, remain the number `0`.
|
||||
|
||||
Known non-finite reals are reported as
|
||||
`{ "$plist_type": "real", "value": "nan" | "+infinity" | "-infinity" }`.
|
||||
These are canonical numeric labels, not a claim about original source spelling.
|
||||
Binary IEEE reals retain NaN versus signed infinity; NaN payload bits and sign
|
||||
are not represented in the JSON value, and the original bytes remain the source
|
||||
for those details. XML CFPropertyList literals (`nan`, `+infinity`, `-infinity`,
|
||||
`infinity`, `inf`, `+inf`, `-inf`, case-insensitively) decode to the same known
|
||||
values. Case and aliases normalize; original archive bytes retain source text.
|
||||
|
||||
XML numeric text whose identity the decoder cannot establish, including
|
||||
unrecognized non-finite text and finite literals that overflow the numeric
|
||||
representation, remains `{ "$plist_type": "real", "value": null }` with a
|
||||
counted limitation. The XML decoder marks these values explicitly before shared
|
||||
projection, so they cannot be confused with known binary IEEE values.
|
||||
Incomplete numeric markup retains its existing compatibility behavior described
|
||||
above. Known non-finite and unresolved numeric values have separate counts.
|
||||
Native `inspect_plist` uses these same labels for plutil's XML conversion.
|
||||
|
||||
## Locate a byte signature
|
||||
|
||||
Use `search_bytes` after opening a target with Ghidra to locate a known constant
|
||||
|
||||
@@ -58,6 +58,11 @@ tools read that copy, which is hashed again after analysis. Capture metadata and
|
||||
literal field values are producer observations; address/protocol counts and DNS
|
||||
associations are derivations. Capture Evidence has historical-reference authority.
|
||||
|
||||
`capture_start` and `capture_end` are RFC 3339 UTC instants derived from
|
||||
`capinfos -S` epoch seconds, retaining all producer fractional digits (up to
|
||||
nanoseconds). Empty captures, malformed timestamps and instants outside years
|
||||
0000–9999 remain `null`; host-local timestamps without a zone are not inferred.
|
||||
|
||||
Addresses count once per packet, including loopback and nested IP occurrences.
|
||||
DNS addresses are associated only with one question in the same response packet;
|
||||
additional records may be included, so this does not prove final resolution.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · **العربية** · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: هندسة عكسية لأي شيء
|
||||
|
||||
@@ -274,7 +274,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## سجل النجوم
|
||||
|
||||
🎉 **250,000 نجمة على GitHub — شكرًا لكم!**
|
||||
🎉 **300,000 نجمة على GitHub — شكرًا لكم!**
|
||||
|
||||
شكرًا لكل من يستخدم REA، ويبلّغ عن الأخطاء، ويقترح ميزات، ويختبر إصدارات البناء، ويساهم بالإصلاحات.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · **Deutsch** · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Alles reverse-engineeren
|
||||
|
||||
@@ -272,7 +272,7 @@ Melde Sicherheitslücken gemäß [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Sternverlauf
|
||||
|
||||
🎉 **250.000 GitHub-Sterne – vielen Dank!**
|
||||
🎉 **300.000 GitHub-Sterne – vielen Dank!**
|
||||
|
||||
Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds testen und Fehlerbehebungen beitragen.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md) · **Ελληνικά**
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md) · **Ελληνικά** · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -285,7 +285,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Ιστορικό αστεριών
|
||||
|
||||
🎉 **250.000 αστέρια στο GitHub — σας ευχαριστούμε!**
|
||||
🎉 **300.000 αστέρια στο GitHub — σας ευχαριστούμε!**
|
||||
|
||||
Ευχαριστούμε όλους όσοι χρησιμοποιούν το REA, αναφέρουν σφάλματα, ζητούν νέες λειτουργίες, δοκιμάζουν builds και συνεισφέρουν διορθώσεις.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · **Español** · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Ingeniería inversa de cualquier cosa
|
||||
|
||||
@@ -272,7 +272,7 @@ Informa de vulnerabilidades siguiendo [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Historial de estrellas
|
||||
|
||||
🎉 **250.000 estrellas en GitHub: ¡gracias!**
|
||||
🎉 **300.000 estrellas en GitHub: ¡gracias!**
|
||||
|
||||
Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban compilaciones y contribuyen con correcciones.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center" dir="rtl">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · **فارسی** · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: مهندسی معکوس هر چیزی
|
||||
|
||||
@@ -257,7 +257,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## تاریخچهٔ ستارهها
|
||||
|
||||
🎉 **۲۵۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
|
||||
🎉 **۳۰۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
|
||||
|
||||
از همهٔ کسانی که از REA استفاده میکنند، خطاها را گزارش میدهند، قابلیتهای جدید پیشنهاد میکنند، buildها را آزمایش میکنند و در بهبود پروژه مشارکت دارند سپاسگزاریم.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · **Français** · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA : rétro-ingénierie de tout
|
||||
|
||||
@@ -276,7 +276,7 @@ Signalez les vulnérabilités via [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Historique des étoiles
|
||||
|
||||
🎉 **250 000 étoiles GitHub — merci !**
|
||||
🎉 **300 000 étoiles GitHub — merci !**
|
||||
|
||||
Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent des fonctionnalités, testent les builds et contribuent des correctifs.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · **Bahasa Indonesia** · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Rekayasa Balik Apa Pun
|
||||
|
||||
@@ -297,7 +297,7 @@ Laporkan kerentanan melalui [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Riwayat Bintang
|
||||
|
||||
🎉 **250.000 bintang GitHub, terima kasih!**
|
||||
🎉 **300.000 bintang GitHub, terima kasih!**
|
||||
|
||||
Terima kasih kepada semua orang yang menggunakan REA, melaporkan bug, meminta fitur, menguji build, dan berkontribusi dalam perbaikan.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · **Italiano**
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -276,7 +276,7 @@ Segnala vulnerabilità tramite [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Cronologia delle stelle
|
||||
|
||||
🎉 **250.000 stelle su GitHub — grazie!**
|
||||
🎉 **300.000 stelle su GitHub — grazie!**
|
||||
|
||||
Grazie a tutti coloro che usano REA, segnalano bug, propongono funzionalità, testano le build e contribuiscono con correzioni.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · **日本語** · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA:あらゆるものをリバースエンジニアリング
|
||||
|
||||
@@ -274,7 +274,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## スター履歴
|
||||
|
||||
🎉 **GitHub スター 250,000 件、ありがとうございます!**
|
||||
🎉 **GitHub スター 300,000 件、ありがとうございます!**
|
||||
|
||||
REA の利用、バグ報告、機能の要望、ビルドのテスト、修正への貢献に感謝します。
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · **한국어** · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: 무엇이든 리버스 엔지니어링
|
||||
|
||||
@@ -283,7 +283,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 스타 기록
|
||||
|
||||
🎉 **GitHub 스타 250,000개, 감사합니다!**
|
||||
🎉 **GitHub 스타 300,000개, 감사합니다!**
|
||||
|
||||
REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · **മലയാളം**
|
||||
|
||||
# REA: എന്തും Reverse Engineer ചെയ്യുക
|
||||
|
||||
### Binaries, applications, runtime behavior എന്നിവയുടെ Reverse Engineering-നായി ഒരൊറ്റ MCP.
|
||||
|
||||
**നിങ്ങൾക്ക് ഇഷ്ടപ്പെട്ട ഒരു feature കണ്ടോ? അത് എങ്ങനെ പ്രവർത്തിക്കുന്നു എന്ന് binary തലത്തിലേക്ക് വരെ മനസ്സിലാക്കൂ.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](../mcp-contracts.md#generated-catalog)
|
||||
[](#what-you-can-analyze)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
[](../../LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[വെബ്സൈറ്റ്](https://rea.tools/) · [ഗൈഡുകൾ](https://rea.tools/guides/) · [ഉദാഹരണങ്ങൾ](https://rea.tools/showcase/)**
|
||||
|
||||
[ദ്രുതാരംഭം](#ദ്രുതാരംഭം) · [REA എങ്ങനെ പ്രവർത്തിക്കുന്നു](#rea-എങ്ങനെ-പ്രവർത്തിക്കുന്നു) · [എന്തൊക്കെ വിശകലനം ചെയ്യാം](#എന്തൊക്കെ-വിശകലനം-ചെയ്യാം) · [ഉദാഹരണങ്ങൾ](#ഉദാഹരണങ്ങൾ) · [പതിവുചോദ്യങ്ങൾ](#പതിവുചോദ്യങ്ങൾ) · [ഡോക്യുമെന്റേഷൻ](#ഡോക്യുമെന്റേഷൻ)
|
||||
|
||||
<code>npx rea-agents setup</code>
|
||||
|
||||
</div>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> REA ലഭ്യമാക്കേണ്ടത് [morluto/rea](https://github.com/morluto/rea),
|
||||
> [rea.tools](https://rea.tools/), അല്ലെങ്കിൽ [rea-agents npm package](https://www.npmjs.com/package/rea-agents) എന്നിവയിൽ നിന്നാണ്.
|
||||
> ഈ ഔദ്യോഗിക ഉറവിടങ്ങളിലെ ഇൻസ്റ്റലേഷൻ, റിലീസ് ലിങ്കുകൾ പിന്തുടരുക. സമാനമായ
|
||||
> repository പേരോ പകർത്തിയ README-യോ മാത്രം ഒരു download ഔദ്യോഗികമാണെന്ന് ഉറപ്പാക്കുന്നില്ല.
|
||||
|
||||
<div align="center">
|
||||
|
||||
<br />
|
||||
|
||||
<img src="../assets/rea-hopper-analysis.png" alt="ഒരു native binary പരിശോധിക്കുന്നതിനിടെ Hopper-ൽ REA അതിന്റെ analysis bridge ആരംഭിക്കുന്നു" width="1200" />
|
||||
|
||||
<br /><br />
|
||||
|
||||
<table aria-label="REA community">
|
||||
<tr>
|
||||
<td align="center" width="360">
|
||||
<a href="https://discord.gg/GkcryMnJDM">
|
||||
<img src="../assets/discord.svg" height="42" alt="Discord" /><br />
|
||||
<strong>Reverse Engineering കമ്മ്യൂണിറ്റിയിൽ ചേരുക</strong>
|
||||
</a><br />
|
||||
<sub>Discord · ചോദ്യോത്തരങ്ങൾ · അനുഭവങ്ങളും കണ്ടെത്തലുകളും പങ്കിടുക</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
നിങ്ങളുടെ സ്വന്തം product-ൽ ഉൾപ്പെടുത്താൻ ആഗ്രഹിക്കുന്ന ഒരു feature ഏതെങ്കിലും app-ൽ കണ്ടോ? REA ഉപയോഗിച്ച് അത് പരിശോധിക്കാൻ നിങ്ങളുടെ agent-നോട് ആവശ്യപ്പെടുക. source code ഇല്ലാതെയും അതിന് app പരിശോധിക്കാനും feature എങ്ങനെ പ്രവർത്തിക്കുന്നുവെന്ന് വിശദീകരിക്കാനും അതിനുള്ള തെളിവുകൾ കാണിക്കാനും നിങ്ങളുടെ project-നായി അതിന് സമാനമായൊരു പതിപ്പ് നിർമ്മിക്കാനും കഴിയും.
|
||||
|
||||
Native binaries, JavaScript, Electron apps, .NET assemblies, websites എന്നിവ പരിശോധിക്കുന്നതിനുള്ള tools-ലേക്ക് REA നിങ്ങളുടെ agent-നെ ബന്ധിപ്പിക്കുന്നു. ഇതേ tools നിങ്ങളുടെ terminal-ൽ നിന്നും ഉപയോഗിക്കാം. വിശകലനം local ആയി നടക്കുന്നു; ഓരോ നിഗമനത്തിനും പിന്നിലെ തെളിവുകളും പരിമിതികളും ഫലങ്ങളിൽ ഉൾപ്പെടുന്നു.
|
||||
|
||||
Setup നിങ്ങളുടെ agent-ിൽ REA രജിസ്റ്റർ ചെയ്യുകയും അനുയോജ്യമായ workflow നിർദ്ദേശങ്ങൾ ഇൻസ്റ്റാൾ ചെയ്യുകയും ചെയ്യുന്നു. Native analysis-നായി നിലവിലുള്ള Hopper, Ghidra, അല്ലെങ്കിൽ IDA installation ഉപയോഗിക്കാം; അനുമതി നൽകിയാൽ setup-ന് Hopper ഇൻസ്റ്റാൾ ചെയ്യാനും കഴിയും. Static JavaScript analysis-ന് native analysis engine ആവശ്യമില്ല.
|
||||
|
||||
> **[REA വെബ്സൈറ്റ് സന്ദർശിക്കുക](https://rea.tools/)** — setup നിർദ്ദേശങ്ങൾക്കും ചിത്രങ്ങളോടുകൂടിയ ഗൈഡുകൾക്കും യഥാർത്ഥ case study-കൾക്കും.
|
||||
|
||||
## ദ്രുതാരംഭം
|
||||
|
||||
Linux-ൽ Ghidra, JADX, firmware tool launches, static JavaScript/Electron analysis എന്നിവയ്ക്ക് process ownership പരിശോധിക്കാൻ REA-യുടെ `PATH`-ൽ procps-compatible `ps` ആവശ്യമാണ്. പരിഹാര നടപടികൾ `docs/installation.md`-യിലെ "Linux owned-process prerequisite" എന്ന വിഭാഗത്തിൽ ലഭ്യമാണ്.
|
||||
|
||||
### നിങ്ങളുടെ agent സജ്ജമാക്കുക
|
||||
|
||||
Node.js-ഉം npm-ഉം ഇൻസ്റ്റാൾ ചെയ്തശേഷം ഈ command പ്രവർത്തിപ്പിക്കുക:
|
||||
|
||||
```bash
|
||||
npx rea-agents setup
|
||||
```
|
||||
|
||||
നിങ്ങൾ ഉപയോഗിക്കുന്ന agents തിരഞ്ഞെടുക്കുക, നിർദ്ദേശിച്ച മാറ്റങ്ങൾ പരിശോധിക്കുക, തുടർന്ന് അവയ്ക്ക് അനുമതി നൽകുക. നിലവിലുള്ള configuration-ന്റെ backup-ുകൾ സൂക്ഷിച്ചുകൊണ്ട് setup, REA-യുടെ MCP server-ഉം അനുയോജ്യമായ workflow നിർദ്ദേശങ്ങളും ചേർക്കും. അതിനുശേഷം നിങ്ങളുടെ agent വീണ്ടും ആരംഭിക്കുക.
|
||||
|
||||
Claude Code, Codex, Cursor, Gemini CLI, Grok Build, കൂടാതെ [മറ്റ് agents](../installation.md#supported-agents) എന്നിവയെ setup പിന്തുണയ്ക്കുന്നു. provider configuration-നും manual MCP registration-നും [installation and setup](../installation.md) കാണുക.
|
||||
|
||||
### നിങ്ങളുടെ agent-നോട് ആവശ്യപ്പെടുക
|
||||
|
||||
```text
|
||||
Notes app-ൽ search എങ്ങനെ പ്രവർത്തിക്കുന്നു എന്ന് മനസ്സിലാക്കി, അതിനുള്ള തെളിവുകൾ കാണിച്ചുതരികയും എന്റെ project-നായി സമാനമായൊരു feature നിർമ്മിക്കുകയും ചെയ്യുക.
|
||||
```
|
||||
|
||||
`Notes` എന്നതിന് പകരം നിങ്ങൾ പരിശോധിക്കാൻ ആഗ്രഹിക്കുന്ന app-ിന്റെയും മനസ്സിലാക്കേണ്ട feature-ിന്റെയും പേരുകൾ നൽകുക.
|
||||
|
||||
### Terminal ഉപയോഗിക്കുക
|
||||
|
||||
Extract ചെയ്ത JavaScript/Electron app directory അല്ലെങ്കിൽ ASAR പരിശോധിക്കുക:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
ഫലത്തിൽ modules, imports, Electron boundaries, അവയ്ക്കുള്ള തെളിവുകൾ എന്നിവ ഉൾപ്പെടും. `/absolute/path/to/app` എന്നതിന് പകരം നിങ്ങളുടെ target-ന്റെ path നൽകുക; Windows-ൽ ഉദാഹരണത്തിന് `"D:/apps/example"` ഉപയോഗിക്കാം.
|
||||
|
||||
സാധാരണ ഉപയോഗത്തിനായി `rea` command ഇൻസ്റ്റാൾ ചെയ്യാൻ:
|
||||
|
||||
```bash
|
||||
npm install --global rea-agents
|
||||
rea --help
|
||||
```
|
||||
|
||||
Native analysis നടത്തുന്നതിന് മുമ്പ് ഒരു provider ക്രമീകരിക്കുക. Native commands, provider തിരഞ്ഞെടുക്കൽ, snapshots, scripting എന്നിവയ്ക്കായി [CLI and Evidence guide](../cli.md) കാണുക.
|
||||
|
||||
### REA അപ്ഡേറ്റ് ചെയ്യുക
|
||||
|
||||
REA-യിൽ പതിവായി മാറ്റങ്ങൾ വരുന്നു; പുതിയ releases-ൽ പല bug fixes-ഉം ഉൾപ്പെടുന്നു. നിങ്ങളുടെ installation ഏറ്റവും പുതിയ പതിപ്പിലേക്ക് അപ്ഡേറ്റ് ചെയ്യുക.
|
||||
|
||||
`npm install --global` അല്ലെങ്കിൽ `bun add --global` ഉപയോഗിച്ച് ഇൻസ്റ്റാൾ ചെയ്ത CLI-ക്കായി:
|
||||
|
||||
```bash
|
||||
rea update
|
||||
```
|
||||
|
||||
Bun-ന്റെ global updates-ന് Bun 1.2.17 അല്ലെങ്കിൽ അതിനുശേഷമുള്ള പതിപ്പ് ആവശ്യമാണ്. പഴയ Bun പതിപ്പാണെങ്കിൽ `rea update` പ്രവർത്തിപ്പിക്കുന്നതിന് മുമ്പ് Bun സ്വമേധയാ അപ്ഗ്രേഡ് ചെയ്യുക.
|
||||
|
||||
Agent registrations-ഉം skill-ഉം പുതുക്കാൻ, update കാണിക്കുന്ന setup command പ്രവർത്തിപ്പിക്കുക.
|
||||
|
||||
`npx` ഉപയോഗിക്കുന്നുവെങ്കിൽ, agent setup പുതുക്കാൻ:
|
||||
|
||||
```bash
|
||||
npx rea-agents@latest setup
|
||||
```
|
||||
|
||||
Setup-ൽ വരുത്തുന്ന മാറ്റങ്ങൾ പരിശോധിച്ച് നിങ്ങളുടെ agent വീണ്ടും ആരംഭിക്കുക. ഒറ്റത്തവണ മാത്രം ഉപയോഗിക്കുന്ന CLI commands-നായി command-ന് മുമ്പ് `npx rea-agents@latest` ഉപയോഗിക്കുക. Bun ഉപയോഗിക്കുമ്പോൾ `npx rea-agents@latest`-ന് പകരം `bunx rea-agents@latest` ഉപയോഗിക്കുക.
|
||||
|
||||
## REA എങ്ങനെ പ്രവർത്തിക്കുന്നു
|
||||
|
||||
Target പരിശോധിക്കാനും പ്രസക്തമായ code-ന്റെ പാത പിന്തുടരാനും നിങ്ങളുടെ agent MCP വഴി REA-യെ വിളിക്കുന്നു. REA കണ്ടെത്തലുകളും അവയ്ക്കുള്ള തെളിവുകളും തിരികെ നൽകുന്നു. തുടർന്ന് agent കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കാനും പെരുമാറ്റം വിശദീകരിക്കാനും implementation എഴുതുകയും പരിശോധിക്കുകയും ചെയ്യാനും അവ ഉപയോഗിക്കുന്നു. CLI commands-ഉം ഇതേ workflows ഉപയോഗിക്കുന്നു.
|
||||
|
||||

|
||||
|
||||
[പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രം തുറക്കുക](../../website/public/assets/figures/rea-investigation-flow.svg).
|
||||
|
||||
<a id="current-status"></a>
|
||||
|
||||
## എന്തൊക്കെ വിശകലനം ചെയ്യാം
|
||||
|
||||
REA-യ്ക്ക് Node.js 22.x (>=22.19), 24.x (>=24.11), അല്ലെങ്കിൽ 26+ പതിപ്പും npm-ഉം ആവശ്യമാണ്. Target അനുസരിച്ച് അധിക tools-ഉം host support-ഉം വ്യത്യാസപ്പെടും:
|
||||
|
||||
| Target | REA നൽകുന്ന ഫലങ്ങൾ | ആവശ്യകതകളും ഗൈഡും |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Native binaries | Pseudocode, assembly, strings, symbols, calls, references | Hopper, Ghidra അല്ലെങ്കിൽ IDA; [native analysis](../native-investigation.md) |
|
||||
| Offline ELF layout | Sections, segments, യഥാർത്ഥ symbols/relocations, static mitigation candidates | Linux x64-ൽ caller നൽകുന്ന pwntools; [binary diagnostics](../binary-diagnostics.md) |
|
||||
| Go binaries | ഉൾച്ചേർത്ത compiler version, modules, replacements, build settings | Built-in static ELF/PE/thin Mach-O പരിശോധന; Go installation ആവശ്യമില്ല |
|
||||
| Rust binaries | ഉൾച്ചേർത്ത cargo-auditable crate പേരുകൾ, versions, sources, dependency edges | Built-in static ELF/PE/thin Mach-O പരിശോധന; [Rust binaries](../rust-binaries.md) |
|
||||
| WASM artifact | Validation, sections, കൃത്യമായ import/export WAT രൂപങ്ങൾ, decoded WAT | Caller നൽകുന്ന WABT 1.0.42; `inspect_wasm_artifact` |
|
||||
| EVM bytecode | Dispatch selectors, byte offsets, അനുമാനിച്ച arguments, mutability | Linux x64-ൽ local raw/hex carrier; [offline EVM guide](../evm-bytecode.md) |
|
||||
| SQLite databases | Tables, columns, indexes, views, triggers, തിരഞ്ഞെടുത്ത സാധാരണ table rows | Local database/WAL snapshot; native SQLite പരിമിതികളോടെ Node.js 24.x >=24.15 അല്ലെങ്കിൽ 26+ |
|
||||
| Recorded Linux crashes | Raw notes, രേഖപ്പെടുത്തിയ ഓരോ thread-ന്റെയും registers/signals, ഐച്ഛിക mapping candidates | Linux x64-ൽ caller നൽകുന്ന pwntools; ഐച്ഛിക GDB/pwndbg; [recorded crashes](../recorded-crashes.md) |
|
||||
| JavaScript / Electron | Modules, imports, source maps, routes, IPC, native add-on ബന്ധങ്ങൾ | Node.js, npm; Linux: procps-compatible `ps`; [application analysis](../javascript-application-workflows.md) |
|
||||
| Websites | Page structure, scripts, network observations, ആവശ്യപ്പെട്ട screenshots | Chrome-family browser; [browser analysis](../browser-observation.md) |
|
||||
| Saved network captures | Requests, responses, പുറത്തുകാണുന്ന payload-ുകൾ, source locations | HAR അല്ലെങ്കിൽ Fiddler Everywhere SAZ; native mitmproxy-നായി Linux-ൽ mitmdump; [capture guide](../web-network-captures.md) |
|
||||
| .NET assemblies | Metadata, CIL instructions, പ്രഖ്യാപിച്ച native dependencies, build comparisons | ഒന്നും ആവശ്യമില്ല (static മാത്രം); [managed-code guide](../managed-code-analysis.md) |
|
||||
| Android APKs | Manifest declarations, classes, decompiled methods, references | Linux/macOS/Windows x64-ൽ Headless JADX-ഉം full JDK-ഉം; [Android guide](../android-analysis.md) |
|
||||
| Android devices | Devices, packages, processes, logs, dumps, screen captures, APK/file transfers | Caller നൽകുന്ന adb (emulator അല്ലെങ്കിൽ hardware); [ADB guide](../adb-device-analysis.md) |
|
||||
| JEB projects | Engine-backed project units, artifact digests, type/method pseudo-code | MCP സേവനം നൽകുന്ന caller ആരംഭിച്ച JEB client; REA_JEB_MCP_URL ഉപയോഗിച്ച് തിരഞ്ഞെടുക്കുക; [JEB guide](../jeb-analysis.md) |
|
||||
| Android resources | Decoded manifest, string tables, locales, version വിവരങ്ങൾ | Caller നൽകുന്ന Apktool launcher; [resource guide](../apktool-resource-analysis.md) |
|
||||
| Flutter apps | Dart snapshot hash, ഓരോ ABI-യിലെയും library digests, engine build provenance | ഒന്നും ആവശ്യമില്ല (static APK parsing); [Flutter guide](../flutter-analysis.md) |
|
||||
| Firmware | Regions, extraction results, native-analysis handoffs | Linux-ൽ Binwalk / Unblob; [firmware guide](../firmware-analysis.md) |
|
||||
| Recorded packets | തിരഞ്ഞെടുത്ത fields, ആവർത്തിക്കുന്ന values, filters, decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](../packet-captures.md) |
|
||||
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution, HTTP requests | Caller നൽകുന്ന tshark, capinfos (Linux-ൽ പരിശോധിച്ചത്); [pcap guide](../pcap-analysis.md) |
|
||||
| Recorded logic signals | Decoder annotations, sample ranges, diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](../signal-recordings.md) |
|
||||
| Packages and resources | File inventories, digests, plists, Apple bundle ഘടന, extracted resources | [Artifact and JavaScript guide](../javascript-artifact-reconstruction.md), [Apple applications](../apple-application-analysis.md) |
|
||||
| Process behavior | Terminal output, interactions, exit, filesystem observations, run comparisons | Native PTY ഉള്ള Linux/macOS; [process capture](../process-capture.md) |
|
||||
|
||||
Static JavaScript, .NET പരിശോധനകൾ application പ്രവർത്തിപ്പിക്കാതെ നൽകിയ files വായിച്ചാണ് നടത്തുന്നത്. Runtime capture, നിങ്ങളുടെ user permissions ഉപയോഗിച്ച് തിരഞ്ഞെടുത്ത target പ്രവർത്തിപ്പിക്കുകയോ അതുമായി ഇടപെടുകയോ ചെയ്യുന്നു; ഓരോ runtime guide-ലും അതിന്റെ പ്രത്യാഘാതങ്ങൾ വിശദീകരിച്ചിട്ടുണ്ട്.
|
||||
|
||||
Offline native-binary പരിശോധനയ്ക്കായി REA, caller നൽകുന്ന GNU objdump, Rizin എന്നിവയും പിന്തുണയ്ക്കുന്നു. Provider setup, CLI commands, Evidence-ന്റെ പരിമിതികൾ എന്നിവയ്ക്കായി [reverse-engineering tools guide](../reverse-engineering-tools.md) കാണുക. ഈ providers ഒന്നും REA ഇൻസ്റ്റാൾ ചെയ്യുകയോ അപ്ഡേറ്റ് ചെയ്യുകയോ ചെയ്യുന്നില്ല.
|
||||
|
||||
<a id="choosing-a-deep-analysis-provider"></a>
|
||||
|
||||
Native formats-ഉം host support-ഉം provider അനുസരിച്ച് വ്യത്യാസപ്പെടും. [Hopper and Ghidra setup](../installation.md#hopper), [IDA guide](../ida-provider.md) (ഐച്ഛിക native MCP bridge ഉൾപ്പെടെ), [Kuna decompiler guide](../kuna-provider.md), [പരീക്ഷണഘട്ടത്തിലുള്ള Windows Ghidra support](../windows-ghidra-p0.md) എന്നിവ കാണുക.
|
||||
Ghidra, [16-bit DOS analysis](../ghidra-dos.md)-ഉം പിന്തുണയ്ക്കുന്നു.
|
||||
വലിയ binaries-നായി `REA_GHIDRA_STARTUP_TIMEOUT_MS` അല്ലെങ്കിൽ `REA_HOPPER_STARTUP_TIMEOUT_MS` ഉപയോഗിച്ച് provider-ന്റെ startup deadline വർധിപ്പിക്കുക.
|
||||
Provider തിരഞ്ഞെടുക്കാൻ [CLI guide](../cli.md#choose-a-provider) കാണുക.
|
||||
ഏറ്റവും പുതിയ npm release-നുശേഷം ചേർത്ത features അറിയാൻ [release availability](../installation.md#released-package-and-main) പരിശോധിക്കുക.
|
||||
|
||||
## ഉദാഹരണങ്ങൾ
|
||||
|
||||
[](https://rea.tools/showcase/)
|
||||
|
||||
### DX-Ball: sound-pan കണക്കുകൂട്ടൽ പുനർനിർമ്മിക്കുക
|
||||
|
||||
ഒരു sound call-നെ അതിന്റെ position-to-pan helper വരെ പിന്തുടരുക, instructions പരിശോധിക്കുക, അപൂർണ്ണമായ pseudocode-നെ C-യാക്കി മാറ്റുക. ഈ പുനർനിർമ്മാണം യഥാർത്ഥ x86-ലെ 3,205 test cases വിജയിക്കുകയും compile ചെയ്ത function-ന്റെ 63 bytes-ഉം അതേപടി പുനരുത്പാദിപ്പിക്കുകയും ചെയ്യുന്നു.
|
||||
|
||||
[Case study വായിക്കുക](https://rea.tools/showcase/dx-ball/) ·
|
||||
[പുനർനിർമ്മാണ repository](https://github.com/N0zoM1z0/dx-ball)
|
||||
|
||||
### Notion: Electron clipboard bridge പിന്തുടരുക
|
||||
|
||||
Renderer-ന്റെ clipboard API കണ്ടെത്തി, അതിനെ preload-ഉം IPC-ഉം വഴി main process വരെ പിന്തുടർന്ന് rich clipboard format പരിശോധിക്കുക.
|
||||
|
||||
[Case study വായിക്കുക](https://rea.tools/showcase/notion/)
|
||||
|
||||
### TH04: DOS bullet-ring കണക്കുകൂട്ടൽ വീണ്ടെടുക്കുക
|
||||
|
||||
യഥാർത്ഥ PC-98 game-ന്റെ 16-bit instructions പരിശോധിക്കുക, fixed, aimed angle കണക്കുകൂട്ടലുകൾ വീണ്ടെടുക്കുക, പുനർനിർമ്മിച്ച C++-നെ ചരിത്രപരമായ compiler output-ുമായി താരതമ്യം ചെയ്യുക.
|
||||
|
||||
[Case study വായിക്കുക](https://rea.tools/showcase/th04/) ·
|
||||
[പുനർനിർമ്മാണ repository](https://github.com/N0zoM1z0/th04)
|
||||
|
||||
REA ഉപയോഗിച്ച് നിങ്ങൾ രസകരമായ എന്തെങ്കിലും പരിശോധിച്ചിട്ടുണ്ടെങ്കിൽ അത് കാണാൻ ഞങ്ങൾ ആഗ്രഹിക്കുന്നു. പരിശോധിച്ച target, നിങ്ങളുടെ ചോദ്യം, REA എങ്ങനെ സഹായിച്ചു, നിങ്ങൾ കണ്ടെത്തിയത് എന്നിവ ഉൾപ്പെടുത്തി [issue](https://github.com/morluto/rea/issues) അല്ലെങ്കിൽ [pull request](https://github.com/morluto/rea/pulls) വഴി നിങ്ങളുടെ case പങ്കിടുക.
|
||||
|
||||
## പതിവുചോദ്യങ്ങൾ
|
||||
|
||||
<details>
|
||||
<summary><strong>ഏതൊക്കെ agents-ന് REA ഉപയോഗിക്കാം?</strong></summary>
|
||||
|
||||
Local MCP servers പിന്തുണയ്ക്കുന്ന ഏത് agent-നും REA ഉപയോഗിക്കാം. Setup, [പിന്തുണയ്ക്കുന്ന agents](../installation.md#supported-agents) ക്രമീകരിക്കുന്നു; മറ്റ് clients-ന് [manual MCP registration](../installation.md#mcp-registry) ഉപയോഗിക്കാം.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Hopper, Ghidra അല്ലെങ്കിൽ IDA ആവശ്യമാണോ?</strong></summary>
|
||||
|
||||
ആഴത്തിലുള്ള native analysis-ന് ഇവയിൽ ഒന്ന് ആവശ്യമാണ്. Static JavaScript, .NET പരിശോധനകൾ native analysis engine ഇല്ലാതെയും പ്രവർത്തിക്കും. അനുമതി ലഭിച്ചാൽ setup-ന് Hopper ഇൻസ്റ്റാൾ ചെയ്യാം; Ghidra, IDA എന്നിവയ്ക്കായി നിങ്ങളുടെ നിലവിലുള്ള installations ഉപയോഗിക്കുന്നു. [Provider setup](../installation.md#hopper) കാണുക.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Hopper ആദ്യം ആരംഭിക്കേണ്ടതുണ്ടോ?</strong></summary>
|
||||
|
||||
ഒരു operation-ന് ആവശ്യമാകുമ്പോൾ REA Hopper ആരംഭിക്കും. macOS-ൽ ആദ്യമായി പ്രവർത്തിപ്പിക്കുമ്പോൾ demo mode തിരഞ്ഞെടുക്കാനോ license activate ചെയ്യാനോ ആവശ്യപ്പെടുന്ന dialog പ്രത്യക്ഷപ്പെടാം. ആദ്യ CLI/MCP operation അല്ലെങ്കിൽ unattended job നടത്തുന്നതിന് മുമ്പ് അതേ user session-ൽ ആ prompt പൂർത്തിയാക്കുക. ആവശ്യമെങ്കിൽ Hopper സ്വമേധയാ തുറക്കുക. [Hopper startup and troubleshooting](../installation.md#launcher-paths-and-troubleshooting) കാണുക.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>skills.sh-ൽ നിന്ന് skill ഇൻസ്റ്റാൾ ചെയ്യുന്നത് എന്താണ് ചെയ്യുന്നത്?</strong></summary>
|
||||
|
||||
ഈ skill നിങ്ങളുടെ agent-ന് അന്വേഷണ നിർദ്ദേശങ്ങൾ നൽകുന്നു. REA-യുടെ MCP server രജിസ്റ്റർ ചെയ്യാനും അനുയോജ്യമായ നിർദ്ദേശങ്ങൾ ഇൻസ്റ്റാൾ ചെയ്യാനും `npx rea-agents setup` ഉപയോഗിക്കുക; തുടർന്ന് നിങ്ങളുടെ agent വീണ്ടും ആരംഭിക്കുക. [Skill-only installation](../installation.md#skill-only-installation) കാണുക.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>REA ഏത് തരത്തിലുള്ള code ആണ് നൽകുന്നത്?</strong></summary>
|
||||
|
||||
Native analysis pseudocode-ഉം assembly-ഉം നൽകുന്നു. JavaScript/Electron analysis modules-ഉം അവയുടെ ബന്ധങ്ങളും വീണ്ടെടുക്കുന്നു. ഈ കണ്ടെത്തലുകൾ ഉപയോഗിച്ച് നിങ്ങളുടെ agent implementation എഴുതുകയും പരിശോധിക്കുകയും ചെയ്യും; പ്രായോഗിക ഉദാഹരണങ്ങൾക്കായി [ഉദാഹരണങ്ങൾ](#showcases) കാണുക.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>REA എന്റെ app upload ചെയ്യുമോ?</strong></summary>
|
||||
|
||||
REA targets local ആയി വിശകലനം ചെയ്യുന്നു. Tool-ൽ നിന്നുള്ള ഫലങ്ങൾ നിങ്ങളുടെ agent-ന് ലഭിക്കും; അതിന്റെ model provider-ന് സ്വന്തമായ data policy ഉണ്ടായിരിക്കും.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>ഒരു bug നേരിട്ടാൽ എന്തുചെയ്യണം?</strong></summary>
|
||||
|
||||
ആദ്യം അപ്ഡേറ്റ് ചെയ്യുക; പുതിയ release-ൽ ആ പ്രശ്നത്തിനുള്ള പരിഹാരം ഇതിനകം ഉണ്ടായേക്കാം.
|
||||
|
||||
`npm install --global` അല്ലെങ്കിൽ `bun add --global` ഉപയോഗിച്ച് ഇൻസ്റ്റാൾ ചെയ്ത CLI-ക്കായി:
|
||||
|
||||
```bash
|
||||
rea update
|
||||
```
|
||||
|
||||
`npx` വഴി agent setup നടത്തുന്നതിനായി:
|
||||
|
||||
```bash
|
||||
npx rea-agents@latest setup
|
||||
```
|
||||
|
||||
നിങ്ങൾ agent ഉപയോഗിക്കുന്നുണ്ടെങ്കിൽ, [setup പുതുക്കൽ](#update-rea) പൂർത്തിയാക്കി അത് വീണ്ടും ആരംഭിക്കുക. അതേ task വീണ്ടും ശ്രമിക്കുക. പ്രശ്നം തുടരുകയാണെങ്കിൽ, നിങ്ങളുടെ REA version, target type, പ്രശ്നം ആവർത്തിക്കാൻ ആവശ്യമായ ഘട്ടങ്ങൾ, error output എന്നിവ ഉൾപ്പെടുത്തി [issue തുറക്കുക](https://github.com/morluto/rea/issues).
|
||||
|
||||
</details>
|
||||
|
||||
## ഡോക്യുമെന്റേഷൻ
|
||||
|
||||
വെബ്സൈറ്റിലെ [പ്രായോഗിക ഗൈഡുകൾ](https://rea.tools/guides/) ഉപയോഗിച്ച് തുടങ്ങുക.
|
||||
കൃത്യമായ options, prerequisites, result contracts എന്നിവയ്ക്കായി:
|
||||
|
||||
- [Installation and setup](../installation.md): agent registration, provider configuration, updates, uninstall.
|
||||
- [Readiness and troubleshooting](../installation.md#check-readiness-for-your-task): ഒരു agent-ന്റെയോ analysis engine-ന്റെയോ പ്രശ്നങ്ങൾ കണ്ടെത്തുക.
|
||||
- [CLI and Evidence](../cli.md): commands, provider തിരഞ്ഞെടുക്കൽ, snapshots, import/export, exit statuses.
|
||||
- [MCP contracts](../mcp-contracts.md), [agent prompts](../mcp-prompts.md): tool results, sessions, guided investigations.
|
||||
- [Tool catalog](../mcp-contracts.md#generated-catalog): tools, providers, CLI commands എന്നിവയുടെ സ്വയമേവ സൃഷ്ടിച്ച പട്ടിക.
|
||||
- [Roadmap](../roadmap.md): ആസൂത്രണം ചെയ്ത പ്രവർത്തനങ്ങളും capability trackers-ഉം.
|
||||
|
||||
സുരക്ഷാ ദുർബലതകൾ [SECURITY.md](../../SECURITY.md) വഴി റിപ്പോർട്ട് ചെയ്യുക.
|
||||
|
||||
## Star ചരിത്രം
|
||||
|
||||
🎉 **250,000 GitHub stars — നന്ദി!**
|
||||
|
||||
REA ഉപയോഗിക്കുന്നവർക്കും, bugs റിപ്പോർട്ട് ചെയ്യുന്നവർക്കും, features ആവശ്യപ്പെടുന്നവർക്കും, builds പരിശോധിക്കുന്നവർക്കും, fixes സംഭാവന ചെയ്യുന്നവർക്കും നന്ദി.
|
||||
|
||||
<a href="https://www.star-history.com/?repos=morluto%2Frea&type=date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&type=date&theme=dark&legend=top-left" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&type=date" />
|
||||
<img alt="REA GitHub star ചരിത്രം" src="https://api.star-history.com/chart?repos=morluto/rea&type=date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## നിരാകരണം
|
||||
|
||||
ഉത്തരവാദിത്തത്തോടെ ഉപയോഗിക്കുന്നതിനുള്ള മാർഗനിർദ്ദേശങ്ങൾക്ക് [DISCLAIMER.md](../../DISCLAIMER.md) കാണുക.
|
||||
|
||||
നിയമവിരുദ്ധമോ അനുമതിയില്ലാത്തതോ ആയ ഉപയോഗത്തെക്കുറിച്ചുള്ള project-ന്റെ നിലപാട് എല്ലാ REA versions-നും distribution channels-നും platforms-നും ബാധകമാണ്; ഓരോ copy-യും അതിന് ബാധകമായ licenses നിലനിർത്തുന്നു.
|
||||
|
||||
നിയമാനുസൃതമായ research, education, interoperability, software preservation, കൂടാതെ നിങ്ങൾക്ക് സ്വന്തമായതോ പരിശോധിക്കാൻ അനുമതിയുള്ളതോ ആയ software-ന്റെ analysis എന്നിവയ്ക്കാണ് REA ഉദ്ദേശിച്ചിരിക്കുന്നത്.
|
||||
|
||||
## സംഭാവനകൾ
|
||||
|
||||
REA-യ്ക്കായി നിങ്ങളുടെ സഹായം ഞങ്ങൾ സ്വാഗതം ചെയ്യുന്നു! bug റിപ്പോർട്ട് ചെയ്യാനോ feature നിർദ്ദേശിക്കാനോ [issue തുറക്കുക](https://github.com/morluto/rea/issues), code അല്ലെങ്കിൽ docs മെച്ചപ്പെടുത്താൻ [pull request അയയ്ക്കുക](https://github.com/morluto/rea/pulls).
|
||||
|
||||
Development setup-നും checks-നും [CONTRIBUTING.md](../../CONTRIBUTING.md), verification lanes-നായി [testing](../testing.md), project ഘടനയ്ക്കായി [architecture map](../architecture.mermaid) എന്നിവ കാണുക.
|
||||
|
||||
## ലൈസൻസ്
|
||||
|
||||
[MIT](../../LICENSE)
|
||||
|
||||
REA-യുടെ രചയിതാക്കൾ സൃഷ്ടിച്ച code MIT license പ്രകാരമാണ് ലൈസൻസ് ചെയ്തിരിക്കുന്നത്. Third-party material അതിന്റെ സ്വന്തം licenses-ഉം copyright notices-ഉം നിലനിർത്തുന്നു; dependencies-ന്റെ പട്ടിക, നിലനിർത്തിയ license text-ുകൾ, external tool നിബന്ധനകൾ എന്നിവയ്ക്കായി [Third-party notices](../../THIRD_PARTY_NOTICES.md) കാണുക.
|
||||
|
||||
**IDA Pro / Hex-Rays decompilers, Hopper, JEB എന്നിവ ഉൾപ്പെടെയുള്ള commercial tools നിയമാനുസൃതമായി സ്വന്തമാക്കുകയും അവയുടെ ബാധകമായ licenses പ്രകാരം ഉപയോഗിക്കാൻ അനുമതിയുണ്ടെന്ന് ഉറപ്പാക്കുകയും വേണം.** REA ഒരു tool license-ഓ key-യോ activation entitlement-ഓ നൽകുന്നില്ല. Trials-ഉം demos-ഉം ഉൾപ്പെടെ vendor നിബന്ധനകൾക്ക് വിധേയമാണ്; ഇതിൽ user, feature, automation, output sharing നിയന്ത്രണങ്ങളും ഉൾപ്പെടാം. Cracked copies, മോഷ്ടിച്ച keys, അനുമതിയില്ലാത്ത key sharing, license നിയന്ത്രണങ്ങൾ മറികടക്കൽ എന്നിവയ്ക്ക് അനുമതിയില്ല. ഈ മാർഗനിർദ്ദേശം MIT അല്ലെങ്കിൽ third-party licenses-ൽ മാറ്റം വരുത്തുന്നില്ല.
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · **Polski** · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -272,7 +272,7 @@ Zgłaszaj podatności zgodnie z [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Historia gwiazdek
|
||||
|
||||
🎉 **250 000 gwiazdek na GitHubie — dziękujemy!**
|
||||
🎉 **300 000 gwiazdek na GitHubie — dziękujemy!**
|
||||
|
||||
Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują nowe funkcje, testują kompilacje i przesyłają poprawki.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · **Português (Brasil)** · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Faça engenharia reversa de qualquer coisa
|
||||
|
||||
@@ -272,7 +272,7 @@ Relate vulnerabilidades conforme [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Histórico de estrelas
|
||||
|
||||
🎉 **250.000 estrelas no GitHub — muito obrigado!**
|
||||
🎉 **300.000 estrelas no GitHub — muito obrigado!**
|
||||
|
||||
Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam builds e contribuem com correções.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · **Русский** · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Реверс-инжиниринг чего угодно
|
||||
|
||||
@@ -272,7 +272,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## История звёзд
|
||||
|
||||
🎉 **250 000 звёзд на GitHub — спасибо!**
|
||||
🎉 **300 000 звёзд на GitHub — спасибо!**
|
||||
|
||||
Спасибо всем, кто использует REA, сообщает об ошибках, предлагает новые функции, тестирует сборки и помогает с исправлениями.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · **ไทย** · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -272,7 +272,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## ประวัติดาว
|
||||
|
||||
🎉 **250,000 ดาวบน GitHub — ขอบคุณทุกคน!**
|
||||
🎉 **300,000 ดาวบน GitHub — ขอบคุณทุกคน!**
|
||||
|
||||
ขอบคุณทุกคนที่ใช้ REA รายงานบั๊ก เสนอฟีเจอร์ ทดสอบบิลด์ และช่วยแก้ไข
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · **Türkçe** · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Her şeyi tersine mühendislikle inceleyin
|
||||
|
||||
@@ -277,7 +277,7 @@ Güvenlik açıklarını [SECURITY.md](../../SECURITY.md) üzerinden bildirin.
|
||||
|
||||
## Yıldız geçmişi
|
||||
|
||||
🎉 **GitHub'da 250.000 yıldız — teşekkürler!**
|
||||
🎉 **GitHub'da 300.000 yıldız — teşekkürler!**
|
||||
|
||||
REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve düzeltmelere katkıda bulunan herkese teşekkürler.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · **Українська** · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Реверс-інжиніринг будь-чого
|
||||
|
||||
@@ -272,7 +272,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Історія зірок
|
||||
|
||||
🎉 **250 000 зірок на GitHub — дякуємо!**
|
||||
🎉 **300 000 зірок на GitHub — дякуємо!**
|
||||
|
||||
Дякуємо всім, хто використовує REA, повідомляє про помилки, пропонує нові функції, тестує збірки й допомагає з виправленнями.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · **Tiếng Việt** · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA: Kỹ thuật dịch ngược cho mọi thứ
|
||||
|
||||
@@ -272,7 +272,7 @@ Báo cáo lỗ hổng theo [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## Lịch sử sao
|
||||
|
||||
🎉 **250.000 sao trên GitHub — xin cảm ơn!**
|
||||
🎉 **300.000 sao trên GitHub — xin cảm ơn!**
|
||||
|
||||
Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính năng, kiểm thử bản dựng và đóng góp bản sửa lỗi.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · **繁體中文** · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA:逆向一切
|
||||
|
||||
@@ -278,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Star 歷史
|
||||
|
||||
🎉 **GitHub Star 達到 250,000 個,感謝大家!**
|
||||
🎉 **GitHub Star 達到 300,000 個,感謝大家!**
|
||||
|
||||
感謝每一位使用 REA、報告錯誤、提出功能需求、測試建置和貢獻修復的朋友。
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · **简体中文** · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
|
||||
# REA:逆向一切
|
||||
|
||||
@@ -278,7 +278,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Star 历史
|
||||
|
||||
🎉 **GitHub Star 达到 250,000 个,感谢大家!**
|
||||
🎉 **GitHub Star 达到 300,000 个,感谢大家!**
|
||||
|
||||
感谢每一位使用 REA、报告错误、提出功能需求、测试构建和贡献修复的朋友。
|
||||
|
||||
|
||||
@@ -61,9 +61,21 @@ The outer provider owns both processes and one stable core snapshot. An owned
|
||||
Python launcher lowers inherited soft limits before replacing itself with GDB.
|
||||
GDB starts without initfiles, auto-load, debuginfod or automatic shared-library
|
||||
loading. Owned empty library search paths and caches are used, with plugin
|
||||
updates disabled. Fixed package code verifies a core connection and no paired
|
||||
executable before querying mapping candidates. No caller GDB command language is
|
||||
exposed. The exact observed GDB/plugin version strings are retained.
|
||||
updates disabled. Debug-file lookup is also restricted to the owned empty
|
||||
search directory. GDB 16 can otherwise locate the recorded executable independently
|
||||
of these settings. While opening the core, fixed package code temporarily selects
|
||||
`/proc/self/exe` (the already running debugger) with `exec-file`, without loading
|
||||
symbols or running an inferior. This occupies GDB's executable slot and prevents
|
||||
its automatic executable discovery. The validated decoder profile selects the
|
||||
core architecture explicitly, so the placeholder cannot select it instead. The
|
||||
bridge discards that selection with `file` before querying mapping candidates,
|
||||
and verifies the core connection and absence of a main symbol file. Before launching GDB, the provider also checks
|
||||
source-bound `NT_FILE` descriptors: mapping names must be absolute Unix paths
|
||||
without dot segments. Relative names bypass GDB's sysroot, and traversal can
|
||||
escape it. Unresolved or unsafe mapping coverage refuses debugger enrichment
|
||||
while retaining the basic decoded observation and original bytes. No caller GDB
|
||||
command language is exposed. The exact observed GDB/plugin version strings are
|
||||
retained.
|
||||
|
||||
Mapping names are producer display metadata, with unknown current file identity.
|
||||
Map permissions are derived from reported flags; zero/unfamiliar flags leave
|
||||
@@ -93,4 +105,6 @@ GDB/Python/pwndbg/strace. Fixture generation explicitly launches its owned test
|
||||
program; subsequent inspection only reads the resulting core. Real acceptance
|
||||
checks recorded threads, high registers, missing/malformed notes, opaque owners,
|
||||
CLI/MCP parity, optional debugger context and historical PID collision with an
|
||||
owned live sentinel. Original inputs and host configuration remain unchanged.
|
||||
owned live sentinel. The syscall trace also rejects access to the still-present
|
||||
source executable. The CI workflow defines Ubuntu 24.04 and Debian 13 (GDB 16)
|
||||
container lanes. Original inputs and host configuration remain unchanged.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"repository": "morluto/rea",
|
||||
"version": "6.7.0",
|
||||
"base": "2c4338db1fdb7773e05376301c558cff2a0b4b80",
|
||||
"target": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf",
|
||||
"commits": [
|
||||
{
|
||||
"sha": "8cbaf6c7021b97dd4de6b5771c35fbed06b240e9",
|
||||
"subject": "fix(tests): isolate command finalization module mocks",
|
||||
"body": "fix(tests): isolate command finalization module mocks\n\nMove the five registered WASM finalization cases into the existing per-file isolated project. Preserve all result, exit-code, observation and cleanup assertions. A preloaded shared CLI graph reproduces three failures before the move; the isolated covered packet passes.\n\nFixes #4959\n",
|
||||
"parents": ["2c4338db1fdb7773e05376301c558cff2a0b4b80"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "morluto",
|
||||
"email": "76467478+morluto@users.noreply.github.com",
|
||||
"user": {
|
||||
"login": "morluto",
|
||||
"__typename": "User"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pullRequests": []
|
||||
},
|
||||
{
|
||||
"sha": "6b180783ed04ebc87548c8385c80e05725804c47",
|
||||
"subject": "fix(tests): budget CLI coverage from parsed options",
|
||||
"body": "fix(tests): budget CLI coverage from parsed options\n\nUse the pinned Vitest parser for coverage and shard flags. Keep ordinary CLI workers at 384 MiB and covered workers at the tested 512 MiB with unchanged concurrency. Retain complete coverage and add actual-config regressions for false, provider-only and spaced-shard options.\n\nValidation: 27 focused cases; 16 covered cases with CLI preloaded; 5 docs checks; typecheck/lint; exact discovery of 1578 files. Measured covered control: 22.18s elapsed, 46.52s CPU, about 2.33 GiB sampled summed process-family RSS on macOS arm64. Full Linux CI pending. No runtime/device verification claimed.\n\nFixes #4960\n",
|
||||
"parents": ["8cbaf6c7021b97dd4de6b5771c35fbed06b240e9"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "morluto",
|
||||
"email": "76467478+morluto@users.noreply.github.com",
|
||||
"user": {
|
||||
"login": "morluto",
|
||||
"__typename": "User"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pullRequests": []
|
||||
},
|
||||
{
|
||||
"sha": "bb212ffecb47e129cc569b9867b2b6e7c9b65685",
|
||||
"subject": "fix(tests): inherit server identity deadline policy",
|
||||
"body": "fix(tests): inherit server identity deadline policy\n\nRemove per-case ten-second overrides from MCP catalog and identity checks. Keep the suite-owned bounded ordinary and coverage deadlines, all assertions, and session cleanup.\n\nFixes #4967\n",
|
||||
"parents": ["6b180783ed04ebc87548c8385c80e05725804c47"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "morluto",
|
||||
"email": "76467478+morluto@users.noreply.github.com",
|
||||
"user": {
|
||||
"login": "morluto",
|
||||
"__typename": "User"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pullRequests": []
|
||||
},
|
||||
{
|
||||
"sha": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf",
|
||||
"subject": "fix(setup): edit the duplicate JSON member that clients read (#4957)",
|
||||
"body": "fix(setup): edit the duplicate JSON member that clients read (#4957)\n\nJSON.parse, JSONC parse, and native Claude Code keep the last duplicate\nobject member, and parseClientConfiguration therefore validates the last\none. The shared setup/uninstall serializer instead edited text through\njsonc-parser's modify and findNodeAtLocation, which select the first\nmatching member, so setup and uninstall on configurations with repeated\nserver tables or rea members wrote into a shadowed member and failed\nreadback.\n\nLocate every JSON text edit by the effective (last) member in one place,\nreplacing the first-member modify path. Removing an effective member\nthat shadows an earlier duplicate would activate that duplicate, whatever\nits ownership, so the serializer now returns a tagged shadowed_member\nfailure and setup and uninstall refuse before writing a backup or the\nconfiguration.\n\nFixes #4952\n\nCo-authored-by: Claude Opus 5.5 <noreply@anthropic.com>",
|
||||
"parents": ["bb212ffecb47e129cc569b9867b2b6e7c9b65685"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kaoru0822",
|
||||
"email": "kanadekitauji@proton.me",
|
||||
"user": {
|
||||
"login": "kaoru0822-kitauji",
|
||||
"__typename": "User"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Claude Opus 5.5",
|
||||
"email": "noreply@anthropic.com",
|
||||
"user": {
|
||||
"login": "claude",
|
||||
"__typename": "User"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pullRequests": [4957]
|
||||
}
|
||||
],
|
||||
"pullRequests": [
|
||||
{
|
||||
"number": 4957,
|
||||
"title": "fix(setup): edit the duplicate JSON member that clients read",
|
||||
"url": "https://github.com/morluto/rea/pull/4957",
|
||||
"author": {
|
||||
"login": "kaoru0822-kitauji",
|
||||
"__typename": "User"
|
||||
},
|
||||
"mergeCommit": {
|
||||
"oid": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"number": 4952,
|
||||
"url": "https://github.com/morluto/rea/issues/4952",
|
||||
"author": {
|
||||
"login": "xu91102",
|
||||
"__typename": "User"
|
||||
},
|
||||
"repository": {
|
||||
"nameWithOwner": "morluto/rea"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+45
-6
@@ -110,9 +110,14 @@ rendered declarations. Keep canonical rejection checks separate from the
|
||||
intentionally reduced compact advertisement, and probe actual client renderers
|
||||
when changing schema representations.
|
||||
|
||||
The reused CLI boundary workers cap V8 old space at 384 MiB, based on their
|
||||
profiled allocation and cleanup behavior. This encourages collection between
|
||||
files; spawned REA processes retain their own heap settings.
|
||||
The reused CLI boundary workers cap V8 old space at 384 MiB for ordinary runs
|
||||
and 512 MiB with coverage enabled. Covered ADB fixtures exhaust the ordinary
|
||||
384 MiB budget; those fixtures also complete with the shared CLI graph preloaded
|
||||
at 512 MiB. This is a measured capacity control, not a suite-wide speedup claim.
|
||||
Both profiles keep the
|
||||
same bounded worker count, and spawned REA processes retain their own heap
|
||||
settings. Explicitly disabling coverage retains the ordinary profile; coverage
|
||||
provider options alone do not enable instrumentation.
|
||||
|
||||
Vitest worker limits do not bound subprocesses launched within a test. Bound
|
||||
those batches separately so repeated CLI validation cannot oversubscribe the
|
||||
@@ -216,6 +221,12 @@ controlled production seam for interleavings that cannot be reproduced reliably.
|
||||
| Cleanup fails after success or primary failure | Public output retains the completed observation or primary diagnostic, remaining resources, and owner recovery action. Restore the denied resource and verify the owning cleanup retry. |
|
||||
| Target exits without an acknowledgement, or transport disappears while it stays alive | Positive owned exit proof resolves target lifetime; other resources still need cleanup. Lost transport alone does not prove exit. Verify unrelated/attached targets survive and same-connection recovery works when supported. |
|
||||
|
||||
Shared process-supervision controls separate leader exit from output closure.
|
||||
Cover output during held or failed cleanup, bounded retention and drain timeout,
|
||||
retry, and borrowed stream authority. The POSIX inherited-stderr control uses a
|
||||
separate pipe as a release barrier after leader exit; it does not establish
|
||||
native Windows Job Object behavior.
|
||||
|
||||
Synchronize cancellation with the finalization boundary being tested; a fixed
|
||||
delay after dispatch does not establish that phase. Use markers, barriers, or
|
||||
owned cleanup seams while preserving real cleanup where feasible. Check server
|
||||
@@ -225,6 +236,21 @@ cover cancellation during final cleanup.
|
||||
Use explicit abort reasons and assert their identity at admission and after
|
||||
provider completion, alongside retained observations and cleanup state.
|
||||
|
||||
Electron fuse regressions cover actual descriptor close denial and retry through
|
||||
the built CLI, same-connection MCP recovery, mixed descriptor outcomes, and
|
||||
server shutdown. Controlled seams cover cancellation during final close and
|
||||
after reader finalization, plus active-provider shutdown during preflight.
|
||||
They preserve real filesystem cleanup where possible; mocked launch boundaries
|
||||
do not establish real Electron teardown or naturally occurring OS failures.
|
||||
|
||||
Active Electron cleanup controls retain independent transport and process-family
|
||||
owners, block replacement captures until recovery, and retry only unresolved
|
||||
owners. Cover late close acknowledgement and rejection after a wait deadline,
|
||||
opaque rejection reasons, cancellation during failed finalization, and missing
|
||||
lineage followed by a confirmed passive family census. Windows controls require
|
||||
a fresh lineage observation and do not signal cached descendant PIDs after root
|
||||
termination. Simulated Windows controls do not establish native Windows behavior.
|
||||
|
||||
For atomic writes, exercise cancellation on both sides of the documented commit
|
||||
point and preserve any publication receipt. A late interruption does not prove
|
||||
that no output was published.
|
||||
@@ -503,7 +529,7 @@ costs. Its queue, execution, and failures are independent of the required gate
|
||||
and publication. CPU time and peak process-family memory are not measured by this API.
|
||||
|
||||
The build job saves portable Turbo outputs across runs. Eight Linux real-system
|
||||
lanes restore this cache without waiting for the build job. The shared action
|
||||
lanes reuse its current-run artifact; standalone dispatches restore the cross-run cache. The shared action
|
||||
derives its key from `test:prepare --dry=json` task hashes, runner OS and
|
||||
architecture, `.npmrc`, `.nvmrc`, and the lockfile; test-only changes can reuse compiled outputs. Every
|
||||
build and verification command still runs, so Turbo checks task inputs and
|
||||
@@ -513,7 +539,15 @@ that generator's entry. Native outputs are not stored in this cache.
|
||||
|
||||
Native package jobs, the Windows curated lane, and the Apple artifact lane reuse
|
||||
the same workflow's Turbo cache for portable compiled JavaScript, skills, and
|
||||
catalogs. The Windows lane relies on the Linux static job for typechecking. They retain the normal build and prepack
|
||||
catalogs. The key parser retains the validated `test:prepare` task identities in
|
||||
`.cache/ci-build-plan.json`; staging reads this plan without starting another
|
||||
Turbo run that could evict cache entries. The producer stages matching archives,
|
||||
metadata, and available manifests into `.cache/ci-build-cache/`.
|
||||
`portable-build-cache.json` records availability per task. Evicted or partially
|
||||
missing entries are omitted as whole entries, and consumers rebuild cache misses,
|
||||
including when every entry is unavailable. Malformed plans, non-file entries,
|
||||
and I/O errors other than disappearance still fail publication. Older cross-run entries stay in the
|
||||
original cache. The Windows lane relies on the Linux static job for typechecking. They retain the normal build and prepack
|
||||
commands, which restore matching outputs or rebuild on a cache miss. Native
|
||||
artifacts, dependency installs, package archives, and runtime verification remain
|
||||
host-local. Keep platform-dependent outputs out of this shared cache.
|
||||
@@ -1536,7 +1570,12 @@ NUL-containing names. It compares exact WAT,
|
||||
section output and selected/tool digests through the compiled CLI and real MCP
|
||||
SDK, validates advertised schemas, checks identical CLI/MCP Evidence IDs and
|
||||
session bundle readback, retains invalid/truncated module diagnostics, preserves
|
||||
all inputs and checks workspace cleanup. Pass an installed package's
|
||||
all inputs and checks workspace cleanup.
|
||||
The lane also injects one cleanup denial for exact private runtime roots and
|
||||
input descriptors, checks recovered CLI errors, and verifies retry on the same
|
||||
MCP connection after repeated runtime-removal denial. These are controlled
|
||||
faults, not naturally occurring permission failures; no WASM target is executed.
|
||||
Pass an installed package's
|
||||
`scripts/rea.mjs` as the first argument to run the same lane against the package.
|
||||
`npm run verify:wasm:package` packs and installs REA into an isolated temporary
|
||||
prefix with lifecycle scripts disabled and runs the same required real-tool lane.
|
||||
|
||||
@@ -79,6 +79,13 @@ providers need not expose identical state machines.
|
||||
and resource release separate. A valid process handle, successful launcher,
|
||||
or selected target establishes only its own fact. Publish available identity
|
||||
and exit observations even when launch fails; retain unknown ownership.
|
||||
- Process exit does not establish output closure. The shared process supervisor
|
||||
retains at most 64 KiB of additional diagnostics from owned stop entry,
|
||||
including pending cleanup and retries, and drains for up to one second after
|
||||
verified stop. It marks output incomplete when closure cannot be
|
||||
observed and releases owned event-loop references without changing the proven
|
||||
process-cleanup outcome. Failed process cleanup retains capture for retry;
|
||||
borrowed processes and streams retain their own lifetime and reference state.
|
||||
- Authenticate and capture identity through the same boundary on initial and
|
||||
recovery handshakes. Bound abandoned unauthenticated listeners where the
|
||||
protocol permits it; that deadline must not expire authenticated idle sessions
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Analysis efficiency measurements
|
||||
|
||||
For authentic old Evidence and mixed investigations, see the
|
||||
[retained-query checkpoint](retained-investigation-workflows.md).
|
||||
`retained-investigation.mjs` compares existing complete delivery with selected
|
||||
pages, including capacity-refusal recovery and original-record round trips.
|
||||
It records negative model results and separates imported-format validation work
|
||||
from the query contract.
|
||||
|
||||
For genuine Fiddler captures, see the [SAZ selection checkpoint](saz-selection-workflows.md).
|
||||
`measure.mjs --saz-selection baseline|focused|retained` compares default complete
|
||||
delivery (including retained-reference recovery), caller-selected ordinals, and
|
||||
|
||||
@@ -187,6 +187,7 @@ def main():
|
||||
'inspect_managed_members', 'inspect_managed_native_boundaries',
|
||||
'inspect_artifact', 'inspect_wasm_artifact',
|
||||
'inspect_evm_interface', 'get_evidence_bundle', 'export_evidence_bundle',
|
||||
'import_evidence_bundle', 'list_unknowns', 'verify_unknown_resolution',
|
||||
'capture_browser_scenario',
|
||||
'resolve_containing_procedure', 'analyze_function', 'close_binary']:
|
||||
config += '[mcp_servers.rea.tools.' + tool + ']\napproval_mode = "approve"\n'
|
||||
|
||||
@@ -0,0 +1,635 @@
|
||||
{
|
||||
"runtime_revision": "f4322b15ed1423d736bd2572cf7a8eadf2b466e1",
|
||||
"host": "Linux x86-64",
|
||||
"node": "24.18.0",
|
||||
"npm": "11.16.0",
|
||||
"codex_cli": "0.161.0",
|
||||
"requested_model": "gpt-6.1-sol",
|
||||
"reasoning": "high",
|
||||
"resolved_model_identity": "unknown",
|
||||
"code_mode": false,
|
||||
"client_enabled_tools": [
|
||||
"import_evidence_bundle",
|
||||
"get_evidence_bundle",
|
||||
"inspect_analysis_view",
|
||||
"list_unknowns"
|
||||
],
|
||||
"catalog": {
|
||||
"tools": 207,
|
||||
"serialized_bytes": 3152125,
|
||||
"sha256": "4f31d9d56c400bc48f5639a9dc4b4cdad7faf862e4be81641d94fa70552438e4"
|
||||
},
|
||||
"bounds": {
|
||||
"cpu_quota_percent": 100,
|
||||
"family_hard_mib": 8192,
|
||||
"early_stop_mib": 7168,
|
||||
"swap": false,
|
||||
"tasks": 192
|
||||
},
|
||||
"counter_scope": "Payload-exit counters with observer alive; kernel family peaks include file cache; terminal whole-scope counters and remote model compute unknown",
|
||||
"sources": {
|
||||
"native": {
|
||||
"target_sha256": "2e6eef9a727f081f0d453b4e5e6cbd8b9ef8b6f86cbf7681cbad444d3b0b55c8",
|
||||
"sections": 26,
|
||||
"symbols": 1479,
|
||||
"relocations": 2963,
|
||||
"all_symbol_locations_names_values_agree_with_independent_ELF_parser": true
|
||||
},
|
||||
"javascript": {
|
||||
"package": "npm",
|
||||
"version": "11.16.0",
|
||||
"source_files": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"sha256": "60ff022e32b5d8291d3e09849fd0cee6bbbff4e4d8352ad258214b1adf070714"
|
||||
},
|
||||
{
|
||||
"path": "bin/npm-cli.js",
|
||||
"sha256": "8e5f6f3429f8cdbe693cdc29904e9d5a7b127a494bd15c804bd54c7403bfcbe7"
|
||||
},
|
||||
{
|
||||
"path": "lib/cli.js",
|
||||
"sha256": "67666f06479f9b0bbc01412c198caadd4287d34d5ed74a02871ea78f186451e7"
|
||||
},
|
||||
{
|
||||
"path": "lib/cli/entry.js",
|
||||
"sha256": "667a44c1fa8795fdbba8ad843736ca7f8f24e64511985e9a7060b002123560c6"
|
||||
},
|
||||
{
|
||||
"path": "lib/commands/install.js",
|
||||
"sha256": "f7a60e8b40ab1bece76a2441b8b15378d199f05c6050eebe455cd9961f67b816"
|
||||
}
|
||||
],
|
||||
"lib_cli_module": {
|
||||
"node_id": "jag_node_131257ad52f3333971c2a5ed325c64357e52604489e42ccc67a8849770cb5b10",
|
||||
"kind": "javascript-module",
|
||||
"path": "lib/cli.js"
|
||||
},
|
||||
"source_bytes_checked": true,
|
||||
"inventory_completeness": "producer-relative, not independently established",
|
||||
"unselected_sources": 1416
|
||||
}
|
||||
},
|
||||
"sdk": [
|
||||
{
|
||||
"name": "sdk-native-complete-1",
|
||||
"status": "passed",
|
||||
"answer_sha256": "4e28c2c2a25620576d27abe846a05835ec49386b889779e33410c31971523b54",
|
||||
"query_ms": 1328.7,
|
||||
"query_reply_bytes": 11224227,
|
||||
"rpc_count": 7,
|
||||
"elapsed_seconds": 14.117,
|
||||
"cpu_seconds": 13.915,
|
||||
"peak_family_mib": 1160.8,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": []
|
||||
},
|
||||
{
|
||||
"name": "sdk-native-complete-2",
|
||||
"status": "passed",
|
||||
"answer_sha256": "4e28c2c2a25620576d27abe846a05835ec49386b889779e33410c31971523b54",
|
||||
"query_ms": 1385.4,
|
||||
"query_reply_bytes": 11224227,
|
||||
"rpc_count": 7,
|
||||
"elapsed_seconds": 14.722,
|
||||
"cpu_seconds": 14.485,
|
||||
"peak_family_mib": 1109.9,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": []
|
||||
},
|
||||
{
|
||||
"name": "sdk-native-focused-1",
|
||||
"status": "passed",
|
||||
"answer_sha256": "4e28c2c2a25620576d27abe846a05835ec49386b889779e33410c31971523b54",
|
||||
"query_ms": 529.7,
|
||||
"query_reply_bytes": 167985,
|
||||
"rpc_count": 25,
|
||||
"elapsed_seconds": 12.907,
|
||||
"cpu_seconds": 12.679,
|
||||
"peak_family_mib": 934.8,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [44.1, 59.6, 24.0, 30.8, 26.7, 47.4]
|
||||
},
|
||||
{
|
||||
"name": "sdk-native-focused-2",
|
||||
"status": "passed",
|
||||
"answer_sha256": "4e28c2c2a25620576d27abe846a05835ec49386b889779e33410c31971523b54",
|
||||
"query_ms": 572.2,
|
||||
"query_reply_bytes": 167985,
|
||||
"rpc_count": 25,
|
||||
"elapsed_seconds": 13.316,
|
||||
"cpu_seconds": 13.116,
|
||||
"peak_family_mib": 1000.0,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [44.0, 64.6, 26.8, 33.2, 29.8, 50.4]
|
||||
},
|
||||
{
|
||||
"name": "sdk-javascript-complete-1",
|
||||
"status": "passed",
|
||||
"answer_sha256": "0fe3ad249eb1a7233a4966a56652c173667c48fdaf395b6c08fc6901ef3b46f7",
|
||||
"query_ms": 11241.5,
|
||||
"query_reply_bytes": 212856,
|
||||
"rpc_count": 31,
|
||||
"elapsed_seconds": 26.3,
|
||||
"cpu_seconds": 26.1,
|
||||
"peak_family_mib": 1333.5,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": true,
|
||||
"warm_page_ms": [1124.6, 1073.8, 1474.7, 1074.7, 1081.6, 1109.5]
|
||||
},
|
||||
{
|
||||
"name": "sdk-javascript-complete-2",
|
||||
"status": "passed",
|
||||
"answer_sha256": "0fe3ad249eb1a7233a4966a56652c173667c48fdaf395b6c08fc6901ef3b46f7",
|
||||
"query_ms": 11938.1,
|
||||
"query_reply_bytes": 212856,
|
||||
"rpc_count": 31,
|
||||
"elapsed_seconds": 27.105,
|
||||
"cpu_seconds": 26.87,
|
||||
"peak_family_mib": 1320.9,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": true,
|
||||
"warm_page_ms": [1155.4, 1096.2, 1489.3, 1121.3, 1088.3, 1484.6]
|
||||
},
|
||||
{
|
||||
"name": "sdk-javascript-focused-2",
|
||||
"status": "passed",
|
||||
"answer_sha256": "0fe3ad249eb1a7233a4966a56652c173667c48fdaf395b6c08fc6901ef3b46f7",
|
||||
"query_ms": 10967.3,
|
||||
"query_reply_bytes": 208350,
|
||||
"rpc_count": 28,
|
||||
"elapsed_seconds": 25.708,
|
||||
"cpu_seconds": 25.505,
|
||||
"peak_family_mib": 1344.0,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [1342.7, 1085.1, 1055.0, 1034.6, 1082.7, 1440.7]
|
||||
},
|
||||
{
|
||||
"name": "sdk-javascript-focused-3",
|
||||
"status": "passed",
|
||||
"answer_sha256": "0fe3ad249eb1a7233a4966a56652c173667c48fdaf395b6c08fc6901ef3b46f7",
|
||||
"query_ms": 10990.4,
|
||||
"query_reply_bytes": 208350,
|
||||
"rpc_count": 28,
|
||||
"elapsed_seconds": 26.197,
|
||||
"cpu_seconds": 25.98,
|
||||
"peak_family_mib": 1336.0,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [1338.1, 1088.5, 1069.1, 1038.3, 1087.5, 1483.0]
|
||||
},
|
||||
{
|
||||
"name": "sdk-mixed-native",
|
||||
"status": "passed",
|
||||
"answer_sha256": "4e28c2c2a25620576d27abe846a05835ec49386b889779e33410c31971523b54",
|
||||
"query_ms": 560.0,
|
||||
"query_reply_bytes": 167991,
|
||||
"rpc_count": 25,
|
||||
"elapsed_seconds": 16.933,
|
||||
"cpu_seconds": 16.714,
|
||||
"peak_family_mib": 1261.4,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [46.9, 51.0, 36.3, 24.4, 38.6, 43.8]
|
||||
},
|
||||
{
|
||||
"name": "sdk-mixed-javascript",
|
||||
"status": "passed",
|
||||
"answer_sha256": "0fe3ad249eb1a7233a4966a56652c173667c48fdaf395b6c08fc6901ef3b46f7",
|
||||
"query_ms": 10904.5,
|
||||
"query_reply_bytes": 208356,
|
||||
"rpc_count": 28,
|
||||
"elapsed_seconds": 26.5,
|
||||
"cpu_seconds": 26.297,
|
||||
"peak_family_mib": 1346.5,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
},
|
||||
"payload_cleanup_verified": true,
|
||||
"response_capacity_refused": false,
|
||||
"warm_page_ms": [1321.9, 1063.3, 1070.8, 1309.9, 1041.7, 1074.2]
|
||||
}
|
||||
],
|
||||
"model_trials": [
|
||||
{
|
||||
"name": "model-javascript-complete-1",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 76.49,
|
||||
"cpu_seconds": 24.687,
|
||||
"peak_family_mib": 818.7,
|
||||
"input_tokens": 454491,
|
||||
"cached_input_tokens": 396288,
|
||||
"uncached_input_tokens": 58203,
|
||||
"output_tokens": 2033,
|
||||
"reported_reasoning_output_tokens": 207,
|
||||
"mcp_calls_including_discovery": 9,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-complete-2",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 58.335,
|
||||
"cpu_seconds": 14.993,
|
||||
"peak_family_mib": 971.4,
|
||||
"input_tokens": 328066,
|
||||
"cached_input_tokens": 280704,
|
||||
"uncached_input_tokens": 47362,
|
||||
"output_tokens": 1676,
|
||||
"reported_reasoning_output_tokens": 129,
|
||||
"mcp_calls_including_discovery": 9,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-focused-1",
|
||||
"facts_pass": false,
|
||||
"elapsed_seconds": 34.206,
|
||||
"cpu_seconds": 16.31,
|
||||
"peak_family_mib": 811.9,
|
||||
"input_tokens": 167470,
|
||||
"cached_input_tokens": 136320,
|
||||
"uncached_input_tokens": 31150,
|
||||
"output_tokens": 633,
|
||||
"reported_reasoning_output_tokens": 172,
|
||||
"mcp_calls_including_discovery": 4,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-focused-2",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 66.455,
|
||||
"cpu_seconds": 14.337,
|
||||
"peak_family_mib": 955.3,
|
||||
"input_tokens": 339528,
|
||||
"cached_input_tokens": 307456,
|
||||
"uncached_input_tokens": 32072,
|
||||
"output_tokens": 2051,
|
||||
"reported_reasoning_output_tokens": 169,
|
||||
"mcp_calls_including_discovery": 10,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-guided-complete-1",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 69.206,
|
||||
"cpu_seconds": 13.65,
|
||||
"peak_family_mib": 888.8,
|
||||
"input_tokens": 168862,
|
||||
"cached_input_tokens": 121728,
|
||||
"uncached_input_tokens": 47134,
|
||||
"output_tokens": 2385,
|
||||
"reported_reasoning_output_tokens": 737,
|
||||
"mcp_calls_including_discovery": 6,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-guided-complete-2",
|
||||
"facts_pass": false,
|
||||
"elapsed_seconds": 29.187,
|
||||
"cpu_seconds": 15.105,
|
||||
"peak_family_mib": 697.4,
|
||||
"input_tokens": 28696,
|
||||
"cached_input_tokens": 23296,
|
||||
"uncached_input_tokens": 5400,
|
||||
"output_tokens": 610,
|
||||
"reported_reasoning_output_tokens": 379,
|
||||
"mcp_calls_including_discovery": 0,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-guided-focused-1",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 53.464,
|
||||
"cpu_seconds": 12.059,
|
||||
"peak_family_mib": 944.5,
|
||||
"input_tokens": 124218,
|
||||
"cached_input_tokens": 92416,
|
||||
"uncached_input_tokens": 31802,
|
||||
"output_tokens": 1746,
|
||||
"reported_reasoning_output_tokens": 330,
|
||||
"mcp_calls_including_discovery": 5,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-javascript-guided-focused-2",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 58.526,
|
||||
"cpu_seconds": 22.852,
|
||||
"peak_family_mib": 924.2,
|
||||
"input_tokens": 129134,
|
||||
"cached_input_tokens": 97536,
|
||||
"uncached_input_tokens": 31598,
|
||||
"output_tokens": 1478,
|
||||
"reported_reasoning_output_tokens": 69,
|
||||
"mcp_calls_including_discovery": 5,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-native-complete-1",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 52.103,
|
||||
"cpu_seconds": 8.686,
|
||||
"peak_family_mib": 1028.5,
|
||||
"input_tokens": 287913,
|
||||
"cached_input_tokens": 249984,
|
||||
"uncached_input_tokens": 37929,
|
||||
"output_tokens": 1724,
|
||||
"reported_reasoning_output_tokens": 121,
|
||||
"mcp_calls_including_discovery": 4,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-native-complete-2",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 58.261,
|
||||
"cpu_seconds": 9.102,
|
||||
"peak_family_mib": 837.4,
|
||||
"input_tokens": 348976,
|
||||
"cached_input_tokens": 286976,
|
||||
"uncached_input_tokens": 62000,
|
||||
"output_tokens": 2009,
|
||||
"reported_reasoning_output_tokens": 246,
|
||||
"mcp_calls_including_discovery": 5,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-native-focused-1",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 62.078,
|
||||
"cpu_seconds": 10.198,
|
||||
"peak_family_mib": 823.9,
|
||||
"input_tokens": 335445,
|
||||
"cached_input_tokens": 293120,
|
||||
"uncached_input_tokens": 42325,
|
||||
"output_tokens": 1924,
|
||||
"reported_reasoning_output_tokens": 87,
|
||||
"mcp_calls_including_discovery": 8,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model-native-focused-2",
|
||||
"facts_pass": true,
|
||||
"elapsed_seconds": 57.933,
|
||||
"cpu_seconds": 8.058,
|
||||
"peak_family_mib": 821.3,
|
||||
"input_tokens": 286938,
|
||||
"cached_input_tokens": 256384,
|
||||
"uncached_input_tokens": 30554,
|
||||
"output_tokens": 1831,
|
||||
"reported_reasoning_output_tokens": 106,
|
||||
"mcp_calls_including_discovery": 8,
|
||||
"payload_cleanup_verified": true,
|
||||
"memory_events": {
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"max": 0,
|
||||
"oom": 0,
|
||||
"oom_kill": 0,
|
||||
"oom_group_kill": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"failed_trials": {
|
||||
"model-javascript-focused-1": "Wrong connector discovery; approval-policy refusal; no source facts established.",
|
||||
"model-javascript-guided-complete-2": "Premature agent refusal with no REA RPC; asserted tool unavailability not established by a connection/discovery observation."
|
||||
},
|
||||
"pilot_failures": [
|
||||
"Private pilot oracle read view.item instead of view.items; corrected before the committed SDK cohorts. No product failure inferred."
|
||||
],
|
||||
"decision": "Existing query APIs suffice for these bounded questions; no general token/time advantage established. Separate imported immutable-format validation optimization is tracked in #4810.",
|
||||
"review_followup_verification": {
|
||||
"review-mixed-native": {
|
||||
"status": "passed",
|
||||
"checks": {
|
||||
"heads_checked": 64,
|
||||
"pages": 32,
|
||||
"revised_unknown_id": "unk_057c8f2dbf30ec98851a66ee054366bc6e2e168874581e6f99cb80673d0ff9b8",
|
||||
"revision_checked": 2,
|
||||
"stale_update_rejected": true,
|
||||
"tampered_import_rejected_atomically": true,
|
||||
"unknowns_after_close": 0,
|
||||
"old_reference_refused": true
|
||||
},
|
||||
"original_records_preserved": true,
|
||||
"elapsed_seconds": 18.444,
|
||||
"cpu_seconds": 18.16515,
|
||||
"peak_family_mib": 1401.6328125,
|
||||
"oom_events_at_payload_exit": 0,
|
||||
"payload_cleanup_verified": true
|
||||
},
|
||||
"review-mixed-javascript": {
|
||||
"status": "passed",
|
||||
"checks": {
|
||||
"heads_checked": 64,
|
||||
"pages": 32,
|
||||
"revised_unknown_id": "unk_057c8f2dbf30ec98851a66ee054366bc6e2e168874581e6f99cb80673d0ff9b8",
|
||||
"revision_checked": 2,
|
||||
"stale_update_rejected": true,
|
||||
"tampered_import_rejected_atomically": true,
|
||||
"unknowns_after_close": 0,
|
||||
"old_reference_refused": true
|
||||
},
|
||||
"original_records_preserved": true,
|
||||
"elapsed_seconds": 27.617,
|
||||
"cpu_seconds": 27.275299,
|
||||
"peak_family_mib": 1444.30859375,
|
||||
"oom_events_at_payload_exit": 0,
|
||||
"payload_cleanup_verified": true
|
||||
},
|
||||
"deterministic_receipt_tests": {
|
||||
"passed": 4,
|
||||
"scope": "Rejected SDK call, protocol error, receipt-write failure, simultaneous call/write failure; not real-provider evidence"
|
||||
},
|
||||
"cleanup_receipt_regressions": {
|
||||
"passed": 6,
|
||||
"scope": "Real stream/file refusals, independent peer close, and injected partial publication at the filesystem boundary. Original failure and call costs survive; failed cleanup retains workspace ownership. Harness failure checks, not real-provider evidence.",
|
||||
"baseline_reproduction": {
|
||||
"revision": "b025a919b36ec091ca888339df6b24e78e89b79b",
|
||||
"method": "Execute the prior committed finalization block with deterministic workspace or log rejection; not a production-provider reproduction.",
|
||||
"workspace_error": {
|
||||
"file_receipt_attempts": 0,
|
||||
"stdout_receipt_attempts": 0
|
||||
},
|
||||
"log_error": {
|
||||
"file_receipt_attempts": 0,
|
||||
"stdout_receipt_attempts": 0
|
||||
}
|
||||
},
|
||||
"partial_retry_baseline": {
|
||||
"revision": "5d6259e658ee54fc9829b9143a8721a2ab426e80",
|
||||
"regression_exit_code": 1,
|
||||
"observed_saved_report": "partial",
|
||||
"method": "Run the unchanged partial-write regression against the prior committed finalizer with deterministic filesystem-boundary failure injection."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
# Retained investigation query checkpoint
|
||||
|
||||
[#3134](https://github.com/morluto/rea/issues/3134) measured existing retained
|
||||
queries on Linux. Selected reads preserved the requested facts, original
|
||||
Evidence and Unknown revisions, including a mixed investigation. Existing
|
||||
contracts suffice for these questions; this checkpoint adds no production API,
|
||||
cache, persistence or default change.
|
||||
|
||||
Imported JavaScript pages repeatedly validated their entire source graph. That
|
||||
separate, observed bottleneck is owned by
|
||||
[#4810](https://github.com/morluto/rea/issues/4810). Smaller replies did not
|
||||
establish a general model-token or whole-task latency improvement.
|
||||
|
||||
The [curated measurements](retained-investigation-results.json) retain individual
|
||||
SDK/model costs and unsuccessful attempts. Raw bundles, transcripts, profiles
|
||||
and detailed receipts stay private and are not build inputs.
|
||||
|
||||
## Sources and questions
|
||||
|
||||
Runtime: `f4322b15ed1423d736bd2572cf7a8eadf2b466e1`, Linux x86-64,
|
||||
Node 24.18.0/npm 11.16.0. The SDK discovered all 207 advertised tools
|
||||
(3,152,125 serialized catalog bytes). Model trials used Codex CLI 0.161.0,
|
||||
requested `gpt-6.1-sol`/high, direct MCP, and the same four enabled tool names:
|
||||
`import_evidence_bundle`, `get_evidence_bundle`, `inspect_analysis_view`, and
|
||||
`list_unknowns`. The resolved service-side model identity is unknown. These are
|
||||
selected-client-tool trials, not an evaluation of full-catalog agent discovery.
|
||||
|
||||
Two public source workflows produced authentic bundles through production MCP:
|
||||
|
||||
- Native: `inspect_binary_layout` on Debian's `libsqlite3.so.0.8.6`, SHA-256
|
||||
`2e6eef9a727f081f0d453b4e5e6cbd8b9ef8b6f86cbf7681cbad444d3b0b55c8`.
|
||||
Pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2 observed 26 sections, 1,479
|
||||
symbols and 2,963 relocations. An independent ELF64 decoder checked section
|
||||
and relocation counts and every symbol's table/entry index, value, raw name
|
||||
bytes and source entry range against the original file.
|
||||
- JavaScript: shipped npm 11.16.0, directory inventory digest
|
||||
`f8e576a7d162542c616168b25a95a5f62b9accf29467cc37e149905ad4b8e603`.
|
||||
`analyze_javascript_application` selected exactly `package.json`,
|
||||
`bin/npm-cli.js`, `lib/cli.js`, `lib/cli/entry.js`, and
|
||||
`lib/commands/install.js`. It parsed four JavaScript files; 1,416 other
|
||||
relevant files remained unselected. The complete retained result contained
|
||||
2,822 application nodes, 1,796 semantic nodes and 13,682 semantic unknowns.
|
||||
Source-file digests and the `lib/cli.js` source-module identity were checked
|
||||
against shipped bytes. Inventory/discovery completeness remains producer-relative.
|
||||
|
||||
Analyst-recorded Unknowns came from the actual source limitations: five native
|
||||
and seven JavaScript questions. These registry heads are distinct from the
|
||||
13,682 semantic-graph unknowns. The exported histories contained 15 and 17
|
||||
records, respectively, and approximately 1.75 and 21.06 MB of canonical JSON.
|
||||
No synthetic payload arrays were used.
|
||||
|
||||
The SDK task read the first six original symbol/module rows and the relevant
|
||||
current Unknown heads. Complete delivery and selected delivery answered the
|
||||
same question; selected delivery intentionally exercised two-row pages. Each
|
||||
fresh server performed three traversals, exported again and closed. The model
|
||||
task additionally requested native identity/counts, or the five JavaScript source
|
||||
choices and exact `lib/cli.js` source module, with explicit runtime uncertainty.
|
||||
Compare arms within each declared task, not SDK costs against model costs.
|
||||
|
||||
## Correctness, capacity and lifetime
|
||||
|
||||
Both fresh-server SDK repetitions passed in both delivery arms. Selected rows,
|
||||
ordering and counts agreed with original records; all original records and
|
||||
Unknown revisions survived export/import, including links, omitted fields,
|
||||
nulls and scalar types. Query calls did not rerun either producer. The resumed
|
||||
servers had no native producer configuration.
|
||||
|
||||
The JavaScript complete bundle exceeded the default 10 MiB receive-buffer
|
||||
boundary. Its typed capacity refusal preserved the complete retained data and a
|
||||
usable connection. Complete-arm costs include that refusal and subsequent
|
||||
selected-view recovery; they are not timings for a successful complete transfer.
|
||||
|
||||
A mixed history combined both authentic investigations and promoted 64 actual
|
||||
semantic unknowns to separately identified analyst follow-ups. One follow-up
|
||||
was updated to investigating. It contained 97 records, 76 heads and 77 revisions.
|
||||
Both source queries preserved all original records/revisions. Thirty-two
|
||||
two-head pages verified every semantic-follow-up head, including revision two.
|
||||
A stale update and a tampered bundle import were rejected without changing the
|
||||
ledger. Export reproduced the complete original mixed bundle. Closing cleared
|
||||
records/Unknowns and made the old reference unavailable; no implicit persistence
|
||||
or producer restart was introduced.
|
||||
|
||||
Public CLI `inspect-evidence-bundle` summary and inline `inspect-analysis-view`
|
||||
summary also passed on both authentic sources. Portable CLI input still requires
|
||||
complete input admission/validation; small output does not remove that cost.
|
||||
|
||||
## SDK costs
|
||||
|
||||
Each row spans two fresh MCP servers. Query time is the sum of SDK RPC elapsed
|
||||
times over three traversals. Local post-call contract validation, oracle checks
|
||||
and transcript writes are included only in the whole harness, along with startup,
|
||||
full discovery, file reads, import, export and close. OS caches were uncontrolled/warm. A fresh imported
|
||||
session is not a cold native-analysis measurement.
|
||||
|
||||
| Source / arm | Three traversals | Query reply bytes | Whole elapsed | Local family CPU | Family peak |
|
||||
| ------------------------------ | ---------------: | ----------------: | --------------: | ---------------: | --------------: |
|
||||
| Native complete | 1.329–1.385 s | 11,224,227 | 14.117–14.722 s | 13.915–14.485 s | 1,110–1,161 MiB |
|
||||
| Native selected | 0.530–0.572 s | 167,985 | 12.907–13.316 s | 12.679–13.116 s | 935–1,000 MiB |
|
||||
| JavaScript complete + recovery | 11.242–11.938 s | 212,856 | 26.300–27.105 s | 26.100–26.870 s | 1,321–1,334 MiB |
|
||||
| JavaScript selected | 10.967–10.990 s | 208,350 | 25.708–26.197 s | 25.505–25.980 s | 1,336–1,344 MiB |
|
||||
|
||||
Mixed-history selected tasks took 16.933 s native and 26.500 s JavaScript,
|
||||
including the larger import and complete round-trip checks. Their family peaks
|
||||
were 1,261 and 1,347 MiB. This does not establish scaling to thousands of
|
||||
registry heads or every provider.
|
||||
|
||||
Original-session npm warm pages measured 8–31 ms; after import, repeated pages
|
||||
cost about 1.1–1.4 s. Source inspection shows that imported immutable Evidence
|
||||
does not carry the format owner's private graph proofs, so each view repeats
|
||||
full validation. Native layout views also parse the complete layout on each
|
||||
page. #4810 owns equivalent before/after verification of a correction; this
|
||||
checkpoint claims no speedup from an unimplemented cache.
|
||||
|
||||
## Actual model tasks
|
||||
|
||||
All twelve attempts retained their costs. Ten supplied every checked fact and
|
||||
current Unknown identity/status; runtime and unselected-source coverage stayed
|
||||
unknown. One initial JavaScript selected attempt searched the wrong connector
|
||||
and received an approval-policy refusal. One guided complete attempt stopped
|
||||
without an REA RPC and asserted tool unavailability without a supporting
|
||||
connection/discovery observation. Both are failed tasks, despite CLI exit zero.
|
||||
|
||||
| Cohort / arm | Successful tasks | Total input tokens | Elapsed |
|
||||
| ------------------------------------- | ---------------: | -----------------------: | -----------------------: |
|
||||
| Native complete | 2/2 | 287,913 / 348,976 | 52.103 / 58.261 s |
|
||||
| Native selected, two-head pages | 2/2 | 335,445 / 286,938 | 62.078 / 57.933 s |
|
||||
| JavaScript complete + recovery | 2/2 | 454,491 / 328,066 | 76.490 / 58.335 s |
|
||||
| JavaScript selected, two-head pages | 1/2 | 167,470 failed / 339,528 | 34.206 failed / 66.455 s |
|
||||
| JavaScript guided complete + recovery | 1/2 | 168,862 / 28,696 failed | 69.206 / 29.187 failed s |
|
||||
| JavaScript guided selected | 2/2 | 124,218 / 129,134 | 53.464 / 58.526 s |
|
||||
|
||||
The separate guided JavaScript cohort named the exact configured tools and
|
||||
allowed one sufficiently large Unknown page for this bounded question. Both
|
||||
arms received the same discovery guidance. Its single completed complete-arm
|
||||
sample is insufficient for a stable repeated-pair benefit claim. Native total
|
||||
input/time overlapped; extra pages and model turns can consume the reply saving.
|
||||
Do not pool failed tasks as fast answers or attribute guidance changes to REA.
|
||||
|
||||
The JSON report records total/cached/uncached input, output, local CPU and peak
|
||||
memory per attempt. These are client-reported usage counters, not tokenizer
|
||||
estimates; output includes the client's reported reasoning accounting without
|
||||
adding it again. Schema/discovery, errors, retries and continuations remain in
|
||||
the task. Remote model compute, full-catalog model behavior and other hosts were
|
||||
not measured.
|
||||
|
||||
Heavy runs were serial, one CPU, 8 GiB family hard limit, 7 GiB early stop,
|
||||
no swap. Kernel peaks include charged file cache. Counter observations were
|
||||
taken at payload exit while the owned observer remained alive; terminal
|
||||
whole-scope counters remain unknown. Observed payload OOM/capacity events were
|
||||
zero, all payloads exited and cleanup was verified. The expected MCP response
|
||||
refusal is a separate transport-capacity outcome.
|
||||
|
||||
## Reproduce
|
||||
|
||||
Build the pinned runtime, acquire the matching real artifacts and produce the
|
||||
two authentic histories with the public operations above. Export before close.
|
||||
Keep bundles, transcripts and model profiles private. For a known source ID:
|
||||
|
||||
```sh
|
||||
node experiments/analysis-efficiency/retained-investigation.mjs \
|
||||
--runtime-root /absolute/built-rea \
|
||||
--revision f4322b15ed1423d736bd2572cf7a8eadf2b466e1 \
|
||||
--bundle /absolute/authentic-history.json \
|
||||
--evidence-id ev_ORIGINAL_SHA256 \
|
||||
--scenario native --delivery focused \
|
||||
--output /absolute/new-receipt-directory
|
||||
```
|
||||
|
||||
Run under the existing process-family resource wrapper, with sufficient bounds
|
||||
for the selected source. Repeat with `--delivery complete`; use
|
||||
`--scenario javascript` for module rows. The task's Unknown domain is
|
||||
`retained-native` or `retained-javascript`. Output is never overwritten.
|
||||
`agent-trial.py --mode direct` supports the same four enabled names; its success
|
||||
exit only establishes client completion. Grade original IDs/digests, exact
|
||||
facts, coverage and Unknowns from the retained transcript and final answer.
|
||||
|
||||
For a privately supplied authentic mixed bundle, add `--verify-mixed`. Besides
|
||||
the normal source query and original-record round trip, this pages every current
|
||||
head in `javascript-semantic-followup`, checks the current updated revision,
|
||||
submits a deliberately stale update and a tampered copy of the bundle, verifies
|
||||
atomic refusal, then checks Unknown/reference lifetime after close. The bundle
|
||||
must contain genuine follow-ups and at least one updated head. The checkpoint's
|
||||
64-head bundle passed this mode for both source scenarios. Its extra verification
|
||||
costs are separate from the original SDK comparison rows above.
|
||||
|
||||
Rejected SDK calls are recorded before propagating the original failure, with
|
||||
request, phase and elapsed time. Reply bytes stay null when no usable reply was
|
||||
received. Receipt-write failures remain distinct; simultaneous call/write
|
||||
failures retain both causes. Deterministic failure tests run with
|
||||
`python3 -m unittest discover -s experiments/analysis-efficiency -p test_retained_queries.py`.
|
||||
|
||||
Finalization independently attempts client/transport close, log completion and
|
||||
both receipt outputs. Cleanup failures preserve the original task failure and
|
||||
call costs, record the failing phase and retain the workspace for retry. A file
|
||||
receipt refusal still permits stdout; a broken stdout write is recorded in a
|
||||
file retry. Files publish through a sibling temporary file and rename; an
|
||||
interrupted retry preserves the previous receipt. Failed publication files stay
|
||||
in the owned output directory. Cleanup or receipt-output failures make the
|
||||
command fail. Six additional regressions exercise stream/file errors, partial
|
||||
publication and peer cleanup; these are harness failure checks, not real-provider
|
||||
evidence.
|
||||
@@ -0,0 +1,310 @@
|
||||
/** Measure public retained queries against an authentic, previously exported bundle. */
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import {
|
||||
appendFile,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, isAbsolute, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
import { finished } from "node:stream/promises";
|
||||
import canonicalize from "canonicalize";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import {
|
||||
requireMcpOperationResult,
|
||||
requireMcpToolError,
|
||||
} from "../../scripts/lib/mcp-verifier-results.mjs";
|
||||
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
|
||||
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
|
||||
import {
|
||||
verifyMixedRetainedHistory,
|
||||
verifyMixedRetainedClosure,
|
||||
retainedUnknownHeads,
|
||||
} from "./retained-mixed-verification.mjs";
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
"runtime-root": {
|
||||
type: "string",
|
||||
default: resolve(dirname(fileURLToPath(import.meta.url)), "../.."),
|
||||
},
|
||||
revision: { type: "string" },
|
||||
bundle: { type: "string" },
|
||||
"evidence-id": { type: "string" },
|
||||
scenario: { type: "string" },
|
||||
delivery: { type: "string" },
|
||||
output: { type: "string" },
|
||||
"verify-mixed": { type: "boolean", default: false },
|
||||
},
|
||||
});
|
||||
for (const key of ["runtime-root", "bundle", "output"])
|
||||
assert.ok(values[key] && isAbsolute(values[key]), `${key} must be absolute`);
|
||||
assert.ok(values.revision && values["evidence-id"]);
|
||||
assert.ok(["native", "javascript"].includes(values.scenario));
|
||||
assert.ok(["complete", "focused"].includes(values.delivery));
|
||||
const root = values["runtime-root"];
|
||||
const output = values.output;
|
||||
await mkdir(output, { recursive: false, mode: 0o700 });
|
||||
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const sourceBytes = await readFile(values.bundle);
|
||||
const original = JSON.parse(sourceBytes);
|
||||
const source = original.records.find(
|
||||
(record) => record.evidence_id === values["evidence-id"],
|
||||
);
|
||||
const operation =
|
||||
values.scenario === "native"
|
||||
? "inspect_binary_layout"
|
||||
: "analyze_javascript_application";
|
||||
assert.equal(source?.operation, operation);
|
||||
const unknownDomain = `retained-${values.scenario}`;
|
||||
const expectedUnknowns = retainedUnknownHeads(original.unknowns, unknownDomain);
|
||||
// These oracles check selected original facts, not producer discovery completeness.
|
||||
const expectedRows =
|
||||
values.scenario === "native"
|
||||
? source.normalized_result.symbols
|
||||
: source.normalized_result.graph.nodes
|
||||
.filter((node) =>
|
||||
["javascript-asset", "javascript-module", "source-module"].includes(
|
||||
node.kind,
|
||||
),
|
||||
)
|
||||
.map((node) => {
|
||||
const candidates = node.observations.flatMap((observation) => {
|
||||
const property = observation.properties.path;
|
||||
const where = observation.evidence.location;
|
||||
return [
|
||||
...(typeof property === "string" ? [property] : []),
|
||||
...(where.available && where.value.kind === "artifact-path"
|
||||
? [where.value.path]
|
||||
: where.available && where.value.kind === "source-range"
|
||||
? [where.value.source]
|
||||
: []),
|
||||
];
|
||||
});
|
||||
return {
|
||||
node_id: node.node_id,
|
||||
kind: node.kind,
|
||||
path:
|
||||
node.identity.strategy === "canonical-path"
|
||||
? node.identity.path
|
||||
: node.identity.strategy === "source-map-original"
|
||||
? node.identity.original_source
|
||||
: (candidates[0] ?? null),
|
||||
};
|
||||
});
|
||||
assert.ok(expectedRows.length >= 6, "This task needs six original rows");
|
||||
const runtimeEntrySha256 = digest(
|
||||
await readFile(join(root, "dist/mcp/main.js")),
|
||||
);
|
||||
const account = await mkdtemp(join(tmpdir(), "rea-retained-query-"));
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [join(root, "scripts/rea.mjs"), "mcp"],
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
HOME: account,
|
||||
NODE_OPTIONS: "--max-old-space-size=2048",
|
||||
REA_LOG_LEVEL: "silent",
|
||||
},
|
||||
stderr: "pipe",
|
||||
});
|
||||
const serverLog = createWriteStream(join(output, "server.log"));
|
||||
const serverLogFinished = finished(serverLog);
|
||||
serverLogFinished.catch(() => {});
|
||||
transport.stderr?.pipe(serverLog);
|
||||
const client = new Client({
|
||||
name: "retained-investigation-measurement",
|
||||
version: "1",
|
||||
});
|
||||
const report = {
|
||||
revision: values.revision,
|
||||
runtime_entry_sha256: runtimeEntrySha256,
|
||||
bundle_sha256: digest(sourceBytes),
|
||||
bundle_bytes: sourceBytes.length,
|
||||
source_evidence_id: source.evidence_id,
|
||||
scenario: values.scenario,
|
||||
delivery: values.delivery,
|
||||
verify_mixed: values["verify-mixed"],
|
||||
calls: [],
|
||||
status: "running",
|
||||
};
|
||||
const call = createRetainedQueryCaller(client, async (receipt, event) => {
|
||||
report.calls.push(receipt);
|
||||
await appendFile(
|
||||
join(output, "transcript.jsonl"),
|
||||
JSON.stringify(event) + "\n",
|
||||
);
|
||||
});
|
||||
const result = (reply, name) => requireMcpOperationResult(reply, name);
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const started = performance.now();
|
||||
const catalog = await client.listTools();
|
||||
report.catalog = {
|
||||
elapsed_ms: performance.now() - started,
|
||||
tools: catalog.tools.length,
|
||||
bytes: Buffer.byteLength(JSON.stringify(catalog)),
|
||||
sha256: digest(JSON.stringify(catalog)),
|
||||
};
|
||||
assert.equal(catalog.nextCursor, undefined);
|
||||
await writeFile(join(output, "catalog.json"), JSON.stringify(catalog));
|
||||
result(
|
||||
await call("import_evidence_bundle", { path: values.bundle }, "import"),
|
||||
"import_evidence_bundle",
|
||||
);
|
||||
const answers = [];
|
||||
for (let repeat = 0; repeat < 3; repeat += 1) {
|
||||
let answer;
|
||||
if (values.delivery === "complete") {
|
||||
const reply = await call("get_evidence_bundle", {}, `complete-${repeat}`);
|
||||
if (reply.isError !== true) {
|
||||
const bundle = result(reply, "get_evidence_bundle");
|
||||
assert.deepEqual(
|
||||
bundle.records.find(
|
||||
(record) => record.evidence_id === source.evidence_id,
|
||||
),
|
||||
source,
|
||||
);
|
||||
assert.deepEqual(bundle.unknowns, original.unknowns);
|
||||
answer = [expectedRows.slice(0, 6), expectedUnknowns];
|
||||
} else {
|
||||
// Keep capacity refusals and every recovery RPC in the completed task.
|
||||
const failure = requireMcpToolError(reply);
|
||||
assert.equal(failure.code, "resource_constraint");
|
||||
assert.equal(failure.details?.resource, "transport");
|
||||
report.complete_delivery_refused = true;
|
||||
}
|
||||
}
|
||||
if (answer === undefined) {
|
||||
const metadata = result(
|
||||
await call(
|
||||
"get_evidence_bundle",
|
||||
{
|
||||
detail: "summary",
|
||||
filters: { evidence_id: source.evidence_id },
|
||||
},
|
||||
`discovery-${repeat}`,
|
||||
),
|
||||
"get_evidence_bundle",
|
||||
);
|
||||
assert.equal(metadata.matching_records, 1);
|
||||
assert.equal(metadata.records[0].evidence_id, source.evidence_id);
|
||||
const rows = [];
|
||||
for (const offset of [0, 2, 4]) {
|
||||
const view = result(
|
||||
await call(
|
||||
"inspect_analysis_view",
|
||||
{
|
||||
source: {
|
||||
kind: "retained-evidence",
|
||||
evidence_id: source.evidence_id,
|
||||
},
|
||||
view: {
|
||||
kind: "page",
|
||||
collection:
|
||||
values.scenario === "native" ? "symbols" : "modules",
|
||||
offset,
|
||||
limit: 2,
|
||||
},
|
||||
},
|
||||
`page-${repeat}-${offset}`,
|
||||
),
|
||||
"inspect_analysis_view",
|
||||
);
|
||||
assert.equal(view.parent_evidence_id, source.evidence_id);
|
||||
assert.equal(view.coverage.total, expectedRows.length);
|
||||
assert.equal(view.coverage.examined, 2);
|
||||
assert.equal(
|
||||
view.coverage.next_offset,
|
||||
offset + 2 < expectedRows.length ? offset + 2 : null,
|
||||
);
|
||||
assert.deepEqual(view.items, expectedRows.slice(offset, offset + 2));
|
||||
rows.push(...view.items);
|
||||
}
|
||||
const unknowns = [];
|
||||
let offset = 0;
|
||||
do {
|
||||
const page = result(
|
||||
await call(
|
||||
"list_unknowns",
|
||||
{ domain: unknownDomain, offset, limit: 2 },
|
||||
`unknowns-${repeat}-${offset}`,
|
||||
),
|
||||
"list_unknowns",
|
||||
);
|
||||
assert.equal(page.total, expectedUnknowns.length);
|
||||
assert.equal(page.offset, offset);
|
||||
assert.deepEqual(
|
||||
page.items,
|
||||
expectedUnknowns.slice(offset, offset + 2),
|
||||
);
|
||||
assert.equal(
|
||||
page.next_offset,
|
||||
offset + 2 < expectedUnknowns.length ? offset + 2 : null,
|
||||
);
|
||||
unknowns.push(...page.items);
|
||||
offset = page.next_offset;
|
||||
} while (offset !== null);
|
||||
assert.deepEqual(unknowns, expectedUnknowns);
|
||||
answer = [rows, unknowns];
|
||||
}
|
||||
answers.push(digest(canonicalize(answer)));
|
||||
}
|
||||
assert.equal(new Set(answers).size, 1);
|
||||
report.answer_sha256 = answers[0];
|
||||
if (values["verify-mixed"])
|
||||
report.mixed_verification = await verifyMixedRetainedHistory({
|
||||
call,
|
||||
original,
|
||||
sourceId: source.evidence_id,
|
||||
output,
|
||||
});
|
||||
const roundtrip = join(output, "roundtrip.json");
|
||||
result(
|
||||
await call("export_evidence_bundle", { path: roundtrip }, "export"),
|
||||
"export_evidence_bundle",
|
||||
);
|
||||
const exported = JSON.parse(await readFile(roundtrip, "utf8"));
|
||||
const exportedRecords = new Map(
|
||||
exported.records.map((record) => [record.evidence_id, record]),
|
||||
);
|
||||
for (const record of original.records)
|
||||
assert.deepEqual(exportedRecords.get(record.evidence_id), record);
|
||||
assert.deepEqual(exported.unknowns, original.unknowns);
|
||||
report.original_records_preserved = true;
|
||||
result(await call("close_binary", {}, "close"), "close_binary");
|
||||
const closed = result(
|
||||
await call("get_evidence_bundle", { detail: "summary" }, "after-close"),
|
||||
"get_evidence_bundle",
|
||||
);
|
||||
assert.equal(closed.total_retained_records, 0);
|
||||
report.close_cleared_records = true;
|
||||
if (values["verify-mixed"])
|
||||
Object.assign(
|
||||
report.mixed_verification,
|
||||
await verifyMixedRetainedClosure(call, source.evidence_id),
|
||||
);
|
||||
assert.equal(digest(await readFile(values.bundle)), report.bundle_sha256);
|
||||
report.status = "passed";
|
||||
} catch (cause) {
|
||||
report.status = "failed";
|
||||
report.failure = { name: cause.name, message: cause.message };
|
||||
} finally {
|
||||
const finalized = await finalizeRetainedQueryReceipt({
|
||||
report,
|
||||
client,
|
||||
transport,
|
||||
account,
|
||||
serverLog,
|
||||
serverLogFinished,
|
||||
output,
|
||||
});
|
||||
if (report.status !== "passed" || !finalized) process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
requireMcpOperationResult as result,
|
||||
requireMcpToolError,
|
||||
} from "../../scripts/lib/mcp-verifier-results.mjs";
|
||||
|
||||
/** Expected current heads from the original exported revisions, not displayed pages. */
|
||||
export const retainedUnknownHeads = (unknowns, domain) => {
|
||||
const heads = new Map();
|
||||
for (const unknown of unknowns) {
|
||||
const previous = heads.get(unknown.unknown_id);
|
||||
if (previous === undefined || previous.revision < unknown.revision)
|
||||
heads.set(unknown.unknown_id, unknown);
|
||||
}
|
||||
return [...heads.values()]
|
||||
.filter((unknown) => unknown.domain === domain)
|
||||
.sort((a, b) =>
|
||||
a.unknown_id < b.unknown_id ? -1 : a.unknown_id > b.unknown_id ? 1 : 0,
|
||||
);
|
||||
};
|
||||
|
||||
const verifyFollowupPages = async (call, original) => {
|
||||
const expected = retainedUnknownHeads(
|
||||
original.unknowns,
|
||||
"javascript-semantic-followup",
|
||||
);
|
||||
assert.ok(
|
||||
expected.length > 0,
|
||||
"Supply an authentic mixed history with semantic follow-ups",
|
||||
);
|
||||
const revised = expected.find((unknown) => unknown.revision > 1);
|
||||
assert.ok(revised, "Mixed verification requires a retained updated Unknown");
|
||||
let offset = 0;
|
||||
let pages = 0;
|
||||
const observed = [];
|
||||
do {
|
||||
const page = result(
|
||||
await call(
|
||||
"list_unknowns",
|
||||
{
|
||||
domain: "javascript-semantic-followup",
|
||||
offset,
|
||||
limit: 2,
|
||||
},
|
||||
`mixed-heads-${offset}`,
|
||||
),
|
||||
"list_unknowns",
|
||||
);
|
||||
assert.equal(page.total, expected.length);
|
||||
assert.equal(page.offset, offset);
|
||||
assert.deepEqual(page.items, expected.slice(offset, offset + 2));
|
||||
assert.equal(
|
||||
page.next_offset,
|
||||
offset + 2 < expected.length ? offset + 2 : null,
|
||||
);
|
||||
observed.push(...page.items);
|
||||
offset = page.next_offset;
|
||||
pages += 1;
|
||||
} while (offset !== null);
|
||||
assert.deepEqual(observed, expected);
|
||||
return { revised, headsChecked: expected.length, pages };
|
||||
};
|
||||
|
||||
const verifyMutationRefusals = async ({
|
||||
call,
|
||||
original,
|
||||
sourceId,
|
||||
output,
|
||||
revised,
|
||||
}) => {
|
||||
const before = result(
|
||||
await call(
|
||||
"get_evidence_bundle",
|
||||
{ detail: "summary" },
|
||||
"mixed-before-refusals",
|
||||
),
|
||||
"get_evidence_bundle",
|
||||
);
|
||||
const stale = await call(
|
||||
"update_unknown",
|
||||
{
|
||||
unknown_id: revised.unknown_id,
|
||||
expected_revision: revised.revision - 1,
|
||||
status: revised.status,
|
||||
severity: revised.severity,
|
||||
supporting_evidence_ids: revised.supporting_evidence_ids,
|
||||
contradicting_evidence_ids: revised.contradicting_evidence_ids,
|
||||
required_authority: revised.required_authority,
|
||||
required_confidence: revised.required_confidence,
|
||||
required_environment: revised.required_environment,
|
||||
recommended_probes: revised.recommended_probes,
|
||||
relationships: revised.relationships,
|
||||
resolution: revised.resolution,
|
||||
},
|
||||
"mixed-stale-update",
|
||||
);
|
||||
assert.equal(requireMcpToolError(stale).code, "revision_conflict");
|
||||
const tampered = structuredClone(original);
|
||||
const target = tampered.records.find(
|
||||
(record) => record.evidence_id === sourceId,
|
||||
);
|
||||
assert.ok(target);
|
||||
target.normalized_result = { checkpoint_tampered: true };
|
||||
const path = join(output, "tampered-import.json");
|
||||
await writeFile(path, JSON.stringify(tampered), { flag: "wx" });
|
||||
const rejected = await call(
|
||||
"import_evidence_bundle",
|
||||
{ path },
|
||||
"mixed-tampered-import",
|
||||
);
|
||||
assert.equal(
|
||||
requireMcpToolError(rejected).code,
|
||||
"evidence_integrity_mismatch",
|
||||
);
|
||||
const after = result(
|
||||
await call(
|
||||
"get_evidence_bundle",
|
||||
{ detail: "summary" },
|
||||
"mixed-after-refusals",
|
||||
),
|
||||
"get_evidence_bundle",
|
||||
);
|
||||
assert.deepEqual(after, before);
|
||||
};
|
||||
|
||||
/** Verify original mixed-history heads, CAS rejection and atomic import failure. */
|
||||
export const verifyMixedRetainedHistory = async (options) => {
|
||||
const { revised, headsChecked, pages } = await verifyFollowupPages(
|
||||
options.call,
|
||||
options.original,
|
||||
);
|
||||
await verifyMutationRefusals({ ...options, revised });
|
||||
return {
|
||||
heads_checked: headsChecked,
|
||||
pages,
|
||||
revised_unknown_id: revised.unknown_id,
|
||||
revision_checked: revised.revision,
|
||||
stale_update_rejected: true,
|
||||
tampered_import_rejected_atomically: true,
|
||||
};
|
||||
};
|
||||
|
||||
/** Closing must neither retain old Unknowns nor rehydrate a closed source. */
|
||||
export const verifyMixedRetainedClosure = async (call, sourceId) => {
|
||||
const unknowns = result(
|
||||
await call("list_unknowns", {}, "mixed-after-close-heads"),
|
||||
"list_unknowns",
|
||||
);
|
||||
assert.equal(unknowns.total, 0);
|
||||
const missing = await call(
|
||||
"inspect_analysis_view",
|
||||
{
|
||||
source: { kind: "retained-evidence", evidence_id: sourceId },
|
||||
view: { kind: "summary" },
|
||||
},
|
||||
"mixed-after-close-reference",
|
||||
);
|
||||
const failure = requireMcpToolError(missing);
|
||||
assert.equal(failure.details?.reason, "missing");
|
||||
return { unknowns_after_close: 0, old_reference_refused: true };
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/** Keep settled and rejected RPCs in the same receipt before returning control. */
|
||||
export const createRetainedQueryCaller =
|
||||
(client, record) => async (name, input, phase) => {
|
||||
const start = performance.now();
|
||||
let reply;
|
||||
try {
|
||||
reply = await client.callTool(
|
||||
{ name, arguments: input },
|
||||
{ timeout: 300000 },
|
||||
);
|
||||
} catch (cause) {
|
||||
const failure =
|
||||
cause instanceof Error
|
||||
? { name: cause.name, message: cause.message }
|
||||
: { name: typeof cause, message: String(cause) };
|
||||
const receipt = {
|
||||
name,
|
||||
input,
|
||||
phase,
|
||||
elapsed_ms: performance.now() - start,
|
||||
reply_bytes: null,
|
||||
is_error: true,
|
||||
failure,
|
||||
};
|
||||
try {
|
||||
await record(receipt, { name, input, phase, failure });
|
||||
} catch (recordingFailure) {
|
||||
throw new AggregateError(
|
||||
[cause, recordingFailure],
|
||||
"MCP call and receipt recording both failed",
|
||||
);
|
||||
}
|
||||
throw cause;
|
||||
}
|
||||
const receipt = {
|
||||
name,
|
||||
input,
|
||||
phase,
|
||||
elapsed_ms: performance.now() - start,
|
||||
reply_bytes: Buffer.byteLength(JSON.stringify(reply)),
|
||||
is_error: reply.isError === true,
|
||||
};
|
||||
// A receipt-write failure is distinct from a rejected MCP call.
|
||||
await record(receipt, { name, input, phase, reply });
|
||||
return reply;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { rename, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
const attempt = async (failures, phase, action) => {
|
||||
try {
|
||||
await action();
|
||||
return true;
|
||||
} catch (cause) {
|
||||
failures.push(`${phase}: ${String(cause)}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const writeReceipt = (stream, text) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const onError = (cause) => reject(cause);
|
||||
stream.once("error", onError);
|
||||
try {
|
||||
stream.write(text, (cause) => {
|
||||
if (cause !== null && cause !== undefined) {
|
||||
// A Writable emits its error after notifying the write callback.
|
||||
reject(cause);
|
||||
} else {
|
||||
stream.off("error", onError);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} catch (cause) {
|
||||
stream.off("error", onError);
|
||||
reject(cause);
|
||||
}
|
||||
});
|
||||
|
||||
/** Preserve task costs through independent cleanup and receipt-output failures. */
|
||||
export const finalizeRetainedQueryReceipt = async ({
|
||||
report,
|
||||
client,
|
||||
transport,
|
||||
account,
|
||||
serverLog,
|
||||
serverLogFinished,
|
||||
output,
|
||||
stdout = process.stdout,
|
||||
}) => {
|
||||
report.cleanup_failures = [];
|
||||
await Promise.all([
|
||||
attempt(report.cleanup_failures, "client", () => client.close()),
|
||||
attempt(report.cleanup_failures, "transport", () => transport.close()),
|
||||
]);
|
||||
await attempt(report.cleanup_failures, "server-log", async () => {
|
||||
if (!serverLog.writableEnded) serverLog.end();
|
||||
await serverLogFinished;
|
||||
});
|
||||
if (report.cleanup_failures.length === 0)
|
||||
await attempt(report.cleanup_failures, "workspace", () =>
|
||||
rm(account, { recursive: true }),
|
||||
);
|
||||
if (report.cleanup_failures.length > 0) report.retained_workspace = account;
|
||||
|
||||
report.receipt_failures = [];
|
||||
let publication = 0;
|
||||
const saveReport = async () => {
|
||||
// Keep failed publications in the owned output directory for inspection.
|
||||
// A partial retry must never truncate an already published cost receipt.
|
||||
const pending = join(output, `.report-${++publication}.json.pending`);
|
||||
await writeFile(pending, JSON.stringify(report, null, 2) + "\n", {
|
||||
flag: "wx",
|
||||
});
|
||||
await rename(pending, join(output, "report.json"));
|
||||
};
|
||||
await attempt(report.receipt_failures, "report-file", saveReport);
|
||||
const stdoutWritten = await attempt(report.receipt_failures, "stdout", () =>
|
||||
writeReceipt(stdout, JSON.stringify(report) + "\n"),
|
||||
);
|
||||
// Preserve a broken stdout diagnostic in the file that may already exist.
|
||||
if (!stdoutWritten)
|
||||
await attempt(report.receipt_failures, "report-file-retry", saveReport);
|
||||
return (
|
||||
report.cleanup_failures.length === 0 && report.receipt_failures.length === 0
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Deterministic RPC receipt failures; not real-provider or model evidence."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
|
||||
class RetainedQueryReceipts(unittest.TestCase):
|
||||
def run_node(self, body):
|
||||
module = Path(__file__).with_name('retained-query-calls.mjs').resolve().as_uri()
|
||||
finalizer = Path(__file__).with_name('retained-query-finalization.mjs').resolve().as_uri()
|
||||
source = ('import assert from "node:assert/strict";\n'
|
||||
'const {createRetainedQueryCaller} = await import(' + json.dumps(module) + ');\n'
|
||||
'const {finalizeRetainedQueryReceipt} = await import(' + json.dumps(finalizer) + ');\n' + body)
|
||||
result = subprocess.run(['node', '--input-type=module', '-'], input=source,
|
||||
text=True, capture_output=True, timeout=15)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def run_finalization(self, body):
|
||||
self.run_node('''
|
||||
import {mkdtemp,mkdir,readFile,writeFile,rm,stat} from 'node:fs/promises';
|
||||
import {join} from 'node:path';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {Writable} from 'node:stream';
|
||||
import {finished} from 'node:stream/promises';
|
||||
const root=await mkdtemp(join(tmpdir(),'rea-receipt-test-'));
|
||||
const account=join(root,'account');const output=join(root,'output');
|
||||
await mkdir(account);await mkdir(output);
|
||||
const chunks=[];
|
||||
const stdout=new Writable({write(chunk,encoding,done){chunks.push(chunk.toString());done();}});
|
||||
const serverLog=new Writable({write(chunk,encoding,done){done();}});
|
||||
const serverLogFinished=finished(serverLog);serverLogFinished.catch(()=>{});
|
||||
const originalFailure={name:'Error',message:'original request failed'};
|
||||
const report={status:'failed',failure:originalFailure,calls:[{phase:'page',elapsed_ms:17}]};
|
||||
const input={report,client:{close:async()=>{}},transport:{close:async()=>{}},
|
||||
account,output,stdout,serverLog,serverLogFinished};
|
||||
const saved=async()=>JSON.parse(await readFile(join(output,'report.json'),'utf8'));
|
||||
try {
|
||||
''' + body + '''
|
||||
} finally {
|
||||
serverLog.destroy();await serverLogFinished.catch(()=>{});stdout.destroy();
|
||||
await rm(root,{recursive:true,force:true});
|
||||
}
|
||||
''')
|
||||
|
||||
def test_rejected_call_preserves_request_phase_cost_and_original_failure(self):
|
||||
self.run_node('''
|
||||
const failure = new Error('owned server closed');
|
||||
const input = {offset:0, limit:2}; const rows=[]; const events=[];
|
||||
const call = createRetainedQueryCaller({callTool:async request=>{
|
||||
assert.strictEqual(request.arguments,input);throw failure;
|
||||
}}, async(row,event)=>{rows.push(row);events.push(event);});
|
||||
await assert.rejects(call('list_unknowns',input,'resume-2'), e=>e===failure);
|
||||
assert.equal(rows.length,1);assert.equal(events.length,1);
|
||||
assert.strictEqual(rows[0].input,input);assert.equal(rows[0].name,'list_unknowns');
|
||||
assert.equal(rows[0].phase,'resume-2');assert.ok(rows[0].elapsed_ms>=0);
|
||||
assert.equal(rows[0].reply_bytes,null);assert.equal(rows[0].is_error,true);
|
||||
assert.deepEqual(events[0].failure,{name:'Error',message:'owned server closed'});
|
||||
''')
|
||||
|
||||
def test_protocol_error_is_a_received_reply_with_known_bytes(self):
|
||||
self.run_node('''
|
||||
const reply={isError:true,content:[{type:'text',text:'capacity refusal'}]};const rows=[];
|
||||
const call=createRetainedQueryCaller({callTool:async()=>reply},async row=>rows.push(row));
|
||||
assert.strictEqual(await call('get_evidence_bundle',{},'complete'),reply);
|
||||
assert.equal(rows.length,1);assert.equal(rows[0].is_error,true);
|
||||
assert.equal(rows[0].reply_bytes,Buffer.byteLength(JSON.stringify(reply)));
|
||||
assert.equal(Object.hasOwn(rows[0],'failure'),false);
|
||||
''')
|
||||
|
||||
def test_receipt_write_error_does_not_reclassify_a_completed_rpc(self):
|
||||
self.run_node('''
|
||||
const failure=new Error('receipt disk unavailable');const rows=[];
|
||||
const call=createRetainedQueryCaller({callTool:async()=>({content:[]})},async row=>{
|
||||
rows.push(row);throw failure;
|
||||
});
|
||||
await assert.rejects(call('list_unknowns',{},'read'),e=>e===failure);
|
||||
assert.equal(rows.length,1);assert.equal(rows[0].is_error,false);
|
||||
assert.equal(Object.hasOwn(rows[0],'failure'),false);
|
||||
''')
|
||||
|
||||
def test_failed_recording_preserves_the_prior_rpc_failure(self):
|
||||
self.run_node('''
|
||||
const rpc=new Error('request timeout');const disk=new Error('receipt close failed');
|
||||
const call=createRetainedQueryCaller({callTool:async()=>{throw rpc;}},async()=>{throw disk;});
|
||||
await assert.rejects(call('inspect_analysis_view',{},'selected'),e=>
|
||||
e instanceof AggregateError && e.errors[0]===rpc && e.errors[1]===disk);
|
||||
''')
|
||||
|
||||
def test_log_stream_failure_keeps_costs_both_receipts_and_workspace(self):
|
||||
self.run_finalization('''
|
||||
const log=new Writable({write(chunk,encoding,done){done(new Error('log disk failed'));}});
|
||||
const logFinished=finished(log);logFinished.catch(()=>{});log.write('diagnostic');
|
||||
assert.equal(await finalizeRetainedQueryReceipt({...input,serverLog:log,
|
||||
serverLogFinished:logFinished}),false);
|
||||
assert.match(report.cleanup_failures[0],/server-log:.*log disk failed/);
|
||||
assert.strictEqual(report.failure,originalFailure);
|
||||
assert.equal(report.calls[0].elapsed_ms,17);assert.equal(report.retained_workspace,account);
|
||||
assert.equal((await stat(account)).isDirectory(),true);
|
||||
assert.deepEqual(await saved(),report);assert.deepEqual(JSON.parse(chunks.join('')),report);
|
||||
''')
|
||||
|
||||
def test_workspace_removal_error_still_writes_the_original_failure(self):
|
||||
self.run_finalization('''
|
||||
const blocked=join(root,'owned-file');await writeFile(blocked,'owned');
|
||||
const malformedAccount=join(blocked,'child');
|
||||
assert.equal(await finalizeRetainedQueryReceipt({...input,account:malformedAccount}),false);
|
||||
assert.match(report.cleanup_failures[0],/^workspace:/);
|
||||
assert.equal(report.retained_workspace,malformedAccount);
|
||||
assert.deepEqual((await saved()).failure,originalFailure);
|
||||
assert.deepEqual(JSON.parse(chunks.join('')),report);
|
||||
''')
|
||||
|
||||
def test_report_file_refusal_preserves_the_stdout_receipt(self):
|
||||
self.run_finalization('''
|
||||
await mkdir(join(output,'report.json'));
|
||||
assert.equal(await finalizeRetainedQueryReceipt(input),false);
|
||||
assert.deepEqual(report.cleanup_failures,[]);
|
||||
assert.match(report.receipt_failures[0],/^report-file:/);
|
||||
assert.deepEqual(JSON.parse(chunks.join('')),report);
|
||||
assert.deepEqual(report.failure,originalFailure);
|
||||
''')
|
||||
|
||||
def test_stdout_write_error_is_handled_and_saved_in_the_file(self):
|
||||
self.run_finalization('''
|
||||
const failedStdout=new Writable({write(chunk,encoding,done){done(new Error('broken pipe'));}});
|
||||
assert.equal(await finalizeRetainedQueryReceipt({...input,stdout:failedStdout}),false);
|
||||
assert.match(report.receipt_failures[0],/stdout:.*broken pipe/);
|
||||
assert.deepEqual(await saved(),report);assert.deepEqual(report.failure,originalFailure);
|
||||
''')
|
||||
|
||||
def test_partial_retry_after_stdout_failure_preserves_the_prior_receipt(self):
|
||||
self.run_finalization('''
|
||||
const fs=await import('node:fs/promises');
|
||||
const {syncBuiltinESMExports}=await import('node:module');
|
||||
const originalWrite=fs.default.writeFile;let writes=0;let before;
|
||||
fs.default.writeFile=async(...args)=>{
|
||||
if(++writes===2){await originalWrite(args[0],'partial',args[2]);throw new Error('disk quota');}
|
||||
return originalWrite(...args);
|
||||
};
|
||||
syncBuiltinESMExports();
|
||||
try {
|
||||
const failedStdout=new Writable({async write(chunk,encoding,done){
|
||||
before=await readFile(join(output,'report.json'),'utf8');done(new Error('broken pipe'));
|
||||
}});
|
||||
assert.equal(await finalizeRetainedQueryReceipt({...input,stdout:failedStdout}),false);
|
||||
assert.equal(writes,2);assert.equal(report.receipt_failures.length,2);
|
||||
assert.match(report.receipt_failures[1],/report-file-retry:.*disk quota/);
|
||||
assert.equal(await readFile(join(output,'report.json'),'utf8'),before);
|
||||
assert.deepEqual((await saved()).failure,originalFailure);
|
||||
assert.equal((await saved()).calls[0].elapsed_ms,17);
|
||||
} finally {fs.default.writeFile=originalWrite;syncBuiltinESMExports();}
|
||||
''')
|
||||
|
||||
def test_failed_client_close_keeps_ownership_and_attempts_peer_cleanup(self):
|
||||
self.run_finalization('''
|
||||
const closed=[];
|
||||
assert.equal(await finalizeRetainedQueryReceipt({...input,
|
||||
client:{close(){closed.push('client');throw new Error('close failed');}},
|
||||
transport:{close:async()=>{closed.push('transport');}}}),false);
|
||||
assert.deepEqual(closed,['client','transport']);
|
||||
assert.match(report.cleanup_failures[0],/client:.*close failed/);
|
||||
assert.equal(report.retained_workspace,account);assert.equal(serverLog.writableEnded,true);
|
||||
assert.equal((await stat(account)).isDirectory(),true);
|
||||
assert.deepEqual(await saved(),report);assert.deepEqual(JSON.parse(chunks.join('')),report);
|
||||
''')
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "rea-agents",
|
||||
"version": "6.5.0",
|
||||
"version": "6.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rea-agents",
|
||||
"version": "6.5.0",
|
||||
"version": "6.7.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "8.0.4",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rea-agents",
|
||||
"mcpName": "io.github.morluto/rea",
|
||||
"version": "6.5.0",
|
||||
"version": "6.7.0",
|
||||
"description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { copyFile, lstat, mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
const output = ".cache/ci-build-cache";
|
||||
const plan = ".cache/ci-build-plan.json";
|
||||
|
||||
function readTasks() {
|
||||
const { tasks } = z
|
||||
.object({
|
||||
tasks: z
|
||||
.array(
|
||||
z.object({
|
||||
taskId: z.string().min(1),
|
||||
// The pinned Turbo version uses eager 64-bit hexadecimal hashes.
|
||||
hash: z.string().regex(/^[0-9a-f]{16}$/u),
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
})
|
||||
.parse(JSON.parse(readFileSync(0, "utf8")));
|
||||
const identities = new Set();
|
||||
for (const { taskId } of tasks) {
|
||||
if (identities.has(taskId))
|
||||
throw new Error(`Duplicate portable build task: ${taskId}`);
|
||||
identities.add(taskId);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
async function copyEntry(hash) {
|
||||
const entries = [];
|
||||
for (const suffix of [".tar.zst", "-meta.json", "-manifest.json"]) {
|
||||
const name = `${hash}${suffix}`;
|
||||
const source = join(".turbo/cache", name);
|
||||
const entry = await lstat(source).catch((error) => {
|
||||
// Eviction can remove a cache hit even during the successful build.
|
||||
// Manifests are also optional in supported local cache entries.
|
||||
if (error.code === "ENOENT") return undefined;
|
||||
throw error;
|
||||
});
|
||||
if (!entry) continue;
|
||||
if (!entry.isFile())
|
||||
throw new Error(
|
||||
`Portable build cache entry must be a regular file: ${source}`,
|
||||
);
|
||||
entries.push({ suffix, name, source, size: entry.size });
|
||||
}
|
||||
// Publish whole reusable entries only. Consumers rebuild missing tasks.
|
||||
if (
|
||||
![".tar.zst", "-meta.json"].every((suffix) =>
|
||||
entries.some((entry) => entry.suffix === suffix),
|
||||
)
|
||||
)
|
||||
return undefined;
|
||||
const copied = [];
|
||||
let missing = false;
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
await copyFile(entry.source, join(output, entry.name));
|
||||
copied.push(entry);
|
||||
} catch (error) {
|
||||
if (error.code !== "ENOENT") throw error;
|
||||
if (entry.suffix === "-manifest.json") continue;
|
||||
missing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (missing) {
|
||||
for (const entry of copied) await rm(join(output, entry.name));
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
files: copied.length,
|
||||
bytes: copied.reduce((sum, entry) => sum + entry.size, 0),
|
||||
};
|
||||
}
|
||||
|
||||
async function snapshot(tasks) {
|
||||
await mkdir(output, { recursive: true });
|
||||
let files = 0;
|
||||
let bytes = 0;
|
||||
const available = new Set();
|
||||
try {
|
||||
for (const hash of new Set(tasks.map((task) => task.hash))) {
|
||||
const entry = await copyEntry(hash);
|
||||
if (!entry) continue;
|
||||
available.add(hash);
|
||||
files += entry.files;
|
||||
bytes += entry.bytes;
|
||||
}
|
||||
// A fully evicted cache still has an explicit artifact and coverage record;
|
||||
// required build outputs and downstream verification remain separate gates.
|
||||
const manifest = `${JSON.stringify({
|
||||
version: 1,
|
||||
tasks: tasks.map((task) => ({
|
||||
...task,
|
||||
available: available.has(task.hash),
|
||||
})),
|
||||
})}\n`;
|
||||
await writeFile(join(output, "portable-build-cache.json"), manifest);
|
||||
files++;
|
||||
bytes += Buffer.byteLength(manifest);
|
||||
} catch (error) {
|
||||
await rm(output, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
tasks: tasks.length,
|
||||
files,
|
||||
bytes,
|
||||
path: output,
|
||||
missing_tasks: tasks
|
||||
.filter((task) => !available.has(task.hash))
|
||||
.map((task) => task.taskId),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const [mode, ...extra] = process.argv.slice(2);
|
||||
if (extra.length || !["key", "snapshot"].includes(mode))
|
||||
throw new Error(
|
||||
"Usage: portable-build-cache.mjs key|snapshot < turbo-dry-plan.json",
|
||||
);
|
||||
// Invalidate this mode's previous result before parsing its input. Failed
|
||||
// validation must not leave stale plans or snapshots for downstream reuse.
|
||||
if (mode === "snapshot") await rm(output, { recursive: true, force: true });
|
||||
else await rm(plan, { force: true });
|
||||
const tasks = readTasks();
|
||||
if (mode === "key") {
|
||||
const prefix = z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^\r\n]+$/u)
|
||||
.parse(process.env.BUILD_CACHE_PREFIX);
|
||||
const hashes = tasks.map(({ taskId, hash }) => `${taskId}:${hash}`).sort();
|
||||
const digest = createHash("sha256").update(hashes.join("\n")).digest("hex");
|
||||
// Retain only validated identities for the later snapshot. Running Turbo
|
||||
// again after the build can evict cache entries before they are copied.
|
||||
await mkdir(".cache", { recursive: true });
|
||||
await writeFile(plan, `${JSON.stringify({ tasks })}\n`);
|
||||
console.log(`key=${prefix}-${digest}\nprefix=${prefix}-`);
|
||||
} else {
|
||||
await snapshot(tasks);
|
||||
}
|
||||
@@ -77,6 +77,7 @@ export const SCOPES = {
|
||||
"tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts",
|
||||
"tests/boundary/cli/nativePlistSelectionFailureCli.test.ts",
|
||||
"tests/boundary/cli/nativeDemangleCli.test.ts",
|
||||
"tests/boundary/cli/nativeDispatchPropertyAttributes.test.ts",
|
||||
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
|
||||
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
|
||||
"tests/boundary/cli/nativeSignatureCli.test.ts",
|
||||
@@ -118,6 +119,7 @@ export const SCOPES = {
|
||||
"scripts/verify/inspector/**",
|
||||
"scripts/verify-package.mjs",
|
||||
"scripts/verify-package-*",
|
||||
"scripts/verify-packed-bridge.*",
|
||||
"scripts/verify-windows-*.mjs",
|
||||
"scripts/build-windows-native.mjs",
|
||||
"scripts/windows-capabilities.mjs",
|
||||
@@ -145,6 +147,7 @@ export const SCOPES = {
|
||||
"tests/boundary/filesystem/webScriptManifestPaths.test.ts",
|
||||
"tests/boundary/process/ProcessEvidence.host.test.ts",
|
||||
"tests/boundary/process/packageNpmInvocation.test.ts",
|
||||
"tests/boundary/process/packedBridgeVerification.test.ts",
|
||||
"tests/boundary/process/windowsOutputCapture.test.ts",
|
||||
"tests/acceptance/webModuleCli.test.ts",
|
||||
"tests/acceptance/webSourceLocationCli.test.ts",
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { CdpConnection } from "../../dist/browser/CdpConnection.js";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { artifactCliEvidence } from "./artifact-e2e.mjs";
|
||||
import { closeVerifierResources } from "./verifier-cleanup.mjs";
|
||||
|
||||
/** Verify real V8 source labels, which retain spelling that navigation normalizes. */
|
||||
export async function verifyBrowserUrlUserInfo(endpoint, origin) {
|
||||
const rawUrl = `${origin.replace("://", "://user:pass@")}\\path@tail/../keep.js?x=1#frag`;
|
||||
const expectedUrl = `${origin}\\path@tail/../keep.js?x=1#frag`;
|
||||
const sources = [rawUrl, expectedUrl].map(
|
||||
(url, index) =>
|
||||
`globalThis.reaUserinfoUrlProbe${index} = () => 4731;\n//# sourceURL=${url}`,
|
||||
);
|
||||
const response = await fetch(`${endpoint}/json/new?about:blank`, {
|
||||
method: "PUT",
|
||||
});
|
||||
assert.equal(
|
||||
response.ok,
|
||||
true,
|
||||
"Browser refused the owned URL fixture target",
|
||||
);
|
||||
const target = await response.json();
|
||||
assert.equal(typeof target.id, "string");
|
||||
let connection;
|
||||
let transport;
|
||||
let client;
|
||||
let verificationError;
|
||||
try {
|
||||
connection = await CdpConnection.connect(
|
||||
target.webSocketDebuggerUrl,
|
||||
"inspect_web_page",
|
||||
);
|
||||
await loadFixturePage(connection, `${origin}/app?userinfo-probe`);
|
||||
const producerUrls = [];
|
||||
connection.onEvent((event) => {
|
||||
if (event.method === "Debugger.scriptParsed")
|
||||
producerUrls.push(event.params.url);
|
||||
});
|
||||
await connection.send("Debugger.enable");
|
||||
const browserUrl = await connection.send("Runtime.evaluate", {
|
||||
expression: `new URL(${JSON.stringify(rawUrl)}).href`,
|
||||
returnByValue: true,
|
||||
});
|
||||
assert.equal(browserUrl.exceptionDetails, undefined);
|
||||
assert.equal(browserUrl.result.value, new URL(rawUrl).href);
|
||||
assert.notEqual(browserUrl.result.value, rawUrl);
|
||||
assert.equal(new URL(browserUrl.result.value).pathname, "/keep.js");
|
||||
for (const source of sources) {
|
||||
const evaluated = await connection.send("Runtime.evaluate", {
|
||||
expression: source,
|
||||
returnByValue: true,
|
||||
});
|
||||
assert.equal(evaluated.exceptionDetails, undefined);
|
||||
}
|
||||
assert.ok(
|
||||
[rawUrl, expectedUrl].every((url) => producerUrls.includes(url)),
|
||||
"Real V8 did not retain the exact sourceURL spelling on the CDP wire",
|
||||
);
|
||||
const assertScripts = (evidence) => {
|
||||
const result = parseEvidence(evidence).normalized_result;
|
||||
return sources.map((source) => {
|
||||
const script = result.scripts.items.find(
|
||||
(item) =>
|
||||
item.source.included && item.source.artifact.text === source,
|
||||
);
|
||||
assert.ok(
|
||||
script,
|
||||
"Public capture omitted the retained V8 fixture script",
|
||||
);
|
||||
assert.equal(script.url, expectedUrl);
|
||||
assert.equal(new URL(script.url).origin, origin);
|
||||
assert.equal(
|
||||
script.source.artifact.sha256,
|
||||
createHash("sha256").update(source).digest("hex"),
|
||||
);
|
||||
return script.url;
|
||||
});
|
||||
};
|
||||
const cliUrl = assertScripts(
|
||||
await artifactCliEvidence("inspect-web-page", endpoint, [
|
||||
target.id,
|
||||
"--allowed-origins",
|
||||
origin,
|
||||
"--include-script-sources",
|
||||
"--observation-ms",
|
||||
"0",
|
||||
]),
|
||||
);
|
||||
transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [fileURLToPath(new URL("../rea.mjs", import.meta.url)), "mcp"],
|
||||
env: { ...process.env, REA_LOG_LEVEL: "silent" },
|
||||
stderr: "pipe",
|
||||
});
|
||||
client = new Client({ name: "browser-url-userinfo-e2e", version: "1" });
|
||||
await client.connect(transport);
|
||||
const mcp = await client.callTool({
|
||||
name: "inspect_web_page",
|
||||
arguments: {
|
||||
cdp_endpoint: endpoint,
|
||||
allowed_origins: [origin],
|
||||
target_id: target.id,
|
||||
include_script_sources: true,
|
||||
observation_ms: 0,
|
||||
},
|
||||
});
|
||||
assert.notEqual(mcp.isError, true, JSON.stringify(mcp));
|
||||
assert.deepEqual(assertScripts(mcp.structuredContent), cliUrl);
|
||||
return {
|
||||
cli: true,
|
||||
stdio_mcp: true,
|
||||
raw_v8_source_url: true,
|
||||
credential_free_control: true,
|
||||
original_source_digest: true,
|
||||
browser_url_parser_normalizes_backslash_and_dot_segments: true,
|
||||
};
|
||||
} catch (cause) {
|
||||
verificationError = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeVerifierResources(
|
||||
[
|
||||
async () => client?.close(),
|
||||
async () => transport?.close(),
|
||||
async () => connection?.close(),
|
||||
async () => {
|
||||
const closed = await fetch(`${endpoint}/json/close/${target.id}`);
|
||||
assert.equal(
|
||||
closed.ok,
|
||||
true,
|
||||
"Browser retained the owned URL fixture target",
|
||||
);
|
||||
},
|
||||
],
|
||||
verificationError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFixturePage(connection, url) {
|
||||
const loaded = new Set();
|
||||
let loaderId;
|
||||
let resolveLoaded;
|
||||
let timer;
|
||||
const load = new Promise((resolve) => {
|
||||
resolveLoaded = resolve;
|
||||
});
|
||||
const unsubscribe = connection.onEvent((event) => {
|
||||
if (event.method !== "Page.lifecycleEvent" || event.params.name !== "load")
|
||||
return;
|
||||
loaded.add(event.params.loaderId);
|
||||
if (event.params.loaderId === loaderId) resolveLoaded();
|
||||
});
|
||||
try {
|
||||
await connection.send("Page.enable");
|
||||
await connection.send("Page.setLifecycleEventsEnabled", { enabled: true });
|
||||
const navigation = await connection.send("Page.navigate", { url });
|
||||
assert.equal(navigation.errorText, undefined);
|
||||
assert.equal(typeof navigation.loaderId, "string");
|
||||
loaderId = navigation.loaderId;
|
||||
if (!loaded.has(loaderId))
|
||||
await Promise.race([
|
||||
load,
|
||||
new Promise((_, reject) => {
|
||||
timer = setTimeout(
|
||||
() => reject(new Error("Owned URL fixture did not finish loading")),
|
||||
10_000,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
assert.equal(
|
||||
(await connection.send("Page.getFrameTree")).frameTree.frame.url,
|
||||
url,
|
||||
);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
unsubscribe();
|
||||
await connection.send("Page.setLifecycleEventsEnabled", { enabled: false });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type {
|
||||
GhidraInstallationHost,
|
||||
GhidraInstallationInspection,
|
||||
GhidraInstallationOptions,
|
||||
} from "../../src/ghidra/GhidraInstallation.js";
|
||||
|
||||
/** Inspect prerequisites with a real owner, including cleanup before completion. */
|
||||
export function inspectVerifierGhidraInstallation(
|
||||
options: Omit<GhidraInstallationOptions, "resourceOwner">,
|
||||
host?: GhidraInstallationHost,
|
||||
): Promise<GhidraInstallationInspection>;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ArtifactResourceScope } from "../../dist/artifacts/ArtifactResourceScope.js";
|
||||
import { AnalysisCleanupLease } from "../../dist/artifacts/AnalysisCleanupLease.js";
|
||||
import { inspectGhidraInstallation } from "../../dist/ghidra/GhidraInstallation.js";
|
||||
import { closeVerifierResources } from "./verifier-cleanup.mjs";
|
||||
|
||||
/** Complete one prerequisite inspection and release its exact retained owners. */
|
||||
export async function inspectVerifierGhidraInstallation(options, host) {
|
||||
const resources = new ArtifactResourceScope();
|
||||
const resourceOwner = {
|
||||
retain(owner, resource) {
|
||||
resources.retain({
|
||||
kind: "lease",
|
||||
resource,
|
||||
lease: AnalysisCleanupLease.forCloseOwner(owner, resource),
|
||||
});
|
||||
},
|
||||
run: (operation) => resources.run(operation),
|
||||
};
|
||||
let primaryError;
|
||||
try {
|
||||
return await inspectGhidraInstallation({ ...options, resourceOwner }, host);
|
||||
} catch (cause) {
|
||||
primaryError = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeVerifierResources([() => resources.close()], primaryError);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { tmpdir } from "node:os";
|
||||
import { basename, dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { inspectGhidraInstallation } from "../../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./ghidra-installation-inspection.mjs";
|
||||
|
||||
/** Exercise qualified annotation names against real demangled C++ ABI symbols. */
|
||||
export async function verifyGhidraNamespaceAnnotations({
|
||||
@@ -353,8 +353,8 @@ async function verifyAddressNames(call, symbols, kind) {
|
||||
}
|
||||
|
||||
async function requireNamespaceDemangler(env) {
|
||||
const inspection = inspectGhidraInstallation({
|
||||
environment: process.env,
|
||||
const inspection = await inspectVerifierGhidraInstallation({
|
||||
environment: env,
|
||||
installDir: env.GHIDRA_INSTALL_DIR,
|
||||
...(env.JAVA_HOME === undefined ? {} : { javaHome: env.JAVA_HOME }),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
mkdtemp,
|
||||
mkdir,
|
||||
readFile,
|
||||
writeFile,
|
||||
readdir,
|
||||
rm,
|
||||
} from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { execFile } from "node:child_process";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { requireMcpToolError } from "./mcp-verifier-results.mjs";
|
||||
import { closeVerifierResources } from "./verifier-cleanup.mjs";
|
||||
import { WABT_OPERATION_TIMEOUT_MS } from "./wabt-verifier-timeouts.mjs";
|
||||
const execute = promisify(execFile);
|
||||
/** Real WABT inspection with authored cleanup denials and same-connection recovery. */
|
||||
export async function verifyWabtCleanup(entrypoint, environment, parent) {
|
||||
const root = await mkdtemp(join(parent, "wabt-cleanup-"));
|
||||
let primaryFailure;
|
||||
try {
|
||||
const valid = join(root, "valid.wasm"),
|
||||
invalid = join(root, "invalid.wasm");
|
||||
await writeFile(
|
||||
valid,
|
||||
Buffer.from("0061736d01000000000a066f7061717565616263", "hex"),
|
||||
);
|
||||
await writeFile(invalid, Buffer.from("0061736d01000000ff", "hex"));
|
||||
const preload = fileURLToPath(
|
||||
new URL("../../tests/fixtures/wasm/cleanupPreload.mjs", import.meta.url),
|
||||
);
|
||||
const records = [];
|
||||
async function setup(name, mode, input) {
|
||||
const temporary = join(root, name);
|
||||
await mkdir(temporary);
|
||||
const trace = join(root, name + ".jsonl");
|
||||
return {
|
||||
temporary,
|
||||
trace,
|
||||
env: {
|
||||
...environment,
|
||||
HOME: root,
|
||||
USERPROFILE: root,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
TMPDIR: temporary,
|
||||
TMP: temporary,
|
||||
TEMP: temporary,
|
||||
REA_CLEANUP_PROBE_ROOT: temporary,
|
||||
REA_CLEANUP_PROBE_TRACE: trace,
|
||||
REA_CLEANUP_PROBE_MODE: mode,
|
||||
REA_CLEANUP_PROBE_INPUT: input,
|
||||
},
|
||||
};
|
||||
}
|
||||
async function observations(configuration) {
|
||||
const trace = await readFile(configuration.trace, "utf8").catch((e) => {
|
||||
if (e.code === "ENOENT") return "";
|
||||
throw e;
|
||||
});
|
||||
return {
|
||||
trace:
|
||||
trace.trim() === "" ? [] : trace.trim().split("\n").map(JSON.parse),
|
||||
remaining: await readdir(configuration.temporary),
|
||||
};
|
||||
}
|
||||
for (const [name, mode, input] of [
|
||||
["normal", "normal", valid],
|
||||
["valid-recovered", "runtime-once", valid],
|
||||
["invalid-recovered", "runtime-once", invalid],
|
||||
["descriptor-recovered", "input-once", valid],
|
||||
]) {
|
||||
const c = await setup(name, mode, input);
|
||||
let response,
|
||||
status = 0;
|
||||
try {
|
||||
response = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
preload,
|
||||
entrypoint,
|
||||
"inspect-wasm-artifact",
|
||||
input,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
env: c.env,
|
||||
timeout: WABT_OPERATION_TIMEOUT_MS,
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
if (typeof e.code !== "number" || typeof e.stdout !== "string") throw e;
|
||||
response = e;
|
||||
status = e.code;
|
||||
}
|
||||
const output = JSON.parse(response.stdout);
|
||||
const facts = await observations(c);
|
||||
assert.deepEqual(facts.remaining, []);
|
||||
if (mode === "normal") {
|
||||
assert.equal(status, 0);
|
||||
assert.equal(output.normalized_result.validation, "valid");
|
||||
} else {
|
||||
assert.equal(status, 1);
|
||||
assert.equal(
|
||||
output.code,
|
||||
name === "valid-recovered" ? "execution_failure" : "invalid_request",
|
||||
);
|
||||
if (name === "valid-recovered") {
|
||||
assert.equal(
|
||||
output.details?.partial_observation?.normalized_result?.validation,
|
||||
"valid",
|
||||
);
|
||||
assert.equal(
|
||||
output.details?.partial_observation?.evidence_id,
|
||||
records[0].output.evidence_id,
|
||||
);
|
||||
}
|
||||
if (mode === "input-once") {
|
||||
assert.equal(
|
||||
output.details?.issues?.[0]?.message,
|
||||
"authored descriptor cleanup denial",
|
||||
);
|
||||
const closes = facts.trace.filter(
|
||||
(fact) => fact.kind === "input-close",
|
||||
);
|
||||
assert.ok(closes.length >= 2);
|
||||
assert.equal(closes[0].path, input);
|
||||
assert.equal(closes[0].denied, true);
|
||||
assert.ok(Number.isInteger(closes[0].descriptor));
|
||||
for (const retry of closes.slice(1)) {
|
||||
assert.equal(retry.path, input);
|
||||
assert.equal(retry.descriptor, closes[0].descriptor);
|
||||
assert.equal(retry.denied, false);
|
||||
}
|
||||
}
|
||||
assert.notEqual(output.details?.cleanup, "incomplete");
|
||||
assert.equal(output.details?.resources, undefined);
|
||||
if (input === invalid) {
|
||||
assert.equal(output.code, "invalid_request");
|
||||
assert.ok(output.details?.captured_output?.stderr);
|
||||
}
|
||||
}
|
||||
if (mode === "runtime-once") {
|
||||
assert.equal(facts.trace.length, 2);
|
||||
assert.equal(facts.trace[0].path, facts.trace[1].path);
|
||||
assert.equal(facts.trace[0].denied, true);
|
||||
assert.equal(facts.trace[1].exists, false);
|
||||
}
|
||||
records.push({ kind: "cli", name, status, output, ...facts });
|
||||
}
|
||||
for (const mode of ["runtime-once", "runtime-until-allowed"]) {
|
||||
const c = await setup("mcp-" + mode, mode, valid);
|
||||
const client = new Client({ name: "wabt-cleanup-probe", version: "1" });
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: ["--import", preload, entrypoint, "mcp"],
|
||||
env: c.env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const calls = [];
|
||||
let callFailure;
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const request = {
|
||||
name: "inspect_wasm_artifact",
|
||||
arguments: { path: valid },
|
||||
};
|
||||
const call = () =>
|
||||
client.callTool(request, { timeout: WABT_OPERATION_TIMEOUT_MS });
|
||||
const first = await call();
|
||||
calls.push(first);
|
||||
assert.equal(requireMcpToolError(first).code, "cleanup_incomplete");
|
||||
if (mode === "runtime-until-allowed") {
|
||||
const second = await call();
|
||||
calls.push(second);
|
||||
assert.equal(requireMcpToolError(second).code, "cleanup_incomplete");
|
||||
assert.equal(
|
||||
(await observations(c)).remaining.filter((name) =>
|
||||
name.startsWith("rea-wabt-"),
|
||||
).length,
|
||||
1,
|
||||
);
|
||||
await writeFile(join(c.temporary, "allow"), "restore");
|
||||
}
|
||||
const last = await call();
|
||||
calls.push(last);
|
||||
assert.notEqual(last.isError, true, JSON.stringify(last));
|
||||
assert.equal(
|
||||
last.structuredContent.normalized_result.validation,
|
||||
"valid",
|
||||
);
|
||||
} catch (cause) {
|
||||
callFailure = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeVerifierResources(
|
||||
[() => client.close(), () => transport.close()],
|
||||
callFailure,
|
||||
);
|
||||
}
|
||||
const facts = await observations(c);
|
||||
assert.deepEqual(
|
||||
facts.remaining,
|
||||
mode === "runtime-until-allowed" ? ["allow"] : [],
|
||||
);
|
||||
records.push({ kind: "mcp", mode, calls, ...facts });
|
||||
}
|
||||
return records.reduce(
|
||||
(count, record) =>
|
||||
count + (record.kind === "cli" ? 1 : record.calls.length),
|
||||
0,
|
||||
);
|
||||
} catch (cause) {
|
||||
primaryFailure = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeVerifierResources(
|
||||
[() => rm(root, { recursive: true, force: true })],
|
||||
primaryFailure,
|
||||
);
|
||||
}
|
||||
}
|
||||
+29
-15
@@ -74,13 +74,13 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
const { createCliResultOutput } =
|
||||
await import("../dist/cli-runtime/cliResultOutput.js");
|
||||
const {
|
||||
normalizeCliOutputArguments,
|
||||
normalizeCliArguments,
|
||||
renderCliOutputArgumentError,
|
||||
renderEmptyFilteredCliOutput,
|
||||
validateCliOutputArguments,
|
||||
} = await import("../dist/cli-runtime/cliOutput.js");
|
||||
guardClosedStdout();
|
||||
const cliArgs = normalizeCliOutputArguments(args);
|
||||
const cliArgs = normalizeCliArguments(args);
|
||||
const outputArguments = validateCliOutputArguments(cliArgs);
|
||||
const configurationError = cliConfigurationError(process.env);
|
||||
if (configurationError !== undefined) {
|
||||
@@ -92,19 +92,33 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
} else {
|
||||
let wroteOutput = false;
|
||||
const resultOutput = createCliResultOutput(cliArgs, process.stdout);
|
||||
await createCli(process.env, resultOutput).serve(cliArgs, {
|
||||
stdout: (output) => {
|
||||
if (resultOutput?.handled) {
|
||||
if (resultOutput.failed && !stdoutClosed)
|
||||
process.stderr.write(output);
|
||||
return;
|
||||
}
|
||||
if (output.length > 0) wroteOutput = true;
|
||||
writeStdout(output);
|
||||
},
|
||||
// A command failure caused by the closed pipe keeps the pipe's status.
|
||||
exit: (code) => process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : code),
|
||||
});
|
||||
try {
|
||||
await createCli(process.env, resultOutput).serve(cliArgs, {
|
||||
stdout: (output) => {
|
||||
if (resultOutput?.handled) {
|
||||
if (resultOutput.failed && !stdoutClosed)
|
||||
process.stderr.write(output);
|
||||
return;
|
||||
}
|
||||
if (output.length > 0) wroteOutput = true;
|
||||
writeStdout(output);
|
||||
},
|
||||
// A command failure caused by the closed pipe keeps the pipe's status.
|
||||
exit: (code) => process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : code),
|
||||
});
|
||||
} catch (cause) {
|
||||
if (wroteOutput) throw cause;
|
||||
const recovered = await resultOutput
|
||||
?.recoverFormattingFailure(cause)
|
||||
.catch((writeFailure) => {
|
||||
// Recovery writes outside Incur's command boundary. A reader that
|
||||
// declined this document keeps the same quiet SIGPIPE outcome.
|
||||
if (!stdoutClosed) throw writeFailure;
|
||||
return true;
|
||||
});
|
||||
if (!recovered) throw cause;
|
||||
process.exitCode = stdoutClosed ? SIGPIPE_EXIT_STATUS : 1;
|
||||
}
|
||||
if (!wroteOutput && !resultOutput?.handled)
|
||||
writeStdout(renderEmptyFilteredCliOutput(cliArgs) ?? "");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@ const run = async (operation, input) => {
|
||||
failures += 1;
|
||||
return null;
|
||||
}
|
||||
if (operation === "decode_android_resources") {
|
||||
const evidence = result.value;
|
||||
const target = evidence.normalized_result.target;
|
||||
check(
|
||||
"resource Evidence preserves the decoded APK subject identity",
|
||||
evidence.subject?.digest.sha256 === target.sha256 &&
|
||||
evidence.subject?.local_path === target.path &&
|
||||
evidence.subject?.format === "apk",
|
||||
);
|
||||
}
|
||||
return result.value.normalized_result;
|
||||
};
|
||||
|
||||
@@ -93,6 +103,29 @@ try {
|
||||
Array.isArray(locale.strings),
|
||||
`${locale.strings.length} string(s)`,
|
||||
);
|
||||
|
||||
const selectedLocales = decoded.locales.slice(0, 2);
|
||||
const batched = await run("decode_android_resources", {
|
||||
path: options.apk,
|
||||
include_strings: true,
|
||||
locales: selectedLocales,
|
||||
});
|
||||
if (batched !== null) {
|
||||
const expectedLocales = [null, ...selectedLocales];
|
||||
const hasExpectedTables =
|
||||
Array.isArray(batched.string_tables) &&
|
||||
expectedLocales.every((expected) =>
|
||||
batched.string_tables.some(
|
||||
(table) =>
|
||||
table.locale === expected && table.status === "present",
|
||||
),
|
||||
);
|
||||
check(
|
||||
"batched string tables projected with expected locales and status",
|
||||
hasExpectedTables,
|
||||
`${String(batched.string_tables?.length)} table(s) present`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
schemaArrays,
|
||||
schemaChildren,
|
||||
schemaMaps,
|
||||
} from "../dist/contracts/inputSchemaPresentation.js";
|
||||
} from "../dist/contracts/jsonSchemaKeywords.js";
|
||||
import {
|
||||
TOOL_CONTRACTS,
|
||||
toolContract,
|
||||
|
||||
@@ -66,6 +66,6 @@ export async function verifyPackagePack({ root, workspace }) {
|
||||
) {
|
||||
throw new Error("package contained generated Python bytecode");
|
||||
}
|
||||
await verifyPackedBridge({ workspace, tarball, packedFiles });
|
||||
await verifyPackedBridge({ tarball, packedFiles });
|
||||
return { tarball };
|
||||
}
|
||||
@@ -442,15 +442,16 @@ try {
|
||||
return evidence.normalized_result;
|
||||
},
|
||||
);
|
||||
const { GHIDRA_MUTATING_OPERATIONS, GHIDRA_SESSION_CAPABILITIES } =
|
||||
await import(
|
||||
pathToFileURL(join(packageRoot, "dist/ghidra/GhidraSessionValues.js"))
|
||||
);
|
||||
const expected = GHIDRA_SESSION_CAPABILITIES.filter(
|
||||
(name) =>
|
||||
name !== "ping" &&
|
||||
name !== "shutdown" &&
|
||||
!GHIDRA_MUTATING_OPERATIONS.has(name),
|
||||
const { GHIDRA_MUTATING_OPERATIONS } = await import(
|
||||
pathToFileURL(join(packageRoot, "dist/ghidra/GhidraSessionValues.js"))
|
||||
);
|
||||
const { GHIDRA_OPERATIONS } = await import(
|
||||
pathToFileURL(
|
||||
join(packageRoot, "dist/ghidra/GhidraProviderCapabilities.js"),
|
||||
)
|
||||
);
|
||||
const expected = GHIDRA_OPERATIONS.filter(
|
||||
(name) => !GHIDRA_MUTATING_OPERATIONS.has(name),
|
||||
);
|
||||
const expectedOperations = new Set([
|
||||
...expected,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/** Validate required shipped bridges and parse packed Python without extraction. */
|
||||
export function verifyPackedBridge(input: {
|
||||
readonly tarball: string;
|
||||
readonly packedFiles: readonly string[];
|
||||
}): Promise<void>;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { GHIDRA_BRIDGE_SOURCES } from "../src/ghidra/GhidraBridgeSources.ts";
|
||||
|
||||
@@ -8,7 +7,7 @@ const packedHopperBridge = "package/bridge/hopper_bridge.py";
|
||||
const packedCutterBridge = "package/bridge/cutter/rea_bridge.py";
|
||||
|
||||
/** Check shipped bridge files; real-provider lanes verify runtime behavior. */
|
||||
export async function verifyPackedBridge({ workspace, tarball, packedFiles }) {
|
||||
export async function verifyPackedBridge({ tarball, packedFiles }) {
|
||||
for (const path of [
|
||||
packedHopperBridge,
|
||||
packedCutterBridge,
|
||||
@@ -18,12 +17,19 @@ export async function verifyPackedBridge({ workspace, tarball, packedFiles }) {
|
||||
]) {
|
||||
if (!packedFiles.includes(path)) throw new Error(`package omitted ${path}`);
|
||||
}
|
||||
await exec("tar", ["-xf", tarball, "-C", workspace]);
|
||||
// Parse the actual packed Python source without loading Hopper globals or
|
||||
// generating bytecode. This check has no dependency on test-only fixtures.
|
||||
// Read the packed source without extracting into the caller's leased root.
|
||||
await exec("python3", [
|
||||
"-c",
|
||||
"import ast, pathlib, sys; p = pathlib.Path(sys.argv[1]); ast.parse(p.read_text(encoding='utf-8'), filename=str(p))",
|
||||
join(workspace, packedHopperBridge),
|
||||
[
|
||||
"import ast, sys, tarfile",
|
||||
"with tarfile.open(sys.argv[1]) as archive:",
|
||||
" member = archive.getmember(sys.argv[2])",
|
||||
" if not member.isfile():",
|
||||
" raise ValueError(f'{member.name} is not a regular file')",
|
||||
" with archive.extractfile(member) as source:",
|
||||
" ast.parse(source.read().decode('utf-8'), filename=sys.argv[2])",
|
||||
].join("\n"),
|
||||
tarball,
|
||||
packedHopperBridge,
|
||||
]);
|
||||
}
|
||||
@@ -268,7 +268,7 @@ try {
|
||||
const capinfosOutput = (
|
||||
await execute(
|
||||
process.env.REA_CAPINFOS_COMMAND ?? "capinfos",
|
||||
["-M", "--", capturePath],
|
||||
["-M", "-S", "--", capturePath],
|
||||
{
|
||||
env: { ...environment, LC_ALL: "C", LANG: "C" },
|
||||
timeout: 60000,
|
||||
@@ -276,16 +276,25 @@ try {
|
||||
},
|
||||
)
|
||||
).stdout;
|
||||
const expectedCaptureStart =
|
||||
/^(?:Earliest|First) packet time:[ \t]*(.+)$/mu
|
||||
.exec(capinfosOutput)?.[1]
|
||||
?.trim() ?? null;
|
||||
// Expected instants come from the independently authored PCAP records above,
|
||||
// rather than a second implementation of the product's epoch decoder.
|
||||
const captureStartText = /^(?:Earliest|First) packet time:[ \t]*(.+)$/mu
|
||||
.exec(capinfosOutput)?.[1]
|
||||
?.trim();
|
||||
const captureEndText = /^(?:Latest|Last) packet time:[ \t]*(.+)$/mu
|
||||
.exec(capinfosOutput)?.[1]
|
||||
?.trim();
|
||||
const expectedCaptureStart = "2023-11-14T22:13:20.000000Z";
|
||||
const expectedCaptureEnd =
|
||||
/^(?:Latest|Last) packet time:[ \t]*(.+)$/mu
|
||||
.exec(capinfosOutput)?.[1]
|
||||
?.trim() ?? null;
|
||||
assert.ok(expectedCaptureStart, capinfosOutput);
|
||||
assert.ok(expectedCaptureEnd, capinfosOutput);
|
||||
capturePath === path
|
||||
? "2023-11-14T22:13:22.000000Z"
|
||||
: expectedCaptureStart;
|
||||
assert.equal(captureStartText, "1700000000.000000", capinfosOutput);
|
||||
assert.equal(
|
||||
captureEndText,
|
||||
capturePath === path ? "1700000002.000000" : "1700000000.000000",
|
||||
capinfosOutput,
|
||||
);
|
||||
const response = await client.callTool({
|
||||
name: pcapTool.name,
|
||||
arguments: { path: capturePath, detail: "summary" },
|
||||
|
||||
@@ -53,6 +53,7 @@ import { artifactCliEvidence, artifactMcpResult } from "./lib/artifact-e2e.mjs";
|
||||
import { verifyScenarioEnvironment } from "./lib/browser-scenario-environment-e2e.mjs";
|
||||
import { verifyScenarioStorage } from "./lib/browser-scenario-storage-e2e.mjs";
|
||||
import { verifyScenarioServiceWorkers } from "./lib/browser-scenario-service-workers-e2e.mjs";
|
||||
import { verifyBrowserUrlUserInfo } from "./lib/browser-url-userinfo-e2e.mjs";
|
||||
|
||||
const SCENARIO_SECRET_VALUE = "rea-browser-verifier-secret";
|
||||
const SCENARIO_URL_SECRET_VALUE = "rea-browser-url-verifier-secret";
|
||||
@@ -378,6 +379,7 @@ try {
|
||||
});
|
||||
const moduleTrace = await verifyBrowserModules(executable);
|
||||
const captureMetadata = await verifyBrowserCaptureMetadataBudget(endpoint);
|
||||
const urlUserInfo = await verifyBrowserUrlUserInfo(endpoint, site.origin);
|
||||
report = {
|
||||
browser: observed.value.browser.product,
|
||||
endpoint,
|
||||
@@ -405,6 +407,7 @@ try {
|
||||
scriptExport,
|
||||
moduleTrace,
|
||||
captureMetadata,
|
||||
urlUserInfo,
|
||||
domDestinations,
|
||||
browserScenarioCli: true,
|
||||
browserScenarioAttachCleanup: "disconnected-external",
|
||||
|
||||
@@ -87,7 +87,7 @@ try {
|
||||
session.session_id,
|
||||
String(session.document_generation),
|
||||
"ij",
|
||||
"--json",
|
||||
"--json-output",
|
||||
);
|
||||
assert.equal(inspection.predicate_type, "rea.cutter.command-observation");
|
||||
assert.ok(
|
||||
|
||||
@@ -90,6 +90,20 @@ try {
|
||||
),
|
||||
);
|
||||
const procedures = parseEvidence(await call("list_procedures"));
|
||||
const strings = parseEvidence(await call("list_strings"));
|
||||
assert.equal(
|
||||
overview.normalized_result.procedure_count,
|
||||
procedures.normalized_result.length,
|
||||
);
|
||||
assert.equal(
|
||||
overview.normalized_result.string_count,
|
||||
strings.normalized_result.length,
|
||||
);
|
||||
assert.ok(
|
||||
overview.limitations.some((text) =>
|
||||
text.includes("Ghidra-defined string Data"),
|
||||
),
|
||||
);
|
||||
const entry = procedures.normalized_result.find(
|
||||
(item) => item.value === "rea_pie_entry",
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildDosComFixture } from "../tests/conformance/ghidra/dos-com-fixture.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { verifyGhidraAddressBoundaries } from "./lib/real-ghidra-address-boundaries.mjs";
|
||||
@@ -28,7 +28,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error(
|
||||
"verify:ghidra:com prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
functionBodySchema,
|
||||
functionDossierSchema,
|
||||
} from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildDosMzFixture } from "../tests/conformance/ghidra/dos-mz-fixture.mjs";
|
||||
import {
|
||||
assertVerifierCleanupComplete,
|
||||
@@ -42,7 +42,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error(
|
||||
"verify:ghidra:dos prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -13,7 +13,7 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildMipsRelocatableFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||
import {
|
||||
assertMipsRelocations,
|
||||
@@ -34,7 +34,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
||||
);
|
||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildMipsFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||
import {
|
||||
buildPspFixture,
|
||||
@@ -50,7 +50,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
||||
);
|
||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseConfig } from "../dist/config/parseConfig.js";
|
||||
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||
import { GhidraProvider } from "../dist/ghidra/GhidraProvider.js";
|
||||
import { silentLogger } from "../dist/logger.js";
|
||||
@@ -35,7 +35,7 @@ assert.ok(
|
||||
);
|
||||
const config = parseConfig(process.env);
|
||||
if (!config.ok) throw config.error;
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: config.value.ghidraInstallDir,
|
||||
javaHome: config.value.ghidraJavaHome,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
@@ -33,7 +33,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
||||
);
|
||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
@@ -25,7 +25,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||
throw new Error(
|
||||
"Set GHIDRA_INSTALL_DIR to a bring-your-own Ghidra installation.",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -22,7 +22,7 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildRiscvFixture } from "../tests/conformance/ghidra/riscv-fixture.mjs";
|
||||
import {
|
||||
mcpTextValue,
|
||||
@@ -42,7 +42,7 @@ for (const key of ["REA_GHIDRA_LANGUAGE_ID", "REA_GHIDRA_COMPILER_SPEC_ID"])
|
||||
throw new Error(
|
||||
`Unset ${key}: this lane verifies provider-pinned selection.`,
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { cleanupOwnedProcessGroup } from "../dist/process/ProcessOwnership.js";
|
||||
import {
|
||||
ProviderProcessSupervisor,
|
||||
@@ -27,7 +27,7 @@ import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
javaHome: process.env.JAVA_HOME,
|
||||
|
||||
@@ -68,13 +68,13 @@ for (const [role, command] of Object.entries(commands)) {
|
||||
}
|
||||
let installation;
|
||||
if (!compilerOnly) {
|
||||
const { inspectGhidraInstallation } =
|
||||
await import("../dist/ghidra/GhidraInstallation.js");
|
||||
const { inspectVerifierGhidraInstallation } =
|
||||
await import("./lib/ghidra-installation-inspection.mjs");
|
||||
assert.ok(
|
||||
process.env.GHIDRA_INSTALL_DIR,
|
||||
"Ghidra switch lane prerequisite missing: GHIDRA_INSTALL_DIR (bring your own)",
|
||||
);
|
||||
installation = inspectGhidraInstallation({
|
||||
installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
parseGhidraInventoryInput,
|
||||
parseGhidraInventoryResult,
|
||||
} from "../dist/ghidra/GhidraInventoryValues.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
||||
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||
@@ -59,7 +59,7 @@ if (installDir === undefined || !isAbsolute(installDir))
|
||||
throw new Error(
|
||||
"Set GHIDRA_INSTALL_DIR to the absolute root of Ghidra 12.1.4.",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
@@ -166,6 +166,16 @@ try {
|
||||
!Array.isArray(strings)
|
||||
)
|
||||
throw new Error("Windows Ghidra inventory proof was incomplete");
|
||||
const overview = await inventory("read_binary_inventory_summary", {});
|
||||
if (
|
||||
overview.document !== documents[0] ||
|
||||
overview.procedure_count !== procedures.length ||
|
||||
overview.string_count !== strings.length ||
|
||||
overview.segment_count !== segments.length
|
||||
)
|
||||
throw new Error(
|
||||
"Windows Ghidra overview aggregates disagree with inventories",
|
||||
);
|
||||
const procedure = procedures.find(
|
||||
(item) => item.procedure?.external === false,
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { resolveGhidraAnalysisProfile } from "../dist/ghidra/GhidraAnalysisProfile.js";
|
||||
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
||||
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||
@@ -49,7 +49,7 @@ if (installDir === undefined || !isAbsolute(installDir))
|
||||
throw new Error(
|
||||
"Set GHIDRA_INSTALL_DIR to the absolute root of an extracted Ghidra 12.1.4 release.",
|
||||
);
|
||||
const installation = inspectGhidraInstallation({
|
||||
const installation = await inspectVerifierGhidraInstallation({
|
||||
environment: process.env,
|
||||
installDir,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { WABT_OPERATION_TIMEOUT_MS } from "./lib/wabt-verifier-timeouts.mjs";
|
||||
|
||||
import { verifyWabtCleanup } from "./lib/verify-wabt-cleanup.mjs";
|
||||
|
||||
const directory = process.env.REA_WABT_BIN_DIRECTORY;
|
||||
if (!isAbsolute(directory ?? "")) {
|
||||
if (process.argv.includes("--require-tools"))
|
||||
@@ -405,6 +407,7 @@ try {
|
||||
cases += 2;
|
||||
}
|
||||
}
|
||||
cases += await verifyWabtCleanup(entrypoint, environment, childTempRoot);
|
||||
} catch (cause) {
|
||||
failures.push(cause);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { JADX_RELEASE } from "../../../dist/android/JadxRelease.js";
|
||||
import fixture from "../../fixtures/android/apidemos.json" with { type: "json" };
|
||||
import { verifyAndroidCliCancellation } from "./cli-cancellation.mjs";
|
||||
import { verifyAndroidMetadata } from "./metadata.mjs";
|
||||
import { verifyAndroidClassSearch } from "./class-search.mjs";
|
||||
import { verifyAndroidFailureRecovery } from "./failure-recovery.mjs";
|
||||
import {
|
||||
resolveJadxConfigurationInputs,
|
||||
@@ -217,6 +218,7 @@ try {
|
||||
overloadedEvidence.normalized_result,
|
||||
);
|
||||
console.log("PASS real overloaded method references through CLI/MCP");
|
||||
await verifyAndroidClassSearch({ client, path, sha256: fixture.sha256 });
|
||||
} catch (cause) {
|
||||
process.stderr.write(stderr);
|
||||
throw cause;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { requireMcpOperationResult } from "../../lib/mcp-verifier-results.mjs";
|
||||
|
||||
/** Compare native selection with an explicit full-inventory oracle on one live session. */
|
||||
export const verifyAndroidClassSearch = async ({ client, path, sha256 }) => {
|
||||
const search = async (query) => {
|
||||
const response = await client.callTool(
|
||||
{ name: "search_android_classes", arguments: { path, query } },
|
||||
{ timeout: 150_000 },
|
||||
);
|
||||
const result = requireMcpOperationResult(
|
||||
response,
|
||||
"search_android_classes",
|
||||
);
|
||||
const evidence = parseEvidence(response.structuredContent);
|
||||
assert.equal(evidence.subject.digest.sha256, sha256);
|
||||
assert.equal(evidence.parameters.query, query);
|
||||
assert.equal(result.query, query);
|
||||
return result;
|
||||
};
|
||||
const full = await search("");
|
||||
assert.equal(full.matches.length, full.total_classes);
|
||||
for (const query of [
|
||||
"ApiDemos",
|
||||
"apidemos",
|
||||
"[.*]",
|
||||
"😀",
|
||||
"日本語",
|
||||
"\0",
|
||||
"\ud800",
|
||||
"\udc00",
|
||||
]) {
|
||||
const selected = await search(query);
|
||||
assert.equal(selected.coverage, "complete");
|
||||
assert.equal(selected.total_classes, full.total_classes);
|
||||
assert.deepEqual(
|
||||
selected.matches,
|
||||
full.matches.filter((name) => name.includes(query)),
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
"PASS native class search matches full inventory, including literal and UTF-16 selectors",
|
||||
);
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { hashAndroidFile } from "./digest.mjs";
|
||||
|
||||
/** Verify folded field assignments and remaining static-block calls through both public adapters. */
|
||||
export const verifyAndroidInitializers = async ({
|
||||
apk,
|
||||
repository,
|
||||
entrypoint,
|
||||
environment,
|
||||
execute,
|
||||
}) => {
|
||||
const digest = await hashAndroidFile(apk);
|
||||
const cases = [
|
||||
{
|
||||
class_name: "fixture.InitializationPolicy",
|
||||
witnesses: [
|
||||
"declared-policy",
|
||||
"->singletonList",
|
||||
"->POLICY:Ljava/util/List;",
|
||||
],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.MixedInitialization",
|
||||
witnesses: ["field-value", "->LABEL:Ljava/lang/String;", "->touch()V"],
|
||||
},
|
||||
];
|
||||
const check = (value, scenario) => {
|
||||
const evidence = parseEvidence(value);
|
||||
assert.equal(evidence.subject.digest.sha256, digest);
|
||||
const result = evidence.normalized_result;
|
||||
assert.equal(result.class_name, scenario.class_name);
|
||||
assert.equal(result.method.name, "<clinit>");
|
||||
assert.equal(result.overload_count, 1);
|
||||
assert.equal(result.body_status, "available");
|
||||
assert.equal(result.representation, "smali");
|
||||
assert.equal(result.fell_back, true);
|
||||
assert.ok(result.markers.includes("static_initializer_disassembly"));
|
||||
assert.equal(result.source.status, "complete");
|
||||
for (const witness of scenario.witnesses)
|
||||
assert.ok(
|
||||
result.source.text.includes(witness),
|
||||
`Missing initializer evidence: ${witness}`,
|
||||
);
|
||||
return evidence;
|
||||
};
|
||||
const cli = new Map();
|
||||
for (const scenario of cases) {
|
||||
const response = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-android-method",
|
||||
apk,
|
||||
scenario.class_name,
|
||||
"<clinit>",
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
timeout: 150_000,
|
||||
maxBuffer: 8 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
cli.set(scenario.class_name, check(JSON.parse(response.stdout), scenario));
|
||||
}
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
stderr: "pipe",
|
||||
});
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
const client = new Client({
|
||||
name: "rea-real-android-initializers",
|
||||
version: "1",
|
||||
});
|
||||
try {
|
||||
await client.connect(transport, { timeout: 30_000 });
|
||||
for (const scenario of cases) {
|
||||
const response = await client.callTool(
|
||||
{
|
||||
name: "inspect_android_method",
|
||||
arguments: {
|
||||
path: apk,
|
||||
class_name: scenario.class_name,
|
||||
method_name: "<clinit>",
|
||||
},
|
||||
},
|
||||
{ timeout: 150_000 },
|
||||
);
|
||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
||||
const evidence = check(response.structuredContent, scenario);
|
||||
assert.deepEqual(
|
||||
evidence.normalized_result,
|
||||
cli.get(scenario.class_name).normalized_result,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evidence.parameters,
|
||||
cli.get(scenario.class_name).parameters,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evidence.provider,
|
||||
cli.get(scenario.class_name).provider,
|
||||
);
|
||||
}
|
||||
await client.ping();
|
||||
} catch (cause) {
|
||||
process.stderr.write(stderr);
|
||||
throw cause;
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
assert.equal(
|
||||
await hashAndroidFile(apk),
|
||||
digest,
|
||||
"Static inspection changed fixture bytes",
|
||||
);
|
||||
console.log(
|
||||
"PASS real CLI/MCP static initializer evidence, identity, parity and unchanged APK",
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { delimiter, join, resolve } from "node:path";
|
||||
import { verifyAndroidInitializers } from "./initializers.mjs";
|
||||
import { verifyAndroidMethodBodies } from "./method-bodies.mjs";
|
||||
import { verifyAndroidDirectInvokes } from "./direct-invokes.mjs";
|
||||
|
||||
/** Prove cold metadata stays unmaterialized and overload identities survive code generation. */
|
||||
@@ -65,14 +65,30 @@ export const verifyAndroidMetadata = async ({
|
||||
],
|
||||
{ timeout: 120_000, maxBuffer: 8 * 1024 * 1024 },
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/LOAD_FAILURE_REGRESSION_PASS typed=true decoy=true suspension=true cancellation=true/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/METADATA_REGRESSION_PASS classes=\d+ overloads=3/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/CLASS_SEARCH_REGRESSION_PASS pages=\d+ nonzero=true/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/INITIALIZER_REGRESSION_PASS folded=2 bounded=true/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/NO_BODY_REGRESSION_PASS native=true abstract=true frozen=true/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=true/u,
|
||||
);
|
||||
assert.match(
|
||||
result.stdout,
|
||||
/DIRECT_INVOKE_REGRESSION_PASS nodes=1 offsets=0,3 cross_dex=true/u,
|
||||
@@ -80,13 +96,19 @@ export const verifyAndroidMetadata = async ({
|
||||
console.log(
|
||||
"PASS real cold metadata, full generic types and stable overloaded method selection",
|
||||
);
|
||||
console.log(
|
||||
"PASS real Java filtered pages, nonzero inventory cursors and complete combined results",
|
||||
);
|
||||
console.log(
|
||||
"PASS real folded and mixed static initializers, absent bodies and bounded smali",
|
||||
);
|
||||
console.log(
|
||||
"PASS real native/abstract absence before and after neighbouring code generation",
|
||||
);
|
||||
console.log(
|
||||
"PASS real raw DEX direct-invoke offsets and cross-DEX source identity",
|
||||
);
|
||||
await verifyAndroidInitializers({
|
||||
await verifyAndroidMethodBodies({
|
||||
apk: resolve(classes, "initializers.apk"),
|
||||
repository,
|
||||
entrypoint,
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { hashAndroidFile } from "./digest.mjs";
|
||||
|
||||
const initializers = [
|
||||
{
|
||||
class_name: "fixture.InitializationPolicy",
|
||||
witnesses: [
|
||||
"declared-policy",
|
||||
"->singletonList",
|
||||
"->POLICY:Ljava/util/List;",
|
||||
],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.MixedInitialization",
|
||||
witnesses: ["field-value", "->LABEL:Ljava/lang/String;", "->touch()V"],
|
||||
},
|
||||
];
|
||||
const cases = [
|
||||
...initializers.map((scenario) => ({
|
||||
...scenario,
|
||||
method_name: "<clinit>",
|
||||
overload_index: 0,
|
||||
overload_count: 1,
|
||||
body_status: "available",
|
||||
representation: "smali",
|
||||
fell_back: true,
|
||||
markers: ["static_initializer_disassembly"],
|
||||
})),
|
||||
...[
|
||||
["fixture.NativeOverload", "readBuffer", 2, "native_method"],
|
||||
["fixture.NativeOverload", "zLastNative", 1, "native_method"],
|
||||
["fixture.AbstractShape", "area", 1, "abstract_method"],
|
||||
].map(([class_name, method_name, overload_count, marker]) => ({
|
||||
class_name,
|
||||
method_name,
|
||||
overload_index: 0,
|
||||
overload_count,
|
||||
body_status: "not_available",
|
||||
representation: "java",
|
||||
fell_back: false,
|
||||
markers: [marker],
|
||||
witnesses: [],
|
||||
})),
|
||||
{
|
||||
class_name: "fixture.MergedMethod",
|
||||
method_name: "highlightText",
|
||||
overload_index: 0,
|
||||
overload_count: 2,
|
||||
body_status: "available",
|
||||
representation: "smali",
|
||||
fell_back: true,
|
||||
markers: ["java_body_unavailable"],
|
||||
witnesses: [".method private highlightText()V", "0x77", "if-nez", ".catch"],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.MergedMethod",
|
||||
method_name: "highlightText",
|
||||
overload_index: 1,
|
||||
overload_count: 2,
|
||||
body_status: "available",
|
||||
representation: "java",
|
||||
fell_back: false,
|
||||
markers: [],
|
||||
witnesses: ["42"],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.MergedMethod",
|
||||
method_name: "decoy",
|
||||
overload_index: 0,
|
||||
overload_count: 1,
|
||||
body_status: "available",
|
||||
representation: "java",
|
||||
fell_back: false,
|
||||
markers: [],
|
||||
witnesses: ["renamed from: highlightText,"],
|
||||
},
|
||||
{
|
||||
class_name: "fixture.NativeOverload",
|
||||
method_name: "readBuffer",
|
||||
overload_index: 1,
|
||||
overload_count: 2,
|
||||
body_status: "available",
|
||||
representation: "java",
|
||||
fell_back: false,
|
||||
markers: [],
|
||||
witnesses: ["readBuffer(byteBuffer, 0)"],
|
||||
},
|
||||
];
|
||||
|
||||
/** Verify original method instructions, missing Java and parsed absence through both adapters. */
|
||||
export const verifyAndroidMethodBodies = async ({
|
||||
apk,
|
||||
repository,
|
||||
entrypoint,
|
||||
environment,
|
||||
execute,
|
||||
}) => {
|
||||
const digest = await hashAndroidFile(apk);
|
||||
const check = (value, scenario) => {
|
||||
const evidence = parseEvidence(value);
|
||||
assert.equal(evidence.subject.digest.sha256, digest);
|
||||
const result = evidence.normalized_result;
|
||||
assert.equal(result.class_name, scenario.class_name);
|
||||
assert.equal(result.method.name, scenario.method_name);
|
||||
assert.equal(result.method.overload_index, scenario.overload_index);
|
||||
assert.equal(result.overload_count, scenario.overload_count);
|
||||
assert.equal(result.body_status, scenario.body_status);
|
||||
assert.equal(result.representation, scenario.representation);
|
||||
assert.equal(result.fell_back, scenario.fell_back);
|
||||
assert.deepEqual(result.markers, scenario.markers);
|
||||
if (scenario.body_status === "not_available")
|
||||
assert.equal(result.source.text, "");
|
||||
assert.equal(result.source.status, "complete");
|
||||
for (const witness of scenario.witnesses)
|
||||
assert.ok(
|
||||
result.source.text.includes(witness),
|
||||
`Missing method evidence: ${witness}`,
|
||||
);
|
||||
return evidence;
|
||||
};
|
||||
const cli = new Map();
|
||||
for (const scenario of cases) {
|
||||
const response = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-android-method",
|
||||
apk,
|
||||
scenario.class_name,
|
||||
scenario.method_name,
|
||||
"--overload-index",
|
||||
String(scenario.overload_index),
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
timeout: 150_000,
|
||||
maxBuffer: 8 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
cli.set(scenario, check(JSON.parse(response.stdout), scenario));
|
||||
}
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
cwd: repository,
|
||||
env: environment,
|
||||
stderr: "pipe",
|
||||
});
|
||||
let stderr = "";
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
const client = new Client({
|
||||
name: "rea-real-android-method-bodies",
|
||||
version: "1",
|
||||
});
|
||||
try {
|
||||
await client.connect(transport, { timeout: 30_000 });
|
||||
for (const scenario of cases) {
|
||||
const response = await client.callTool(
|
||||
{
|
||||
name: "inspect_android_method",
|
||||
arguments: {
|
||||
path: apk,
|
||||
class_name: scenario.class_name,
|
||||
method_name: scenario.method_name,
|
||||
overload_index: scenario.overload_index,
|
||||
},
|
||||
},
|
||||
{ timeout: 150_000 },
|
||||
);
|
||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
||||
assert.equal(response.content.length, 1);
|
||||
assert.equal(response.content[0].type, "text");
|
||||
assert.deepEqual(
|
||||
JSON.parse(response.content[0].text),
|
||||
response.structuredContent,
|
||||
);
|
||||
const evidence = check(response.structuredContent, scenario);
|
||||
assert.deepEqual(
|
||||
evidence.normalized_result,
|
||||
cli.get(scenario).normalized_result,
|
||||
);
|
||||
assert.deepEqual(evidence.parameters, cli.get(scenario).parameters);
|
||||
assert.deepEqual(evidence.provider, cli.get(scenario).provider);
|
||||
}
|
||||
await client.ping();
|
||||
} catch (cause) {
|
||||
process.stderr.write(stderr);
|
||||
throw cause;
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
assert.equal(
|
||||
await hashAndroidFile(apk),
|
||||
digest,
|
||||
"Static inspection changed fixture bytes",
|
||||
);
|
||||
console.log(
|
||||
"PASS real CLI/MCP original method bodies, absence facts, identity, parity and unchanged APK",
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { parseBinaryTarget } from "../../../dist/application/BinaryTargetResolver.js";
|
||||
import { ArtifactResourceScope } from "../../../dist/artifacts/ArtifactResourceScope.js";
|
||||
import { inspectAppleDispatchMetadata } from "../../../dist/native/AppleDispatchMetadata.js";
|
||||
import {
|
||||
artifactCliEvidence,
|
||||
@@ -52,10 +53,20 @@ const fixupCoverage = (metadata) =>
|
||||
const inspect = async (path) => {
|
||||
const target = await parseBinaryTarget(path);
|
||||
if (!target.ok) throw target.error;
|
||||
return {
|
||||
target: target.value,
|
||||
metadata: (await inspectAppleDispatchMetadata(target.value, 5000)).result,
|
||||
};
|
||||
const resources = new ArtifactResourceScope();
|
||||
try {
|
||||
return {
|
||||
target: target.value,
|
||||
metadata: (
|
||||
await resources.run(() =>
|
||||
inspectAppleDispatchMetadata(target.value, 5000, resources),
|
||||
)
|
||||
).result,
|
||||
};
|
||||
} finally {
|
||||
// Retry any descriptor whose close failed, surfacing unresolved cleanup.
|
||||
await resources.close();
|
||||
}
|
||||
};
|
||||
|
||||
const checkObjc = (metadata, mode) => {
|
||||
|
||||
Loaded 100 of 685 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user