Merge remote-tracking branch 'origin/main' into eb

The environment-coverage sentence joins main's BEAM additions to the view description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Edgar ArakelyanandClaude Opus 5.5 committed 2026-10-23 22:41:50 -07:00
commit 53a03c06a5
272 files changed
+21570 -3711

No files matched your search

@@ -251,6 +251,14 @@ explicit path or endpoint and do not need it.
absence of that table, not proof the image is unsigned in every catalog sense.
Signatures are not verified. See the
[PE certificate guide](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
- PE import and delay-load tables: `inspect_pe_imports` with the explicit
local PE32/PE32+ file. This built-in target-free inspection requires no
Windows SDK. Preserve DLL names, named and ordinal functions, ILT/IAT RVAs,
delay-load descriptors, and the pefile import hash of the standard import
table. A missing import directory is observed absence of that table, not
proof the image never imported APIs through another table. Bound snapshots
and loaded IAT contents remain unobserved. See the
[PE import guide](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
file. This built-in target-free inspection requires no disassembler. Preserve
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
@@ -267,8 +275,10 @@ explicit path or endpoint and do not need it.
- Erlang BEAM IFF container: `inspect_erlang_beam` with the explicit local
`.beam` file. This built-in target-free inspection requires no Erlang
installation. Preserve chunk names, atom order, export/import/lambda tables,
and the Code header. Missing optional tables stay null. Bytecode is not
executed. See the
and the Code header. For selected facts, prefer `detail: "summary"` plus a
`beam` view (collection, names/indices, fields and paging), retaining the full
Evidence. CLI summary requires `--evidence_output`. Missing table counts stay
null; decoded empty tables count zero. Bytecode is not executed. See the
[Erlang BEAM guide](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
@@ -17,9 +17,10 @@ the complete artifact inventory and identity but reads source text and analyzes
only selected files. The result and summary record `source_selection`; files
outside scope and their relationships remain unknown, so partial application
coverage does not mean the selected source failed. Use repeated `--source-path`
on either static application CLI route. Omit the selection when the question
requires complete application analysis; `source_paths: []` is invalid, not an
unfiltered request. `detail: "summary"` reduces delivery
on either static application CLI route, or `--source-paths-json` with a JSON
file for selections too long for one command line. Omit the selection when the
question requires complete application analysis; `source_paths: []` is invalid,
not an unfiltered request. `detail: "summary"` reduces delivery
and does not by itself reduce analysis work.
ASAR header/index admission defaults to 16 MiB / 16777216 bytes;
@@ -112,6 +112,19 @@ not proof the image is unsigned in every catalog or overlay sense. Signatures,
chains, timestamps, and Authenticode digest coverage remain unobserved. See
[PE Authenticode certificate tables](https://github.com/morluto/rea/blob/main/docs/pe-certificates.md).
## PE import and delay-load tables
Use `inspect_pe_imports` for an explicit local PE32/PE32+ image when the
question concerns imported DLL and function names, ordinals, delay-load
descriptors, or the pefile import hash. No `open_binary`, Windows SDK, or
target execution is required. The CLI equivalent is `inspect-pe-imports PATH`.
Preserve artifact SHA-256, directory file ranges, original DLL spellings, and
ILT/IAT RVAs. A missing `IMAGE_DIRECTORY_ENTRY_IMPORT` directory is observed
absence of that table, not proof the image never imported APIs through another
table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## ELF note tables
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
@@ -140,7 +153,11 @@ question concerns IFF chunks, atom names, exports, imports, lambdas, or the
Code header. No `open_binary`, Erlang installation, or bytecode execution is
required. The CLI equivalent is `inspect-erlang-beam PATH`. Preserve artifact
SHA-256, chunk file ranges, 1-based atom indices, and original atom spellings.
Missing optional tables stay null. See
Missing optional tables stay null. For a few facts, use `detail: "summary"` with
a `beam` view selecting a collection, exact names/indices and fields; read later
pages from `parent_evidence_id` or the complete Evidence bundle. Same-name
exports retain every arity and original atom index. CLI summary delivery requires
`--evidence_output`. Null table counts remain distinct from decoded empty tables. See
[Erlang BEAM files](https://github.com/morluto/rea/blob/main/docs/erlang-beam.md).
## SQLite snapshots
+1 -1
View File
@@ -789,7 +789,7 @@ jobs:
id: npm-cache
uses: ./.github/actions/npm-cache
- run: npm ci
- name: Run the public CLI/MCP reconstruction journey
- name: Verify static CLI/MCP journeys and synthetic report conformance
env:
REA_READINESS_REPORT_PATH: readiness-report.json
run: |
+43 -16
View File
@@ -81,12 +81,29 @@ jobs:
echo "frozen=true" >> "$GITHUB_OUTPUT"
fi
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
- name: Check out proposal controller and complete Git history
if: steps.editorial.outputs.frozen != 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
if: steps.editorial.outputs.frozen != 'true'
with:
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: main
skip-github-release: true
node-version-file: .nvmrc
package-manager-cache: false
- name: Install proposal controller dependencies
if: steps.editorial.outputs.frozen != 'true'
run: npm ci
- name: Update release proposal
if: steps.editorial.outputs.frozen != 'true'
env:
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
SOURCE_SHA: ${{ github.sha }}
run: node scripts/release-proposal.mjs --target-branch main --source-sha "$SOURCE_SHA"
merged-release:
name: Release reviewed main merge
@@ -259,15 +276,25 @@ jobs:
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
--source "$SOURCE_SHA"
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
id: release
with:
- name: Prepare checkpoint release proposal
if: inputs.phase == 'prepare'
id: proposal
env:
# A dedicated credential starts PR CI automatically; the built-in
# token creates approval-required PR runs for maintainers to approve.
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
run: node scripts/release-proposal.mjs --target-branch "$RELEASE_BRANCH" --source-sha "$SOURCE_SHA"
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
if: inputs.phase == 'publish'
id: release
with:
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: ${{ inputs.release_branch }}
skip-github-release: ${{ inputs.phase == 'prepare' }}
skip-github-pull-request: ${{ inputs.phase == 'publish' }}
skip-github-release: false
skip-github-pull-request: true
- name: Require a merged release PR for publication
if: inputs.phase == 'publish' && steps.release.outputs.release_created != 'true'
@@ -287,26 +314,26 @@ jobs:
fi
- name: Check out release pull request
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
ref: ${{ fromJSON(steps.proposal.outputs.pr).headBranchName }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js for generated documentation
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
run: npm ci
- name: Validate prepared release version and migration notes
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
env:
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
@@ -316,11 +343,11 @@ jobs:
--source-sha "$SOURCE_SHA"
- name: Regenerate release documentation
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
run: npm run docs:generate
- name: Validate generated release documentation
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
if: inputs.phase == 'prepare' && steps.proposal.outputs.prs_created == 'true'
run: npm run docs:check && git diff --exit-code
qualification:
+1
View File
@@ -192,6 +192,7 @@ Additional tools and host support depend on the target:
| Godot PCK | Archive inventory and extraction, original header/directory facts and selected content identities | Built-in static formats 2, 3 and 4; no Godot required |
| Rust binaries | Embedded cargo-auditable crate names, versions, sources and dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](docs/rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](docs/pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](docs/pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](docs/elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas and the Code header | Built-in static BEAM inspection; [Erlang BEAM](docs/erlang-beam.md) |
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
+1
View File
@@ -16,6 +16,7 @@ notices supplied with the software you actually obtain or redistribute.
| Material | Use and licensing records |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| npm dependencies | [Complete inventory](licenses/npm-dependencies.txt) of every entry in `package-lock.json`, including transitive, optional platform, development, and website dependencies. [Runtime notices](licenses/npm-runtime-notices.txt) preserve the copyright, license, and NOTICE documents from integrity-verified release archives, plus the MIT notice for Rolldown's generated runtime helpers. Development dependencies are inventoried but are not all distributed in REA's npm package. |
| pefile ordinal tables | Import-hash ordinal names are derived from [pefile 2024.8.26](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4), copyright 2004–2024 Ero Carrera. Preserve the [MIT license](licenses/pefile-2024.8.26-LICENSE.txt). The Python package is not a runtime dependency. |
| NativeAOT parser | REA's derived parser uses the MIT-licensed [ghidra-nativeaot](https://github.com/Washi1337/ghidra-nativeaot/tree/effeb734fc570c32650f88b159608979dc7b423e) work of Washi and the NativeAOT layout definitions in [dotnet/runtime](https://github.com/dotnet/runtime/tree/a3b2d40328be0be3f8dd43f0e5cc925b8827b044). Preserve [Washi's MIT notice](licenses/nativeaot-parser-MIT.txt) and the [.NET Foundation MIT notice](licenses/dotnet-runtime-MIT.txt). |
| EVM analysis | `evmole@0.9.3`, MIT, copyright 2023 Maxim Andreev. The published npm archive omits its license document; REA retains the [original upstream license](third_party/evmole/LICENSE) and [provenance](third_party/evmole/README.md). It is also included in the runtime notices. |
| Windows native addon | The release addon is compiled with MinGW-w64 and statically links GCC runtime libraries. Preserve the [MinGW-w64 runtime notices](licenses/MinGW-w64-runtime.txt), [GPLv3 text](licenses/GPL-3.0.txt), and [GCC Runtime Library Exception 3.1](licenses/GCC-Runtime-Library-Exception-3.1.txt). The exception governs eligible compiled combinations; it does not relicense the runtime itself under MIT. The Node-API build headers are from Node 22.19.0; its [original license and component notices](licenses/Node-22.19.0-LICENSE.txt) are retained. Node itself is supplied by the user. Snapshot provenance and release-toolchain review requirements are in [licenses/README.md](licenses/README.md). |
+1
View File
@@ -33,6 +33,7 @@ export default defineConfig({
{ text: "Model artifacts", link: "/model-artifacts" },
{ text: "Rust dependencies", link: "/rust-binaries" },
{ text: "PE certificates", link: "/pe-certificates" },
{ text: "PE imports", link: "/pe-imports" },
{ text: "ELF notes", link: "/elf-notes" },
{ text: "CPython pyc files", link: "/python-pyc" },
{ text: "Erlang BEAM", link: "/erlang-beam" },
+8 -2
View File
@@ -957,8 +957,14 @@ remain unchanged. Clock-format comparison and respelling recognize four-digit
ISO years `0000`–`0099` as well as later years, with normal calendar validation.
Respelling preserves the original value when the target zone would require a year
outside `0000`–`9999`; generated writes refuse a clock outside that format.
Capture-state extraction still uses millisecond coordinates for request intervals
and their ordering, and generated-clock progression compares milliseconds.
Capture-state start ordering uses the same exact recorded timestamp facts,
including minute-only times and fractions longer than nine digits. Recorded
spellings may use `T` or a space, `Z` or an extended/compact offset, or no zone
(interpreted as UTC). Equal starts use ordinal order; a missing or unreadable
start makes the capture use record order. Request interval and overlap checks
still use millisecond coordinates, and generated-clock progression compares
milliseconds. Derived ISO clock formats still require seconds and at most nine
fractional digits.
Comparison keeps response representations distinct: JSON values compare structurally, text compares as text, and a
retained base64 body compares by bytes (including with UTF-8 text carrying those same bytes), never by its base64
+4
View File
@@ -93,6 +93,10 @@ still suppresses all string projection, including in this mode.
`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.
A change during snapshot acquisition reports retryable `artifact_changed`;
wait for a stable input before retrying. Host read permission failures report
`access_denied`, while a missing or non-regular selected path reports
`invalid_request` with the rejected `path` field.
- **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.
+2 -2
View File
@@ -86,8 +86,8 @@ graph TD
TESTS["tests/<br/>Vitest suite<br/>fixtures/ holds fake launcher, bridge, and CDP seams"]
end
subgraph "Evaluation"
EVALUATION["src/evaluation/<br/>Agent release evaluation<br/>session transcript scoring"]
subgraph "Development Evaluation"
EVALUATION["tests/evaluation/model-evals/<br/>Agent release evaluation<br/>session transcript scoring"]
end
DISPATCH --> CLI
+11
View File
@@ -290,6 +290,17 @@ embedded X.509 serial, issuer, subject, and validity fields. A missing security
directory is observed absence of that table. See
[PE Authenticode certificate tables](pe-certificates.md).
Inspect PE import and delay-load tables without executing the image:
```bash
rea inspect-pe-imports /absolute/path/to/application.exe --json
```
The workflow reports DLL names, named and ordinal functions, delay-load
descriptors, and a pefile-compatible import hash of the standard import table.
A missing import directory is observed absence of that table. See
[PE import and delay-load tables](pe-imports.md).
Inspect ELF PT_NOTE and SHT_NOTE tables without executing the image:
```bash
+10 -8
View File
@@ -305,15 +305,17 @@ for the supported policy values.
When direct launcher paths prove the same executable or Node entrypoint,
setup retains the existing caller-selected spelling. An otherwise aligned
registration therefore needs no rewrite or recovery backup merely because
REA was invoked through another symlink. Unresolved identity does not prove
equivalence.
REA was invoked through another symlink. Setup also retains that spelling when
another field, such as an invalid timeout, needs repair. Unresolved identity
does not prove equivalence.
| Settings | Setup ownership |
| ----------------------------------------- | --------------------------------------------------------------------- |
| Launcher, transport and activation | Refreshes the REA stdio connection and enables it. |
| Explicit server environment | Preserves configured overrides and merges selected provider settings. |
| Codex positive timeouts and client policy | Retains caller choices; adds missing recommended REA timeouts. |
| Unrelated or future server settings | Retains their parsed values. |
| Settings | Setup ownership |
| ------------------------------------------- | --------------------------------------------------------------------------------------- |
| Launcher, transport and activation | Refreshes the REA stdio connection and enables it. |
| Explicit server environment | Preserves configured overrides and merges selected provider settings. |
| Codex timeouts | Retains positive values; repairs missing or invalid values to recommended REA defaults. |
| Codex approval policies and tool selections | Retains caller choices; refuses known malformed values. |
| Unrelated or future server settings | Retains their parsed values. |
Dry-run applies the same policy validation and writes no files. A successful
refresh keeps an exact recovery snapshot of the original configuration;
+6 -1
View File
@@ -458,6 +458,11 @@ real local Codex CLI with:
npm run verify:agent
```
The verifier's scenarios, transcript scoring, fixture rubrics, and report gates
are development verification code under `tests/evaluation/model-evals/`. The
source-owned evaluator runs alongside the compiled REA tools; it is excluded
from the shipped runtime package.
The lane runs scenarios sequentially in standalone Codex (`--no-daemon`) with a
disposable HOME and CODEX_HOME under `~/.cache/rea-agent-evaluations/`. Set
`REA_AGENT_EVAL_ROOT` to choose another parent directory outside the OS temporary
@@ -487,7 +492,7 @@ boundary instead of stopping at Codex approval. Its scenario contract accepts
one `provider_unavailable` open refusal without a close, or one successful open
followed by a successful close. Other errors, an unclosed session, and a close
without acquisition fail. These outcomes and the prompt are owned together in
`src/evaluation/AgentEvaluationScenarios.ts`; generic successful-tool sequences
`tests/evaluation/model-evals/AgentEvaluationScenarios.ts`; generic successful-tool sequences
still apply to scenarios that require them. The provider scenario checks routing,
reported limitations, and cleanup; free-form answer correctness remains
unassessed. It does not validate a real native engine or authorize runtime execution.
+59
View File
@@ -54,6 +54,65 @@ rules apply to the last occurrence of each chunk name; an empty final occurrence
does not revive an earlier payload of that name. Nonempty truncated tables remain
format failures.
## Select retained facts
Complete output remains the default. Request a summary with a `beam` view to
read a few facts in the first call while retaining the complete inspection:
```json
{
"name": "inspect_erlang_beam",
"arguments": {
"path": "/artifacts/app.beam",
"detail": "summary",
"view": {
"kind": "beam",
"collection": "exports",
"names": ["module_info"],
"fields": ["function_index", "function", "arity", "label"],
"limit": 20
}
}
}
```
`normalized_result.page.items` contains the matching rows. `source_index` is
zero-based in the original collection and scoped to `parent_evidence_id` and
that collection. It does not replace the original one-based atom `index` or a
lambda's own `index`. Every matching function name survives, including different
arities. Names are exact retained display strings; raw indexes remain available
when names are unresolved or displayed names collide.
Collections are `atoms`, `exports`, `imports`, `lambdas`, and `chunks`. `names`
selects atom/chunk names or function names; imports additionally support exact
`modules`. Filters intersect. `indices` selects original collection indexes;
`fields` selects that collection's own fields. Empty selector arrays match
nothing, and `fields: []` returns only `source_index`. Offset/limit page matching
rows in original order (defaults 0/64). Unfiltered pages slice the retained
collection; queries do not decode the file again.
`beam` keeps the module, form/Code bounds, original source coverage, and table
counts. A null count means no table was decoded; zero means a decoded empty
table. Missing tables and unresolved atom names remain explicit, and an empty
page does not establish runtime absence. Page coverage is separate from the
source's examined chunks and parser budgets.
CLI summary delivery requires a destination for the complete Evidence bundle:
```sh
rea inspect-erlang-beam /artifacts/app.beam --detail summary \
--view '{"kind":"beam","collection":"exports","names":["module_info"],"fields":["function","arity"]}' \
--evidence_output /reports/app-beam.evidence.json --json
rea inspect-analysis-view '{"source":{"kind":"evidence-file","path":"/reports/app-beam.evidence.json"},"view":{"kind":"beam","collection":"imports","modules":["erlang"]}}' --json
```
The bundle retains the complete report. Subsequent MCP queries may use
`source: {kind: "retained-evidence", evidence_id: parent_evidence_id}` or an
Evidence file. Retained and offline views still work after the original BEAM
file is deleted. Supplying `view` requires `detail: "summary"`; omitting it
returns only the structural summary. Selection changes delivery, not parsing or
coverage, and executes no bytecode.
## Limits and failures
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
+1 -1
View File
@@ -50,7 +50,7 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
| Task | Guides |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [ELF notes](elf-notes.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [ELF notes](elf-notes.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
+5 -5
View File
@@ -88,11 +88,11 @@ 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`.
Detected provider settings replace only matching environment keys.
Setup manages REA's launcher, selected provider settings, and supported timeout
defaults. Client approval policies remain user choices, but the current Codex
refresh drops those policies inside the REA entry; see the
[Codex preservation limitation](client-configuration.md#codex-and-devin) before
refreshing a customized registration.
Setup manages REA's launcher, selected provider settings, and recommended timeout
defaults. Codex approval policies, tool selections, and other caller-owned server
settings are preserved. Known malformed policy values block setup before it
writes the configuration or a backup; see
[Codex settings preservation](client-configuration.md#codex-and-devin).
Setup and doctor report a malformed REA environment map or invalid runtime
setting before setup writes files. Repair the named setting in the client
configuration; setup does not silently discard the rest of the map.
+5 -2
View File
@@ -427,8 +427,11 @@ value. Earlier `break`/`continue` paths can leave a var initializer unexecuted.
Legacy non-strict block-function copies are indexed on their outer variable
owner, while strict/module and lexical-shadowing boundaries keep their values.
A catch-shadowed var declaration keeps declaration ownership separate from its
initializer's lexical assignment. Ordinary functions own an implicit arguments
object; arrows retain lexical arguments lookup. Promise source identity uses
initializer's lexical assignment, including object and array destructuring slots.
Projected initializer facts retain their source and alias identity on the lexical
recipient, so a later mutation through that alias prevents reporting the original
object field as an unchanged constant. Ordinary functions own an implicit
arguments object; arrows retain lexical arguments lookup. Promise source identity uses
the same read-position selection and requires an unmodified intrinsic factory.
Rebound values are evaluated at their read position: saving an incoming parameter
+18 -3
View File
@@ -101,6 +101,18 @@ caller's request and first-occurrence priority, analyzing the file once.
Omit the selection for the existing complete
application workflow.
For a long selection, pass the paths as a JSON array with
`--source-paths-json`, either inline or as a JSON file path. A file keeps the
command below host command-line limits, such as the 8,191-character limit of
the `cmd.exe` shim that `npx` uses on Windows. Its entries follow any
`--source-path` values and stay artifact-relative; they are not resolved
against the working directory:
```bash
rea analyze-javascript-application /absolute/path/to/app.asar \
--source-paths-json /absolute/path/to/paths.json --json
```
The result and summary retain `source_selection.requested_paths` and
`source_selection.unselected_files`. Selecting every relevant file leaves this
count at zero and adds no unselected-source caveat. Every relevant file keeps its
@@ -109,8 +121,8 @@ original inventory identity; unselected files report `text_status:
coverage is partial while any file remains outside scope. Selection does not
claim whole-application completeness or change integrity policy. Summary detail
only reduces delivery; source selection reduces text acquisition and analysis.
Explicit native provider or snapshot routing rejects `--source-path` rather
than ignoring it.
Explicit native provider or snapshot routing rejects `--source-path` and
`--source-paths-json` rather than ignoring them.
For directory input, a permission-denied child file remains an unavailable
inventory occurrence, and an unreadable child directory records an unknown
@@ -301,7 +313,10 @@ Selected bounded text is then parsed as inert data to recover:
- local HTML script entrypoints;
- Webpack and Rspack chunk registrations and module factories represented as
AST literals, plus esbuild `__commonJS` module tables; recovered esbuild
CommonJS exports use its factory's `exports`-then-`module` parameter order;
CommonJS exports use its factory's `exports`-then-`module` parameter order.
Export receivers and loader helpers retain those calling-convention roles only
while the binding at the read still holds its original parameter value;
replacing a parameter does not give the replacement object that role;
- static imports, dynamic imports, CommonJS `require` calls, workers, and
service workers (only `new Worker(new URL(path, import.meta.url))` resolves
relative to the calling module; string worker and service-worker paths use
+23 -16
View File
@@ -426,8 +426,12 @@ Native target admission checks Mach-O slice headers before assigning FAT
identity. A shared `CAFEBABE` prefix whose declarations do not identify Mach-O
images receives the generic unsupported-format refusal, including JVM class
files larger than the bounded admission probe. This does not identify a partial
prefix as a validated JVM class. Malformed recognized FAT declarations retain
their format-specific reasons.
prefix as a validated JVM class. For files sharing `CAFEBABE`, target admission
checks complete JVM framing within the same 16 MiB class identity bound as the
Mach-O image reader. A complete class remains unsupported even if its constant
pool resembles a known FAT CPU declaration. Above that bound, an ambiguous
malformed FAT interpretation remains unresolved. Malformed recognized FAT
declarations retain their format-specific reasons.
`inspect_artifact` and `extract_artifact` retain universal Mach-O slices under
their exact `lipo` names, including 32-bit ARM implementations such as `armv7`,
@@ -464,24 +468,26 @@ numbers requiring XML type facts retain the `plutil` route. Invalid UTF-8 and
encoding-declaration mismatches remain explicit failures. Direct-source
provenance records `file` without claiming conversion; conversion limitations
and invocations appear only when conversion actually ran.
Malformed XML structure remains an input failure; malformed scalar literals
retain their exact source text, reason and counted unknown-scalar limitation,
using the same scalar interpretation as archive readers.
XML sources must also pass native format validation, with native refusals retaining
their diagnostics and successful validation recorded in provenance. Original
unknown scalar text, reasons and limitations survive successful validation;
output-budget omissions remain omissions.
Source input is limited to 64 MiB. Binary and direct XML decoding reserve an
estimated 64 MiB decode budget before parsing; this is not a hard process-memory
bound. JSON decode memory has not been measured and has no equivalent decode
estimate. A refused input reports non-retryable `resource_constraint` with
`reported_limits.maximum_input_bytes` or `maximum_decoded_bytes` as applicable.
Ordinary direct-source inspection admits 998 source nesting levels, derived from
the existing 1000-level JSON result capacity by reserving one level for typed
scalar fields and one for the result's `value` field. Deeper input reports
Ordinary direct-source decoding has a ceiling of 998 source nesting levels,
derived from the existing 1000-level JSON result capacity by reserving one level
for typed scalar fields and one for the result's `value` field. Deeper input reports
`resource_constraint` with `reported_limits.maximum_nesting_depth: 998` and names
the fixed nesting allowance. Compatibility conversions can encounter an earlier
native utility refusal; their diagnostics remain explicit. Archive and Interface
Builder decoding retain their separate 128-level allowance. Worker limits and
host memory do not raise these fixed allowances, and the unchanged request fails
the same way.
the fixed nesting allowance. XML admission also requires native validation, which
can refuse input below this decoding ceiling. Compatibility conversions can
likewise encounter an earlier native utility refusal; both retain native
diagnostics. Archive and Interface Builder decoding retain their separate
128-level allowance. Worker limits and host memory do not raise these fixed
allowances, and the unchanged request fails the same way.
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
@@ -724,9 +730,10 @@ complete observation.
examined type records and fully decoded declarations. Class vtable recovery
reads the same descriptors, so an unreadable type record also keeps
`swift_class_vtable_descriptors` partial.
Nominal declarations, conformance type references and class vtables share one
snapshot-scoped name/context reader. Conformance `type_name` and dispatch
`owner` retain enclosing named types; conformances carry the known module.
Nominal declarations, conformance type and protocol references, and class
vtables share one snapshot-scoped name/context reader. Conformance `type_name`,
`protocol_name`, and dispatch `owner` retain enclosing named types;
conformances carry the known module.
Unresolved parents preserve the leaf name and a partial identity reason,
while independently readable witness implementations remain available.
Protocol instance properties use the same property-list reader as classes.
+73
View File
@@ -0,0 +1,73 @@
# PE import and delay-load tables
`inspect_pe_imports` / `inspect-pe-imports` answers whether one selected local
PE32/PE32+ image declares an `IMAGE_DIRECTORY_ENTRY_IMPORT` table, which DLLs
and functions that table names, which delay-load descriptors are present, and
the pefile-compatible import hash of the standard import table. It reads the
file without executing it and does not require a Windows SDK or disassembler.
```bash
rea inspect-pe-imports /absolute/path/to/application.exe --json
```
The CLI resolves relative paths against the caller's working directory. MCP
and the advertised input schema require an absolute filesystem path and
preserve that exact selection:
```json
{
"name": "inspect_pe_imports",
"arguments": { "path": "/artifacts/application.exe" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`import_directory` and `delay_import_directory` report the data directory RVA
and size, or `null` when a directory is absent or zero. Their `locations` are
the file spans actually read for the descriptor array and its terminator. When
consecutive descriptors map to discontiguous file bytes (for example across a
section boundary), each span is reported separately, and each becomes its own
Evidence file-offset range.
If the first descriptor cannot be examined, the directory declaration remains
present with no consumed `locations`, and coverage is partial. Descriptors,
thunks and NUL-terminated names stay within PE's 32-bit RVA space, including
PE32+ images; earlier valid rows survive an overflowing descriptor walk.
Each `imports[]` item reports the DLL name, descriptor RVAs, and named or
ordinal functions with ILT/IAT RVAs. `delay_imports[]` reports RVA-based
delay-load descriptors separately. `imphash` is pefile's `get_imphash()` over
the standard import table only; delay-load rows do not participate. Ordinal
imports use pefile's ordinal name tables and otherwise its `ord<decimal>`
fallback.
`imphash.md5` is `null` with a `reason` whenever the value would not be
pefile's hash of this image: the standard import table is partial, or pefile
would read a different row sequence (names it treats as invalid or truncates,
ordinal 0, descriptors without functions, or tables its bounds and import
symbol cap truncate). Delay-load issues do not withhold the hash.
A missing import directory is observed absence of that table. It is not proof
the image never imported APIs through another table or overlay.
## Limits and failures
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
256 import and delay-load descriptors are examined by default (4096 maximum),
and at most 8192 functions across every table (262144 maximum). Malformed PE
headers and exhausted DLL or function budgets fail without partial success.
Decoded DLL and function names are limited to a fixed 16 MiB of retained text
with no runtime override; `max_file_bytes`, `max_dlls` and `max_functions` do
not raise it. Resource failures report the exhausted allowance as `limit`
(`dlls`, `functions` or `retained_text`) with matching recovery.
Unreadable DLL names, hint/name entries, or thunks are reported as partial
coverage with `issues`. A descriptor whose required table RVA is zero (a
standard `FirstThunk`, or a delay-load import address or name table) is
reported as `malformed_entry` with no functions, never as a complete empty
inventory. Delay-load descriptors that store virtual addresses rather than RVAs
are recorded as `unsupported_form` and are not decoded.
This workflow does not resolve bound imports, loaded IAT contents, or
export-forward names in another DLL.
+1
View File
@@ -154,6 +154,7 @@ npx rea-agents@latest setup
| الملفات الثنائية للشيفرة الأصلية | شيفرة شبه برمجية، وتعليمات تجميع، وسلاسل نصية، ورموز، واستدعاءات، ومراجع | Hopper أو Ghidra أو IDA؛ [تحليل الشيفرة الأصلية](../native-investigation.md) |
| بنية ELF دون تشغيل | الأقسام والمقاطع والرموز ومعلومات إعادة التموضع الأصلية، وآليات الحماية المحتملة المستدلّ عليها بالفحص الثابت | pwntools يوفّره المستدعي على Linux x64؛ [تشخيص الملفات الثنائية](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| شيفرة EVM البايتية | محددات التوجيه، وإزاحات البايتات، والمعاملات المستنتجة، وقابلية تغيير الحالة | مدخل محلي يحمل بايتات خامًا أو تمثيلًا سداسيًا عشريًا على Linux x64؛ [دليل EVM دون تشغيل](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -154,6 +154,7 @@ REA benötigt Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) oder 26+ (>=26.12.1) so
| Native Binärdateien | Pseudocode, Assemblercode, Zeichenketten, Symbole, Aufrufe und Verweise | Hopper, Ghidra oder IDA; [native Analyse](../native-investigation.md) |
| Offline-ELF-Struktur | Sektionen, Segmente, ursprüngliche Symbole/Relokationen und mögliche Schutzmaßnahmen aus statischer Analyse | Vom Aufrufer bereitgestelltes pwntools unter Linux x64; [Binärdiagnostik](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| EVM-Bytecode | Dispatch-Selektoren, Byte-Offsets, abgeleitete Argumente und Veränderbarkeit des Zustands | Lokale Eingabe mit Rohbytes oder Hexdarstellung unter Linux x64; [Offline-EVM-Leitfaden](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -156,6 +156,7 @@ npx rea-agents@latest setup
| Δυαδικά αρχεία Go | Ενσωματωμένη έκδοση μεταγλωττιστή, modules, αντικαταστάσεις και ρυθμίσεις μεταγλώττισης | Ενσωματωμένη στατική εξέταση ELF/PE/thin Mach-O· δεν απαιτείται εγκατάσταση του Go |
| Δυαδικά αρχεία Rust | Ενσωματωμένα ονόματα, εκδόσεις, πηγές και σχέσεις εξάρτησης των crates από το cargo-auditable | Ενσωματωμένη στατική εξέταση ELF/PE/thin Mach-O· [δυαδικά αρχεία Rust](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| Αρχείο WASM | Έλεγχο εγκυρότητας, ενότητες, ακριβείς μορφές εισαγωγών/εξαγωγών σε WAT και αποκωδικοποιημένο WAT | WABT 1.0.42 που παρέχει ο χρήστης· `inspect_wasm_artifact` |
+1
View File
@@ -154,6 +154,7 @@ REA requiere Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), adem
| Binarios nativos | Pseudocódigo, ensamblador, cadenas, símbolos, llamadas y referencias | Hopper, Ghidra o IDA; [análisis nativo](../native-investigation.md) |
| Estructura ELF sin ejecutar | Secciones, segmentos, símbolos/reubicaciones originales y posibles medidas de mitigación detectadas mediante análisis estático | pwntools proporcionado por quien llama, en Linux x64; [diagnóstico de binarios](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Bytecode EVM | Selectores de despacho, desplazamientos en bytes, argumentos inferidos y mutabilidad | Entrada local con bytes sin procesar o representación hexadecimal, en Linux x64; [guía de EVM sin ejecutar](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -146,6 +146,7 @@ REA به Node.js 22.x (>=22.23.4)، 24.x (>=24.22.1) یا 26+ (>=26.12.1)، به
| باینری‌های بومی | شبه‌کد، اسمبلی، رشته‌ها، نمادها، فراخوانی‌ها و ارجاعات | Hopper، Ghidra یا IDA؛ [تحلیل بومی](../native-investigation.md) |
| ساختار ELF آفلاین | بخش‌ها، سگمنت‌ها، نمادها و relocationهای اصلی و گزینه‌های احتمالی کاهش آسیب‌پذیری ایستا | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ [تشخیص باینری](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| بایت‌کد EVM | selectorهای dispatch، offsetهای بایتی، آرگومان‌های استنباط‌شده و قابلیت تغییر وضعیت | دادهٔ خام/hex محلی روی Linux x64؛ [راهنمای EVM آفلاین](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -153,6 +153,7 @@ REA nécessite Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ou 26+ (>=26.12.1), ai
| Binaires natifs | Pseudo-code, assembleur, chaînes, symboles, appels et références | Hopper, Ghidra ou IDA ; [analyse native](../native-investigation.md) |
| Structure ELF hors ligne | Sections, segments, symboles et relocations d'origine, et protections statiques candidates | pwntools fourni par l'utilisateur sous Linux x64 ; [diagnostics binaires](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Artefact WASM | Validation, sections, formes WAT exactes d'import/export et WAT décodé | WABT 1.0.42 fourni par l'utilisateur ; `inspect_wasm_artifact` |
| Bytecode EVM | Sélecteurs de dispatch, offsets en octets, arguments inférés et mutabilité | Fichier local brut ou hexadécimal sous Linux x64 ; [guide EVM hors ligne](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
+1
View File
@@ -156,6 +156,7 @@ Alat tambahan dan dukungan sistem host bergantung pada target yang dianalisis:
| Biner native | Pseudocode, assembly, string, simbol, pemanggilan, dan referensi | Hopper, Ghidra, atau IDA; [analisis native](../native-investigation.md) |
| Tata letak ELF offline | Bagian, segmen, simbol/relokasi asli, dan kandidat mitigasi statis | pwntools yang disediakan pemanggil pada Linux x64; [diagnostik biner](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Artefak WASM | Validasi, bagian, bentuk WAT import/ekspor yang tepat, dan WAT yang didekode | WABT 1.0.42 yang disediakan pemanggil; `inspect_wasm_artifact` |
| Bytecode EVM | Selector dispatch, offset byte, argumen yang disimpulkan, dan sifat mutabilitas | Pembawa data raw/hex lokal; [panduan EVM offline](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
+1
View File
@@ -153,6 +153,7 @@ REA richiede Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), oltre
| Binari nativi | Pseudocodice, assembly, stringhe, simboli, chiamate e riferimenti | Hopper, Ghidra o IDA; [analisi nativa](../native-investigation.md) |
| Layout ELF offline | Sezioni, segmenti, simboli/relocazioni originali e candidati mitigazioni statiche | pwntools fornito dal chiamante su Linux x64; [diagnostica binaria](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Artefatto WASM | Convalida, sezioni, forme WAT di import/export esatte e WAT decodificato | WABT 1.0.42 fornito dal chiamante; `inspect_wasm_artifact` |
| Bytecode EVM | Selettori di dispatch, offset dei byte, argomenti inferiti e mutabilità | Carrier raw/hex locale; [guida EVM offline](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
+1
View File
@@ -154,6 +154,7 @@ REA には Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)、または 26+ (>=
| ネイティブバイナリ | 疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照 | Hopper、Ghidra、または IDA;[ネイティブ分析](../native-investigation.md) |
| オフライン ELF レイアウト | セクション、セグメント、元のシンボル/再配置情報、静的解析によるセキュリティ対策の候補 | Linux x64 で利用者が用意する pwntools;[バイナリ診断](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| EVM バイトコード | ディスパッチセレクター、バイトオフセット、推定された引数と状態変更の可否 | Linux x64 上のローカルの生バイト/16 進数の入力ファイル;[オフライン EVM ガイド](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -156,6 +156,7 @@ REA에는 Node.js 22.x(>=22.23.4), 24.x(>=24.22.1) 또는 26+ (>=26.12.1)와 npm
| Go 바이너리 | 내장된 컴파일러 버전, 모듈, `replace` 지시어, 빌드 설정 | REA 내장 ELF/PE/thin Mach-O 정적 검사; Go 설치 불필요 |
| Rust 바이너리 | cargo-auditable이 내장한 크레이트 이름, 버전, 소스, 의존성 관계 | REA 내장 ELF/PE/thin Mach-O 정적 검사; [Rust 바이너리](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| WASM 아티팩트 | 유효성 검사, 섹션, import/export의 정확한 WAT 형식, 디코딩된 WAT | 호출자 제공 WABT 1.0.42; `inspect_wasm_artifact` |
+1
View File
@@ -156,6 +156,7 @@ REA-യ്ക്ക് Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), അല്ല
| 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) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| WASM artifact | Validation, sections, കൃത്യമായ import/export WAT രൂപങ്ങൾ, decoded WAT | Caller നൽകുന്ന WABT 1.0.42; `inspect_wasm_artifact` |
+1
View File
@@ -154,6 +154,7 @@ REA wymaga Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) lub 26+ (>=26.12.1) oraz n
| Natywne pliki binarne | Pseudokod, kod asemblera, ciągi znaków, symbole, wywołania i odwołania | Hopper, Ghidra lub IDA; [analiza kodu natywnego](../native-investigation.md) |
| Układ pliku ELF w trybie offline | Sekcje, segmenty, oryginalne symbole/relokacje i potencjalne mechanizmy zabezpieczeń wskazane przez analizę statyczną | pwntools dostarczone przez użytkownika w systemie Linux x64; [diagnostyka plików binarnych](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Kod bajtowy EVM | Selektory wywołań, przesunięcia bajtowe, wywnioskowane argumenty i mutowalność | Lokalny plik z surowymi bajtami lub zapisem szesnastkowym w systemie Linux x64; [przewodnik EVM offline](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -154,6 +154,7 @@ O REA requer Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ou 26+ (>=26.12.1), alé
| Binários nativos | Pseudocódigo, assembly, strings, símbolos, chamadas e referências | Hopper, Ghidra ou IDA; [análise nativa](../native-investigation.md) |
| Estrutura ELF sem execução | Seções, segmentos, símbolos/relocações originais e possíveis mecanismos de mitigação identificados por análise estática | pwntools fornecido pelo chamador no Linux x64; [diagnóstico de binários](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Bytecode EVM | Seletores de despacho, offsets de bytes, argumentos inferidos e mutabilidade | Entrada local com bytes brutos ou representação hexadecimal no Linux x64; [guia de EVM sem execução](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -156,6 +156,7 @@ REA требует Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) или 26+ (>=26.1
| Байт-код EVM | Селекторы диспетчеризации, смещения байтов, предполагаемые аргументы и изменяемость состояния | Локальные необработанные байты или шестнадцатеричное представление в Linux x64; [руководство по EVM без запуска](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| Записанные сбои Linux | Необработанные записи note, регистры/сигналы каждого записанного потока и необязательные кандидаты сопоставления с памятью | pwntools, предоставленный вызывающей стороной, в Linux x64; необязательные GDB/pwndbg; [записанные сбои](../recorded-crashes.md) |
+1
View File
@@ -154,6 +154,7 @@ REA ต้องใช้ Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) หรื
| ไบนารีเนทีฟ | โค้ดเทียม แอสเซมบลี สตริง สัญลักษณ์ การเรียก และการอ้างอิง | Hopper, Ghidra หรือ IDA; [การวิเคราะห์เนทีฟ](../native-investigation.md) |
| โครงสร้าง ELF แบบออฟไลน์ | เซกชัน เซกเมนต์ สัญลักษณ์/รีโลเคชันต้นฉบับ และตัวเลือกกลไกป้องกันที่พบจากการวิเคราะห์แบบสถิต | pwntools ที่ผู้ใช้จัดเตรียมบน Linux x64; [การวินิจฉัยไบนารี](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ไบต์โค้ด EVM | ตัวเลือกฟังก์ชันสำหรับการเรียก ออฟเซ็ตไบต์ อาร์กิวเมนต์และความสามารถในการเปลี่ยนสถานะที่อนุมานได้ | ไฟล์บนเครื่องที่มีไบต์ดิบหรือข้อมูลเลขฐานสิบหกบน Linux x64; [คู่มือ EVM แบบออฟไลน์](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -154,6 +154,7 @@ REA; Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) veya 26+ (>=26.12.1) ve npm gere
| Go ikili dosyaları | Gömülü compiler sürümü, modüller, replacement'lar ve build ayarları | Yerleşik statik ELF/PE/thin Mach-O incelemesi; Go kurulumu gerekmez |
| Rust ikili dosyaları | Gömülü cargo-auditable crate adları, sürümleri, kaynakları ve bağımlılık ilişkileri | Yerleşik statik ELF/PE/thin Mach-O incelemesi; [Rust ikili dosyaları](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| WASM artefaktı | Doğrulama, bölümler, tam import/export WAT biçimleri ve decode edilmiş WAT | Çağıranın sağladığı WABT 1.0.42; `inspect_wasm_artifact` |
+1
View File
@@ -154,6 +154,7 @@ REA потребує Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) або 26+ (>=26
| Нативні бінарні файли | Псевдокод, асемблерний код, рядки, символи, виклики й посилання | Hopper, Ghidra або IDA; [нативний аналіз](../native-investigation.md) |
| Структура ELF без запуску | Секції, сегменти, початкові символи/релокації та можливі захисні механізми за даними статичного аналізу | pwntools, наданий стороною, що викликає інструмент, у Linux x64; [діагностика бінарних файлів](../binary-diagnostics.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| Байт-код EVM | Селектори диспетчеризації, зміщення байтів, виведені припущення про аргументи та змінюваність стану | Локальні необроблені байти або шістнадцяткове представлення у Linux x64; [посібник EVM без запуску](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
+1
View File
@@ -196,6 +196,7 @@ Mazeed tools aur host support target par depend karte hain:
| Godot PCK | Archive inventory aur extraction, original header/directory facts aur selected content identities | Built-in static formats 2, 3 aur 4; Godot zaroori nahi |
| Rust binaries | Embedded cargo-auditable crate names, versions, sources aur dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries aur PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import aur delay-load DLL names, ordinals, IAT RVAs aur pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id aur ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas aur Code header | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| WASM artifact | Validation, sections, exact import/export WAT forms aur decoded WAT | Aapka diya hua WABT 1.0.42; `inspect_wasm_artifact` |
+1
View File
@@ -156,6 +156,7 @@ REA yêu cầu Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) hoặc 26+ (>=26.12.1)
| Bytecode EVM | Bộ chọn điều phối, độ lệch byte, tham số suy luận và khả năng thay đổi trạng thái | Đầu vào cục bộ chứa byte thô hoặc dạng thập lục phân trên Linux x64; [hướng dẫn EVM ngoại tuyến](../evm-bytecode.md) |
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| Sự cố Linux đã ghi lại | Bản ghi note thô, thanh ghi/tín hiệu của mọi luồng được ghi lại và các ứng viên ánh xạ tùy chọn | pwntools do bên gọi cung cấp trên Linux x64; GDB/pwndbg tùy chọn; [sự cố đã ghi lại](../recorded-crashes.md) |
+1
View File
@@ -157,6 +157,7 @@ REA 需要 Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)或 26+ (>=26.12.1)
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| 已記錄的 Linux 崩潰 | 原始 note 記錄、每個已記錄執行緒的暫存器/訊號及可選的對映候選項 | Linux x64 上由呼叫方提供的 pwntools;可選的 GDB/pwndbg;[已記錄崩潰](../recorded-crashes.md) |
| 桌面安裝目錄 | 執行階段候選、支持檔案、元件標記與後續操作 | 有界的目錄與檔頭檢查;[桌面應用](../desktop-applications.md) |
+1
View File
@@ -157,6 +157,7 @@ REA 需要 Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)或 26+ (>=26.12.1)
| Rust binaries | Embedded cargo-auditable dependency list | [Rust binaries](../rust-binaries.md) |
| Erlang BEAM | IFF chunks, atoms, exports, and tables | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| 已记录的 Linux 崩溃 | 原始 note 记录、每个已记录线程的寄存器/信号及可选的映射候选项 | Linux x64 上由调用方提供的 pwntools;可选的 GDB/pwndbg;[已记录崩溃](../recorded-crashes.md) |
| 桌面安装目录 | 运行时候选、支持文件、组件标记和后续操作 | 有界的目录与文件头检查;[桌面应用](../desktop-applications.md) |
+65 -29
View File
@@ -1,19 +1,25 @@
# Reconstruction readiness conformance
# Reconstruction readiness verification
Run the source-owned native, JavaScript CLI, Electron, Evidence, comparison,
obligation, replay, and cleanup conformance verifier from a clean checkout:
Run the public static-analysis journeys and synthetic report-evaluator checks
from a clean checkout:
```sh
npm ci
npm run verify:readiness
```
The command builds the redistributable native conformance fixtures, inventories
the native executable through the public CLI, analyzes the generated JavaScript
CLI and Electron fixtures through both CLI and MCP, evaluates an internal
nine-stage conformance report, and proves stale/tampered replay rejection. The
report evaluator is part of the repository verifier, not a public REA tool. It
prints one compact JSON summary.
The command builds the redistributable native conformance fixtures, inspects
the native executable through the public CLI, and analyzes the JavaScript CLI
and Electron fixture sources through both CLI and MCP. It checks matching
CLI/MCP Evidence identities for those two application analyses. The fixture
applications are not executed, and native MCP parity is not exercised.
Separately, the command evaluates a wholly synthetic nine-stage input and
checks evaluator determinism, Evidence tampering rejection, and stale-source
digest rejection. These checks concern report evaluation; they do not establish
application runtime behavior, authority recovery, cancellation cleanup,
behavioral equivalence, or reconstruction closure for the analyzed fixtures.
The command prints one compact JSON summary.
Set `REA_READINESS_REPORT_PATH` to retain the complete machine-readable report:
@@ -24,15 +30,37 @@ REA_READINESS_REPORT_PATH=readiness-report.json npm run verify:readiness
CI runs this command on Linux, publishes the compact summary, and retains the
full report as the `reconstruction-readiness` artifact.
## Project conformance contract
## Report scope and ownership
The verifier runs as `npm run verify:readiness`; it is not exposed as a public
CLI command or MCP tool. The synthetic report input and evaluator exist to
check project-owned conformance fixtures and fail-closed report behavior.
CLI command or MCP tool. Its report separates two kinds of verification:
## Required stages
- `observed_journeys` retains the actual static-analysis Evidence and the
observed CLI/MCP identity checks, with their coverage limits.
- `evaluator_conformance` retains the synthetic baseline report and the
evaluator checks. Its nine-stage `pass` describes acceptance of supplied
synthetic observations.
| Stage | Required proof |
The verifier's overall success means its listed checks passed. It is not an
end-to-end reconstruction-readiness verdict. Actual analysis Evidence is not
grafted into the synthetic report or used to close its checks.
The retained artifact now uses `schema: "ReconstructionReadinessVerificationReport"`
and `schema_version: 1`. The previous unversioned artifact was the nine-stage
evaluator report itself. Consumers should read the two explicit sections; the
synthetic nine-stage report now lives at `evaluator_conformance.baseline_report`.
Observed CLI and MCP Evidence are retained in separate bundles under
`observed_journeys.evidence_bundles`, preserving each surface's records.
The evaluator, schemas, synthetic input, and semantic tests live under
`tests/conformance/readiness/report/`. They are development verification code,
excluded from the compiled runtime package. The evaluator authenticates Evidence
commitments and checks supplied observations for consistency; it does not
independently observe the events described by those observations.
## Synthetic evaluator stages
| Stage | Required supplied observations |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `discover-classify` | target-free startup, artifact inventory, compatible workflow selection, and exact visible limits |
| `diagnose-environment` | broken runtime separated from analysis failure, version skew detected, and revision changed after recovery |
@@ -44,13 +72,13 @@ check project-owned conformance fixtures and fail-closed report behavior.
| `verify-reconstruction-closure` | incomplete reconstruction detected, incremental verifier closure, and authority-delegating candidates rejected |
| `export-replay` | CLI/MCP parity, process/structured failure parity, deterministic replay, tamper rejection, and stale-input rejection |
Every stage and required check must occur exactly once and link to Evidence in
the embedded Evidence bundle. `fail`, `unknown`, `unsupported`, `truncated`,
and `skipped` never aggregate to `pass`.
Within the synthetic input, every stage and required check must occur exactly
once and link to Evidence in its embedded bundle. `fail`, `unknown`,
`unsupported`, `truncated`, and `skipped` never aggregate to `pass`.
## Fail-closed rules
The evaluator rejects or withholds readiness when:
The evaluator rejects or withholds a passing synthetic report when:
- a bounded capability omits its effective numeric limits;
- a fixture lacks exactly one compatible selected workflow;
@@ -63,8 +91,8 @@ The evaluator rejects or withholds readiness when:
- contradiction Evidence or its affected comparison is missing;
- the reconstruction obligation ledger is not `ready`, has no required
obligation, or retains an unowned/unverified required obligation;
- a process exceeds its declared target/lifecycle, cancellation loses diagnostics, or owned
resources remain;
- a denied or cancelled grant reports a process launch, a cancelled cleanup
observation omits diagnostic Evidence, or owned resources remain;
- replay is nondeterministic, tampering/staleness is not detected, or the
expected source digest differs.
@@ -95,19 +123,27 @@ Differing transport status is checked on each observation separately.
## Fixtures
The verifier uses only source-owned, redistributable fixtures:
The static-analysis journeys use source-owned, redistributable fixtures:
- `tests/conformance` native executables with stable symbols and deliberate
version divergence;
- `build/conformance/c`, built from `tests/conformance/c/fixture.c`, with stable
symbols and strings;
- `tests/conformance/readiness/javascript-cli`, which exposes prompts,
alternate-screen behavior, configuration precedence, child process,
persistence, loopback HTTP, and signal teardown to static reconstruction;
- `tests/conformance/readiness/electron`, which exposes main, preload,
renderer, utility, IPC, renderer restart, blocked shell effect, and deep-link
surfaces;
- the shared incomplete reconstruction and broken-runtime observations in
`RECONSTRUCTION_READINESS_EXAMPLE`.
surfaces to static analysis.
The synthetic example is a public typed input, not a global equivalence claim.
It demonstrates that every deliberate fault remains represented in the final
report and that only comparable Evidence can close the associated obligation.
The separate `RECONSTRUCTION_READINESS_EXAMPLE` supplies synthetic incomplete
reconstruction, broken-runtime, authority, comparison, cleanup, and closure
observations. Its semantic tests preserve malformed-Evidence rejection,
fail-closed stage results, contradiction handling, repeated CLI/MCP outcomes,
and closure-history consistency. Run those source tests independently:
```sh
npm run test:local -- tests/conformance/readiness/report/reconstructionReadiness.test.ts
```
Passing evaluator tests does not establish real-provider or fixture runtime
behavior. Use the applicable [real-toolchain lanes](testing.md#real-toolchain-verification-lanes)
for those claims.
+17
View File
@@ -90,6 +90,23 @@ releases are outside this recovery path.
For a release that needs an independently frozen application checkpoint, use
the optional manual path below. It retains explicit prepare and publish phases.
Automatic main proposals and manual checkpoint preparation use
`scripts/release-proposal.mjs` with the pinned release-please dependency.
The controller applies `patches/release-please+17.3.0.patch` only when invoked;
normal dependency installation and published-package installation do not apply it.
The patch avoids commit-file backfills and path splitting for a single root Node
package with the default changelog, default or always-bump-minor versioning,
no path exclusions and no explicit plugins. Other configurations retain the
upstream file attribution workflow. Associated PR metadata and commit overrides
still come from GitHub. The controller sizes commit search from the complete
selected Git history, and a missing known release boundary fails before proposal
creation. Publication continues through the pinned release-please action.
When upgrading release-please, review the upstream manifest implementation,
refresh the versioned patch and run the release proposal and workflow boundary
tests. Version mismatch or a patch that neither applies cleanly nor matches an
already-applied patch fails closed.
## Contributor-aware release notes
Use [the rea-changelog-update skill](https://github.com/morluto/rea/blob/main/.agents/skills/rea-changelog-update/SKILL.md)
+5
View File
@@ -184,6 +184,11 @@ retains the completed Evidence and the resource owner for cleanup retry. MCP
records that Evidence before delivering the error, which carries its retained
evidence ID for later retrieval or export.
If a worker fails and cleanup remains incomplete, the error retains the original
cancellation, timeout, diagnostic overflow or memory failure. Successful cleanup
retry restores that failure's classification; it does not make the inspection
successful.
Changed input identities, special files, malformed databases, unsupported
formats and resource failures are reported as errors with the selected path and
meaningful reason. Worker resource refusals expose the caller-selected file in
+38 -21
View File
@@ -169,14 +169,15 @@ 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 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.
The reused CLI boundary workers cap V8 old space at 512 MiB, with or without
coverage. Ordinary CI also exhausted the former 384 MiB budget after earlier
files loaded the shared source/contract graph, while later command registration
and schema/help rendering still needed transient allocation space. This is a
measured capacity control, not a suite-wide speedup claim. The worker count
remains bounded at two, and spawned REA processes retain their own heap settings.
Coverage enablement still controls instrumentation, test deadlines and coverage
thresholds; explicitly disabling it or selecting only a provider does 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
@@ -233,13 +234,16 @@ the next command starts.
IL2CPP metadata, and dylib-resolution commands use that session. File teardown
stops the shared CLI process, including startup still in flight, and does not
open another after close.
`exportedFunctionBindingsCli.test.ts` keeps its own compiled CLI parity
process for the file and still compares that process's JSON command results
with the MCP session. A command that does not respond is rejected. That
process stays owned until it exits, and another process does not start while
it remains. A process that stays running after SIGKILL remains owned so a
later close can retry. Closing the session reports a failure when the process
does not exit after stdin closes, once the child has been stopped.
`exportedFunctionBindingsCli.test.ts` uses the shared compiled CLI session for
its file and compares its JSON command results with the MCP session. It selects
20-second startup and command deadlines; other callers retain their existing
deadline behavior. Retired processes stay owned until exit, block replacement,
and remain available for a later close to retry after failed termination.
Shared session cleanup closes stdin so the worker can release JavaScript
resources, waits the bounded grace, then escalates to SIGTERM and SIGKILL only
if needed. Confirmed exit succeeds; an unconfirmed exit reports cleanup failure.
When startup or a command fails and cleanup also fails, the reported aggregate
retains both failures and the original failure as its cause.
`applicationWorkflowCli.test.ts` keeps one compiled CLI process for its JSON
commands, including a non-zero status whose stdout is still a JSON document,
and closes that process after the file.
@@ -545,12 +549,16 @@ uses the same ownership rule to find retained fixtures under a private temporary
root. Third-party caches remain outside that ownership rule.
Real Hopper, Ghidra, IDA, browser, package, and managed-code claims belong to their
explicit `npm run verify:*` lanes. The reconstruction-readiness lane also
checks deterministic rerun, tamper, and stale-input handling; those checks do
not execute extracted JavaScript modules. When application runtime behavior is
needed, exercise the actual target through browser, Electron, or process
capture. Real model trials are manual; Vitest covers deterministic evaluator
logic.
explicit `npm run verify:*` lanes. The reconstruction-readiness lane separates
observed native CLI inspection and JavaScript/Electron static CLI/MCP analysis
from synthetic report-evaluator conformance. Its determinism, tamper, and
stale-source checks concern the synthetic evaluator; they do not execute fixture
applications or verify runtime authority, cleanup, comparison, or reconstruction
closure. The evaluator and its semantic tests belong to
`tests/conformance/readiness/report/`, outside the shipped runtime. When
application runtime behavior is needed, exercise the actual target through
browser, Electron, or process capture. Real model trials are manual; Vitest
covers deterministic evaluator logic.
`verify:managed` runs the portable PE byte-fixture conformance entrypoint under
`scripts/verify/managed/`, with its byte builder under
@@ -1695,6 +1703,15 @@ See [Packet capture analysis](pcap-analysis.md) for contracts and budgets.
## Apple native metadata and UI
`tests/boundary/mcp/nativeDispatchBoundTarget.test.ts` uses the production
stdio MCP server on macOS to bind an inert, self-authored Mach-O, then replace
its path with missing, directory and symlink-loop states. Each refusal must
report `artifact_changed`; restoring the same bytes must succeed in the same
session. A separate non-root case checks real read permission denial as
`access_denied`. These tests read the target without executing it. Controlled
provider tests retain the physical acquisition failure through descriptor close
denial and confirm cleanup retry releases the original owner.
`npm run verify:apple-dispatch` compiles the Objective-C fixture (classes,
protocols, a property and an `NSString` category) and the Swift
conformance/vtable fixture.
+8 -2
View File
@@ -201,8 +201,10 @@ providers need not expose identical state machines.
[`finishAnalysisCommand`](https://github.com/morluto/rea/blob/main/src/cli/finishAnalysisCommand.ts) where its contract
applies rather than duplicate close/finally handling.
- Give descriptor-backed reads an owner before opening the file. A rejected
close must retain the exact handle and preserve any earlier failure. If an
abortable caller stops waiting, keep the underlying work in that owner's
close must retain the exact handle and preserve any earlier failure.
Stable file reads expose completed values separately from cancellation so
consumers retain acquired identity facts after successful close or cleanup retry.
If an abortable caller stops waiting, keep the underlying work in that owner's
lifetime so shutdown cannot report release before finalization settles.
Admit the entire inspection once, including later reads and finalization.
Helpers must borrow that admission or use an operation child scope; re-entering
@@ -254,6 +256,10 @@ File-error wrappers retain the format owner's specific diagnostic and recovery
text. Strict JSON file input, for example, keeps the byte-order-mark explanation
and UTF-8 recovery through CLI/MCP projection rather than replacing it with
generic invalid-file advice. Consumers do not classify parser message text.
When an artifact boundary falls back to an I/O error, retain the original cause
on the typed error so provider callers can inspect its filesystem evidence.
Keep public error fields under the canonical projector's ownership; retaining
a cause does not require exposing it in transport output.
Update canonical contracts, output schemas, examples, generated artifacts, and
relevant docs together. Verify converted advertised schemas and success,
+1
View File
@@ -119,3 +119,4 @@ Then rerun this installer.",implemented,verified by shell branch inspection
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped; the Ghidra session remains usable.",Low risk; names the setting and keeps the session,Keep current copy,implemented,provider projection test and real-provider lane
91,src/application/binaryDiagnostics/ByteEntropyService.ts,Invalid file or window,"Expected a regular file.; Selected file could not be found.; Selected byte window starts beyond the end of the file.; Selected byte window extends beyond the end of the file.; File size exceeds exact numeric offset representation.",Invalid selection cannot produce complete entropy Evidence,Choose a readable regular file and a window within its byte size,implemented,entropy service and MCP boundary tests
92,src/domain/analysisView/byteEntropyView.ts,Non-summary entropy view,Byte entropy Evidence supports the summary view; retrieve parent Evidence for the complete block series.,Unsupported view selection,Use the summary view or retrieve complete parent Evidence,implemented,typed input rejection
93,src/windows/WindowsNativeLoader.ts:load refused,Windows refuses to load the digest-verified native addon (for example Smart App Control or App Control policy),"REA Windows native controls are unavailable: Windows refused to load the verified native/windows/build/rea-windows-x64.node: {OS error} The file matched the package manifest, so restoring or reinstalling it will not help. ...",Previous artifact copy told users to restore or rebuild files that were already intact,"Name the OS refusal, say restoring cannot help, state that Ghidra and JADX need native controls while setup can still register clients, and point to a policy-allowed host or a maintainer-signed build",implemented,simulated loader refusal unit test; real Smart App Control block not reproduced
1 harm_rank location trigger current_copy user_risk proposed_replacement implementation_status verification_result
119
120
121
122
@@ -0,0 +1,86 @@
# JavaScript source-selection JSON verification — 2026-10-24
## Scope and integration
PR #5029 adds `--source-paths-json` to the two static application CLI routes.
One inline JSON array or selected local JSON file appends exact inventoried
paths after repeated `--source-path` values. The application `source_paths`
contract, ordering, duplicate requests, artifact identity and MCP contract are
preserved. This does not add folders, globs or implicit dependencies; #5048 is
separate.
The CLI adapter reuses the existing JSON file reader and domain array schema.
Entries remain artifact-relative. It preserves cancellation and shared file
ownership rather than introducing another reader/parser. The native lane
refuses source-selection options before starting an engine. Main integration
retains existing delivery validation, the caller's summary/view settings,
environment selection and process-owned JavaScript worker cleanup.
The initial main `7f70797b` integration passes **44** selected CLI, filesystem
JSON-input and actual SDK source-selection checks, plus full `npm run check`.
The JSON-file acceptance case now analyzes an ASAR and verifies the original
package's path and SHA-256; the inline case covers an extracted directory.
Both combine repeated and JSON entries and preserve their requested order.
Invalid shapes, empty entries, native-lane refusal and cancellation are covered.
This is static source analysis; the selected application is not executed.
## Completed-task comparison
Compare main `7f70797b98c226009163358f91a9cfcedf03b390` with feature integration
`e886393ae698d4ed411179430c8dbb10ce6c5647`. Three alternating pairs analyze the
same ASAR, selecting `package.json` followed by 210 named JavaScript files.
The baseline repeats 211 path options; the candidate uses one repeated option
plus an existing JSON file with the other 210 paths. All **six** tasks finish.
Every complete Evidence object is exactly equal across all runs, including its
ID, selected paths, provenance, coverage, uncertainty and limitations. Each
result reports 210 parsed JavaScript files, no unselected files, and retains all
210 expected named functions. The ASAR remains 75,620 bytes with SHA-256
`f1089a49e40f4477e0850787654411c0ec208934262837a19e65c2aa4dc5f035`.
| Completed CLI task cost | Repeated options | Existing JSON file |
| ----------------------------------------- | ---------------: | -----------------: |
| Displayed invocation characters | 17,977 | 201 |
| Estimated complete Evidence stdout tokens | 1,861,409 | 1,861,409 |
| Estimated invocation tokens | 5,078 | 62 |
| Estimated stdout + invocation tokens | 1,866,487 | 1,861,471 |
| Elapsed median s | 19.149 | 18.389 |
| Waited-child CPU median s | 18.250 | 17.340 |
| Sampled family RSS median MiB | 601.4 | 594.1 |
This solves command-line capacity without reducing the complete analysis
result. Microsoft documents an
[8,191-character Cmd.exe command-line limit](https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation).
The ASCII fixture's displayed invocation crosses that limit before the change
and stays below it with a file. This is an inference from the documented limit;
the completed tasks ran on Linux, not through Windows Cmd.exe or `npx`.
The caller-provided selection file already exists in this timed task; its
reading/validation is included. If an agent must create it first, the file
contents add an estimated **4,411 tokens**, plus an extra write call's framing
and execution costs, which are unmeasured. Do not treat the shorter invocation
as a whole-workflow token saving in that scenario. File creation, discovery,
prompts, rendering and client JSON parsing are outside the measured scope.
No speedup is claimed: elapsed ranges are 19.142–19.841 s before and
18.277–21.384 s after; CPU ranges are 18.140–18.821 s and 17.278–21.026 s.
The ranges overlap and include a slower candidate run. Tokenx 1.3.0 estimates
stdout and invocation text; these are not billed/model token counts.
Each task starts a fresh Node 24.22.1 CLI and JavaScript worker with CPU affinity
0,1. OS caches were not flushed and the host is shared. These are fresh-process
runs with uncontrolled warm filesystem caches; cold-disk and warm persistent
worker costs are unmeasured. CPU is waited-child user plus system time. RSS is
a 50 ms recursive process-family sample, which can double-count shared pages
and miss short-lived or unobserved descendants. Token estimation and equality
checks run outside timing. Later main changes are outside these pinned costs.
## Final integration checks
Integration with main `cd10ee1f8a8e124be4864e77750a16e76c561809` passes **58**
checks, extending the initial selection with active JSON-read cancellation,
process-level error status and existing route/delivery coverage. This takes
167.935 s, 182.796 s waited-child CPU and 2,056.1 MiB sampled family RSS; these
are suite costs rather than analyst-task costs. Full `npm run check`,
`npm run docs:check` and test discovery pass. These selections overlap the
earlier checks. Later upstream integrations remain outside the cost comparison.
+255
View File
@@ -0,0 +1,255 @@
# PE import inventory verification — 2026-10-24
The analyst task is to list a local PE's standard and delay imports, keep source
locations and partial facts, and obtain a pefile-compatible imphash only when
the retained standard import sequence supports it. CLI and MCP use the same
application service and static parser. Targets are read without execution.
## Revisions and ownership
- Original PR: `0b65cbcfe2bf78a808d41e12fa5aee9c0fc93da4`.
- Main integration and mapping index: `336f83cbb08dcc02a2d4c932f9c82f2c7b83554e`.
- Measured baseline: `073e2612e783dc38d3603cd7dcc9dc721c0d9412`, the preceding
integration with provider identity imports and artifact-failure classification
repaired so the runtime builds. The measured candidate uses the same provider.
- Measured candidate: `105f4ea66ebc82e559fe1000aabe75ced3024a41`.
`readPeImage` owns machine, image base, directories, alignments and declared raw
section pointers. The import parser consumes those facts rather than reading the
PE header again or substituting a missing base with zero. The shared mapping
index preserves canonical ambiguous/unmapped failures and adjacent mappings.
Its construction is O(n log n), with O(log n) nonempty range queries; both normal
and failed lookups avoid a linear fallback. Indexed and canonical CString reads
share one implementation and can reconstruct strings across physical sections.
These structures belong to one parse, with no cross-request cache or invalidation.
Derived ILT/IAT slots are checked against the 32-bit RVA space before a row is
returned. A malformed table preserves preceding valid rows and reports partial
coverage instead of failing the output schema. Named thunks are checked before
conversion from bigint, retaining the exact source hex when reserved high bits
are set. The format's named hint/name pointer uses 31 bits; see the
[Microsoft PE format specification](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-lookup-table).
Early cancellation preserves the caller's reason before input validation in
PE imports and the two adjacent certificate/ELF-note services. Artifact failure
classification consumes the shared owner's origin facts and regular-file error
predicate; cleanup ownership and retry remain with `ArtifactResourceScope`.
## Independent oracle and regressions
The oracle is pefile **2024.8.26**, installed without dependencies into the
temporary verification directory. Its `pefile.py` SHA-256 is
`0d0eb68f0f169182613dc64b3ab50b20855508c1c1d2faafe8fccf23edb6a345`.
The release's upstream commit is
[`4b3b1e2e568a88d4f1897d694d684f23d9e270c4`](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4).
All **696** copied ordinal names match its three complete maps: 425 oleaut32,
196 ws2_32 and 75 wsock32. Own-key lookup and `ord<decimal>` fallback are retained.
The upstream MIT license is preserved in `licenses/` and the notices inventory.
Independent byte fixtures establish these differences:
- A cross-section function name is `Function128` in native mappings; pefile
reads `Functi`. A cross-section DLL name is `KERNEL32.dll` natively and
`KERNEL32.d` in pefile. Complete native facts remain useful, while hashing them
would produce a different valid-looking MD5.
- pefile stops after 2,048 section headers. With imports in section 2,049, native
parsing retains both functions while pefile finds no import table.
- pefile also stops at a null section header or a section with at least three
of its structural warnings. Native parsing can still see a later import table.
The candidate checks byte-mapping compatibility from the parsed owner facts,
including pointer/address normalization, section clipping and those stop rules.
Unproven geometry withholds only the hash, with an explicit reason. Delay-only
issues do not invalidate the standard table's hash. This is conservative
compatibility checking, not universal qualification of every unusual PE layout.
PE32/PE32+ IAT overflow and exact wide-thunk regressions fail on the old producer.
The three section-stop cases fail on the precursor before its stop guard was
added; the independent oracle sees no imports in each. All pass after correction.
Each of the three early-cancellation services fails its null-reason control
before the fix; afterward both null and caller-object identity are preserved.
The existing retained-text failure already propagates through its tagged error
and is not claimed as a newly fixed defect.
Executed checks at the candidate stages:
- **58** parser/mapping/image/cancellation checks pass. Randomized mapping
comparisons use the canonical mapper as an independent semantic oracle.
- **172** selected domain/service/filesystem/CLI/MCP/artifact-lifecycle checks
pass before the final section-stop guard; sets overlap and are not added.
- The final parser file passes after the stress fixture's header/section ranges
are made disjoint. **5** public CLI cases then pass, including both IAT widths,
a complete cross-section name, section-cap and null-section hash refusals.
- Full static gates pass after provider integration and after the compatibility
guard. The initial missing artifact-failure API compile error was corrected.
The public CLI checks parse actual responses with the named output contract.
MCP tests exercise the real SDK and advertised schema. The artifact-consumer
suite covers physical read errors, cancellation, cleanup failure and retry with
the PE provider included. No deep native engine or Windows runtime was newly
qualified by these static-format checks.
## Shipped PE artifacts
The two launchers come from the official
[pip 24.0 wheel](https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl),
SHA-256 `ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc`.
They were extracted as analysis artifacts, without installation or execution.
| Artifact | Bytes | SHA-256 | Oracle result |
| -------- | ------: | ------------------------------------------------------------------ | --------------------------------------------------------- |
| t32.exe | 97,792 | `6b4195e640a85ac32eb6f9628822a622057df1e459df7c17a12f97aeabc9415b` | 2 DLLs / 85 functions; `5e24f42b46c247f13d78f0f21a4a2bf7` |
| t64.exe | 108,032 | `81a618f21cb87db9076134e70388b6e9cb7c2106739011b6a51772d22cae06b7` | 2 DLLs / 86 functions; `c51d659b4b1142d4af3795d09f1d63f7` |
For both launchers the ordered DLLs, names, ordinals, hints, IAT RVAs, machine,
bitness and imphash agree with pefile. The benchmark verifies the complete named
rows, not merely the function count or MD5.
## Completed-task costs
Three alternating pairs per fixture execute one fresh public CLI process:
`rea inspect-pe-imports PATH --json`. All **24** tasks complete. Complete Evidence
objects are equal across all six runs of each fixture, including provenance,
coverage, limitations, locations and identity; nothing is stripped for equality.
Artifact digests remain unchanged. The stress fixture contains 2,048 independent
sections and 2,048 functions, SHA-256
`0540676161eb7408805277a75e4d47e3d752732b6d5f294c59c8c670c7c1e905`.
The empty fixture is 3,072 bytes, SHA-256
`b32058d9121313554af54bc0aed762871d3d8eb5e2d9eeab6a365f0518c6f897`.
| Complete CLI task | Estimated stdout + invocation tokens, before → after | Elapsed median s | CPU median s | Sampled family RSS median MiB |
| -------------------------- | ---------------------------------------------------: | ---------------: | ------------: | ----------------------------: |
| No imports | 676 → 676 | 1.817 → 1.867 | 2.157 → 2.219 | 364.6 → 369.9 |
| Shipped PE32 | 3,678 → 3,678 | 1.868 → 1.867 | 2.201 → 2.221 | 365.2 → 366.4 |
| Shipped PE32+ | 3,715 → 3,715 | 1.868 → 1.868 | 2.238 → 2.220 | 367.1 → 370.3 |
| 2,048 sections / functions | 70,342 → 70,342 | 1.969 → 2.069 | 2.413 → 2.482 | 388.4 → 391.2 |
Tokens use tokenx 1.3.0 estimates after timing, not actual model usage. The task
uses known CLI commands without catalog/schema/help or continuation calls.
Prompts, model framing, rendering and client JSON parsing are unmeasured.
Node is 24.22.1 on Linux 6.1.0-54 x86-64, affinity CPUs 0–1, on a shared host.
Every process is fresh; OS caches were not flushed and had prior oracle reads.
There is no warm provider-cache measurement. Build/setup costs are separate.
Elapsed ranges overlap, and the 50 ms profiler poll quantizes short-task timing;
no speedup is claimed. The largest case's median increases about 0.10 s elapsed,
0.07 s CPU and 2.9 MiB RSS for the additional correctness checks. RSS is the
50 ms observed sum across descendants: shared pages may be counted twice and
short/unobserved processes missed. CPU accounts for waited children. No resource
stop occurred. The mapping index was already in the baseline, so these numbers
do not isolate its speedup or qualify a 65,535-section worst case.
## Main integration verification
Integration `a5a1f919e896488d0651e621f334efec84e9132f` includes main
`044c9705eadf62f7c47ddd42a0b2dc065b26b228`. The PE refinement is a type
intersection with main's new container union; runtime PE interpretation is
unchanged. The newly added Roman Urdu README receives the same PE imports link
as the canonical table. The full static gate passes; documentation/license
validation and test discovery pass after this link correction. The complete
selected parser/mapping/image/service/filesystem/CLI/MCP/lifecycle selection
passes **201** checks in 54.062 s (60.363 s waited-child CPU). Sets above overlap.
Later main-integration performance was not remeasured; the cost table remains
pinned to its recorded revisions. These tests use built-in static parsing, not
a simulated claim of deep-engine behavior.
## Additional RVA review and independent ordinal control
The pre-merge review found a real missing boundary: PE32+ virtual mappings could
carry descriptors, their terminator, or a DLL name beyond the 32-bit RVA end.
Five new controls fail before correction (four standard/delay directory cases
and one NUL beyond the RVA end). The producer now owns one RVA-span predicate
used by primitive reads and partial row admission. CString reads include only
bytes within that space. A name whose NUL is the final legal byte still passes.
An unexamined first descriptor retains the declared directory with zero consumed
locations and partial coverage. The schema represents that observation; the
service rejects an empty consumed-span list claimed as complete. Four public
CLI cases and the real MCP SDK/advertised AJV schema exercise both zero-span and
preceding-row outcomes. The expanded selected workflow passes **213** checks
in 71.408 s (71.595 s waited-child CPU). The full static gate passes. The earlier
cost table remains pinned to its revisions; these later RVA checks were not
included in that first measurement; the separate comparison below covers them.
The ordinal-mask suggestion was tested rather than applied: installed pefile
2024.8.26 itself uses `addr_of_data & 0x7FFFFFFF` in its ordinal corruption
heuristic, including PE32+. Independent images with both ILT/IAT words rewritten
produce these results:
| Raw PE32+ thunk | pefile result | Native result |
| ------------------ | ----------------------------------------------------------- | -------------------------------------- |
| `8000000100000001` | ordinal 1 + ExitProcess; `72d9cd67504bb3b9f29c37a226bf4331` | identical, complete raw thunk retained |
| `8000000000010001` | import table discarded; empty hash | hash withheld |
| `8000000000000001` | ordinal 1 + ExitProcess; `72d9cd67504bb3b9f29c37a226bf4331` | identical |
All three controls already pass before the RVA correction. Expanding the mask
to 63 payload bits would diverge from this pinned pefile behavior. The reason
text now specifies the low 31-bit payload; the raw thunk remains available, and
matching pefile does not claim loader validity for reserved bits.
## RVA-guard completed-task follow-up
To measure the final guard separately from other integration changes, repeat
three alternating pairs of the same four completed CLI tasks, now comparing
`a7406eca8137c9fae58a12cb8354f042047f5045` against
`feda57ae4ef420bc72e8e04393b75810da86cc3c` on their common main integration.
All **24** tasks complete. All complete Evidence objects match across each
fixture's six runs, and every ordered inventory field still matches the
independent pefile oracle. Artifact digests stay unchanged; stderr is empty.
The token estimator, invocation/discovery scope, process/cache conditions and
profiler limitations are the same as the earlier measurement.
| Complete CLI task | Estimated stdout + invocation tokens, before → after | Elapsed median s | CPU median s | Sampled family RSS median MiB |
| -------------------------- | ---------------------------------------------------: | ---------------: | ------------: | ----------------------------: |
| No imports | 676 → 676 | 2.220 → 2.019 | 2.383 → 2.382 | 367.6 → 364.0 |
| Shipped PE32 | 3,678 → 3,678 | 1.918 → 1.918 | 2.253 → 2.203 | 366.8 → 367.9 |
| Shipped PE32+ | 3,715 → 3,715 | 1.973 → 1.968 | 2.363 → 2.293 | 368.9 → 369.3 |
| 2,048 sections / functions | 70,342 → 70,342 | 2.825 → 2.221 | 2.423 → 2.428 | 382.9 → 389.0 |
No speedup is claimed. In the largest case, elapsed ranges are 2.019–3.085 s
before and 2.019–2.776 s after. CPU medians differ by about 0.004 s; sampled
RSS increases about 6.1 MiB (1.6%). Shared-host variance dominates the apparent
elapsed improvement. This validates the measured completed tasks, not a maximum
format-size or transport-buffer qualification. Later main changes remain
outside these pinned measurements.
## Coverage consistency
Three new service controls reject contradictory standard-import, delay-import,
and complete descriptor counts; all three fail before the correction. The
service now compares counts with the retained rows before issuing Evidence.
Partial results may still report more examined descriptors than retained DLLs,
as verified with an unsupported delay descriptor. Complete results must agree
and carry no issues. Existing function-count traversals are reused; the change
adds no further row scan. These validation checks postdate the pinned cost
measurements above.
The expanded selection passes **217** checks in 73.179 s (70.360 s
waited-child CPU), with a sampled process-family peak of 2,039.3 MiB. Full
`npm run check` passes. These counts overlap earlier selections. A subsequent
main integration at `7f70797b98c226009163358f91a9cfcedf03b390` includes the
upstream plist-fixture correction for the three unrelated native-provider
failures seen in CI run 40055148308.
## Descriptor mapping stops
A later review identified the same partial-result boundary when a descriptor's
uint32 RVA is legal but lacks a file mapping. Four controls (standard/delay,
first descriptor/after one row) all threw before correction. Both walks now
use one descriptor admission function: range failures and mapping failures
append explicit issues, then preserve the directory declaration, consumed
spans and preceding rows. Only mapping failures are caught; unrelated failures
continue to propagate. Standard-table failures withhold imphash; delay-table
failures preserve an independently complete standard hash.
The public CLI covers all four mapping-stop cases alongside the RVA cases.
The actual MCP SDK and advertised output schema cover an unmapped first
descriptor. These later validation changes are outside the pinned performance
comparisons above; no additional speed or maximum-size claim is made.
Final integration at main `7f70797b` passes **277** selected checks: the
expanded PE/shared lifecycle selection plus native-provider tests that cover
the upstream plist-fixture correction. This takes 126.978 s, 127.567 s
waited-child CPU and 2,061.9 MiB sampled process-family RSS. These are test
suite costs, not analyst-task costs. Full `npm run check`, `npm run docs:check`
and test discovery pass; discovery accounts for all 2,323 files exactly once.
+5 -2
View File
@@ -80,8 +80,11 @@ Windows x64 package; changing MCP registration cannot supply that artifact.
A filesystem or access failure identifies the failed native constraint.
An incompatible host uses `unsupported_host`. Missing, mismatched or unloadable
native artifacts use `runtime_missing` and doctor classification
`missing_dependency`. A failed native invocation or invalid inspection uses
`runtime_unavailable` and doctor classification `runtime_failure`; it does not
`missing_dependency`. A verified artifact that Windows refuses to load, for
example under Smart App Control or an App Control policy, reports failure stage
`load`; restoring or reinstalling the file cannot fix it. A failed native
invocation or invalid inspection uses `runtime_unavailable` and doctor
classification `runtime_failure`; it does not
mean Windows x64 itself is unsupported. Provider diagnostics retain the failure
stage under `windows_native_failure`, together with the corresponding remediation.
Self-test failures preserve the native operation and OS error text. Check the
@@ -0,0 +1,94 @@
"""Verify 18 completed entropy tasks and report whole-task resource/token costs."""
import argparse
from pathlib import Path
import hashlib, json, statistics
import tiktoken
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--receipts', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
options = parser.parse_args()
b = options.receipts
cohort = b / 'cohort'
index = json.loads((cohort / 'index.json').read_text())
assert len(index) == 18 and all((r['exit'] == 0 for r in index))
assert len({item['name'] for item in index}) == 18
enc = tiktoken.get_encoding('cl100k_base')
compact = lambda d: json.dumps(d, ensure_ascii=False, separators=(',', ':'), allow_nan=False)
count = lambda s: len(enc.encode(s, disallowed_special=()))
digest = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
rows = []
for item in index:
p = cohort / item['name']
r = json.loads((p / 'report.json').read_text())
catalog = json.loads((p / 'catalog.json').read_text())
tx = json.loads((p / 'transcript.json').read_text())
scope = next((json.loads(l) for l in reversed((cohort / (item['name'] + '.log')).read_text().splitlines()) if l.startswith('{"resource_scope":')))
obs = scope['payload_resource_observation']
events = obs['resource_events']
assert r['oracle_sha256'] == digest(b / f"{item['case']}-oracle.json")
assert item['exit'] == 0 and r['status'] == 'passed' and r['complete_source_export_verified'] and (not r['cleanup_failures']) and (not r['receipt_failures'])
assert scope['abort'] is None and scope['payload_resource_observation_verified'] and (obs['payload_exit_code'] == 0) and obs['payload_cleanup_verified']
assert all((events['memory.events'][k] == 0 for k in ['high', 'max', 'oom', 'oom_kill'])) and events['pids.events']['max'] == 0
assert [e['name'] for e in tx] == ['inspect_byte_entropy', 'export_evidence_bundle']
assert all((not e['reply'].get('isError', False) for e in tx))
inp = count(compact([{k: t[k] for k in ['name', 'description', 'inputSchema']} for t in catalog['tools']]))
args = sum((count(compact({'name': e['name'], 'arguments': e['input']})) for e in tx))
replies = sum((count(c['text']) for e in tx for c in e['reply']['content'] if c['type'] == 'text'))
prompt = count(r['question'])
instructions = count(r['server_instructions'])
rows.append({
**item,
'catalog_tools': len(catalog['tools']),
'catalog_sha256': digest(p / 'catalog.json'),
'parent_evidence_id': r['parent_evidence_id'],
'source_report_sha256': r['source_report_sha256'],
'runtime_identity_sha256': r['runtime_identity_sha256'],
'wall_seconds': scope['elapsed_seconds'],
'cpu_seconds': obs['cpu_stat']['usage_usec'] / 1000000.0,
'kernel_family_peak_bytes': obs['memory_peak_bytes'],
'inspection_ms': r['calls'][0]['elapsed_ms'],
'discovery_ms': r['phases']['discovery_ms'],
'token_estimate': {
'question': prompt,
'instructions': instructions,
'directory_once': inp,
'arguments': args,
'text_replies': replies,
'whole_task': prompt + instructions + inp + args + replies,
'raw_catalog': count(compact(catalog)),
},
'receipt_sha256': {n: digest(p / n) for n in ['report.json', 'transcript.json', 'catalog.json', 'source.evidence.json']},
'payload_exit_events': events,
'payload_cleanup_verified': True,
'whole_scope_events_final': scope['resource_events_final'],
})
summary = {}
for case in ['llvm-small-block', 'llvm-large-block', 'ls']:
rs = [r for r in rows if r['case'] == case]
for k in ['catalog_sha256', 'source_report_sha256', 'parent_evidence_id']:
assert len({r[k] for r in rs}) == 1, (case, k)
summary[case] = {}
for lane in ['before', 'after']:
ls = [r for r in rs if r['lane'] == lane]
assert len(ls) == 3
assert {r['repeat'] for r in ls} == {1, 2, 3}
metrics = {k: [r[k] for r in ls] for k in ['wall_seconds', 'cpu_seconds', 'kernel_family_peak_bytes', 'inspection_ms', 'discovery_ms']}
metrics['whole_task_tokens'] = [r['token_estimate']['whole_task'] for r in ls]
summary[case][lane] = {k: {'median': statistics.median(v), 'min': min(v), 'max': max(v)} for k, v in metrics.items()}
identities = {lane: json.loads((b / f'{lane}-runtime-identity.json').read_text()) for lane in ['before', 'after']}
assert all(identity['source_clean'] for identity in identities.values())
for row in rows:
assert row['runtime_identity_sha256'] == digest(b / f"{row['lane']}-runtime-identity.json")
result = {
'tiktoken': tiktoken.__version__,
'encoding': enc.name,
'actual_model_usage': None,
'revisions': {l: i['source_revision'] for l, i in identities.items()},
'identity_receipts': {l: digest(b / f'{l}-runtime-identity.json') for l in identities},
'oracles': {c: {'sha256': digest(b / f'{c}-oracle.json'), 'artifact': json.loads((b / f'{c}-oracle.json').read_text())['expected']['artifact']} for c in summary},
'summary': summary,
'recordings': rows,
'measurement': 'Three alternating fresh production stdio SDK sessions per lane/case; exact discovered toolDefinition/default validator, fresh HOME, Node compile cache disabled, OS caches unflushed. Each task discovers complete catalog, inspects bytes and exports/compares full source Evidence; all oracle facts checked. Whole process-family CPU and kernel peak include client/server, runtime fingerprint verification, serialization, export and cleanup. Oracle generation and tokenization outside measured tasks. One-CPU quota, 3GiB early stop/4GiB hard/no swap; payload exit/cleanup/events verified, final whole-scope counters unavailable. Token serialization estimate includes question/instructions/all input name+description+schema once/all arguments/all text replies, raw catalog separately. Host framing, replay, model inference quality and billing unmeasured.',
}
options.output.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(summary, indent=2))
@@ -0,0 +1,56 @@
"""Independent Python histogram oracle; reads artifact bytes without execution."""
import argparse
from collections import Counter
import hashlib
import json
import math
from pathlib import Path
def entropy(counts, size):
if not size:
return 0
value = -sum((counts[k] / size) * math.log2(counts[k] / size) for k in sorted(counts) if counts[k])
return math.floor(value * 10000 + 0.5) / 10000
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--path', required=True)
parser.add_argument('--output', required=True)
parser.add_argument('--block-size', type=int, required=True)
parser.add_argument('--offset', type=int, default=0)
parser.add_argument('--length', type=int)
args = parser.parse_args()
path = Path(args.path)
before = path.stat()
counts = Counter()
digest = hashlib.sha256()
with path.open('rb') as source:
while chunk := source.read(1024 * 1024):
digest.update(chunk)
counts.update(chunk)
length = args.length if args.length is not None else before.st_size - args.offset
assert 0 <= args.offset <= before.st_size and 0 <= length <= before.st_size - args.offset
blocks, runs = [], []
source.seek(args.offset)
for offset in range(args.offset, args.offset + length, args.block_size):
chunk = source.read(min(args.block_size, args.offset + length - offset))
value = entropy(Counter(chunk), len(chunk))
block = {'offset': offset, 'length': len(chunk), 'entropy': value}
blocks.append(block)
classification = 'high' if value >= 7.2 else 'low' if value <= 1 else None
if classification is not None:
if runs and runs[-1]['classification'] == classification and runs[-1]['offset'] + runs[-1]['length'] == offset:
runs[-1]['length'] += len(chunk)
else:
runs.append({'offset': offset, 'length': len(chunk), 'classification': classification})
after = path.stat()
assert (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, before.st_ctime_ns) == (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns)
expected = {'artifact': {'path': args.path, 'bytes': before.st_size, 'sha256': digest.hexdigest()}, 'block_size':args.block_size,'window':{'offset':args.offset,'length':length},'file_entropy':entropy(counts,before.st_size),'thresholds':{'high':7.2,'low':1},'blocks':blocks,'runs':runs}
request = {'path':args.path,'block_size':args.block_size,'offset':args.offset}
if args.length is not None:
request['length'] = args.length
Path(args.output).write_text(json.dumps({'input':request,'expected':expected},indent=2)+'\n')
print(json.dumps({'path':args.path,'sha256':digest.hexdigest(),'blocks':len(blocks),'runs':len(runs)}))
if __name__ == '__main__':
main()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,158 @@
# Byte entropy: separate physical reads from report resolution
Issue: [#6947](https://github.com/morluto/rea/issues/6947).
The question is to return the full-file SHA-256 and entropy, every block in a
selected byte window, and adjacent high/low runs, preserving complete source
Evidence. `ByteEntropyService` previously used `block_size` for both reporting
and physical reads, including outside the selected window. Small report blocks
therefore caused hundreds of thousands of asynchronous reads of a large file.
The shared service now fills a bounded 64 KiB buffer before calculating the
caller-selected blocks. Supported power-of-two block sizes divide that buffer;
the window starts its own block sequence, and only its final block can be short.
Short physical reads are joined before interpretation. The same stable-file,
whole-file hash, cancellation, capacity and descriptor-cleanup owners remain.
No new cache, parser, contract or output selection is introduced.
## Fixed comparison
Baseline: `1d896e03b776453282beca7fd89338be778bfca2`.
Measured implementation: `3ed3276d0ee79c291e1006901ace5b06626c1d4e`.
Both were clean and built with Node 24.22.1, npm 11.16.0 and SDK 2.3.1.
The runtime receipt fingerprints 2,582 compiled/catalog/entrypoint/dependency
and driver files in each lane, checked before and after every task.
Real artifacts are installed Linux x64 binaries, inspected without execution:
| Artifact | Bytes | SHA-256 |
| ----------------------------------------------------- | ----------: | ------------------------------------------------------------------ |
| LLVM 16 (`/usr/lib/x86_64-linux-gnu/libLLVM-16.so.1`) | 123,379,936 | `f62d254b7f2bf42df8c8b07d46ee3bb4c2cafeca436b2e6bc6ccbe4581f58f40` |
| GNU ls (`/usr/bin/ls`) | 151,344 | `cb30d69b24245bf2ecdc9e7f53bbad19159999970b6d82c0c00c7d32d9e37aa4` |
`byte-entropy-oracle.py` independently computes Python byte histograms,
Shannon entropy, selected blocks, rounded-value run classifications and digests.
It checks file state around its reads. Every returned fact agrees with this
oracle, and both lanes have identical complete report hashes and Evidence IDs.
There are three alternating fresh production stdio MCP sessions per lane/case,
18 tasks total. Each discovers all 222 tools, uses exact discovered
`toolDefinition` validation, inspects the bytes, exports the complete Evidence
bundle and verifies its parent record. Fresh HOME/client/server, Node compile
cache disabled, OS caches unflushed. These are fresh sessions; no warm-runtime
speedup is claimed. Cached-catalog SDK bulk validation was not measured here.
| Task (3 samples per lane) | Whole elapsed median, s | Family CPU median, s | Kernel family peak median, MiB | Whole-task estimated tokens |
| ----------------------------------------------------------- | ----------------------: | -------------------: | -----------------------------: | --------------------------: |
| LLVM: 256-byte blocks, offset 3, length 65,539 (257 blocks) | 29.842 → 8.584 | 25.286 → 6.358 | 629.5 → 672.4 | 62,000 → 62,000 |
| LLVM: whole file, 65,536-byte blocks (1,883 blocks) | 12.314 → 12.115 | 7.581 → 7.325 | 667.9 → 674.3 | 91,900 → 91,900 |
| ls: whole file, 256-byte blocks (592 blocks) | 7.075 → 8.286 | 5.804 → 5.528 | 659.1 → 659.3 | 67,215 → 67,215 |
The large fine-resolution task improves elapsed median by 71.2% and CPU by
74.9%. Elapsed ranges are 28.541–33.290 → 7.981–10.396 seconds; CPU ranges
23.600–25.429 → 6.049–6.633 seconds. Its kernel peak increases by about
43 MiB (ranges 620.4–637.5 → 665.7–673.2 MiB). This is a speed/CPU improvement
with higher measured peak memory, not a memory optimization.
The other controls do not establish a stable whole-task speedup. The small
file's elapsed median is worse; ranges overlap at 6.469–9.892 → 6.169–9.188
seconds. No general latency or memory benefit is claimed. Full-file reading
and hashing remain necessary even when the selected window is short or empty.
A separate single real-file service probe delegated to actual `open/stat/read`
while counting reads: 481,955 → 1,885 physical reads of all 123,379,936 bytes,
maximum request 256 → 65,536 bytes, service elapsed 17.09 → 1.34 seconds.
The complete Evidence objects were equal and independently checked. This probe
locates the work removed; it is not the repeated public-task performance table.
The [machine-readable report](./byte-entropy-reads.json) preserves ranges,
per-sample phase timings, calls, receipt hashes, artifact identities and commands.
`cl100k_base` via tiktoken 0.14.0 counts the fixed question, server instructions,
all tool names/descriptions/input schemas once, every argument and every text
reply including export. Input-directory cost is 56,685 tokens; raw catalog with
output schemas is separately 838,375. Actual model billing, host framing/replay,
model reasoning quality and inference latency were not measured. No token saving
is claimed: replies and catalog remain unchanged.
## Resource and correctness verification
All tasks ran serially under a one-CPU quota, 3 GiB early memory stop, 4 GiB hard
limit, zero swap and 192-task ceiling. Whole process-family CPU and kernel memory
peak include client/server, runtime fingerprint reads, serialization, source
export and cleanup; kernel peak includes charged file cache. Independent oracle
generation and tokenization are outside timed tasks. Payload exits, remaining
owned processes and memory/PID-limit events were checked: all task payloads
exited 0, cleanup verified, no guard stop/OOM. Final whole-scope counters were
unavailable after transient-scope removal and are not represented as verified.
Executed additional checks:
- Six real CLI calls (three cases in both lanes): every oracle fact, complete
CLI Evidence equality and matching MCP parent identity passed.
- 36 tests in four exact source/public-boundary files passed, including short
reads, unaligned windows across buffers, partial tails at minimum/intermediate/
maximum block sizes, empty windows, cancellation through finalization,
identity changes, capacity refusal, retained summaries and cleanup retry.
- Six serial static tasks (typecheck, lint including module boundaries, format,
knip, metadata and required compilation), plus architecture guards passed.
Static family peak was 1.96 GiB; focused-test/preparation peak was 2.15 GiB.
- An initial typecheck failure in the new short-read test's injected callback
was fixed with an explicitly typed positional boundary. Its failed receipt is
retained, not counted as a successful run. Initial local formatter path and
early profile verification attempts also failed before their intended checks;
corrected checks are recorded separately.
The public experiments qualify static byte inspection on these Linux files.
They do not establish behavior on every filesystem or operating system and do
not execute LLVM or ls. CLI/MCP contracts, Evidence fields, input limits and
provider authority remain unchanged; generated catalogs and builds are ignored.
After integrating main `21a20306b`, the six serial static tasks, architecture
guards and all 36 focused cases passed again. Three fresh real MCP/source-export
tasks and three CLI calls matched every oracle fact and the frozen cohort's
report/parent identity. The integrated test/preparation family peaked at
2.23 GiB with verified payload exit/cleanup and no guard stop/OOM. These are
integration qualification runs; they do not replace the fixed performance pins.
The committed cost verifier also reproduced the complete measurement JSON.
## Reproduction
Build both pinned checkouts using their locked toolchain, serially. Supply an
independent installed dependency tree matching the lock, without upgrading host
software. Use the exact artifact digests above. The oracle accepts explicit
`--path`, `--block-size`, `--offset`, `--length` and `--output`; omit length for
the two whole-file controls. For example:
```sh
python3 experiments/analysis-efficiency/byte-entropy-oracle.py \
--path /usr/lib/x86_64-linux-gnu/libLLVM-16.so.1 \
--block-size 256 --offset 3 --length 65539 --output /absolute/oracle.json
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/byte-entropy-reads.mjs \
--runtime /absolute/pinned-checkout --oracle /absolute/oracle.json \
--identity /absolute/runtime-identity.json --output /absolute/new-task-directory
```
Runtime identity JSON contains `source_revision`, `source_clean`,
`node_sha256` and `files: [{path, sha256}]` with absolute paths. Fingerprint the
entire compiled runtime and catalogs, scripts, lock/toolchain files, actual SDK
JavaScript dependencies and the driver/helpers. Both sessions must use the same
Node and dependency contents. Use the same one-CPU/memory policy for the harness;
the historical runs additionally pinned the payload to CPUs 0–1 and the initial
observer to CPU 15. Do not compare an unguarded default-concurrency run as an
equivalent resource measurement.
Retained raw receipts are on the investigation host at
`/tmp/rea-perf-byte-entropy-20261024/`. This includes `cohort/index.json`,
per-task catalog/transcript/report/source export/guard logs, runtime identities,
three oracle JSONs, CLI stdout/stderr, boundary profiles and verification logs.
The cost verifier takes that receipt directory and a separate output:
```sh
python3 experiments/analysis-efficiency/byte-entropy-costs.py \
--receipts /absolute/receipt-directory --output /absolute/costs.json
```
It requires all 18 successful verified samples, matching parent/report/catalog
identity across lanes, successful source exports and oracle/runtime receipts.
Failed or stopped attempts cannot enter the published cohort.
@@ -0,0 +1,154 @@
/** Complete entropy question through production stdio, with full source export. */
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { finished } from "node:stream/promises";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
const { values } = parseArgs({
options: Object.fromEntries(
["runtime", "oracle", "identity", "output"].map((name) => [
name,
{ type: "string" },
]),
),
});
for (const value of Object.values(values)) assert.equal(value, resolve(value));
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
const oracleBytes = await readFile(values.oracle);
const oracle = JSON.parse(oracleBytes);
const identityBytes = await readFile(values.identity);
const identity = JSON.parse(identityBytes);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-entropy-task-"));
const client = new Client({ name: "entropy-complete-task", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
maxBufferSize: 10485760,
env: {
HOME: account,
TMPDIR: account,
PATH: process.env.PATH,
REA_LOG_LEVEL: "silent",
NODE_OPTIONS: "--max-old-space-size=2048",
NODE_DISABLE_COMPILE_CACHE: "1",
UV_THREADPOOL_SIZE: "2",
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log"));
const serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const report = {
status: "running",
question:
"Return the file SHA-256, file-wide entropy, every entropy block and adjacent high/low run in the requested byte window; preserve the complete source Evidence.",
request: oracle.input,
calls: [],
phases: {},
client:
"Production stdio SDK, exact discovered toolDefinition and default validator",
runtime_identity_sha256: sha256(identityBytes),
oracle_sha256: sha256(oracleBytes),
driver_sha256: sha256(await readFile(new URL(import.meta.url))),
};
const transcript = [];
let definitions;
const call = createRetainedQueryCaller(
{
callTool(params, options) {
return client.callTool(params, {
...options,
toolDefinition: definitions.get(params.name),
});
},
},
(receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
},
);
const observe = async (name, args, phase) => {
const result = await call(name, args, phase);
assert.notEqual(result.isError, true, JSON.stringify(result));
assert.ok(result.structuredContent);
return result.structuredContent;
};
const verifyRuntime = async () => {
assert.equal(sha256(await readFile(values.oracle)), sha256(oracleBytes));
assert.equal(sha256(await readFile(values.identity)), sha256(identityBytes));
assert.equal(sha256(await readFile(process.execPath)), identity.node_sha256);
assert.equal(
sha256(await readFile(new URL(import.meta.url))),
report.driver_sha256,
);
for (const item of identity.files)
assert.equal(sha256(await readFile(item.path)), item.sha256, item.path);
};
try {
await verifyRuntime();
const start = performance.now();
await client.connect(transport);
report.server_instructions = client.getInstructions() ?? "";
const catalog = await client.listTools();
definitions = new Map(catalog.tools.map((tool) => [tool.name, tool]));
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
report.phases.discovery_ms = performance.now() - start;
const parent = await observe("inspect_byte_entropy", oracle.input, "inspect");
for (const [key, value] of Object.entries(oracle.expected))
assert.deepEqual(parent.normalized_result[key], value, key);
report.parent_evidence_id = parent.evidence_id;
report.source_report_sha256 = sha256(
JSON.stringify(parent.normalized_result),
);
report.artifact = parent.normalized_result.artifact;
await observe(
"export_evidence_bundle",
{ path: join(values.output, "source.evidence.json") },
"export",
);
const bundle = JSON.parse(
await readFile(join(values.output, "source.evidence.json")),
);
assert.deepEqual(
bundle.records.find((item) => item.evidence_id === parent.evidence_id),
parent,
);
report.complete_source_export_verified = true;
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.error = String(cause);
} finally {
try {
await verifyRuntime();
} catch (cause) {
report.status = "failed";
report.identity_failure = String(cause);
}
await finalizeRetainedQueryReceipt({
client,
transport,
account,
serverLog,
serverLogFinished,
report,
transcript,
output: values.output,
});
if (
report.status !== "passed" ||
report.cleanup_failures.length ||
report.receipt_failures.length
)
process.exitCode = 1;
}
@@ -0,0 +1,111 @@
"""Verify completed BEAM selection/export tasks and count a declared token projection."""
import argparse
import hashlib
import json
from pathlib import Path
import statistics
import tiktoken
def compact(value):
return json.dumps(value, ensure_ascii=False, separators=(',', ':'), allow_nan=False)
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def inspect(path, encoding):
report = json.loads((path / 'report.json').read_text())
catalog = json.loads((path / 'catalog.json').read_text())
transcript = json.loads((path / 'transcript.json').read_text())
scope = next(json.loads(line) for line in reversed(path.with_suffix('.log').read_text().splitlines())
if line.startswith('{"resource_scope":'))
observed = scope.get('payload_resource_observation') or {}
events = observed.get('resource_events', {})
assert report['status'] == 'passed' and report['runtime_identity_sha256']
assert report['complete_source_export_verified'] is True
assert not report['cleanup_failures'] and not report['receipt_failures']
assert scope['abort'] is None and scope['payload_resource_observation_verified'] is True
assert observed['payload_exit_code'] == 0 and observed['payload_cleanup_verified'] is True
assert all(events.get('memory.events', {}).get(key) == 0 for key in ['high', 'max', 'oom', 'oom_kill'])
assert events.get('pids.events', {}).get('max') == 0
assert len(catalog['tools']) == report['catalog_tools'] == 221
assert transcript[0]['name'] == 'inspect_erlang_beam'
assert transcript[-1]['name'] == 'export_evidence_bundle'
assert all(event['name'] == 'inspect_analysis_view' for event in transcript[1:-1])
assert all('reply' in event and not event['reply'].get('isError', False) for event in transcript)
count = lambda text: len(encoding.encode(text, disallowed_special=()))
directory = [{key: tool[key] for key in ['name', 'description', 'inputSchema']} for tool in catalog['tools']]
request = transcript[0]['input']
question = report['question']
assert ('detail' in request) == (report['lane'] == 'selected')
arguments = sum(count(compact({'name': event['name'], 'arguments': event['input']})) for event in transcript)
replies = sum(count(block['text']) for event in transcript for block in event['reply']['content'] if block['type'] == 'text')
inputs, prompt = count(compact(directory)), count(question)
instructions = count(report['server_instructions'])
return dict(case=path.name, lane=report['lane'], artifact=report['artifact'],
answer_count=report['answer_count'], answer_sha256=report['answer_sha256'],
parent_evidence_id=report['parent_evidence_id'], source_report_sha256=report['source_report_sha256'],
runtime_identity_sha256=report['runtime_identity_sha256'], catalog_sha256=digest(path / 'catalog.json'),
calls=report['calls'], phases=report['phases'], wall_seconds=scope['elapsed_seconds'],
cpu_seconds=observed['cpu_stat']['usage_usec'] / 1e6,
kernel_family_peak_bytes=observed['memory_peak_bytes'],
events_at_payload_exit=events, payload_cleanup_verified=True,
final_whole_scope_events=scope.get('resource_events_final'),
token_proxy=dict(question=prompt, server_instructions=instructions, input_catalog_once=inputs, arguments=arguments,
text_replies=replies, combined=prompt + instructions + inputs + arguments + replies,
complete_transport_catalog=count(compact(catalog))),
receipt_sha256={file.name: digest(file) for file in [path / 'report.json', path / 'catalog.json',
path / 'transcript.json', path / 'evidence.json', path.with_suffix('.log')]})
def summarize(rows):
result = {}
for artifact in sorted({row['artifact']['sha256'] for row in rows}):
cases = [row for row in rows if row['artifact']['sha256'] == artifact]
for field in ['answer_count', 'answer_sha256', 'parent_evidence_id', 'source_report_sha256']:
assert len({row[field] for row in cases}) == 1, field
result[artifact] = {}
for lane in ['complete', 'selected']:
samples = [row for row in cases if row['lane'] == lane]
assert len(samples) == 3
for field in ['runtime_identity_sha256', 'catalog_sha256']:
assert len({row[field] for row in samples}) == 1, field
metrics = {key: [row[key] for row in samples] for key in ['wall_seconds', 'cpu_seconds', 'kernel_family_peak_bytes']}
metrics['projected_tokens'] = [row['token_proxy']['combined'] for row in samples]
metrics['mcp_reply_bytes'] = [sum(call['reply_bytes'] for call in row['calls']) for row in samples]
result[artifact][lane] = {key: dict(median=statistics.median(values), minimum=min(values), maximum=max(values))
for key, values in metrics.items()}
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--cohort', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
manifest = json.loads((args.cohort / 'manifest.json').read_text())
index = json.loads((args.cohort / 'index.json').read_text())
assert len(index) == 18 and all(case['exit'] == 0 for case in index)
for file, expected in manifest['driver_files'].items():
assert digest(Path(file)) == expected, file
identities = {}
for lane, path in manifest['runtime_identities'].items():
identity = json.loads(Path(path).read_text())
assert identity['source_clean'] is True
identities[lane] = dict(sha256=digest(Path(path)), source_revision=identity['source_revision'])
encoding = tiktoken.get_encoding('cl100k_base')
rows = [inspect(args.cohort / case['name'], encoding) for case in index]
assert all(row['runtime_identity_sha256'] == identities[row['lane']]['sha256'] for row in rows)
receipt = dict(tiktoken=tiktoken.__version__, encoding=encoding.name, actual_model_usage=None,
cohort=manifest, commands=index, runtime_identities=identities,
projection='Scripted fresh SDK tasks, not model runs. One fixed question, server instructions and complete input directory (name/description/inputSchema), every argument and MCP text reply including export. Raw catalog including output schemas reported separately. No framing, host injection, history replay, inference quality or billable usage measured.',
resources='CPU quota100%, payload affinity0-1, initial observer15. Early stop3GiB, hard4GiB, swap0. Fresh HOME/client/server; Node compile cache disabled, OS caches unflushed. Whole process-family CPU and kernel memory peak include file cache, runtime identity checks, hashing, assertions, SDK/server serialization and cleanup. Oracle generation and tokenization outside timed tasks. Payload-exit counters verified; final whole-scope counters unavailable.',
summary=summarize(rows), recordings=rows)
args.output.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n')
if __name__ == '__main__':
main()
@@ -0,0 +1,108 @@
"""Independent struct oracle for the fixed public OTP 27 benchmark artifacts.
Reads bytes only. It deliberately refuses compact atom layouts; format-wide
coverage belongs to the production reader and its separate regressions.
"""
from pathlib import Path
import getopt
import hashlib
import json
import struct
import sys
def decode(path):
data = path.read_bytes()
assert len(data) <= 64 * 1024 * 1024
u32 = lambda at: struct.unpack_from('>I', data, at)[0]
assert data[:4] == b'FOR1' and data[8:12] == b'BEAM'
assert u32(4) + 8 == len(data)
chunks, selected, at = [], {}, 12
while at < len(data):
name = data[at:at + 4].decode('ascii')
size = u32(at + 4)
end = at + 8 + size
padded = (end + 3) & ~3
assert end <= padded <= len(data)
chunks.append(dict(index=len(chunks), name=name,
location=dict(offset=at, bytes=padded - at),
payload=dict(location=dict(offset=at + 8, bytes=size),
bytes=size, sha256=hashlib.sha256(data[at + 8:end]).hexdigest())))
selected[name] = (at + 8, end)
at = padded
assert at == len(data)
atom_name = 'AtU8' if 'AtU8' in selected else 'Atom'
start, end = selected[atom_name]
n = u32(start)
assert n < 65536, 'Fixed OTP 27 oracle does not decode compact atom tables'
at, atoms = start + 4, []
for i in range(n):
size = data[at]
at += 1
name = data[at:at + size].decode('utf-8' if atom_name == 'AtU8' else 'latin1')
atoms.append(dict(index=i + 1, name=name))
at += size
assert at == end
def atom(i):
return atoms[i - 1]['name'] if 0 < i <= len(atoms) else None
def table(name, keys):
if name not in selected:
return None
start, end = selected[name]
if name == 'FunT' and start == end:
return None
n, width = u32(start), len(keys)
assert start + 4 + n * 4 * width == end
rows = []
for i in range(n):
values = struct.unpack_from('>' + 'I' * width, data, start + 4 + i * width * 4)
row = dict(zip(keys, values))
if 'module_index' in row:
row['module'] = atom(row['module_index'])
row['function'] = atom(row['function_index'])
rows.append(row)
return rows
exports = table('ExpT', ['function_index', 'arity', 'label'])
imports = table('ImpT', ['module_index', 'function_index', 'arity'])
lambdas = table('FunT', ['function_index', 'arity', 'label', 'index', 'free_vars', 'old_unique'])
code = None
if 'Code' in selected:
start, end = selected['Code']
size = u32(start)
assert size >= 16 and start + 4 + size <= end
code = dict(zip(['instruction_set', 'opcode_max', 'labels', 'functions'],
struct.unpack_from('>IIII', data, start + 4)))
code.update(extra_header_bytes=size - 16,
instructions=dict(offset=start + 4 + size, bytes=end - start - 4 - size))
return dict(artifact=dict(path=str(path), sha256=hashlib.sha256(data).hexdigest(), bytes=len(data)),
form=dict(location=dict(offset=0, bytes=len(data)), size=len(data) - 8),
chunks=chunks, atoms=atoms, atom_encoding='utf8' if atom_name == 'AtU8' else 'latin1',
exports=exports, imports=imports, lambdas=lambdas, code=code,
module=atoms[0]['name'],
coverage=dict(status='complete', examined_chunks=len(chunks), atoms=len(atoms)))
def main():
options, remaining = getopt.getopt(sys.argv[1:], '', ['input=', 'output=', 'name='])
assert not remaining
inputs = [v for k, v in options if k == '--input']
outputs = [v for k, v in options if k == '--output']
names = [v for k, v in options if k == '--name']
assert len(inputs) == len(outputs) == 1 and names
path = Path(inputs[0])
assert path.is_absolute()
expected = decode(path)
answer = [dict(source_index=i, **row) for i, row in enumerate(expected['exports'] or [])
if row['function'] in names]
result = dict(oracle='Independent Python struct decoding of fixed OTP27 bytes; no BEAM execution. '
'Every table, chunk range/hash and Code header checked. Other encodings/malformed inputs unqualified.',
source='https://www.erlang.org/doc/apps/stdlib/beam_lib.html',
expected=expected, names=names, answer=answer)
Path(outputs[0]).write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
if __name__ == '__main__':
main()
@@ -0,0 +1,534 @@
{
"method": "3alternating freshsessions perlane; SDK cachesentirecatalog thenusesdefaultvalidator withouttoolDefinition override. Diagnostic client startup control, separate from18exactnamedtasks. Samequestion/fullsourceexports/oracle; allpayloadexit/cleanupverified.",
"summary": {
"716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55": {
"complete": {
"wall_seconds": {
"median": 19.764,
"minimum": 17.257,
"maximum": 21.577
},
"cpu_seconds": {
"median": 16.276557,
"minimum": 15.287268,
"maximum": 16.561897
},
"kernel_family_peak_bytes": {
"median": 1073950720,
"minimum": 1041948672,
"maximum": 1078980608
},
"projected_tokens": {
"median": 76907,
"minimum": 76907,
"maximum": 76907
},
"mcp_reply_bytes": {
"median": 147003,
"minimum": 147003,
"maximum": 147003
}
},
"selected": {
"wall_seconds": {
"median": 19.375,
"minimum": 18.052,
"maximum": 22.397
},
"cpu_seconds": {
"median": 16.867381,
"minimum": 16.139353,
"maximum": 17.069666
},
"kernel_family_peak_bytes": {
"median": 1105092608,
"minimum": 1059520512,
"maximum": 1117036544
},
"projected_tokens": {
"median": 58090,
"minimum": 58090,
"maximum": 58090
},
"mcp_reply_bytes": {
"median": 7469,
"minimum": 7469,
"maximum": 7469
}
}
}
},
"recordings": [
{
"case": "erl_lint-1-complete",
"lane": "complete",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "1b4b40330a2e21d31c06a07d10937cb6a3b37cd0bcbf3d129380341d486b916e",
"catalog_sha256": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam"
},
"phase": "inspect",
"elapsed_ms": 18092.161941,
"reply_bytes": 146627,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-1-complete/evidence.json"
},
"phase": "export",
"elapsed_ms": 52.40052199999991,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1018.838796
},
"wall_seconds": 21.577,
"cpu_seconds": 16.561897,
"kernel_family_peak_bytes": 1073950720,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 55409,
"arguments": 78,
"text_replies": 20875,
"combined": 76907,
"complete_transport_catalog": 829075
},
"receipt_sha256": {
"report.json": "3717e681ac50a70db8bd7303ac18d87bd2028d4321104a793421695a82579290",
"catalog.json": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"transcript.json": "c718ac6e9b6c63533bc28d18c90c73a03abc1697516ae216a444b6dd55827255",
"evidence.json": "ca66367cd1bb325d7d4e47f958c86234d01aaf749b68778ac5212cac246aacb3",
"erl_lint-1-complete.log": "a90d1345cd09e90865c8a75f732abe2561c2299d0084718e91205f2cb904f38d"
}
},
{
"case": "erl_lint-1-selected",
"lane": "selected",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "a74d203a5a12d7eb82aaa1e0fda6595b9647af2255f318bf116953777052460f",
"catalog_sha256": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"detail": "summary",
"view": {
"kind": "beam",
"collection": "exports",
"names": ["module", "exprs", "expr"],
"fields": ["function_index", "function", "arity", "label"],
"offset": 0,
"limit": 64
}
},
"phase": "inspect",
"elapsed_ms": 18394.333454,
"reply_bytes": 7093,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-1-selected/evidence.json"
},
"phase": "export",
"elapsed_ms": 58.59494199999972,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1056.9951270000001
},
"wall_seconds": 22.397,
"cpu_seconds": 17.069666,
"kernel_family_peak_bytes": 1117036544,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 56359,
"arguments": 120,
"text_replies": 1066,
"combined": 58090,
"complete_transport_catalog": 834792
},
"receipt_sha256": {
"report.json": "c3c17495f33ee4ea9daabd7a353f698fa0a804451c4bbdbe9eb1adf905ab483a",
"catalog.json": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"transcript.json": "fd8c589a6e0dbcd0783ef5a526b51db7a3c208cd4067986ed99804bda06c4498",
"evidence.json": "2373bc2e57e40aac881f3ef91fa77187b098dd61d6cf44d37a4b8fe56d022b40",
"erl_lint-1-selected.log": "fdd55def718ffa7c972b3cb9b6be1b7f66a7fa5fb1c371cc492051ffb0d08f62"
}
},
{
"case": "erl_lint-2-selected",
"lane": "selected",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "a74d203a5a12d7eb82aaa1e0fda6595b9647af2255f318bf116953777052460f",
"catalog_sha256": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"detail": "summary",
"view": {
"kind": "beam",
"collection": "exports",
"names": ["module", "exprs", "expr"],
"fields": ["function_index", "function", "arity", "label"],
"offset": 0,
"limit": 64
}
},
"phase": "inspect",
"elapsed_ms": 15135.096698,
"reply_bytes": 7093,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-2-selected/evidence.json"
},
"phase": "export",
"elapsed_ms": 37.63045200000124,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1335.57118
},
"wall_seconds": 19.375,
"cpu_seconds": 16.139353,
"kernel_family_peak_bytes": 1059520512,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 56359,
"arguments": 120,
"text_replies": 1066,
"combined": 58090,
"complete_transport_catalog": 834792
},
"receipt_sha256": {
"report.json": "d25a255b106e1bb232237474915e2900e313ba354aded0d27dad81ea74cfe0c6",
"catalog.json": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"transcript.json": "2c0f92c25f97bb825b99133b84d792c7aac315b4c100986d75cb01939af6ce77",
"evidence.json": "2373bc2e57e40aac881f3ef91fa77187b098dd61d6cf44d37a4b8fe56d022b40",
"erl_lint-2-selected.log": "011553e956860fb3ce9d4e54ca7d4a2baf88a260786d82f60b1d0b70974fdbb7"
}
},
{
"case": "erl_lint-2-complete",
"lane": "complete",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "1b4b40330a2e21d31c06a07d10937cb6a3b37cd0bcbf3d129380341d486b916e",
"catalog_sha256": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam"
},
"phase": "inspect",
"elapsed_ms": 15133.678018999999,
"reply_bytes": 146627,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-2-complete/evidence.json"
},
"phase": "export",
"elapsed_ms": 44.53432299999986,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1000.9708420000002
},
"wall_seconds": 19.764,
"cpu_seconds": 15.287268,
"kernel_family_peak_bytes": 1041948672,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 55409,
"arguments": 78,
"text_replies": 20875,
"combined": 76907,
"complete_transport_catalog": 829075
},
"receipt_sha256": {
"report.json": "61aa7d7c78db145af4399b22d57aa39c2008c479530b28a49d401a6d564e46f7",
"catalog.json": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"transcript.json": "3729bcc5547ab06f80b578c24bbb7937a438b0c190ee6680d0d7868cb5885159",
"evidence.json": "ca66367cd1bb325d7d4e47f958c86234d01aaf749b68778ac5212cac246aacb3",
"erl_lint-2-complete.log": "7bb8d637df3cb7ba542345c9d93ea09fe967d11c90cf5383083db7cd2c5fafb8"
}
},
{
"case": "erl_lint-3-complete",
"lane": "complete",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "1b4b40330a2e21d31c06a07d10937cb6a3b37cd0bcbf3d129380341d486b916e",
"catalog_sha256": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam"
},
"phase": "inspect",
"elapsed_ms": 13658.772345000001,
"reply_bytes": 146627,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-3-complete/evidence.json"
},
"phase": "export",
"elapsed_ms": 48.42420299999867,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1121.564358
},
"wall_seconds": 17.257,
"cpu_seconds": 16.276557,
"kernel_family_peak_bytes": 1078980608,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 55409,
"arguments": 78,
"text_replies": 20875,
"combined": 76907,
"complete_transport_catalog": 829075
},
"receipt_sha256": {
"report.json": "d683aab80d34c4f0fb474981c5715a4ba1ee706bf4e74be10bf69c40358ec011",
"catalog.json": "f4a683209df5bed1ed25ae42c690fdd803348742132e88f77ba2e4e8333ca562",
"transcript.json": "12380e1e67368b8df7787d54be828c953d4e2ef63007c04cd9fc912b42cc42b5",
"evidence.json": "ca66367cd1bb325d7d4e47f958c86234d01aaf749b68778ac5212cac246aacb3",
"erl_lint-3-complete.log": "404f609e4dc10bbf0d8f1d3076751b2cb8b271ff401b00ea161a00944a55d558"
}
},
{
"case": "erl_lint-3-selected",
"lane": "selected",
"artifact": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"sha256": "716d78394b7c3da983e36aac7aadd6673e5041677ba7de20413cc2704d958d55",
"bytes": 233968
},
"answer_count": 4,
"answer_sha256": "34ce3091dbea9ac8cd20831cf6443c7a1de6e02b753246cafa3e0e22b83907c9",
"parent_evidence_id": "ev_df4bfa8b8f0391ad6291542fdccf9062ae3bb8dbabf01c2b166ca6f3493051fc",
"source_report_sha256": "db64350ae25bff9e77f5e167f4a4ac095ed872e09c15de5ce3b2728c078a16d2",
"runtime_identity_sha256": "a74d203a5a12d7eb82aaa1e0fda6595b9647af2255f318bf116953777052460f",
"catalog_sha256": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"calls": [
{
"name": "inspect_erlang_beam",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/real-beam/erl_lint.beam",
"detail": "summary",
"view": {
"kind": "beam",
"collection": "exports",
"names": ["module", "exprs", "expr"],
"fields": ["function_index", "function", "arity", "label"],
"offset": 0,
"limit": 64
}
},
"phase": "inspect",
"elapsed_ms": 14405.966256000002,
"reply_bytes": 7093,
"is_error": false
},
{
"name": "export_evidence_bundle",
"input": {
"path": "/tmp/rea-perf-static-tool-audit-20261024/sdk-cache-cohort/erl_lint-3-selected/evidence.json"
},
"phase": "export",
"elapsed_ms": 31.32568199999878,
"reply_bytes": 376,
"is_error": false
}
],
"phases": {
"discovery_ms": 1107.032897
},
"wall_seconds": 18.052,
"cpu_seconds": 16.867381,
"kernel_family_peak_bytes": 1105092608,
"events_at_payload_exit": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"payload_cleanup_verified": true,
"final_whole_scope_events": null,
"token_proxy": {
"question": 32,
"server_instructions": 513,
"input_catalog_once": 56359,
"arguments": 120,
"text_replies": 1066,
"combined": 58090,
"complete_transport_catalog": 834792
},
"receipt_sha256": {
"report.json": "732e5b4f880d3a981d5362ece88578a2da9e68e80e97eca2dec2ae5856ebbdee",
"catalog.json": "9c09aa2944f10c806df78d1cfa2e1daeddc0a487c432701cb67fa65e14213b0c",
"transcript.json": "e62dc35d939b21418989093a8addadbe533d358a046feb7d99cecab10cedc2d4",
"evidence.json": "2373bc2e57e40aac881f3ef91fa77187b098dd61d6cf44d37a4b8fe56d022b40",
"erl_lint-3-selected.log": "2f35909a9ad5efa9e8ddeffe665887e8a9e9c97c13b4a030b765dd1ceecb14ef"
}
}
]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,125 @@
# Selected BEAM exports: completed-task measurements
Issue #6938. The analyst asks for every exported function with a small set of
exact names, preserving arities, original table/atom indexes and labels. Both
lanes export and validate the **complete** parent report, not only the answer.
Baseline `e09b06369d923e9cccedd91f6839ba643dfbd893`; selected delivery
`bcc3f7611` (full SHA and source-clean runtime identities in the JSON receipt).
The reader, format budgets and complete Evidence identity are unchanged.
Node 24.22.1, SDK client/core/server 2.3.1, npm 11.16.0; Linux x86-64. The
18-task cohort alternates three fresh complete/selected sessions per artifact.
Fresh HOME/client/server and disabled Node compile cache; OS caches unflushed.
This is a scripted client task, not an actual model investigation.
## Results
Medians, before → after. Seconds and kernel process-family MiB (includes file
cache), not parent-only RSS. Ranges and every receipt hash are in
[the 18-task JSON](erlang-beam-views.json).
| Real artifact | Whole-task estimated tokens | Wall s | Family CPU s | Family peak MiB |
| ------------- | --------------------------: | ------------: | ------------: | --------------: |
| erl_lint | 76,907 → 58,090 | 6.773 → 6.870 | 5.581 → 5.312 | 667.0 → 630.9 |
| erl_parse | 72,499 → 58,087 | 6.367 → 6.974 | 5.627 → 5.555 | 666.1 → 657.5 |
| pg2 | 57,733 → 58,018 | 6.365 → 7.576 | 5.295 → 5.615 | 648.2 → 626.9 |
For erl_lint the estimate drops 24.5%; erl_parse drops 19.9%. Small pg2 grows 0.5%
(285 tokens), so selected delivery does not benefit every input. Complete stays
the default. No stable general elapsed/CPU/memory improvement is established;
the small selected task has a higher wall/CPU median. Full parsing/retention and
cold client setup remain. Matching source-report, parent-ID and answer digests
agree across all six tasks for each artifact.
The projection counts one question, server instructions, all 221 input-tool
names/descriptions/schemas once, and every argument/text reply including export.
`tiktoken 0.14.0/cl100k_base` serialization estimates are not actual model usage.
Input directory cost grows 55,409→56,359tokens; the complete transport catalog
including output schemas grows 829,075→834,792tokens and is reported separately.
Protocol framing, host injection/history replay, output-schema presentation to
a model and inference quality are unmeasured. Reported context reduction is
specific to this declared client projection.
## SDK cached-catalog control
Six additional alternating fresh tasks use the SDK's cached catalog/default
validator without an explicit `toolDefinition`. They answer/export the same
question and pass the same independent oracle and cleanup checks. They are
separate from the 18-task primary cohort; do not pool their startup costs.
Median wall 19.764→19.375s (ranges 17.257–21.577 / 18.052–22.397);
family CPU 16.277→16.867s (ranges 15.287–16.562 / 16.139–17.070);
family peak 1024.2→1053.9MiB.
No general cached-client startup improvement is claimed. See
[all six control receipts](erlang-beam-sdk-cache.json).
## Correctness and resources
The unmodified Debian `erlang-base_27.3.4.1+dfsg-1+deb13u3_amd64.deb` supplies
actual OTP 27 compiler/stdlib modules: erl_lint 233,968 bytes (915 atoms, 119 imports,
112 lambdas), erl_parse 608,240 bytes (1,035 atoms), and pg2 1,152 bytes (4 atoms).
The package URL/SHA256, every selected artifact digest, source revision, driver
hash and runtime fingerprint are retained in the JSON records. The independent
[Python struct oracle](erlang-beam-oracle.py) checks every decoded table, chunk
range/payload digest and Code header, then the selected export answer. It reads
bytes only and deliberately qualifies these fixed OTP 27 layouts, not every
historical/compact format. No Erlang installation, emulator or target execution
was used. Default CLI and selected CLI on all three inputs also matched every
oracle fact and parent ID and published exactly the complete source Evidence.
Each of the 24 SDK tasks exited 0 with payload cleanup verified and no guard stop,
OOM or task-cap event. Aggregate CPUQuota 100%, payload affinity0–1,3 GiB early
stop/4 GiB hard limit, no swap, TasksMax 192. CPU/peak counters are observed after
the payload exits while the observer lives; final whole-scope counters remain
unavailable. Costs include SDK discovery/schema validation, runtime/dependency
hashing, assertions, source retention/export and cleanup. Sample acquisition,
oracle generation and tokenization occur outside timed tasks. Node, all compiled dist
files, selected entrypoints, 59 actual SDK files and driver files are hashed
before/after; the source revisions were clean when fingerprints were created.
Public regressions additionally exercise all five collections, duplicate names
and arities, selectors/fields/pages, missing versus empty tables, unresolved atom
names, unavailable retention, source deletion, offline bundles and refusal before
file reads. They establish those boundaries; real new-view qualification here is
OTP 27 on Linux, not every platform or provider.
## Reproducing the question
Build clean pinned checkouts with the repository's pinned toolchain and acquire
the public package linked in the JSON. Keep extracted files outside the repo.
Generate each oracle, for example:
```sh
python3 experiments/analysis-efficiency/erlang-beam-oracle.py \
--input /samples/erl_lint.beam --output /runs/erl_lint-oracle.json \
--name module --name exprs --name expr
node experiments/analysis-efficiency/erlang-beam-views.mjs \
--runtime /built/rea --identity /runs/runtime-identity.json \
--oracle /runs/erl_lint-oracle.json --output /runs/erl_lint-selected \
--lane selected
```
A runtime fingerprint supplies `compiled_files` and `entrypoints` with relative
paths/SHA256, `driver_files` relative to this experiment directory, actual SDK
`dependency_files` with absolute paths/SHA256, and `node_sha256`. Source revision
and clean-state fields record preparation. Create fingerprints before timing;
the driver verifies them before and after each task. The publication records
fingerprint/receipt hashes and full commands; the original complete fingerprint
files, SDK transcripts, complete Evidence bundles and resource logs remain at
`/tmp/rea-perf-static-tool-audit-20261024/`. The local cohort controller is retained
there too. These local receipts are not shipped binaries or installed providers.
Wrap each invocation with `resource-run.py --observe-payload-exit`, setting the
pinned Node PATH/heap, process affinity and resource policy as in its recorded
command. Use a fresh output directory/session each time, alternate lanes three
times per artifact, and count with
[erlang-beam-costs.py](erlang-beam-costs.py). `--page-size` can qualify extra
retained-page calls; `--sdk-cache` selects the separate cached-catalog control.
The18 published primary tasks use the exact discovered toolDefinition/default
validator and 64-row pages, requiring two calls each including full-source export.
The initial exploratory baseline used earlier main `e2463372`; it is not mixed
into this table. Failed development checks remain in the local record: an
incorrect formatter path and copied service import, followed by exact complete
contract narrowing in old test consumers; corrected type/format/public checks
passed. These were not successful benchmark samples.
@@ -0,0 +1,255 @@
/** Compare a completed selected-export question through production stdio MCP. */
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { finished } from "node:stream/promises";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
const { values } = parseArgs({
options: {
...Object.fromEntries(
["runtime", "oracle", "output", "identity"].map((k) => [
k,
{ type: "string" },
]),
),
lane: { type: "string", default: "complete" },
"page-size": { type: "string", default: "64" },
"sdk-cache": { type: "boolean", default: false },
},
});
assert.ok(["complete", "selected"].includes(values.lane));
const pageSize = Number(values["page-size"]);
assert.ok(Number.isSafeInteger(pageSize) && pageSize > 0);
for (const key of ["runtime", "oracle", "output", "identity"])
assert.equal(values[key], resolve(values[key]));
const { createRetainedQueryCaller } =
await import("./retained-query-calls.mjs");
const { finalizeRetainedQueryReceipt } =
await import("./retained-query-finalization.mjs");
const hash = (x) => createHash("sha256").update(x).digest("hex"),
oracleBytes = await readFile(values.oracle),
expected = JSON.parse(oracleBytes),
identityBytes = await readFile(values.identity),
identity = JSON.parse(identityBytes);
assert.equal(
hash(await readFile(expected.expected.artifact.path)),
expected.expected.artifact.sha256,
);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-beam-task-"));
const client = new Client({ name: "beam-selected-exports", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
maxBufferSize: 10485760,
env: {
HOME: account,
TMPDIR: account,
PATH: process.env.PATH,
REA_LOG_LEVEL: "silent",
NODE_OPTIONS: "--max-old-space-size=2048",
NODE_DISABLE_COMPILE_CACHE: "1",
UV_THREADPOOL_SIZE: "2",
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log")),
serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const report = {
status: "running",
lane: values.lane,
artifact: expected.expected.artifact,
calls: [],
phases: {},
question: `Return every exported function named ${expected.names.join(", ")} with its original export-table index, function atom index, arity and code label. Preserve complete source Evidence.`,
client: values["sdk-cache"]
? "Production stdio SDK, cached catalog definitions and default validator"
: "Production stdio SDK, exact discovered toolDefinition and default validator",
runtime_identity_sha256: hash(identityBytes),
oracle_sha256: hash(oracleBytes),
driver_sha256: hash(await readFile(new URL(import.meta.url))),
};
const verifyRuntime = async () => {
assert.equal(hash(await readFile(values.identity)), hash(identityBytes));
assert.equal(hash(await readFile(values.oracle)), hash(oracleBytes));
for (const r of [...identity.compiled_files, ...identity.entrypoints])
assert.equal(
hash(await readFile(join(values.runtime, r.path))),
r.sha256,
r.path,
);
for (const r of identity.dependency_files ?? [])
assert.equal(hash(await readFile(r.path)), r.sha256, r.path);
for (const r of identity.driver_files)
assert.equal(
hash(await readFile(new URL(r.path, import.meta.url))),
r.sha256,
r.path,
);
assert.equal(hash(await readFile(process.execPath)), identity.node_sha256);
assert.equal(
hash(await readFile(new URL(import.meta.url))),
report.driver_sha256,
);
};
const transcript = [];
let definitions;
const caller = createRetainedQueryCaller(
{
callTool(params, options) {
return client.callTool(
params,
values["sdk-cache"]
? options
: {
...options,
toolDefinition: definitions.get(params.name),
},
);
},
},
(receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
},
);
const observe = async (name, input, phase) => {
const result = await caller(name, input, phase);
assert.notEqual(result.isError, true, JSON.stringify(result));
assert.ok(result.structuredContent);
return result.structuredContent;
};
const verifySource = (source) => {
for (const [key, value] of Object.entries(expected.expected))
assert.deepEqual(source[key], value, key);
assert.equal(source.coverage.status, "complete");
};
try {
await verifyRuntime();
const started = performance.now();
await client.connect(transport);
report.server_instructions = client.getInstructions() ?? "";
const catalog = await client.listTools();
definitions = new Map(catalog.tools.map((t) => [t.name, t]));
report.catalog_tools = catalog.tools.length;
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
report.phases.discovery_ms = performance.now() - started;
const selected = values.lane === "selected";
const selectedView = (offset) => ({
kind: "beam",
collection: "exports",
names: expected.names,
fields: ["function_index", "function", "arity", "label"],
offset,
limit: pageSize,
});
const response = await observe(
"inspect_erlang_beam",
{
path: expected.expected.artifact.path,
...(selected ? { detail: "summary", view: selectedView(0) } : {}),
},
"inspect",
);
report.parent_evidence_id = selected
? response.normalized_result.parent_evidence_id
: response.evidence_id;
let answer;
if (!selected) {
verifySource(response.normalized_result);
answer = (response.normalized_result.exports ?? [])
.map((r, i) => ({ source_index: i, ...r }))
.filter((r) => expected.names.includes(r.function));
} else {
answer = [];
let offset = 0,
page = response.normalized_result;
for (;;) {
assert.equal(page.kind, "beam");
assert.equal(page.page.collection, "exports");
assert.deepEqual(page.beam.artifact, expected.expected.artifact);
assert.deepEqual(page.beam.coverage, expected.expected.coverage);
assert.equal(page.beam.counts.exports, expected.expected.exports.length);
assert.equal(page.coverage.total, expected.answer.length);
answer.push(...page.page.items);
if (page.coverage.next_offset === null) break;
assert.ok(page.coverage.next_offset > offset);
offset = page.coverage.next_offset;
page = (
await observe(
"inspect_analysis_view",
{
source: {
kind: "retained-evidence",
evidence_id: report.parent_evidence_id,
},
view: selectedView(offset),
},
"selected-page",
)
).normalized_result;
}
}
assert.deepEqual(answer, expected.answer);
report.answer_count = answer.length;
report.answer_sha256 = hash(
JSON.stringify(
answer.map((r) =>
Object.fromEntries(
Object.entries(r).sort(([a], [b]) => a.localeCompare(b)),
),
),
),
);
await writeFile(join(values.output, "answer.json"), JSON.stringify(answer));
await observe(
"export_evidence_bundle",
{ path: join(values.output, "evidence.json") },
"export",
);
const bundle = JSON.parse(
await readFile(join(values.output, "evidence.json")),
);
const parent = bundle.records.find(
(r) => r.evidence_id === report.parent_evidence_id,
);
assert.ok(parent);
assert.equal(parent.operation, "inspect_erlang_beam");
verifySource(parent.normalized_result);
report.source_report_sha256 = hash(JSON.stringify(parent.normalized_result));
report.complete_source_export_verified = true;
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.error = String(cause);
} finally {
try {
await verifyRuntime();
} catch (cause) {
report.status = "failed";
report.runtime_identity_failure = String(cause);
}
await finalizeRetainedQueryReceipt({
client,
transport,
serverLog,
serverLogFinished,
account,
report,
output: values.output,
transcript,
});
if (
report.status !== "passed" ||
report.cleanup_failures.length ||
report.receipt_failures.length
)
process.exitCode = 1;
}
-2
View File
@@ -10,8 +10,6 @@
"src/composition/javascriptAnalysisWorkerProcess.ts",
"src/composition/webBundleAnalysisWorkerProcess.ts",
"src/contracts/errorSchemas.ts",
"src/evaluation/AgentEvaluationFixtures.ts",
"src/application/RealHopperAssertions.ts",
"scripts/verify/fridaBindingsCompatibility.ts",
"scripts/verify/termux/browser.mjs",
"scripts/verify-real-ghidra-noreturn.mjs",
File diff suppressed because it is too large. Load diff
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2004-2024 Ero Carrera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5
View File
@@ -73,5 +73,10 @@
"file": "licenses/protobuf-es-v2.16.0-LICENSE.txt",
"source": "https://raw.githubusercontent.com/bufbuild/protobuf-es/b3b232f7f466eda27c66e8f63c82f930578b6cc6/LICENSE",
"sha256": "c04b4216f1cd4c5a4f7fb2f2a1b0ae70d847e9e0cac7c9dee9bf8cc03177c449"
},
{
"file": "licenses/pefile-2024.8.26-LICENSE.txt",
"source": "https://raw.githubusercontent.com/erocarrera/pefile/4b3b1e2e568a88d4f1897d694d684f23d9e270c4/LICENSE",
"sha256": "b44409c067c6da52bfb54bb6624fb11a7b52157c3a13ae1400232f8196e86ad3"
}
]
+2205
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -69,6 +69,7 @@
"bridge/android/ReaJadxBridge.java",
"bridge/mitmproxy/capture.py",
"scripts/rea.mjs",
"scripts/lib/cli-process-environment.mjs",
"scripts/rea-android-workspace-guardian.mjs",
"scripts/electron-active-hook.cjs",
"scripts/electron-active-hook-boundaries.cjs",
@@ -397,6 +398,7 @@
"lint-staged": "16.4.0",
"oxfmt": "0.72.0",
"oxlint": "1.87.0",
"release-please": "17.3.0",
"rolldown": "1.2.12",
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
"tsx": "4.23.15",
+71
View File
@@ -0,0 +1,71 @@
--- a/build/src/manifest.js
+++ b/build/src/manifest.js
@@ -261,9 +261,19 @@
this.logger.info('Collecting commits since all latest releases');
const commits = [];
this.logger.debug(`commit search depth: ${this.commitSearchDepth}`);
+ // The root receives every commit without path splitting. Node's strategy,
+ // default changelog and extra-file updaters do not inspect commit.files.
+ // Keep the general path for exclusions, plugins and other strategies.
+ const rootConfig = this.repositoryConfig[exports.ROOT_PROJECT_PATH];
+ const rootOnlyNode = Object.keys(this.repositoryConfig).length === 1 &&
+ rootConfig?.releaseType === 'node' &&
+ (!rootConfig.changelogType || rootConfig.changelogType === 'default') &&
+ (!rootConfig.versioning || ['default', 'always-bump-minor'].includes(rootConfig.versioning)) &&
+ !rootConfig.excludePaths?.length &&
+ this.plugins.length === 0;
const commitGenerator = this.github.mergeCommitIterator(this.targetBranch, {
maxResults: this.commitSearchDepth,
- backfillFiles: true,
+ backfillFiles: !rootOnlyNode,
batchSize: this.commitBatchSize,
});
const releaseShas = new Set(Object.values(releaseShasByPath));
@@ -272,6 +282,7 @@
// sha => release pull request
const releasePullRequestsBySha = {};
let releaseCommitsFound = 0;
+ let historyBoundaryFound = false;
for await (const commit of commitGenerator) {
if (releaseShas.has(commit.sha)) {
if (commit.pullRequest) {
@@ -284,14 +295,17 @@
}
if (this.lastReleaseSha && this.lastReleaseSha === commit.sha) {
this.logger.info(`Using configured lastReleaseSha ${this.lastReleaseSha} as last commit.`);
+ historyBoundaryFound = true;
break;
}
else if (needsBootstrap && commit.sha === this.bootstrapSha) {
this.logger.info(`Needed bootstrapping, found configured bootstrapSha ${this.bootstrapSha}`);
+ historyBoundaryFound = true;
break;
}
else if (!needsBootstrap && releaseCommitsFound >= expectedShas) {
// found enough commits
+ historyBoundaryFound = true;
break;
}
commits.push({
@@ -301,16 +315,18 @@
pullRequest: commit.pullRequest,
});
}
+ if (releaseCommitsFound < expectedShas && !historyBoundaryFound) {
+ throw new Error(`Incomplete release history: expected ${expectedShas} release boundaries, found ${releaseCommitsFound}. Refusing a partial release proposal.`);
+ }
if (releaseCommitsFound < expectedShas) {
this.logger.warn(`Expected ${expectedShas} commits, only found ${releaseCommitsFound}`);
}
// split commits by path
this.logger.info(`Splitting ${commits.length} commits by path`);
- const cs = new commit_split_1.CommitSplit({
+ const splitCommits = rootOnlyNode ? {} : new commit_split_1.CommitSplit({
includeEmpty: true,
packagePaths: Object.keys(this.repositoryConfig),
- });
- const splitCommits = cs.split(commits);
+ }).split(commits);
// limit paths to ones since the last release
let commitsPerPath = {};
for (const path in this.repositoryConfig) {
-2
View File
@@ -28,7 +28,6 @@ const FIXTURE_SOURCES = [
"tests/conformance/swift/**",
"tests/conformance/versions/**",
];
const READINESS_SOURCES = ["tests/conformance/readiness/**"];
// These reporters affect CI output only; they do not select a host or provider
// verification lane when changed on their own.
const PORTABLE_CI_REPORTERS = new Set([
@@ -281,7 +280,6 @@ if (tier === "reused") {
selected.fixtures = true;
selected.readiness = true;
}
if (matchesAny(file, READINESS_SOURCES)) selected.readiness = true;
if (file.startsWith("tests/")) continue;
if (
/^(src\/process\/|src\/filesystem\/|src\/contracts\/tool|src\/server\/createServer|src\/cli\.ts|scripts\/ci\/)/.test(
+6
View File
@@ -2,6 +2,10 @@
// Shared build inputs are classified once in plan.mjs rather than repeated in
// every workflow.
export const SCOPES = {
readiness: [
"tests/conformance/readiness/**",
"scripts/verify-reconstruction-readiness.mjs",
],
reverse_tools: [
".github/workflows/mutation.yml",
"src/application/reverse/**",
@@ -141,6 +145,7 @@ export const SCOPES = {
"scripts/prepack.mjs",
"scripts/prepare.mjs",
"scripts/rea.mjs",
"scripts/lib/cli-process-environment.mjs",
"install.sh",
"src/config/**",
"src/application/Setup*.ts",
@@ -373,6 +378,7 @@ export const SCOPES = {
"src/contracts/toolContracts.ts",
"src/contracts/toolEffects.ts",
"scripts/rea.mjs",
"scripts/lib/cli-process-environment.mjs",
"src/process/**",
"src/artifacts/SafeOutputTree.ts",
"scripts/verify/javascript/recovery.mjs",
+7
View File
@@ -260,6 +260,13 @@ export const providerCatalog = (sources) => {
({ name }) => name === "inspect_pe_certificates",
),
},
{
identity: sources.artifactProviders.PE_IMPORTS_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_pe_imports",
),
},
{
identity: sources.artifactProviders.ELF_NOTES_PROVIDER,
contracts:
+26
View File
@@ -0,0 +1,26 @@
import { join } from "node:path";
/** Prepare the selected process environment before importing CLI or MCP dependencies. */
export const prepareCliProcessEnvironment = (
environment,
platform,
packageVersion,
) => {
environment.REA_PACKAGE_VERSION = packageVersion;
// Windows home lookup throws for an empty USERPROFILE. Dependencies resolve
// home during import, before REA's own resolver can apply its account fallback.
if (platform === "win32" && environment.USERPROFILE === "") {
delete environment.USERPROFILE;
}
// Playwright's registry assumes a desktop host while choosing its cache.
// Termux needs an explicit path before the registry initializes.
if (
platform === "android" &&
environment.PLAYWRIGHT_BROWSERS_PATH === undefined
) {
const cacheRoot =
environment.XDG_CACHE_HOME ?? join(environment.HOME ?? ".", ".cache");
environment.PLAYWRIGHT_BROWSERS_PATH = join(cacheRoot, "ms-playwright");
}
};
+35
View File
@@ -0,0 +1,35 @@
/** Extract one real procedure address from the complete inline inventory. */
export function firstProcedureAddress(input: unknown): string;
/** Reject empty and success-shaped embedded decompilation failures. */
export function requirePseudocode(input: unknown, operation: string): string;
/** Validate a complete Hopper function dossier semantically. */
export function requireFunctionDossier(
input: unknown,
expectedAddress: string,
): Record<string, unknown>;
/** Positive semantic facts required from one source-owned Hopper fixture. */
export interface FunctionDossierOracle {
readonly procedure_address: string;
readonly callee_address?: string;
readonly caller_address?: string;
readonly referenced_string?: string;
readonly referenced_name?: string;
readonly comment?: string;
readonly require_cfg_successor?: boolean;
readonly require_assembly?: boolean;
}
/** Reject a structurally valid dossier that omits fixture-proven semantics. */
export function requireFunctionDossierOracle(
input: unknown,
oracle: FunctionDossierOracle,
): Record<string, unknown>;
/** Require every relationship to be an actual hexadecimal address. */
export function requireAddressArray(
input: unknown,
operation: string,
): string[];
@@ -3,7 +3,7 @@ import { z } from "zod";
const addressSchema = z.string().regex(/^0x[0-9a-f]+$/iu);
const identitySchema = z.object({ address: addressSchema, name: z.string() });
/** Extract one real procedure address from the complete inline inventory. */
export const firstProcedureAddress = (input: unknown): string => {
export const firstProcedureAddress = (input) => {
const first = z
.array(z.object({ address: addressSchema }))
.min(1)
@@ -13,10 +13,7 @@ export const firstProcedureAddress = (input: unknown): string => {
};
/** Reject empty and success-shaped embedded decompilation failures. */
export const requirePseudocode = (
input: unknown,
operation: string,
): string => {
export const requirePseudocode = (input, operation) => {
if (typeof input !== "string" || input.trim().length === 0)
throw new TypeError(`${operation} returned empty pseudocode`);
if (input === "No output" || input.startsWith("Error:"))
@@ -25,10 +22,7 @@ export const requirePseudocode = (
};
/** Validate a complete Hopper function dossier semantically. */
export const requireFunctionDossier = (
input: unknown,
expectedAddress: string,
): Record<string, unknown> => {
export const requireFunctionDossier = (input, expectedAddress) => {
const value = objectValue(
input,
"analyze_function returned no function dossier",
@@ -40,29 +34,14 @@ export const requireFunctionDossier = (
throw new TypeError("analyze_function omitted the procedure name");
requirePseudocode(value.pseudocode, "analyze_function");
for (const field of COLLECTION_FIELDS) list(value, field);
for (const field of ["callers", "callees"] as const)
for (const field of ["callers", "callees"])
z.array(identitySchema).parse(list(value, field));
validateBlocks(list(value, "basic_blocks"));
return value;
};
/** Positive semantic facts required from one source-owned Hopper fixture. */
export interface FunctionDossierOracle {
readonly procedure_address: string;
readonly callee_address?: string;
readonly caller_address?: string;
readonly referenced_string?: string;
readonly referenced_name?: string;
readonly comment?: string;
readonly require_cfg_successor?: boolean;
readonly require_assembly?: boolean;
}
/** Reject a structurally valid dossier that omits fixture-proven semantics. */
export const requireFunctionDossierOracle = (
input: unknown,
oracle: FunctionDossierOracle,
): Record<string, unknown> => {
export const requireFunctionDossierOracle = (input, oracle) => {
const value = requireFunctionDossier(input, oracle.procedure_address);
if (oracle.callee_address !== undefined) {
const callees = z.array(identitySchema).parse(list(value, "callees"));
@@ -158,12 +137,7 @@ const referenceEdgeSchema = z.object({
target_address: addressSchema,
});
const requireReferencedValue = (
dossier: Record<string, unknown>,
field: "referenced_strings" | "referenced_names",
expected: string,
normalize: (value: string) => string,
): void => {
const requireReferencedValue = (dossier, field, expected, normalize) => {
const values = z.array(referencedValueSchema).parse(list(dossier, field));
const references = z
.array(referenceEdgeSchema)
@@ -183,10 +157,7 @@ const requireReferencedValue = (
};
/** Require every relationship to be an actual hexadecimal address. */
export const requireAddressArray = (
input: unknown,
operation: string,
): string[] => {
export const requireAddressArray = (input, operation) => {
const parsed = z.array(addressSchema).parse(input);
if (parsed.some((address) => address.length === 0))
throw new TypeError(`${operation} returned an invalid address list`);
@@ -203,21 +174,17 @@ const COLLECTION_FIELDS = [
"referenced_strings",
"referenced_names",
"basic_blocks",
] as const;
];
const list = (value: Record<string, unknown>, field: string): unknown[] =>
z.array(z.unknown()).parse(value[field]);
const list = (value, field) => z.array(z.unknown()).parse(value[field]);
const validateBlocks = (items: readonly unknown[]): void => {
const validateBlocks = (items) => {
for (const block of items)
if (!Array.isArray(objectValue(block, "Invalid basic block").successors))
throw new TypeError("analyze_function omitted CFG successor evidence");
};
const objectValue = (
input: unknown,
message: string,
): Record<string, unknown> => {
const objectValue = (input, message) => {
const parsed = z.record(z.string(), z.unknown()).safeParse(input);
if (!parsed.success) throw new TypeError(message);
return parsed.data;
+1 -13
View File
@@ -1,17 +1,6 @@
import assert from "node:assert/strict";
/**
* `requireFunctionDossierOracle` is only used inside `verifyRealHopperFixture`,
* which runs against the real Hopper bridge after `build:cached`. Loading it
* lazily keeps this module importable from `test:fast` (which docs promise to
* run "without build" — see docs/testing.md § test scripts) so that mock-only
* callers such as `requireCurrentDocument` and `resolveFixtureProcedure` do
* not require `dist/`.
*/
const loadFunctionDossierOracle = () =>
import("../../dist/application/RealHopperAssertions.js").then(
(module) => module.requireFunctionDossierOracle,
);
import { requireFunctionDossierOracle } from "./real-hopper-assertions.mjs";
/** Require truthful tri-state permissions from real Hopper memory regions. */
export const requireTruthfulMemoryRegions = (segments) => {
@@ -99,7 +88,6 @@ export async function verifyRealHopperFixture({
oracle,
normalizedResult,
}) {
const requireFunctionDossierOracle = await loadFunctionDossierOracle();
const main = await resolveFixtureProcedure(
client,
options,
+5 -1
View File
@@ -53,7 +53,7 @@ export const releaseInventorySchema = z.object({
pullRequests: z.array(pullRequest),
});
export async function readReleaseHistory(run, base, target) {
export async function requireCompleteReleaseHistory(run) {
if (
(await run("git", ["rev-parse", "--is-shallow-repository"])).trim() !==
"false"
@@ -61,6 +61,10 @@ export async function readReleaseHistory(run, base, target) {
throw new Error(
"Release inventory requires complete Git history; fetch --unshallow first",
);
}
export async function readReleaseHistory(run, base, target) {
await requireCompleteReleaseHistory(run);
const resolve = async (ref) =>
sha.parse(
(
+6
View File
@@ -0,0 +1,6 @@
export function applyReleasePleasePatch(directory?: string): Promise<void>;
export function releaseProposalSearchDepth(
run: (command: string, args: string[]) => Promise<string>,
sourceSha: string,
): Promise<number>;
+61
View File
@@ -0,0 +1,61 @@
import { execFile } from "node:child_process";
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, relative } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { requireCompleteReleaseHistory } from "./release-notes.mjs";
const execute = promisify(execFile);
const require = createRequire(import.meta.url);
const project = fileURLToPath(new URL("../../", import.meta.url));
const patch = fileURLToPath(
new URL("../../patches/release-please+17.3.0.patch", import.meta.url),
);
export async function applyReleasePleasePatch(
directory = dirname(require.resolve("release-please/package.json")),
) {
const metadata = JSON.parse(
await readFile(`${directory}/package.json`, "utf8"),
);
if (metadata.version !== "17.3.0") {
throw new Error(
"The release proposal patch requires release-please 17.3.0",
);
}
const args = ["--directory", relative(project, directory), patch];
const check = async (reverse) => {
try {
await execute(
"git",
["apply", "--check", ...(reverse ? ["--reverse"] : []), ...args],
{ cwd: project },
);
return true;
} catch (error) {
if (error.code === 1) return false;
throw error;
}
};
if (await check(false)) {
await execute("git", ["apply", ...args], { cwd: project });
} else if (!(await check(true))) {
throw new Error(
"The release proposal dependency patch does not match the installed source",
);
}
}
export async function releaseProposalSearchDepth(run, sourceSha) {
if (!/^[a-f0-9]{40}$/u.test(sourceSha ?? "")) {
throw new Error("Expected an exact release proposal source SHA");
}
await requireCompleteReleaseHistory(run);
const text = (await run("git", ["rev-list", "--count", sourceSha])).trim();
const count = Number(text);
if (!/^[1-9][0-9]*$/u.test(text) || !Number.isSafeInteger(count)) {
throw new Error("Git returned an invalid release history size");
}
return count;
}
+1
View File
@@ -45,6 +45,7 @@ if (await pathExists(join(root, "src"))) {
run(process.execPath, ["scripts/generate-skill-metadata.mjs", "--check"]);
} else {
for (const path of [
"scripts/lib/cli-process-environment.mjs",
"dist/main.js",
"dist/mcp/main.js",
"dist/cli-runtime/cli.js",
+8 -22
View File
@@ -1,9 +1,11 @@
#!/usr/bin/env node
import { access } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { prepareCliProcessEnvironment } from "./lib/cli-process-environment.mjs";
// Route production MCP before importing Incur. Incur owns registration helpers
// such as `mcp add`, while the MCP runtime serves the tool catalog over stdio
// or, with `--http`, over streamable HTTP.
@@ -11,7 +13,11 @@ const args = process.argv.slice(2);
const { default: packageJson } = await import("../package.json", {
with: { type: "json" },
});
process.env.REA_PACKAGE_VERSION = packageJson.version;
prepareCliProcessEnvironment(
process.env,
process.platform,
packageJson.version,
);
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const builtinArgumentRuntime = "dist/cli-runtime/cliBuiltinArguments.js";
const builtinArguments = (await compiledRuntimeExists([builtinArgumentRuntime]))
@@ -36,26 +42,6 @@ const isMcpHttpMode =
const SIGPIPE_EXIT_STATUS = 141;
// Set once the reader of CLI output has closed the pipe.
let stdoutClosed = false;
// Windows home lookup throws for an empty USERPROFILE. Dependencies resolve
// home during import, before REA's own resolver can apply its account fallback.
if (process.platform === "win32" && process.env.USERPROFILE === "") {
delete process.env.USERPROFILE;
}
// Playwright's registry assumes one of its three desktop host platforms when
// it chooses the browser cache directory. Termux reports `android`, although
// its Node runtime and filesystem are POSIX-compatible. Keep the cache path
// explicit so REA can start on Termux. Browser launch still requires a
// caller-supplied Android-compatible Chromium executable.
if (
process.platform === "android" &&
process.env.PLAYWRIGHT_BROWSERS_PATH === undefined
) {
const cacheRoot =
process.env.XDG_CACHE_HOME ?? join(process.env.HOME ?? ".", ".cache");
process.env.PLAYWRIGHT_BROWSERS_PATH = join(cacheRoot, "ms-playwright");
}
const runtimeFiles = [
builtinArgumentRuntime,
...(isMcpMode || isMcpHttpMode
-8
View File
@@ -1,15 +1,7 @@
import semver from "semver";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
export function releaseNpmTag(version) {
if (version === undefined || semver.valid(version) !== version)
throw new Error("Expected an exact SemVer version.");
return semver.prerelease(version) === null ? "latest" : "next";
}
if (
process.argv[1] !== undefined &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
)
process.stdout.write(releaseNpmTag(process.argv[2]));
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env node
import { execFile } from "node:child_process";
import { appendFile } from "node:fs/promises";
import { parseArgs, promisify } from "node:util";
import {
applyReleasePleasePatch,
releaseProposalSearchDepth,
} from "./lib/release-proposal.mjs";
const execute = promisify(execFile);
async function main() {
const { values } = parseArgs({
options: {
"target-branch": { type: "string" },
"source-sha": { type: "string" },
},
});
const repository = process.env.GITHUB_REPOSITORY;
const token = process.env.GH_TOKEN;
if (
!repository ||
!/^[\w.-]+\/[\w.-]+$/u.test(repository) ||
!token ||
!values["target-branch"]
) {
throw new Error("Expected GITHUB_REPOSITORY, GH_TOKEN and --target-branch");
}
const depth = await releaseProposalSearchDepth(
async (command, args) =>
(await execute(command, args, { encoding: "utf8" })).stdout,
values["source-sha"],
);
// Patch before import: only proposal generation uses this maintained upstream fix.
// Publication continues through the pinned release-please action.
await applyReleasePleasePatch();
const { GitHub, Manifest } = await import("release-please");
const [owner, repo] = repository.split("/");
const branch = values["target-branch"];
const github = await GitHub.create({
owner,
repo,
token,
defaultBranch: branch,
});
const manifest = await Manifest.fromManifest(
github,
branch,
undefined,
undefined,
{
commitSearchDepth: depth,
},
);
const prs = (await manifest.createPullRequests()).filter(Boolean);
if (process.env.GITHUB_OUTPUT) {
await appendFile(
process.env.GITHUB_OUTPUT,
`prs_created=${prs.length > 0}\n${prs.length ? `pr=${JSON.stringify(prs[0])}\n` : ""}`,
);
}
}
await main().catch((error) => {
process.stderr.write(
`Release proposal failed: ${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
+32 -14
View File
@@ -11,26 +11,44 @@ import {
import { homedir } from "node:os";
import { delimiter, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { tsImport } from "tsx/esm/api";
import { evaluateCodexEvents } from "../dist/evaluation/CodexAgentEval.js";
import {
MISSING_TARGET_SCENARIO,
nativeProviderScenario,
} from "../dist/evaluation/AgentEvaluationScenarios.js";
import {
createAgentEvaluationFixtures,
agentFixtureClaims,
agentFixtureAnswerInstructions,
} from "../dist/evaluation/AgentEvaluationFixtures.js";
import {
agentEvaluationPassed,
summarizeFactualCorrectness,
} from "../dist/evaluation/AgentEvaluationReport.js";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import { createAgentEvaluationCommands } from "./lib/agent-evaluation-commands.mjs";
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
const { evaluateCodexEvents } = await tsImport(
new URL("../tests/evaluation/model-evals/CodexAgentEval.ts", import.meta.url)
.href,
import.meta.url,
);
const { MISSING_TARGET_SCENARIO, nativeProviderScenario } = await tsImport(
new URL(
"../tests/evaluation/model-evals/AgentEvaluationScenarios.ts",
import.meta.url,
).href,
import.meta.url,
);
const {
createAgentEvaluationFixtures,
agentFixtureClaims,
agentFixtureAnswerInstructions,
} = await tsImport(
new URL(
"../tests/evaluation/model-evals/AgentEvaluationFixtures.ts",
import.meta.url,
).href,
import.meta.url,
);
const { agentEvaluationPassed, summarizeFactualCorrectness } = await tsImport(
new URL(
"../tests/evaluation/model-evals/AgentEvaluationReport.ts",
import.meta.url,
).href,
import.meta.url,
);
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const verifierRun = createVerifierRun();
const timeoutMs = Number(process.env.REA_AGENT_EVAL_TIMEOUT_MS ?? 480_000);
+2
View File
@@ -37,6 +37,8 @@ export async function verifyPackagePack({ root, workspace }) {
throw new Error("package did not expose both rea command entry points");
if (packedManifest.mcpName !== "io.github.morluto/rea")
throw new Error("package did not retain its MCP Registry ownership marker");
if (!packedFiles.includes("package/scripts/lib/cli-process-environment.mjs"))
throw new Error("package omitted the CLI process environment owner");
if (packedFiles.some((path) => path.startsWith("package/.agents/")))
throw new Error(
"package included authored skill sources instead of only the generated bundle",
+1 -1
View File
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { firstProcedureAddress } from "../dist/application/RealHopperAssertions.js";
import { firstProcedureAddress } from "./lib/real-hopper-assertions.mjs";
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.js";
import { AnalysisCleanupLease } from "../dist/artifacts/AnalysisCleanupLease.js";
+1 -1
View File
@@ -10,7 +10,7 @@ import {
requireAddressArray,
requireFunctionDossier,
requirePseudocode,
} from "../dist/application/RealHopperAssertions.js";
} from "./lib/real-hopper-assertions.mjs";
import { HOPPER_PROVIDER_IDENTITY } from "../dist/hopper/HopperProviderCapabilities.js";
import { HOPPER_TARGET_LEASE_DIRECTORY } from "../dist/process/HopperLease.js";
import { REA_WORKFLOW_PROVIDER } from "../dist/domain/investigationProviders.js";
+152 -96
View File
@@ -6,14 +6,32 @@ import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { tsImport } from "tsx/esm/api";
import { toolContract } from "../dist/contracts/toolContracts.js";
import { RECONSTRUCTION_READINESS_EXAMPLE } from "../dist/contracts/reconstructionReadinessExample.js";
import { parseEvidence } from "../dist/domain/evidence.js";
import { createEvidenceBundle } from "../dist/domain/evidenceBundle.js";
import { createReconstructionReadinessReport } from "../dist/domain/reconstructionReadiness.js";
import { reconstructionReadinessReportSchema } from "../dist/domain/reconstructionReadinessSchemas.js";
import { PACKAGE_METADATA } from "../dist/generatedPackageMetadata.js";
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
const [
{ RECONSTRUCTION_READINESS_EXAMPLE },
{ createReconstructionReadinessReport },
{ reconstructionReadinessReportSchema },
] = await Promise.all(
[
"reconstructionReadinessExample",
"reconstructionReadiness",
"reconstructionReadinessSchemas",
].map((name) =>
tsImport(
new URL(
`../tests/conformance/readiness/report/${name}.ts`,
import.meta.url,
).href,
import.meta.url,
),
),
);
const execute = promisify(execFile);
const root = process.cwd();
@@ -50,6 +68,8 @@ const client = new Client({
capabilities: { elicitation: {} },
});
let verificationReport;
let primaryError;
try {
await client.connect(transport);
const catalog = await client.listTools();
@@ -61,6 +81,7 @@ try {
{ name: "binary_session", arguments: {} },
{ toolDefinition: sessionTool },
);
assert.notEqual(sessionReply.isError, true);
const sessionStatus = toolContract("binary_session").outputSchema.parse(
sessionReply.structuredContent,
).result;
@@ -77,76 +98,93 @@ try {
"CLI/MCP Electron analysis Evidence identity drifted",
);
const input = structuredClone(RECONSTRUCTION_READINESS_EXAMPLE);
input.identity.cli_version = PACKAGE_METADATA.version;
input.identity.server_version = PACKAGE_METADATA.version;
input.identity.catalog_digest =
sessionStatus.server_identity.catalog.digests.combined_sha256;
const records = [
...input.evidence_bundle.records,
nativeEvidence,
javascriptCliEvidence,
electronCliEvidence,
].map(parseEvidence);
input.evidence_bundle = createEvidenceBundle(records);
setFixtureDigest(input, "native", nativeEvidence);
setFixtureDigest(input, "javascript-cli", javascriptCliEvidence);
setFixtureDigest(input, "electron", electronCliEvidence);
linkCheck(input, "discover-classify", "artifact-inventory", nativeEvidence);
linkCheck(input, "static-analysis", "native-routed", nativeEvidence);
linkCheck(
input,
"static-analysis",
"javascript-routed",
javascriptCliEvidence,
);
linkCheck(
input,
"reactive-scenarios",
"electron-correlated",
electronCliEvidence,
);
const readinessReport = reconstructionReadinessReportSchema.parse(
createReconstructionReadinessReport(input),
);
assert.equal(readinessReport.status, "pass");
if (process.env.REA_READINESS_REPORT_PATH !== undefined)
await writeFile(
resolve(root, process.env.REA_READINESS_REPORT_PATH),
`${JSON.stringify(readinessReport, null, 2)}\n`,
);
const tampered = structuredClone(input);
tampered.replay.expected_source_digest = readinessReport.source_digest;
tampered.identity.server_version = "tampered";
const tamperedReport = reconstructionReadinessReportSchema.parse(
createReconstructionReadinessReport(tampered),
);
assert.equal(tamperedReport.status, "fail");
assert.ok(
tamperedReport.findings.some(
({ code }) => code === "replay-source-digest-mismatch",
),
);
process.stdout.write(
`${JSON.stringify({
status: readinessReport.status,
report_id: readinessReport.report_id,
report_digest: readinessReport.report_digest,
stages: readinessReport.summary.stages,
findings: readinessReport.summary.findings,
native_evidence_id: nativeEvidence.evidence_id,
javascript_evidence_id: javascriptCliEvidence.evidence_id,
electron_evidence_id: electronCliEvidence.evidence_id,
provider: "local-stdio",
})}\n`,
);
verificationReport = {
schema: "ReconstructionReadinessVerificationReport",
schema_version: 1,
status: "pass",
observed_journeys: {
evidence_kind: "observed",
scope: "static-cli-mcp",
status: "pass",
server_identity: sessionStatus.server_identity,
evidence_bundles: {
cli: createEvidenceBundle([
nativeEvidence,
javascriptCliEvidence,
electronCliEvidence,
]),
mcp: createEvidenceBundle([javascriptMcp, electronMcp]),
},
journeys: [
{
journey_id: "native-artifact-cli",
operation: "inspect-artifact",
surfaces: ["cli"],
status: "pass",
evidence_ids: [nativeEvidence.evidence_id],
checks: ["cli-completed", "evidence-authenticated"],
},
...[
["javascript-static-cli-mcp", javascriptCliEvidence],
["electron-static-cli-mcp", electronCliEvidence],
].map(([journeyId, evidence]) => ({
journey_id: journeyId,
operation: "analyze-javascript-application",
surfaces: ["cli", "mcp"],
status: "pass",
evidence_ids: [evidence.evidence_id],
checks: [
"cli-completed",
"mcp-completed",
"evidence-authenticated",
"evidence-identity-parity",
],
})),
],
limitations: [
"Native MCP parity was not exercised.",
"Fixture application runtime behavior was not executed.",
"Authority recovery, cancellation cleanup, behavioral comparison, and reconstruction closure were not exercised.",
],
},
evaluator_conformance: verifySyntheticEvaluator(),
};
} catch (cause) {
primaryError = cause;
throw cause;
} finally {
await client.close();
await closeVerifierResources(
[() => client.close(), () => transport.close()],
primaryError,
);
}
if (process.env.REA_READINESS_REPORT_PATH !== undefined)
await writeFile(
resolve(root, process.env.REA_READINESS_REPORT_PATH),
`${JSON.stringify(verificationReport, null, 2)}\n`,
);
process.stdout.write(
`${JSON.stringify({
schema: verificationReport.schema,
schema_version: verificationReport.schema_version,
status: verificationReport.status,
observed_journeys: {
status: verificationReport.observed_journeys.status,
scope: verificationReport.observed_journeys.scope,
journeys: verificationReport.observed_journeys.journeys,
limitations: verificationReport.observed_journeys.limitations,
},
evaluator_conformance: {
status: verificationReport.evaluator_conformance.status,
evidence_kind: verificationReport.evaluator_conformance.evidence_kind,
checks: verificationReport.evaluator_conformance.checks,
report_digest:
verificationReport.evaluator_conformance.baseline_report.report_digest,
},
})}\n`,
);
async function runCli(arguments_) {
const { stdout } = await execute(
process.execPath,
@@ -166,32 +204,50 @@ async function analyzeMcp(client_, inputPath) {
arguments: { input_path: inputPath },
});
assert.notEqual(response.isError, true);
const projected = requireObject(response.structuredContent);
return { evidence_id: projected.evidence_id };
}
function requireObject(value) {
if (typeof value !== "object" || value === null || Array.isArray(value))
throw new TypeError("Expected an object");
return value;
}
function setFixtureDigest(input, kind, evidence) {
const fixture = input.fixtures.find(
({ kind: fixtureKind }) => fixtureKind === kind,
toolContract("analyze_javascript_application").outputSchema.parse(
response.structuredContent,
);
if (fixture === undefined || evidence.subject === null)
throw new TypeError(`Missing ${kind} fixture identity`);
fixture.artifact_sha256 = evidence.subject.digest.sha256;
return parseEvidence(response.structuredContent);
}
function linkCheck(input, stageId, checkId, evidence) {
const stage = input.stages.find(({ stage_id: id }) => id === stageId);
const check = stage?.checks.find(({ check_id: id }) => id === checkId);
if (stage === undefined || check === undefined)
throw new TypeError(`Missing readiness check ${stageId}/${checkId}`);
stage.evidence_ids = [
...new Set([...stage.evidence_ids, evidence.evidence_id]),
].sort();
check.evidence_ids = [evidence.evidence_id];
function verifySyntheticEvaluator() {
const input = structuredClone(RECONSTRUCTION_READINESS_EXAMPLE);
const evaluate = (value) =>
reconstructionReadinessReportSchema.parse(
createReconstructionReadinessReport(value),
);
const baseline = evaluate(input);
assert.equal(baseline.status, "pass");
assert.deepEqual(evaluate(structuredClone(input)), baseline);
const tampered = structuredClone(input);
const record = tampered.evidence_bundle.records[0];
assert.ok(record, "Synthetic conformance requires Evidence");
record.operation += "_tampered";
assert.throws(
() => evaluate(tampered),
/Evidence semantic identifier does not match its record/u,
);
const stale = structuredClone(input);
stale.replay.expected_source_digest = baseline.source_digest;
stale.identity.server_version += "-changed";
const staleReport = evaluate(stale);
assert.equal(staleReport.status, "fail");
assert.ok(
staleReport.findings.some(
({ code }) => code === "replay-source-digest-mismatch",
),
);
return {
evidence_kind: "synthetic",
status: "pass",
checks: [
"baseline-accepted",
"deterministic-evaluation",
"tampered-evidence-rejected",
"stale-source-rejected",
].map((checkId) => ({ check_id: checkId, status: "pass" })),
baseline_report: baseline,
};
}
+55
View File
@@ -100,6 +100,12 @@ const checkObjc = (metadata, mode) => {
const protocol = metadata.objc_protocols.find(
({ name }) => name === "ReaDispatchProtocol",
);
const protocolProperty = protocol?.properties.find(
({ name }) => name === "fixtureName",
);
assert.equal(protocolProperty?.name, "fixtureName");
assert.equal(protocolProperty?.type_encoding, '@"NSString"');
assert.equal(protocolProperty?.atomicity, "nonatomic");
assert.equal(protocol?.methods[0]?.is_required, true);
assert.equal(protocol?.methods[0]?.is_optional, false);
assert.equal(protocol?.optional_methods[0]?.is_required, false);
@@ -161,6 +167,31 @@ const checkObjc = (metadata, mode) => {
const checkSwift = (metadata, mode) => {
assert.equal(fixupCoverage(metadata)?.status, "complete");
for (const context of ["ReaNestedA", "ReaNestedB"]) {
const type = `${context}.Implementation`;
const protocol = `${context}.Requirement`;
assert.ok(
metadata.swift_conformances.some(
(item) =>
item.type_name === type &&
item.protocol_name === protocol &&
item.module === "ReaWitnessFixture" &&
item.decode.status === "decoded",
),
`Nested Swift conformance ${type}: ${protocol} missing (${mode.mode})`,
);
assert.ok(
metadata.swift_dispatch_slots.some(
(item) =>
item.table_kind === "witness_table" &&
item.owner === `${type}: ${protocol}` &&
item.implementation_address !== null &&
item.decode.status === "decoded",
),
`Nested Swift witness ${type}: ${protocol} missing (${mode.mode})`,
);
}
if (
!metadata.swift_dispatch_slots.some(
(slot) =>
@@ -204,6 +235,10 @@ const EXPECTED_SWIFT_DECLS = [
["struct", "ReaDeclFixtureOuter.Inner", false],
["struct", "ReaDeclFixtureGeneric", true],
["actor", "ReaDeclFixtureActor", false],
["enum", "ReaNestedA", false],
["struct", "ReaNestedA.Implementation", false],
["enum", "ReaNestedB", false],
["struct", "ReaNestedB.Implementation", false],
];
/** Stable declaration facts compared across the symbols and stripped builds. */
@@ -336,6 +371,26 @@ try {
await checkSwiftSymbolAddresses(swiftPath, declarations);
unstrippedDeclarations = declarations;
} else assert.deepEqual(declarations, unstrippedDeclarations);
if (mode.mode === "chained" && variant === "stripped") {
const cli = await artifactCliEvidence(
"inspect-native-dispatch-metadata",
swiftPath,
);
const cliResult = nativeDispatchMetadataResultSchema.parse(
cli.normalized_result,
);
assert.equal(cliResult.target_sha256, target.sha256);
checkSwift(cliResult.result, mode);
await withArtifactMcp(swiftPath, async (client) => {
const mcpResult = nativeDispatchMetadataResultSchema.parse(
await artifactMcpResult(client, "inspect_native_dispatch_metadata"),
);
assert.equal(mcpResult.target_sha256, target.sha256);
checkSwift(mcpResult.result, mode);
assert.deepEqual(mcpResult.result, cliResult.result);
});
}
results.push({
variant: `swift-${mode.mode}-${variant}`,
target_sha256: target.sha256,
@@ -452,3 +452,48 @@ it("classifies source state changes during snapshot copying as artifact changes"
await commands.close();
}
});
it.each(["EACCES", "EPERM", "EIO", "ENOENT"] as const)(
"preserves the %s acquisition failure classification before snapshot admission",
async (code) => {
const root = await PrivateRuntimeRoot.create({
prefix: "rea-apktool-admission-test-",
});
const commands = new ApktoolCommandCleanup();
const workspace = new ApktoolWorkspace(root, commands);
const input = join(root.path, "source.apk");
await fs.writeFile(input, "authored-apk");
const failure = Object.assign(new Error("authored source open failure"), {
code,
});
vi.mocked(fs.open).mockImplementation(async (path, flags, mode) => {
if (String(path) === input) throw failure;
return await actual.open(path, flags, mode);
});
try {
const result: unknown = await workspace
.snapshotInput(input, "decode_android_resources")
.catch((cause: unknown) => cause);
if (code === "EIO") expect(result).toBe(failure);
else
expect(result).toMatchObject({
_tag:
code === "ENOENT"
? "AnalysisInputError"
: "AnalysisAccessDeniedError",
operation: "decode_android_resources",
cause: failure,
...(code === "ENOENT" ? {} : { path: input, systemCode: code }),
});
expect(workspace.snapshot).toBeUndefined();
await expect(fs.stat(join(root.path, "input.apk"))).rejects.toMatchObject(
{
code: "ENOENT",
},
);
} finally {
await workspace.close();
await commands.close();
}
},
);
+29 -8
View File
@@ -10,16 +10,21 @@ import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { DirectoryArtifactReader } from "../artifacts/DirectoryArtifactReader.js";
import {
assertArtifactReadActive,
artifactSelectedPathErrno,
mapArtifactFileFailure,
withArtifactFileHandle,
withArtifactRegularFile,
} from "../artifacts/ArtifactFileOperation.js";
import {
AnalysisInputError,
AnalysisAccessDeniedError,
AnalysisArtifactChangedError,
AnalysisProtocolError,
} from "../domain/analysisErrorCore.js";
import { sameRegularFileState } from "../filesystem/RegularFile.js";
import {
NonRegularFileReadError,
sameRegularFileState,
} from "../filesystem/RegularFile.js";
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
import type { ApktoolCommandCleanup } from "./ApktoolCommandCleanup.js";
@@ -132,13 +137,29 @@ export class ApktoolWorkspace {
"follow",
);
} catch (cause) {
throw mapArtifactFileFailure(cause, (physical) =>
admitted ||
(physical instanceof ArtifactReaderFailure &&
physical.reason === "cancelled")
? physical
: new AnalysisInputError(operation, { cause: physical }),
);
throw mapArtifactFileFailure(cause, (physical) => {
if (admitted) return physical;
if (
physical instanceof Error &&
"code" in physical &&
(physical.code === "EACCES" || physical.code === "EPERM")
)
return new AnalysisAccessDeniedError(operation, path, physical.code, {
cause: physical,
});
if (
physical instanceof NonRegularFileReadError ||
artifactSelectedPathErrno(physical) !== undefined
)
return new AnalysisInputError(operation, { cause: physical }, [
{
path: ["path"],
reason: "invalid_value",
message: "APK input must resolve to an accessible regular file",
},
]);
return physical;
});
}
}
+26
View File
@@ -75,11 +75,17 @@ import {
type ExecutableMetadata,
} from "../domain/binaryTarget.js";
import {
classifyFatContainerIdentity,
readFatContainerHeader,
readMachoHeaderPrefixFacts,
type FatSliceHeaderEvidence,
type MachoHeaderPrefixFacts,
} from "../domain/apple/machoContainer.js";
import {
isJvmClassFile,
MAX_JVM_CLASS_IDENTITY_BYTES,
JVM_CLASS_IDENTITY_LIMIT_REASON,
} from "../domain/apple/jvmClassFile.js";
import {
MachoLoadCommandCursor,
readMachoLoadCommandTableHeader,
@@ -865,12 +871,32 @@ const readExecutableMetadata = async (
signal,
});
if (thin !== null) return thin;
const hasJvmMagic = bytes.length >= 4 && bytes.readUInt32BE(0) === 0xcafebabe;
if (
hasJvmMagic &&
fileSize > bytes.length &&
fileSize <= MAX_JVM_CLASS_IDENTITY_BYTES
) {
const complete = await readHandleRange(handle, 0, fileSize, signal);
if (complete.byteLength === fileSize && isJvmClassFile(complete))
return err("unsupported binary format");
}
const fatEvidence = await readFatSliceHeaderEvidence(
handle,
bytes,
fileSize,
signal,
);
if (
hasJvmMagic &&
fileSize > MAX_JVM_CLASS_IDENTITY_BYTES &&
fatEvidence !== undefined &&
classifyFatContainerIdentity(
fatEvidence.header,
(offset) => fatEvidence.sliceHeadersByOffset.get(offset)?.magic,
).state === "malformed"
)
return err(JVM_CLASS_IDENTITY_LIMIT_REASON);
const parsed = parseExecutableHeader(
bytes,
hostArchitecture,
@@ -17,6 +17,7 @@ import {
import { BrowserObservationError } from "../domain/browserObservationError.js";
import { err, ok } from "../domain/result.js";
import { passiveNetworkCapture } from "../../tests/fixtures/passiveNetworkCapture.js";
import { analyzeWebBundleFixture } from "../../tests/support/browser/analyzeWebBundleFixture.js";
import { inspectWebPage } from "./BrowserObservationService.js";
import { analyzeWebBundleInputSchema } from "../domain/webBundleAnalysis.js";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";
@@ -163,11 +164,9 @@ it("retains captured page Evidence when bundle analysis fails after capture", as
});
it("retains completed bundle facts as bundle Evidence after cancellation", async () => {
const { analyzeCapturedWebBundle } =
await import("../domain/webBundleAnalyzer.js");
const { AnalysisCancelledError } =
await import("../domain/analysisErrorCore.js");
const bundle = analyzeCapturedWebBundle(passiveNetworkCapture());
const bundle = analyzeWebBundleFixture(passiveNetworkCapture());
const reason = { requested: "stop after completed script" };
const failure = new AnalysisCancelledError("analyze_web_bundle", {
cause: reason,
+1 -1
View File
@@ -529,7 +529,7 @@ const workflowAvailabilityFor = ({
};
if (name === "inspect_pe_resources" || name === "inspect_native_exports")
return { reason: "available", remediation: null };
if (name === "inspect_pe_certificates")
if (name === "inspect_pe_certificates" || name === "inspect_pe_imports")
return { reason: "available", remediation: null };
if (name === "inspect_elf_notes")
return { reason: "available", remediation: null };
+46 -19
View File
@@ -21,22 +21,36 @@ const tomlTimeoutSchema = z.union([
z.bigint().positive(),
]);
/** Common launcher facts, without assigning a shared meaning to client policy. */
const registrationSchema = z
const launcherSchema = z
.object({
command: z.string().min(1),
args: z.array(z.string()).default([]),
startup_timeout_sec: tomlTimeoutSchema.optional(),
tool_timeout_sec: tomlTimeoutSchema.optional(),
startupTimeoutMs: z.number().positive().optional(),
toolTimeoutMs: z.number().positive().optional(),
type: z.string().optional(),
transport: z.string().optional(),
serverName: z.string().optional(),
disabled: z.boolean().optional(),
enabled: z.boolean().optional(),
})
.passthrough();
const opencodeLauncherSchema = z
.object({
type: z.literal("local"),
command: z.array(z.string()).min(1),
})
.passthrough();
const normalizeOpencodeLauncher = (
entry: z.output<typeof opencodeLauncherSchema>,
): z.output<typeof launcherSchema> => {
const [command = "", ...args] = entry.command;
return { ...entry, command, args };
};
const registrationSchema = launcherSchema.extend({
startup_timeout_sec: tomlTimeoutSchema.optional(),
tool_timeout_sec: tomlTimeoutSchema.optional(),
startupTimeoutMs: z.number().positive().optional(),
toolTimeoutMs: z.number().positive().optional(),
type: z.string().optional(),
transport: z.string().optional(),
serverName: z.string().optional(),
disabled: z.boolean().optional(),
enabled: z.boolean().optional(),
});
export type ClientRegistration = z.output<typeof registrationSchema>;
@@ -76,6 +90,22 @@ const registrationError = (error: z.ZodError): ConfigurationError =>
})),
});
/** Launcher identity remains usable when unrelated readiness settings are invalid. */
export const parseClientRegistrationLauncher = (
value: unknown,
client: SetupClient,
): Result<{ command: string; args: string[] }, ConfigurationError> => {
const parsed =
client.format === "opencode"
? opencodeLauncherSchema
.transform(normalizeOpencodeLauncher)
.safeParse(value)
: launcherSchema.safeParse(value);
return parsed.success
? ok({ command: parsed.data.command, args: parsed.data.args })
: err(registrationError(parsed.error));
};
/** Policy belongs to the client dialect, and is validated before refresh writes. */
const validateClientPolicy = (
value: unknown,
@@ -109,17 +139,14 @@ export const parseClientRegistration = (
: err(registrationError(parsed.error));
}
if (client.format === "opencode") {
const entry = z
.object({
type: z.literal("local"),
command: z.array(z.string()).min(1),
const entry = opencodeLauncherSchema
.extend({
environment: z.record(z.string(), z.string()).optional(),
})
.passthrough()
.transform(normalizeOpencodeLauncher)
.safeParse(value);
if (!entry.success) return err(registrationError(entry.error));
const [command = "", ...args] = entry.data.command;
value = { ...entry.data, command, args };
value = entry.data;
}
const parsed = registrationSchema.safeParse(value);
if (!parsed.success) return err(registrationError(parsed.error));
@@ -182,7 +209,7 @@ export const refreshClientRegistration = (
const existing = source.success ? source.data : {};
const policy = validateClientPolicy(existing, client);
if (!policy.ok) return policy;
const prior = parseClientRegistration(existing, client);
const prior = parseClientRegistrationLauncher(existing, client);
const priorCommand = prior.ok
? [prior.value.command, ...prior.value.args]
: undefined;
+47 -13
View File
@@ -179,17 +179,23 @@ export const retryRegularFileCleanupFailures = async (
const failureMessage = (cause: unknown): string =>
cause instanceof Error ? cause.message : String(cause);
/** Admit one regular-file handle, retain it through the consumer, and always close it. */
export const withRegularFile = async <Value>(
/** Completed stable read facts survive cancellation during descriptor finalization. */
export interface RegularFileCompletion<Value> {
readonly value: Value;
readonly cancellation?: { readonly reason: unknown } | undefined;
}
/** Admit one regular-file handle, retaining completion through close and cleanup retry. */
export const withRegularFileCompletion = async <Value>(
path: string,
read: (handle: FileHandle, stats: Stats) => Promise<Value>,
options: RegularFileReadOptions = {},
): Promise<Value> => {
): Promise<RegularFileCompletion<Value>> => {
const { signal, resources } = options;
let admitted: FileHandle | undefined;
const operation = async () => {
const operation = async (): Promise<RegularFileCompletion<Value>> => {
try {
return await withOwnedRegularFile(
const value = await withOwnedRegularFile(
path,
{
...(signal === undefined ? {} : { signal }),
@@ -214,6 +220,7 @@ export const withRegularFile = async <Value>(
return value;
},
);
return { value };
} catch (cause: unknown) {
// Caller reasons are opaque, including objects shaped like file failures.
// They do not transfer ownership of any descriptor to this operation.
@@ -253,8 +260,16 @@ export const withRegularFile = async <Value>(
throw cause.operationFailure;
throw retry.outcome.cause;
}
return retry.outcome.value;
return {
value: retry.outcome.value as Value,
cancellation: regularFileCancellation(cause),
};
}
if (cause instanceof RegularFileCancelled)
return {
value: cause.outcome.value as Value,
cancellation: { reason: cause.reason },
};
const cancellation = regularFileCancellation(cause);
if (cancellation !== undefined) {
if (options.cancellationFailure !== undefined)
@@ -266,15 +281,34 @@ export const withRegularFile = async <Value>(
throw cause;
}
};
const value = await (resources === undefined
const completion = await (resources === undefined
? operation()
: resources.run(operation));
// A retained close may have recovered after the descriptor owner's outcome
// was captured. Include that retry and the scope's finalization in cancellation.
if (signal?.aborted === true && options.cancellationFailure !== undefined)
throw options.cancellationFailure(signal.reason);
signal?.throwIfAborted();
return value;
// Include successful cleanup retries and scope finalization in cancellation.
return completion.cancellation !== undefined || signal?.aborted !== true
? completion
: { ...completion, cancellation: { reason: signal.reason } };
};
/** Read consumers without completion facts retain their existing cancellation contract. */
export const withRegularFile = async <Value>(
path: string,
read: (handle: FileHandle, stats: Stats) => Promise<Value>,
options: RegularFileReadOptions = {},
): Promise<Value> => {
const completion = await withRegularFileCompletion(path, read, options);
const cancellation =
completion.cancellation ??
(options.signal?.aborted === true
? { reason: options.signal.reason }
: undefined);
if (cancellation !== undefined) {
const { reason } = cancellation;
if (options.cancellationFailure !== undefined)
throw options.cancellationFailure(reason);
throw reason;
}
return completion.value;
};
/** Admit a regular file without waiting for a pipe, then read its verified handle. */
+4 -19
View File
@@ -22,7 +22,7 @@ import { defaultKunaCacheDirectory } from "../config/kunaCacheDirectory.js";
import { linuxHopperReleaseCacheDirectory } from "./LinuxHopperCacheDirectory.js";
import { ClientConfigurationPublication } from "./ClientConfigurationPublication.js";
import { z } from "zod";
import { parseClientRegistrationLauncher } from "./ClientRegistration.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import {
@@ -734,21 +734,9 @@ const resolveUninstallConfigPath = async (
/** Ownership rule for persistent MCP registrations created by REA setup. */
const isOwnedRegistration = (value: unknown, client: SetupClient): boolean => {
if (client.format === "opencode") {
const parsed = z
.object({ type: z.literal("local"), command: z.array(z.string()).min(1) })
.passthrough()
.safeParse(value);
if (!parsed.success) return false;
const command = parsed.data.command;
return (
classifyClientRegistrationCommand(command, CURRENT_ENTRYPOINT_PATH) ===
"owned"
);
}
const parsed = registrationSchema.safeParse(value);
if (!parsed.success) return false;
const { command, args } = parsed.data;
const parsed = parseClientRegistrationLauncher(value, client);
if (!parsed.ok) return false;
const { command, args } = parsed.value;
return (
classifyClientRegistrationCommand(
[command, ...args],
@@ -1183,8 +1171,5 @@ const uninstallStatusPriority = (status: UninstallItem["status"]): number => {
}
}
};
const registrationSchema = z
.object({ command: z.string(), args: z.array(z.string()) })
.passthrough();
const isMissing = (cause: unknown): boolean =>
cause instanceof Error && "code" in cause && cause.code === "ENOENT";
@@ -0,0 +1,49 @@
import { expect, it } from "vitest";
import { ElfNotesService } from "./ElfNotesService.js";
import { PeCertificatesService } from "./PeCertificatesService.js";
import { PeImportsService } from "./PeImportsService.js";
import {
ELF_NOTES_PROVIDER,
PE_CERTIFICATES_PROVIDER,
PE_IMPORTS_PROVIDER,
} from "../../domain/investigationProviders.js";
const inspect = (): never => {
throw new Error("An already cancelled request must not reach its provider");
};
it.each([
[
"PE imports",
() => new PeImportsService({ identity: PE_IMPORTS_PROVIDER, inspect }),
],
[
"PE certificates",
() =>
new PeCertificatesService({
identity: PE_CERTIFICATES_PROVIDER,
inspect,
}),
],
[
"ELF notes",
() => new ElfNotesService({ identity: ELF_NOTES_PROVIDER, inspect }),
],
] as const)(
"preserves an early cancellation reason for %s before validation",
async (_label, create) => {
const service = create();
try {
for (const reason of [null, { caller: "selected task" }]) {
const result = await service.inspect(null, {
signal: AbortSignal.abort(reason),
});
if (result.ok) throw new Error("Expected early cancellation");
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(result.error.cause).toBe(reason);
}
} finally {
await service.close();
}
},
);
@@ -38,6 +38,70 @@ it("keeps whole-file identity and entropy while selecting unaligned blocks and a
expect(result.value.confidence).toBe("observed");
});
it.each([256, 4096, 65536])(
"keeps %i-byte blocks relative to an unaligned window across short physical reads",
async (blockSize) => {
const root = await createTestTempDirectory("rea-entropy-short-reads-");
const path = join(root, "bytes");
const bytes = Buffer.from(
Array.from({ length: 3 * 65536 + 256 }, (_, i) => i % 256),
);
await writeFile(path, bytes);
let reads = 0;
const service = new ByteEntropyService({
stat,
open: async (...args) => {
const file = await open(...args);
const read = file.read.bind(file);
Object.defineProperty(file, "read", {
value: async (
buffer: Buffer,
offset: number,
length: number,
position: number,
) => {
reads += 1;
return read(buffer, offset, Math.min(length, 3571), position);
},
});
return file;
},
});
try {
const result = await service.inspect({
path,
offset: 3,
length: 2 * 65536 + 1,
block_size: blockSize,
});
if (!result.ok) throw result.error;
const report = byteEntropySchema.parse(result.value.normalized_result);
const fullBlocks = (2 * 65536) / blockSize;
expect(report.blocks).toEqual([
...Array.from({ length: fullBlocks }, (_, i) => ({
offset: 3 + i * blockSize,
length: blockSize,
entropy: 8,
})),
{ offset: 3 + 2 * 65536, length: 1, entropy: 0 },
]);
expect(report.runs).toEqual([
{ offset: 3, length: 2 * 65536, classification: "high" },
{ offset: 3 + 2 * 65536, length: 1, classification: "low" },
]);
expect(report.file_entropy).toBe(8);
expect(report.artifact.sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
// Even with 3,571-byte short reads, finer report resolution does not
// require hundreds of asynchronous reads of this 193 KiB file.
expect(reads).toBeLessThan(100);
} finally {
await service.close();
}
},
);
it("rejects missing files, non-files and windows beyond EOF, but accepts empty windows", async () => {
const root = await createTestTempDirectory("rea-entropy-input-");
const path = join(root, "empty");
@@ -241,7 +241,10 @@ export class ByteEntropyService {
);
const end = input.offset + length;
const blocks: ByteEntropy["blocks"] = [];
const buffer = Buffer.alloc(input.block_size);
// Physical reads must not shrink with report resolution, including
// outside the selected window. Every admitted power-of-two block
// size divides this buffer, so only the window's final block is short.
const buffer = Buffer.alloc(65_536);
const counts = new Float64Array(256);
const hash = createHash("sha256");
let position = 0;
@@ -250,7 +253,7 @@ export class ByteEntropyService {
const selected = position >= input.offset && position < end;
const boundary =
position < input.offset ? input.offset : selected ? end : size;
const wanted = Math.min(input.block_size, boundary - position);
const wanted = Math.min(buffer.length, boundary - position);
let filled = 0;
while (filled < wanted) {
assertArtifactReadActive(options?.signal);
@@ -267,11 +270,18 @@ export class ByteEntropyService {
hash.update(bytes);
for (const byte of bytes) counts[byte] = (counts[byte] ?? 0) + 1;
if (selected)
blocks.push({
offset: position,
length: filled,
entropy: computeBlockEntropy(bytes),
});
for (
let offset = 0;
offset < filled;
offset += input.block_size
) {
const block = bytes.subarray(offset, offset + input.block_size);
blocks.push({
offset: position + offset,
length: block.length,
entropy: computeBlockEntropy(block),
});
}
position += filled;
}
if (
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff. Show more