mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 22:06:10 +00:00
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:
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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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). |
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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) |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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,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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
Generated
+2205
File diff suppressed because it is too large.
Load diff
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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(
|
||||
(
|
||||
|
||||
@@ -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>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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]));
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
Reference in new issue
Block a user