feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)

* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
This commit is contained in:
morluto authored and GitHub committed 2026-11-10 05:11:20 -05:00
1 parent 55b6d2ca20
commit cb4e5cfbff
515 files changed
+21066 -3637

No files matched your search

+4
View File
@@ -111,6 +111,10 @@ product pages. The VitePress documentation source is this `docs/` tree.
## Development and verification
- [Settings and recovery](design/limits-and-recovery.md)
- [Flutter budget calibration](verification/flutter-budget-calibration.md)
- [Source-map budget calibration](verification/source-map-budget-calibration.md)
- [Source-location delivery qualification](verification/source-location-delivery.md)
- [JavaScript precision calibration](verification/javascript-precision-calibration.md)
- [MCP tool design](development/tool-design.md)
- [Testing REA](development/testing.md)
- [Client and agent verification](development/client-verification.md)
+18 -3
View File
@@ -24,7 +24,7 @@ entry-point status, sizes and SHA-256. Sourcemaps, bytecode and module-info regi
are separate children. Source names such as `/$bunfs/root/main.js` remain in
metadata; extraction uses a relative path. Unsafe or non-UTF-8 names receive an
indexed path and retain their source spelling or bytes. Virtual names deeper
than 256 logical path parts also use an indexed path with an explicit limitation,
than the selected `path_parts` allowance (default 256) also use an indexed path with an explicit limitation,
preventing unbounded path-tree work while retaining the source fact. Duplicate names remain
separate occurrences; ordinary extraction collision rules apply.
@@ -43,12 +43,27 @@ compatible source files; bytecode is retained as evidence, not decoded.
Module parsing reads exact metadata ranges rather than loading stored asset
bodies or whole pages around small pointers. A cache retains at most 256 KiB
and 1,024 ranges for one reader preparation. Cumulative uncached metadata reads
are limited to 256 MiB, checked before allocation or I/O. The module table,
default to 256 MiB, checked before allocation or I/O. The module table,
decoded strings and combined logical path tree have separate admission checks;
at most 250,000 distinct path prefixes are admitted before inventory construction.
by default at most 250,000 distinct path prefixes are admitted before inventory construction.
Stored bodies are range-streamed for hashing and extraction. Header/suffix observations are reused
only for the same admitted file state, with selected-path checks before publication.
Both artifact operations accept `artifact_limits.bun`. Its independent controls
are `payload_bytes`, `module_count`, `representation_bytes`, `metadata_read_bytes`,
`recognition_table_bytes`, `recognition_load_command_bytes`, `path_parts` and
`path_nodes`. Failed recognition remains explicitly limited instead of proving
absence. ELF section names are examined at their actual offsets, including names
beyond a large section-name prefix. Select `artifact_limits.expanded_bytes` to
change the aggregate referenced-plaintext work allowance.
For a reported metadata-read refusal, keep the same carrier and retry with
`--artifact-limits '{"bun":{"metadata_read_bytes":536870912}}'` or the equivalent
MCP `artifact_limits` object. Omitted fields keep defaults. The response names
the effective limit and exact repair; accepted overrides are reported separately
from caller selections. Changes apply on the next call without restarting REA.
Summary and selected extraction do not change the producer's admission.
See [limits and recovery](design/limits-and-recovery.md) for admission ceilings.
Synthetic tests cover the carriers and malformed declarations. Run
`node scripts/verify-bun-standalone.mjs` after `npm run build:cached` for local Bun
+105 -22
View File
@@ -172,17 +172,20 @@ checks all happen before publishing the replacement snapshot; a rejected batch
cannot partially apply. Clients coordinating writes can supply
`expected_revision`; without it, valid updates apply to the current state.
| Runtime setting | Application point |
| ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `asar.max_header_bytes` | Next archive operation, including full JavaScript application analysis |
| `ghidra.startup_timeout_ms`, `hopper.startup_timeout_ms` | Next provider startup attempt |
| `ghidra.decompile_timeout_seconds` | Next operation on the existing Ghidra Program; `null` means unbounded |
| `jadx.operation_timeout_ms` | Next JADX operation |
| `regex.matching_deadline_ms`, `regex.worker_heap_request_mib` | Next search / new regex worker |
| `capture.decoder_old_generation_mib` | Next owned capture decoder |
| `javascript.analysis_timeout_ms`, `javascript.max_heap_mb` | Next source analysis / new JavaScript worker |
| `wasm.command_timeout_ms` | Next WABT operation |
| `native.matching_representation_bytes` | Next `match_native_functions` request |
| Runtime setting | Application point |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `asar.max_header_bytes` | Next archive operation, including full JavaScript application analysis |
| `asar.max_listing_bytes` | Next expanded ASAR listing, independently of packed header bytes |
| `ghidra.startup_timeout_ms`, `hopper.startup_timeout_ms` | Next provider startup attempt |
| `ghidra.decompile_timeout_seconds` | Next operation on the existing Ghidra Program; `null` means unbounded |
| `ghidra.decompile_max_payload_mib` | Next native decompile; `null` preserves the selected provider's payload default |
| `ghidra.value_flow_max_operations`, `ghidra.value_flow_max_inputs_per_operation`, `ghidra.value_flow_max_edges` | Next function value-flow projection on the existing Program |
| `jadx.operation_timeout_ms` | Next JADX operation |
| `regex.matching_deadline_ms`, `regex.worker_heap_request_mib` | Next search / new regex worker |
| `capture.decoder_old_generation_mib` | Next owned capture decoder |
| `javascript.analysis_timeout_ms`, `javascript.max_heap_mb` | Next source analysis / new JavaScript worker |
| `wasm.command_timeout_ms` | Next WABT operation |
| `native.matching_representation_bytes` | Next `match_native_functions` request |
### Precedence and lifetime
@@ -202,6 +205,48 @@ for an unknown or omitted value elsewhere.
A write does not extend active work, relaunch a provider, clear retained
cleanup, or invalidate successful Evidence.
Expanded ASAR listing admission defaults to 64 MiB and has its own launch seed,
`REA_ASAR_MAX_LISTING_BYTES`. Raising `asar.max_header_bytes` alone cannot admit
expanded paths and entries that exceed this separate budget. The listing bound
uses exact safe-integer byte accounting; the host heap and transport constraints
remain independent. A lower runtime value is enforced on the next listing even
when the archive header was already examined.
Ghidra value-flow controls default to 3,000 operations, 64 inputs per operation,
and 12,000 edges. They accept positive Java integer counts and use the respective
`REA_GHIDRA_VALUE_FLOW_MAX_OPERATIONS`,
`REA_GHIDRA_VALUE_FLOW_MAX_INPUTS_PER_OPERATION`, and
`REA_GHIDRA_VALUE_FLOW_MAX_EDGES` launch seeds. Truncation names the exhausted
control. Completed Evidence remains accessible, but a new function dossier is
observed with the accepted controls instead of replaying an earlier projection.
The native decompiler payload control is separate from those projections and
from decompile time. Its launch seed is
`REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB`. Accepted explicit values are 1..2047 MiB:
the supported Ghidra implementation shifts that value into a signed integer byte
capacity. Omission or runtime `null` keeps the provider's own default; REA does
not guess that default from a different provider version. Changing payload
options takes effect before the next native decompile.
### Per-call resource policies and selection
An agent can also use controls declared directly by an operation. These do not
require a runtime setter or restart. Keep omitted fields omitted in Evidence;
report the resolved policy separately where needed. See the owning guides for
[Apktool string pages](../guides/android/apktool-resource-analysis.md),
[source maps](../guides/web/web-source-location.md),
[historical captures](../guides/web/web-network-captures.md),
[JavaScript precision](../guides/javascript/javascript-application-graph.md),
[managed inspection](../guides/managed/managed-code-analysis.md), and
[Frida capture](../guides/runtime/frida-instrumentation.md).
The same accepted policy must reach acquisition, parsing, worker requests,
serialization, cached-result admission and generated consumers where those
stages exist. Desktop state views and emitted AgentEnv Python share one policy;
delivery pagination never substitutes for producer selection. Capture limits
must be selected before an experiment: another experiment cannot recover bytes
that were dropped from a previous live observation.
Overrides belong to one REA runtime, survive target close/open, and disappear
when REA exits. Connections sharing that runtime share its overrides. No files,
ambient environment, registrations or upstream software are edited. Overrides
@@ -419,17 +464,55 @@ editable setting or active worker telemetry.
## Bun standalone module graphs
`MAX_BUN_MODULE_COUNT` admits at most 100,000 module records.
`MAX_BUN_RETAINED_BYTES` admits at most 256 MiB of serialized graph payload and
separately bounds retained entry metadata. Physical metadata read work is also
limited to 256 MiB per preparation, including repeated uncached ranges. A
256 KiB / 1,024-entry exact-range cache avoids full-page amplification. The
combined logical path tree admits at most 250,000 distinct prefixes; this is
checked before adding nodes, with shared prefixes counted once. The shared
artifact expansion budget also bounds the sum of referenced member lengths. Refusals name the boundary.
There is no runtime override; additional host memory or `NODE_OPTIONS` does not
raise these ceilings. Use a smaller producer fixture or a separately reviewed
reader for larger graphs. No partial extraction is claimed on admission failure.
`inspect_artifact` and `extract_artifact` accept per-call `artifact_limits`.
The `bun` group defaults to 100,000 modules, independent 256 MiB payload,
retained-representation and physical metadata-read allowances, 256 path parts
and 250,000 logical path nodes. Recognition separately admits a 1 MiB ELF
section table and 16 MiB Mach-O load commands. Exhausted recognition remains
unknown rather than proving the carrier has no Bun graph. Inventory preserves
native-root facts or another confirmed pack and reports the exact unexamined
alternative inline; it does not assert Bun presence. ELF section names
are examined with exact bounded reads, including names beyond the former
prefix window.
For example, keep the same complete carrier and retry a reported recognition
limitation with `{"artifact_limits":{"bun":{"recognition_table_bytes":2097152}}}`.
The CLI spelling is `--artifact-limits '{"bun":{"recognition_table_bytes":2097152}}'`.
No REA restart is needed. A smaller later selection is re-admitted; previously
parsed facts cannot bypass it. The 256 KiB / 1,024-entry exact-range cache
avoids full-page amplification and remains an internal optimization, not an agent
resource setting. Shared logical path prefixes count once before nodes are added.
## Artifact reader admission
The same request provides `dotnet_bundle`, `node_sea` and `godot` groups. Their
controls apply when recognition or inventory encounters that format; supplying
a policy does not assert the format is present. Unsupported keys are rejected.
Each group follows one domain-owned default policy. See [.NET bundles](../dotnet-bundles.md),
[Node SEA](../node-sea.md), [Godot PCK](../godot-pck.md) and
[Bun standalone](../bun-standalone.md) for format semantics.
`artifact_limits.expanded_bytes` selects aggregate plaintext work for ZIP, Bun,
SEA and PCK readers, including repeated readable logical occurrences. Omission
retains each archive's encoded-size-derived default. The .NET bundle group's
own `expanded_bytes` controls its independently enforced bundle expansion.
Failures preserve `details.reader_limit` with the exact parameter, effective
value, units, supported range, required value when known, next-call scope and
`can_increase`. When `can_increase` is true, raise that field and retry the same
complete artifact. At the supported maximum, or when the required representation
exceeds it, the failure is not retryable by increasing that budget; inspect the
declared dimensions and input validity, or explicitly choose a suitable reader.
Summary, pagination and
selected extraction do not bypass producing admission. Intrinsic format,
safe-integer, path validity and runtime representation constraints remain fixed.
Evidence parameters retain the exact selected object, including omission or
`{}`. When an object was selected, inventory and extraction report the resolved
`effective_limits`; summaries preserve those facts. These are accepted policies,
not process-memory telemetry or a claim that every format reader ran. Complete
source identities and graph manifests remain content-addressed independently
of delivery selection. No partial extraction is claimed on admission failure.
## Deno compile sections
+17 -2
View File
@@ -290,8 +290,8 @@ found nowhere are listed in
not an ELF image (a launcher script, for example), `ldconfig`, `dpkg-query` or
`apt-cache` is missing, `dpkg-query -S` could not tell which package owns a
library (it exits 2 rather than 1, which only says no package owns the paths
it did not name), a library could not be read or names more than 512
libraries, or the walk stopped after 512 libraries. A failed owner query does
it did not name), a library could not be read or exceeds the selected dependency
inventory budget, or the walk reached that budget. A failed owner query does
not list the libraries it covered in `unpackaged_libraries`, since a lookup
that could not finish is not proof that no package owns them. Completed batches
retain their package and unowned-library facts. Paths containing package-query
@@ -428,3 +428,18 @@ evidence for attribution, not proof.
Clicks and keystrokes are not recorded. Window titles are sampled every half
second, so short-lived titles can be missed.
Dependency inventory headroom is selected for each capture with
`--library-policy '{"libraries":1024,"string_bytes":8192}'`. Unset fields retain
their defaults: `libraries: 512` bounds both the closure walk and distinct
DT_NEEDED offsets in each ELF image; `search_path_entries: 16` bounds distinct
DT_RUNPATH and DT_RPATH offsets per kind; `string_bytes: 4096` includes the NUL
terminator of each decoded string; `table_bytes: 1048576` bounds each program
header or dynamic table. Values must be positive safe integers. A budget refusal
names the field that needs headroom; partial package facts and unresolved
coverage remain visible. The policy applies during this capture's admission,
without restarting REA, and explicit selections are retained in Evidence. Raising
content retention or a worker heap does not raise these inventory budgets.
Choosing a new capture still launches the selected application; changing these
read budgets does not add execution authority or recover an earlier capture's
omitted inventory.
+15 -1
View File
@@ -560,7 +560,21 @@ or whose retained value nests more than 500 levels deep, gets an `error` instead
REA. Duplicate keys keep their last value before the depth check. Text that is not one JSON
document, such as JSON Lines, stays text. Format detection follows REA's
rules. Files larger than 64 MiB get an `error` instead of a view
and are not read. XML is read with Python's `expat` twice: with namespace
and are not read. The selected `file_bytes` also applies to scrubbed seed task
views before admission, independently of how much seed data the export can
copy. SQLite table views have a shared `sqlite_view_bytes` allowance, default
16 MiB of compact UTF-8 JSON per table, checked before retaining each row.
An oversized table keeps its error while other tables remain readable. Raising
this view allowance does not raise the independent 16 MiB native SQLite
value/record limit or REA worker reply limit; worker facts include additional
schema and typed-cell metadata and may be refused earlier. The TypeScript
and exported Python readers count their respective native compact JSON encodings;
float and exponent spelling can make their exact byte thresholds differ even
when they report equal numeric facts. The Python byte counter quotes large
strings in bounded chunks before retention.
Exported SQLite readers require Python 3.11 or later for the native connection limits; the
AgentEnv compatibility workflow uses Python 3.12.
XML is read with Python's `expat` twice: with namespace
processing, which refuses what REA's parser refuses, such as an undeclared
prefix, then without it, so element and attribute names keep the prefixes the
document spells and namespace declarations stay attributes, as in REA's view;
+59
View File
@@ -11,6 +11,65 @@ again.
rea inspect-desktop-application-state ./mousepad-capture --format json
```
Views use bounded defaults. When a view reports truncation or a byte-budget
refusal, retry the same capture with `--view-policy` JSON overrides:
```bash
rea inspect-desktop-application-state ./mousepad-capture \
--state-path /home/ada/Documents/notes.txt \
--view-policy '{"text_lines":10000,"file_bytes":134217728}' --format json
```
`--state-path` selects an exact original captured path and can be repeated.
Omitting it examines every captured path. Selection preserves the captured
checkpoint and digest; it does not substitute the file's current contents.
A path absent from the capture is an input error.
`--view-policy` accepts positive safe-integer overrides for `file_bytes`,
`sqlite_tables`, `sqlite_rows`, `sqlite_view_bytes`, `text_lines`, `xml_nodes`, `xml_depth`,
`parsed_bytes`, `json_depth`, `zip_entries`, `zip_entry_bytes`, `zip_bytes`,
and `zip_ratio`. Unspecified controls keep the defaults described below.
`xml_depth` admits at most 494 and `json_depth` at most 991: these bounds
follow the fixed Evidence JSON nesting limit, including the captured-version
and ZIP-member wrappers and XML's element/children representation. A request
above either bound is rejected before reading the capture.
Evidence parameters retain only the supplied `view_policy` overrides; omission
and an explicitly empty selection remain distinct. State inspection and task
drafts report the resolved policy separately as
`normalized_result.effective_view_policy`. Changes apply on this call, without
a restart. File and ZIP byte counts refer to retained file bytes or uncompressed
ZIP member bytes, not transport size. Gzip document views share `zip_entry_bytes`
and `zip_ratio` with ZIP entry views; `zip_bytes` bounds a ZIP archive, not a
gzip stream. These selections also apply to the decompressed document's format
view. `sqlite_view_bytes` instead bounds each
projected table's compact UTF-8 JSON, including column names, primary keys,
rows and truncation metadata; it defaults to 16 MiB. Rows are charged before
retention. A table that exceeds this allowance keeps an actionable `error`,
while other tables remain readable. Increase this control and retry the same
capture for a projected-view refusal. It does not raise the SQLite worker's
separate fixed value/record or complete-reply limits, which may refuse earlier.
Each reader counts its native compact JSON encoding. Float spelling can differ:
Python emits `16.0` where JavaScript emits `16`, and exponent spelling also
varies. Identical numeric facts can therefore cross this byte threshold at
different settings. This is not a common transport-byte guarantee.
The same option is available on `describe-desktop-application-task` and
`export-desktop-application`. Export uses the accepted policy for task
validation and emits it into the Python readers used by the environment's
`data/get`; re-export with another policy to change those reader budgets.
Task checks apply `file_bytes` to the actual scrubbed seed bytes, including any
growth from replacing the capture home with `/home/user`. A seed over that
view allowance may still be exported under the independent acquisition limit,
but its unreadable view cannot establish a known task failure.
The TypeScript and exported Python readers share the policy's defaults.
These desktop capture workflows remain CLI-only.
Raising a view budget cannot recover content the capture never retained,
change numeric exactness, enable encrypted or unsupported archive formats,
or raise SQLite worker, Evidence nesting, process-memory or transport limits.
Keep increases specific to the exhausted budget. Truncation and incomplete
comparison coverage remain visible until the requested facts were examined.
The result is Evidence with operation `inspect_desktop_application_state` and
predicate type `rea.desktop-application-state`. Its confidence is `inferred`,
because roles come from heuristics; the views themselves are read from the
+84 -7
View File
@@ -3,11 +3,12 @@
A [desktop application export](desktop-application-export.md) can carry one
task: a prompt for a computer-use agent and checks that grade what the agent
left in the application's files. An agent, not REA, writes the prompt and the
checks from the change the capture recorded. REA validates the task against
the capture and keeps it only when its checks fail on the seed and all pass on
the captured end state. REA runs no model and no code the agent wrote: the
checks are declarative, and the exported verifier evaluates them the same way
REA does.
checks from the change the capture recorded. For file checks, REA validates
the task against the capture and keeps it only when its checks fail on the
seed and all pass on the captured end state. The checks are declarative;
optional [readers](#readers) run commands to interpret files, and
[screen judges](#screen-judges) use a model when no file shows the change.
The exported verifier uses the same checks and reader runner as REA.
```sh
rea describe-desktop-application-task ./mousepad-capture --format json > draft.json
@@ -518,7 +519,7 @@ and `CIRCLE`, and only `CIRCLE` going from 5 to 9 is the user's change.
and hyphens); `prompt` is what the user asks for, naming the app, since a
solving agent's own shell cannot see the computer's files and the app's name
sends it to the screen; `checks` holds 1 to 32
checks. Each check has a `kind`, a `file` given as a path under the home such as
checks. Each file check has a `kind`, a `file` given as a path under the home such as
`Documents/todo.txt`, and an optional `id` (`check-1`, `check-2` and so on by
position otherwise). A check reads the typed view `data/get` returns for that
file, in the shapes of the state view.
@@ -589,6 +590,82 @@ value, or one of only whitespace with `normalize_whitespace`, an empty
content, such as a
counter the app bumps on every save, is for the author to leave out.
## Readers
For a file REA cannot interpret itself, such as an app's binary document,
the task can define `readers`: 1 to 8 commands using the app's own reading
capability. Each reader has a unique `id` of lowercase letters, digits and
hyphens, a home-relative `file`, an `argv` array and `output`, either `json`
or `lines`. Its `argv`, or optional `stdin` text, must contain `{file}`;
the runner replaces it with the path of a private copy of that file.
The command must be installed where the export runs and in the exported
computer where the task will be graded.
The command prints its answer in lines starting with `REA `; other stdout
is ignored. With `output: "json"`, those marked lines join into one JSON
value for `value_equals`, `value_absent` and `member_count`. With
`output: "lines"`, they supply the lines for `lines_present`, `lines_absent`
and `line_count`. A check selects a reader with `"reader": "READER-ID"`
and must give that reader's file as its `file`. Every reader must be used by
at least one check.
Readers execute commands, including any code in `argv` or `stdin`; review
them before exporting or running a task. The runner uses a private copy, a
fresh home, no session bus or computer control token, and no display unless
the reader requests `display: true`. This is execution isolation from the
running app's normal profile, not a filesystem sandbox. `timeout_s` defaults
to 60 seconds and accepts 1 to 300. A failed command, timeout, malformed
answer or missing marked output reports a reader error; it does not establish
that the document lacks the expected value. Correct the command or its output and retry; increase
`timeout_s` within its accepted range when the reader needs longer.
Export runs readers on both retained ends, using the seed bytes as the bundle
actually ships them. The witness and exported verifier run them in the
computer after the app's files settle. Cleanup failures retain the reader's
reported resources: retry the check or close the build computer to retry
cleanup; when the command's outcome is unknown, confirm it stopped before
removing its folder.
## Screen judges
A task whose result exists only on screen can use `screen_judge` checks:
```json
{
"name": "show-list",
"prompt": "In Notes, show the shopping list.",
"checks": [
{
"id": "shown",
"kind": "screen_judge",
"question": "Does the window show the shopping list?"
}
]
}
```
Each question must be a nonempty yes-or-no question of at most 2,000
characters, false on the seed and true when the task is done. A screen task
contains only screen checks and takes no readers. Export refuses it when an
owned file's readable change could be checked instead, or when the build
computer's seed or end screenshot is missing or exceeds 2 MiB. Capture those
checkpoints in a build computer before exporting.
Export ships the two screenshots as labelled failing and passing examples;
it does not call the judge or establish that the questions distinguish them.
The [build witness](desktop-build-witness.md) runs the exported judge on a
fresh screenshot at each stage. The verifier takes a screenshot after the
solving agent stops. For each question, the judge sees both examples and
votes three times; a majority of yes answers passes the check.
The judge uses `[model.roles] judge` in the runner's
`.agentenv/config.toml`, then `[model] default`, then
`anthropic/claude-sonnet-5-5`. Configure its endpoint with
`LITELLM_BASE_URL` and `LITELLM_API_KEY`, or `[model] base_url` and `api_key`.
`agent-env run --judge-model` does not reach this verifier. Without an
endpoint, grading raises and the run fails rather than scoring the agent;
configure the endpoint and rerun the witness or task.
## Pointers and selectors
An app that rewrites a document moves things: FreeCAD 1.0 records more
@@ -796,7 +873,7 @@ that may hold only part of the array.
## Validation and export
`rea export-desktop-application <capture> <directory> --task <task.json>`
evaluates the checks on the capture's state view: on each file as it was at
evaluates file checks on the capture's state view: on each file as it was at
the seed checkpoint, which is the seed, as the bundle ships it with the
capture host's name and user removed, and at the end checkpoint, the
captured end state. Those are the capture's first and last checkpoints unless
+14 -1
View File
@@ -50,7 +50,8 @@ explicitly with the managed tools to establish PE/CLI facts.
## Admission and containment
Metadata is range-read with a 64 KiB cache. Policy budgets are 16 MiB of
Metadata is range-read with a cache of at most 64 KiB, bounded by the remaining
selected metadata allowance. Default policy budgets are 16 MiB of
manifest metadata, 100,000 files, 64 KiB per UTF-8 string, 512 MiB of stored or
expanded bytes per file, 256 path parts per member, 250,000 distinct in-memory
path-tree nodes, and 2 GiB of aggregate expanded bytes. The node budget counts
@@ -60,6 +61,18 @@ streams 64 KiB chunks and reuses completed probe facts during extraction;
ordinary native files without a marker incur one extra
read pass before host hashing.
Both artifact tools accept `artifact_limits.dotnet_bundle` to select
`metadata_bytes`, `entry_count`, `string_bytes`, `member_bytes`,
`expanded_bytes`, `path_parts`, and `path_nodes` for the next operation. The
string allowance applies to every manifest string, including the bundle ID.
For example, retry a complete bundle whose manifest exceeds the default with
`{"artifact_limits":{"dotnet_bundle":{"metadata_bytes":33554432}}}`.
The CLI accepts the same selection through `--artifact-limits` JSON. Omitted
fields preserve defaults; errors name the exact selected control, effective
value and required minimum when known. No REA restart is needed. Summary
delivery and paging do not change producer admission, and larger reader
budgets do not raise MCP response or Node representation limits.
Extraction hashes the whole host again through the same descriptor used to read
members and compares it with the inventory's root digest before creating an
output directory. This adds a full-host read pass to extraction, including when
+26 -1
View File
@@ -144,9 +144,34 @@ keys, decode Godot resources, decompile GDScript bytecode, analyze signals,
apply patch/removal entries or execute targets.
Readable logical member bytes, including repeated physical ranges, share the ZIP
archive expansion policy: at most the larger of 256 MiB or 500 times the PCK's
archive expansion policy by default: at most the larger of 256 MiB or 500 times the PCK's
encoded byte span. Embedded carrier bytes do not enlarge that budget. The full
directory is checked before any member is yielded, hashed or extracted. Encrypted
members are not read and do not consume this plaintext-work allowance. Small
bounded aliases remain valid. This is a processing/output resource bound, separate
from the directory metadata retention budget and format validity.
Directory retention defaults to 64 MiB of encoded metadata. Select
`artifact_limits.godot.directory_bytes` to change it; path/source facts reserve
worst-case JSON expansion before declared path bytes are read, and the reader
then charges actual retained metadata. A preallocation `required_value` is the
allowance needed by that admission check, not a measurement of final output size.
Select `artifact_limits.expanded_bytes` independently to change aggregate
plaintext work, including repeated readable logical occurrences. Both controls
apply to inspection and extraction, before any member is yielded.
For example:
```sh
rea inspect-artifact ./game.pck --artifact-limits '{"godot":{"directory_bytes":134217728},"expanded_bytes":1073741824}' --json
```
MCP uses the same object under `artifact_limits` on `inspect_artifact` or
`extract_artifact`. A refusal reports the exact `reader_limit` parameter,
effective value, unit, supported range and required value when known. Raise the
named admission and retry the same complete PCK on the next call; no REA restart
is needed. Summary or page size does not change producer admission. Omitted
policy selections stay omitted in Evidence parameters; a selected object records
the accepted selection and `effective_limits`, conditional on which readers are
encountered. Raising these policies does not admit malformed offsets, unsupported
flags, unsafe extraction paths or unavailable encrypted contents.
@@ -66,6 +66,8 @@ rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR
rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR --string-tables-only
rea decode-android-resources /targets/Example.apk --no-strings
rea decode-android-resources /targets/Example.apk --decode-timeout-ms 900000
rea decode-android-resources /targets/Example.apk --string-offset 2000 --string-limit 100 --string-tables-only
rea decode-android-resources /targets/Example.apk --max-string-table-bytes 16777216 --max-total-string-table-bytes 67108864
```
Equivalent MCP methods are `inspect_apktool_client` and
@@ -87,6 +89,31 @@ responses and charges the aggregate output budget for one copy per table.
Omitting the option preserves existing responses. `include_strings: false`
still suppresses all string projection, including in this mode.
Use `string_offset` and `string_limit` (CLI `--string-offset` and
`--string-limit`) to select a page of valid parsed entries from each requested
table, in source order. Defaults are offset 0 and limit 2000. Explicit page or
budget controls include `string_tables` even for a single primary table; its
`page` gives the requested offset and limit, returned count, and `next_offset`.
Retry that offset with the same locale and confirm `target.sha256` matches.
Each call decodes a fresh owned snapshot; it does not retain a workspace or
promise identity across changed APKs. A null continuation means the page reaches
the end, while table coverage remains partial if earlier entries were omitted.
An offset at or beyond the end returns an empty, present table with known counts.
If the continuation equals the requested offset, no output entry fit: increase
`max_total_strings` or use `string_tables_only` before retrying.
`max_total_strings` (CLI `--max-total-strings`, default 10000) bounds returned
string copies across all tables. Raise it, request fewer locales, or use
`string_tables_only` when aggregate output is exhausted. The legacy
multi-table shape charges both copies of primary strings. `max_string_table_bytes`
(CLI `--max-string-table-bytes`, default 8388608) bounds one XML source read;
`max_total_string_table_bytes` (CLI `--max-total-string-table-bytes`, default 33554432) bounds XML reads across distinct physical tables. Locale aliases
share the physical read and parsed page. Raise the relevant read budget to
retry an oversized table; a larger string limit or timeout does not raise it.
These controls take nonnegative safe-integer offsets and positive safe-integer
budgets, apply on the next decode, and require no REA restart. Process memory
and response transport capacity still apply.
## Interpret the result
- **An owned APK snapshot is digested and decoded.** `target.sha256` and
@@ -117,8 +144,8 @@ still suppresses all string projection, including in this mode.
Malformed XML and a wrong document root are whole-document projection
failures: the result is partial, the failure reason is disclosed, and entry
counts remain unknown. They are not counted as one unsupported string.
The default table is bounded to 2000 entries with partial coverage when more
exist. `locales` lists observed regular `strings.xml` tables whose exact
The default page returns up to 2000 entries with partial coverage when more
exist; its limitation names the next `string_offset`. `locales` lists observed regular `strings.xml` tables whose exact
`values-<locale>` directory is selectable by the request's locale grammar.
Locale suffixes allow up to 23 ASCII characters, derived from Apktool's
language, script, region, and variant fields; for example,
@@ -133,9 +160,10 @@ still suppresses all string projection, including in this mode.
When multiple tables are requested with `locales`, `string_tables` records
each projected table with its locale, status (`present`, `missing`,
`oversized`, `malformed`, or `skipped`), entry counts, and coverage,
bounded at an aggregate budget of 10,000 strings and 32 MiB XML. When `locales`
is omitted and `string_tables_only` is not true, legacy single-table payloads are preserved
without adding `string_tables`.
with default aggregate budgets of 10,000 strings and 32 MiB XML. When `locales`
is omitted, `string_tables_only` is not true and no string page or budget
control is selected, legacy single-table payloads are preserved without adding
`string_tables`.
- **Smali is skipped by design** (`apktool d --no-src`): Java-source
recovery belongs to the JADX family. Resource smali edits and rebuild are
a follow-up lane with mutation authority.
@@ -156,8 +184,8 @@ still suppresses all string projection, including in this mode.
- The version probe waits 20000 ms and retains 65536 bytes. This release has
no runtime override for that probe. `decode_timeout_ms` does not raise it.
- Manifest reads are bounded to 512 KiB before decoding UTF-8. Strings XML
reads are bounded to 8 MiB; larger documents are skipped with partial
coverage and an explicit limit, rather than reported as missing.
reads default to 8 MiB per table; larger documents are skipped with partial
coverage and an explicit read-budget recovery, rather than reported as missing.
- `decoded_file_count` is capped at 100,000 files; the cap is reported in
the limitations when hit.
- An ordinary nonzero decode exit surfaces as `unsupported_target` naming
+44
View File
@@ -217,3 +217,47 @@ The Gallery workflow and all 147 focused Flutter tests also pass on macOS Sonoma
including the conflicting-load and note-range-limit CLI/MCP regressions. Windows
verification runs under a non-administrator account. Verifiers use a shared
temporary parent so each CLI/MCP process can own and release its own snapshots.
## Selecting and recovering string-pool observations
`inspect_dart_aot` accepts `pool_policy`; the CLI uses `--pool-policy` JSON:
```sh
rea inspect-dart-aot app.apk --pool-policy '{"contains":"package:my_app/"}'
rea inspect-dart-aot app.apk --pool-policy '{"uri_limit":2000,"token_limit":4000,"run_bytes":4096,"token_bytes":256}'
```
A literal, case-sensitive `contains` filter runs before distinct collection,
so an omitted URI or identifier can be selected without returning every other
string. URI/token counts describe matching entries; `printable_run_count` counts
complete printable runs before filtering. Unset budgets preserve the defaults:
500 returned entries per URI category, 2000 identifier-like tokens, 1024 bytes
per printable run, and 128 bytes per identifier. Budget values must be positive
safe integers. Collection retains at most twice the selected category limit;
counts remain lower bounds when that capacity is reached. A run exceeding
`run_bytes` is omitted whole, rather than reported as a fabricated prefix.
The result names the budget reached, and `string_pool.policy` records the
selected filter and effective values. A larger budget or a narrower selection
applies to the next call without a REA restart; explicit choices stay in Evidence.
For a larger package-URI inventory, select the category and its relevant budget:
```sh
rea inspect-dart-aot app.apk --pool-policy '{"contains":"package:","uri_limit":2048}'
```
The equivalent MCP input is `pool_policy: {contains: "package:", uri_limit: 2048}`.
This recovers all bounded package-URI observations in the measured Gallery and
LocalSend APKs. Check the result's limits and coverage for other targets; long
runs and unsupported snapshot layouts remain separate limitations. The
[calibration report](../../verification/flutter-budget-calibration.md)
keeps the default at 500 because increasing every complete overview also delivers
more strings when they are unnecessary. Use `detail: "summary"` for the MCP
identity/count task described above.
These controls do not raise APK, ELF, library or response budgets, deserialize
Dart objects, or establish typed class/function semantics. Retained
`inspect_analysis_view` pages cover the returned selection; exhausting those
pages does not recover facts that the producing pool scan omitted. Re-run the
producer with the reported selection or budget change to obtain a new
observation, checking its APK and library digests before comparing it.
+22 -4
View File
@@ -117,8 +117,8 @@ are never passed to TShark.
Installed libraries, global configuration and plugins remain trusted, are not
fully fingerprinted, and can influence dissection. This is not an OS sandbox.
Input is limited to 32 MiB before reading, each command to 30 seconds, and
combined stdout/stderr to 4 MiB during collection. Output overflow fails instead
Default limits are 32 MiB input before reading, 30 seconds per command, and
4 MiB combined stdout/stderr during collection. Output overflow fails instead
of returning truncated success. Before execution, `prlimit` enforces **1 GiB
address space per process**, **64 MiB per file**, **30 CPU seconds** and no core
dumps. Stricter inherited soft/hard limits are preserved; Evidence records the effective
@@ -128,6 +128,23 @@ These are not aggregate process-memory or filesystem quotas. The selected
limiter is trusted; its path, digest and arguments are retained in Evidence.
No arbitrary process flags are accepted.
Each capture/recording operation accepts `producer_limits` with positive safe
integer overrides: `input_bytes`, `output_bytes`, `command_timeout_ms`,
`address_space_bytes`, `file_bytes`, and `cpu_seconds`. The wall-clock timeout
is at most 2,147,483,647 milliseconds, matching Node timers. Omitted fields keep
the defaults above. Discovery accepts the same controls except `input_bytes`,
since it does not read a capture or recording. Packet field discovery defaults
to 32 MiB output rather than 4 MiB.
CLI supplies this object as JSON with `--producer-limits`, for example
`--producer-limits '{"input_bytes":67108864,"output_bytes":8388608,"cpu_seconds":60}'`.
Values apply to the next call without restarting REA. Effective native ceilings
still preserve any tighter inherited soft and hard limit. Errors identify the
exhausted producer control and retain partial output and cleanup status; follow
that repair after cleanup settles. Raising producer budgets does not raise
separate parser representations, transport buffers or external client deadlines.
Never truncate the selected artifact to fit an input budget.
## Verification
```sh
@@ -182,8 +199,9 @@ proof that the field appears in a selected recording.
The adapter uses the documented `tshark -G fields` grammar under its existing
private empty profile and Linux process limits. TShark 4.6.9's full glossary was
measured at 22,303,197 bytes; discovery admits at most **32 MiB** combined command
output (capture inspection remains at **4 MiB**). Parsing walks lines once and
measured at 22,303,197 bytes; discovery defaults to **32 MiB** combined command
output (capture inspection defaults to **4 MiB**); `producer_limits.output_bytes`
can change either request budget. Parsing walks lines once and
constructs objects only for the selected page. Returned strings are copied so
retained pages do not retain the complete glossary string. Before constructing a selected
record it charges six times the source UTF-8 bytes plus 512 bytes against an
+20 -11
View File
@@ -118,16 +118,25 @@ CLI keeps its existing complete result.
## Resource and lifecycle limits
Inputs are limited to 4 MiB, complete replies to 16 MiB and combined diagnostic
output to 1 MiB. An owned worker has a 256 MiB JavaScript heap; its profile
maxima are 3 GiB virtual address space and 30 CPU seconds, with tighter inherited
limits retained. The worker command deadline is
`timeout_ms` (default 30000 ms, integer 1 through 2147483647) on the next call,
or `--timeout-ms` on `rea inspect-evm-interface`. Omission keeps 30000 ms.
Those byte, address-space, and CPU ceilings have no runtime override, and
`timeout_ms` does not raise them. An oversized selected carrier returns a
non-retryable `resource_constraint` with resource `file-size` and its fixed
input allowance in `reported_limits.maximum_input_bytes`.
Per-call `producer_limits` selects `input_bytes`, `output_bytes`,
`diagnostic_bytes`, `address_space_bytes`, `file_bytes` and `cpu_seconds`.
Omission keeps 4 MiB input, 16 MiB reply/file, 1 MiB diagnostics, 3 GiB address
space and 30 CPU seconds. Omitted `file_bytes` follows the selected
`output_bytes`, so raising the reply allowance does not silently meet the old
file-write allowance. Explicit file limits remain independent. These fields
accept positive safe integers; changes apply to the next call without a REA
restart. The CLI accepts the same exact policy as JSON, for example:
```sh
rea inspect-evm-interface ./runtime.hex hex --producer-limits '{"input_bytes":8388608,"output_bytes":33554432}'
```
The owned worker's 256 MiB JavaScript heap remains separate. The worker command
deadline is `timeout_ms` (default 30000 ms, integer 1 through 2147483647), or
`--timeout-ms` on the CLI. Raising it does not raise producer, heap or client
limits. An oversized carrier reports non-retryable `resource_constraint` with
`reported_limits.maximum_input_bytes` and recovery naming
`producer_limits.input_bytes`.
Complete-reply allowance refusals use the same non-retryable resource family,
with `reported_limits.maximum_reply_bytes`; malformed JSON remains unreadable
output. This refusal leaves no complete report to retrieve through another
@@ -135,7 +144,7 @@ delivery channel. Virtual address space differs from resident memory. The worker
soft/hard limits, reports its configured soft limits inline and leaves hard limits
unchanged. WASM trap reservations are disabled
so the engine can run within that virtual limit; actual memory is not equated to
its 3 GiB allowance. Limits fail without partial success. Stable snapshots,
the selected address-space allowance. Limits fail without partial success. Stable snapshots,
private files and owned processes are cleaned independently of caller cancellation.
Invalid-input, unsupported, cancellation, timeout and malformed-reply failures
retain bounded stdout/stderr under `details.captured_output`, including whether
@@ -763,7 +763,7 @@ These safeguards are not caller-selectable output budgets. Source-map parsing
reports truncation when its format or parser safety boundary is reached. The
application graph has no aggregate node, edge, root, or observation prefix cap.
Semantic evaluation retains at most 256 distinct primitive candidates per
By default, semantic evaluation retains at most 256 distinct primitive candidates per
expression and at most 1 MiB of worst-case JSON string bytes across a normalized
primitive value's string candidates. The byte budget estimates six JSON bytes
per UTF-16 code unit, the expansion bound for escaped strings, and bounds both
@@ -783,13 +783,48 @@ available for endpoint queries, tracing and comparison. Each analysis records en
later projections: a selected healthy sibling can remain exact while whole-file
coverage reports the encountered bound. Projection preserves limiting values
from the shared property-slot owner. Expression
evaluation and provenance walks stop after 256 nested levels. Union products
evaluation and provenance walks default to 256 nested levels. Union products
and string lengths are checked before allocating combinations or concatenated
strings. When a boundary is reached, the value stays unknown, semantic coverage
becomes partial, and graph evidence retains the expression location and typed
limiting reason. The number of unresolved alternatives is unknown; it is not
reported as an exact omission count.
`semantic_precision` selects per-source evaluator budgets independently of
retained graph capacity, worker memory and deadlines. Both
`analyze_javascript_application` and `analyze_web_bundle` accept a partial object:
| Field | Default | Unit |
| ---------------------- | ------: | ------------------------------------------- |
| `primitive_candidates` | 256 | alternatives per normalized primitive value |
| `primitive_bytes` | 1048576 | estimated JSON bytes per primitive value |
| `expression_depth` | 256 | nested expression/provenance levels |
| `getter_expansions` | 512 | scheduled states per precise getter query |
| `alias_selections` | 512 | additional precise selector states |
| `wrapper_visits` | 1000000 | wrapper visits/substitutions per source |
| `wrapper_depth` | 128 | active symbolic wrapper levels |
`primitive_bytes` charges six estimated JSON bytes per UTF-16 code unit plus
quotes before creating concatenated strings. Ordinary ASCII can exhaust that
conservative estimate before its actual JSON encoding reaches the selected
byte count. The [calibration record](../../verification/javascript-precision-calibration.md)
keeps existing defaults for its measured literal-value tasks; it does not
establish optimal candidate, getter or alias budgets for larger applications.
All fields accept positive safe integers; worker memory, execution and JSON
representation bounds remain independently enforced. Omission keeps the defaults;
invalid values fail validation. For example, retry a finite candidate-limit
observation with `semantic_precision: {"primitive_candidates": 512}`. The CLI
accepts `--semantic-precision '{"primitive_candidates":512}'` or a JSON file.
An override applies to that next analysis without restarting REA, remains in
Evidence parameters and effective coverage, and cannot change already retained
facts. Increasing these budgets does not raise graph retention, worker heap,
transfer admission, or parent result memory. Cyclic or unsupported evaluations
remain unknown. Wrapper limitations name visits and depth separately so a
caller can change the resource actually exhausted. Finite inline literal object
spreads use iterative syntax traversal and no longer inherit a duplicate
semantic-depth cut.
Byte, entry, path, and graph-shape bounds are hard limits. The parse deadline is
checked before and between bounded parsing and traversal phases; the synchronous
Babel and JSON parser calls cannot be preempted mid-call, so their input byte
+25 -7
View File
@@ -264,12 +264,30 @@ is retained in the classification evidence list.
### Resource admission
The static provider admits regular input files up to 128 MiB before retaining
their bytes. Reads remain bounded if a file grows after admission. Metadata,
CIL decoding, and projected inspection records have a separate conservative
64 MiB representation budget; shared heap strings are cached, but repeated
output occurrences still consume that budget. These are inspection resource
policies, not PE format limits or a hard bound on total V8 heap use.
The static provider defaults to a 128 MiB eagerly retained PE snapshot and a
separate conservative 64 MiB budget for metadata, CIL decoding and projected
inspection records. All three static inspection tools accept
`max_snapshot_bytes` and `max_metadata_representation_bytes` as positive safe
integers in bytes. CLI inspection and path comparison expose
`--max-snapshot-bytes` and `--max-metadata-representation-bytes`. An omitted
value keeps its default; explicit values apply to that call without a restart.
Snapshot refusal names its selected byte budget and tells the caller to raise
it for the same complete file. Representation exhaustion retains partial or
unavailable coverage and names the selected budget in its `resource-limit`
issue; raise that budget on the next inspection, or select fewer `type_names`
for members. For example:
```bash
rea inspect-managed-members /absolute/path/Assembly.dll --max-snapshot-bytes 268435456 --max-metadata-representation-bytes 134217728
```
Reads remain bounded if a file grows after admission. A cached snapshot is
rechecked against the current call's snapshot allowance and on-disk digest.
Shared heap strings are cached, but repeated output occurrences still consume
the representation budget. These are per-call resource policies, not PE format
limits or a bound on total V8 heap use. Raising one does not raise the other,
transport limits or available host memory.
Path-based member comparison uses the same per-input snapshot policy and
inspects its two selected paths sequentially, retaining each inspection rather
@@ -326,7 +344,7 @@ original `resource-limit` coordinate. It does not return partially admitted
member collections: empty fields, methods, references and edges are unexamined,
not proof of absence; exported types, user strings and signature-resolution
attestation remain null. Summary and retained pages keep this partial coverage.
The capacity limit is unchanged. If metadata or the type inventory itself cannot
The selected capacity applies to the metadata and member projection together. If metadata or the type inventory itself cannot
be admitted, the result remains unavailable.
Use the returned `types[].full_name` values to select declaring types before
+9 -3
View File
@@ -120,7 +120,13 @@ CLI output are unchanged.
Complete results have a 32 MiB input, 64 MiB reply, 1 MiB combined diagnostics
and 30-second owned command deadline. `REA_PWNTOOLS_MAX_INPUT_BYTES` raises the
input bound alone, up to 1 GiB; the reply and deadline bounds are unchanged. The
launch input default alone, up to 1 GiB. Per-call `producer_limits` selects
`input_bytes`, `output_bytes`, `diagnostic_bytes`, `address_space_bytes`,
`file_bytes`, and `cpu_seconds`; positive safe integers apply on the next call
without restart. `input_bytes` overrides the launch default. File admission
follows selected `output_bytes` unless `file_bytes` is explicit. The separate
`pwntools_timeout_ms` controls each command wall deadline. On CLI pass
`--producer-limits '{"cpu_seconds":60,"output_bytes":134217728}'`. The
reply budget applies to the decoder record; the CLI/MCP Evidence envelope adds
copies and encoding overhead. The
pinned MCP SDK defaults to a 10 MiB receive buffer. Large complete results need
@@ -128,8 +134,8 @@ a caller-configured `StdioClientTransport({ maxBufferSize: ... })` and a matchin
server `REA_MCP_MAX_RESPONSE_BYTES`. See [MCP delivery budgets](../../reference/mcp-contracts.md#tool-results).
These settings affect delivery, not decoder coverage or limits. The large
extended-index verification uses 256 MiB and an explicit five-minute request
timeout in its separate opt-in MCP lane. REA does not silently truncate the result. Python lowers resource soft limits to at most 3 GiB virtual address space,
30 CPU seconds and 64 MiB file output. Inherited tighter soft/hard limits are
timeout in its separate opt-in MCP lane. REA does not silently truncate the result. Python lowers resource soft limits to the selected policy: defaults are 3 GiB
virtual address space, 30 CPU seconds and 64 MiB file output. Inherited tighter soft/hard limits are
retained, and the effective values are returned in the result limitations. Virtual address space is not RSS;
Unicorn needs a large virtual map. Limits fail with no partial success, and
owned process/root cleanup completes independently of cancellation.
+40 -20
View File
@@ -116,18 +116,22 @@ The result's limitations state this explicitly. A module-mode build with a main
module record and no dependency records has an observed empty dependency list.
When `include_string_literals: true` (CLI `--include-string-literals`, optional
`max_string_literals` / `--max-string-literals`, default `2000`, maximum `10000`)
`max_string_literals` / `--max-string-literals`, default `2000`)
is selected, `string_literals` reports `evidence_kind: "inferred"` alongside
deduplicated printable UTF-8 literals referenced by word-aligned two-word
`(ptr, len)` string headers in non-executable initialized data. Because Go packs
read-only string data contiguously without NUL terminators, slicing by `(ptr, len)`
recovers exact literal boundaries instead of concatenated C-string runs. Each
target slice (`1`–`4096` bytes) must stay within a single read-only mapped
target slice (default `1`–`4096` bytes) must stay within a single read-only mapped
section or segment, decode as printable UTF-8 (`\t`, `\n`, `\r`, joiners
`U+200C` / `U+200D` and printable Unicode scalar values, excluding control and
other format characters), and fit within
a 64 MiB header-scan budget and 512 KiB aggregate string budget
(`truncated: true` when capped). Compiler-inlined or
a default 64 MiB header-scan budget and 512 KiB aggregate string budget
(`truncated: true` when capped). Select `max_string_literal_bytes`,
`max_string_bytes`, `max_string_header_scan_bytes`, or `max_string_candidates`
to change these independent budgets. Their CLI flags use hyphens, for example
`--max-string-literal-bytes 16384`. `limits_reached` names exhausted controls;
increase those controls for a new inspection of the same complete artifact. Compiler-inlined or
stack-constructed strings without static headers, unapplied relocation addends
and Mach-O `LC_DYLD_CHAINED_FIXUPS` pointer encodings are noted in `limitations`.
@@ -201,23 +205,39 @@ host permission denial, changed artifacts and cancellation retain distinct
errors. The selected file is never modified; this inspection creates no
temporary files, starts no subprocesses and accesses no network endpoint.
An input above 256 MiB returns `resource_constraint` with resource `file-size`
and `reported_limits.maximum_input_bytes`. Select a suitable input within that
fixed allowance; changing an output-write or transport budget does not admit it.
The existing default resource budgets are now explicit per-call controls:
Resource guards bound the selected file and cumulative header searches to 256 MiB
each, aggregate embedded string contents to 1 MiB (varint length prefixes are
framing and do not count toward this allowance), structural table decoding to
16 MiB and the complete normalized metadata report to 8 MiB. Inline strings must
also fit within their actual mapped source range. Identical search spans with
the same virtual alignment are searched once; partially overlapping spans
consume the cumulative search budget
and fail with a CPU resource constraint when it is exhausted. Oversized results
fail without partial success. MCP also applies
its [response transport budget](../../reference/mcp-contracts.md), which counts both text and
structured content; a report within the metadata limit can still require export
from retained Evidence. These limits are not operating-system memory or CPU
isolation.
| Parameter | Default | What it admits |
| ------------------------------ | ----------- | ------------------------------------------------------------- |
| `max_input_bytes` | 256 MiB | Complete stable input file |
| `max_report_bytes` | 8 MiB | Complete normalized metadata representation |
| `max_build_info_bytes` | 1 MiB | Aggregate embedded string contents, excluding length prefixes |
| `max_header_search_bytes` | 256 MiB | Cumulative build-info search work |
| `max_string_literals` | 2,000 | Returned inferred string rows |
| `max_string_literal_bytes` | 4,096 bytes | Each inferred string candidate |
| `max_string_bytes` | 512 KiB | Aggregate returned inferred text |
| `max_string_header_scan_bytes` | 64 MiB | String-header and candidate-byte examination |
| `max_string_candidates` | 100,000 | Distinct inferred candidates retained before projection |
Every value is a positive safe integer. Input and string byte maxima follow the
runtime Buffer/string representation, with base64 expansion accounted for in
build-info admission; schemas advertise their exact maxima. CLI options use the same names with
hyphens, such as `--max-build-info-bytes 2097152`. Inferred-string controls
require `include_string_literals: true`. Omitted controls keep the defaults
and stay omitted in request Evidence. Error recovery names the exhausted
parameter and effective bytes; increase that parameter on the next call,
without restarting REA. The original complete artifact and its identity are
preserved. An unchanged retry cannot increase a deterministic allowance.
The shared native container reader separately bounds structural table decoding
to 16 MiB. None of these Go controls changes that boundary. Inline strings must
also fit their actual mapped source range. Identical search spans with the same
virtual alignment are searched once; partially overlapping spans consume the
cumulative search budget. Oversized complete reports fail without partial
success and without allocating the full JSON serialization. MCP's separate
[response transport budget](../../reference/mcp-contracts.md) can still require
export or paging of successfully retained Evidence. These controls do not
establish operating-system memory or CPU isolation.
Embedded metadata is evidence of file content. It does not authenticate a
compiler, dependency, commit or supply chain, and it does not establish runtime
+21 -5
View File
@@ -1247,7 +1247,7 @@ resource, REA releases that runtime and admits the next observation.
Cancellation observed while output and Evidence are finalized returns partial
cancellation Evidence, even if the target has already exited.
- the outcome, exit status and captured stdout/stderr (1 MiB kept per stream
- the outcome, exit status and captured stdout/stderr (1 MiB by default kept per stream
while excess bytes are drained and counted without growing capture files).
Each stream reports `complete`; when draining could not finish, its byte
count is an observed lower bound and truncation is explicit;
@@ -1260,16 +1260,32 @@ cancellation Evidence, even if the target has already exited.
code makes coverage partial;
- limitations.
The bridge limits retained event JSON to 8 MiB and caller frames to 65,536
across the run. It checks admission before retaining another complete event;
The bridge defaults to 8 MiB retained event JSON and 65,536 caller frames
across the run. Select `capture_limits` before launch to adjust `trace_bytes`,
`trace_frames`, `breakpoint_location_bytes`, `other_stops`, `selector_bytes`,
and `output_bytes`. Defaults are respectively 8388608, 65536, 8388608, 256,
1024, and 1048576. Byte budgets accept positive safe integers up to the smaller of the running
Node Buffer and single-string representation ceilings; counts are positive safe
integers. These representability ceilings do not establish available memory or
raise complete Evidence/transport admission. Omitted fields keep their defaults. The journal shares
`trace_bytes`; completed and partial capture readers use the selected budgets.
For example, `"capture_limits":{"trace_bytes":16777216,"output_bytes":2097152}`
selects 16 MiB trace/journal and 2 MiB retained output per stream. These choices
do not extend `duration_ms`, `max_events`, the outer client deadline or MCP
receive allowance, and require no REA restart. A larger budget applies to a new
authorized observation; it cannot recover facts omitted from an earlier run. It checks admission before retaining another complete event;
`resource-limit` reports which observations could not be completed without
silently changing the requested event or frame settings.
Resolved breakpoint locations have a separate aggregate 8 MiB metadata budget.
Resolved breakpoint locations have a separate aggregate metadata budget,
default 8 MiB (`capture_limits.breakpoint_location_bytes`).
If their details exceed it, `location_count` retains the full observed match
count, coverage is partial, and `breakpoint_locations_truncated` is true. This
post-run metadata limit does not imply that the target was killed.
Accepted entries and signal stops are also flushed to a bounded 8 MiB journal.
Accepted entries and signal stops are also flushed to the bounded trace journal.
When event admission stops, `coverage.capture_limit` identifies `trace_bytes`
or `trace_frames` when the bridge supplied that fact; historical reports that
lack it retain the combined limitation without guessing the exhausted resource.
If cancellation, timeout, capture failure, or a later bridge error prevents a
full result, the error retains this evidence in `details.partial_observation`
with partial coverage. Available output prefixes remain inline; their byte
+8 -2
View File
@@ -105,8 +105,14 @@ Initial Linux x64 limits: 32 MiB input, 64 MiB decoder reply, 1 MiB diagnostics
stage, 30 seconds owned wall deadline per stage, 3 GiB address-space soft ceiling
and 30 seconds CPU soft ceiling. `REA_PWNTOOLS_MAX_INPUT_BYTES` raises the input
bound alone, up to 1 GiB, and returns `provider_unavailable` when it is not a
decimal integer in range; the reply, diagnostic and deadline bounds stay fixed, so a
large core that outruns them still fails explicitly instead of partially. Tighter
decimal integer in range. Per-call `producer_limits` selects input/output/diagnostic,
file/address-space bytes and CPU seconds; positive safe integers apply on the
next call without restart. `input_bytes` overrides the launch default, and the
file allowance follows selected output unless `file_bytes` is explicit. Both
Python decoder and optional core-only debugger consume the same accepted policy.
Use CLI `--producer-limits '{"cpu_seconds":60,"output_bytes":134217728}'`.
`pwntools_timeout_ms` is the independent per-stage wall deadline. A large core
that still exceeds the selected complete-reply allowance fails explicitly. Tighter
inherited soft/hard limits are retained; hard limits are not raised. Complete
oversized results fail explicitly. These process limits do not constitute a sandbox
for caller-supplied engine code.
+1 -1
View File
@@ -26,6 +26,6 @@ Use `get_evidence_bundle` with `detail="summary"` and `filters={"evidence_id":"P
Failed startup cleanup retains acquired Frida handles, including a paused spawn that has not attached, for later cleanup retries. Its remote connection stays leased until session detach and target resume complete. CLI commands await provider cleanup before returning; MCP retains these resources for cleanup when its server closes. Server shutdown stops admitting new Frida operations and waits for admitted startup, discovery, and queued work before releasing their resources. A failed cleanup remains reported and can be retried by closing the server again, rather than treating an unregistered session as fully released.
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Local device lookup waits 1000 ms. This release has no runtime override for that lookup; another REA timeout does not raise it. Remote endpoints do not use that lookup. Message capture is bounded to 16 MiB and reports truncation. Metadata, type, and text field caps, and the JSON node cap, are also fixed. Evidence names a cap when that observation actually dropped data. This release has no runtime override for those capture caps. Script source is not stored in Evidence; its selected path and SHA-256 digest are. For a file, `source_sha256` identifies the original selected bytes. Invalid UTF-8 continues to use replacement decoding; Evidence then records that limitation and `raw_result.source_representation` with the encoding, decoding mode, and `loaded_source_sha256` of the UTF-8 string passed to Frida. Valid UTF-8 files and inline scripts do not need this additional representation fact. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. A cancelled script load on an existing session releases only its new script before queued operations can run; the session and previously loaded scripts remain available. If cancellation is observed when the native call returns, partial Evidence retains the source identity and captured messages even if that call rejects, including messages emitted during awaited cleanup. The caller's cancellation reason remains primary and a native rejection is retained as a sanitized diagnostic. A failure already observed before cleanup stays primary if cancellation arrives during cleanup. The cancellation cutoff is REA's observation of native completion, rather than a claim about unobservable native event order. `script_id` is REA's load-attempt identity. When creation rejects without returning a handle, `script_cleanup: "no_handle"` means REA acquired no handle to unload; it does not prove that the engine performed no work. Once the provider commits a successful load, later request cancellation does not unload that script; use `unload_frida_script` to release it. If script cleanup fails, the script remains owned and a paused target cannot resume until the pending instrumentation is released. Queued script loads, resumes, and unloads check cancellation before dispatching native effects. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Local device lookup waits 1000 ms. This release has no runtime override for that lookup; another REA timeout does not raise it. Remote endpoints do not use that lookup. Message capture defaults to 16 MiB per session, 4 KiB per type, 32 KiB per error text field, 250,000 JSON nodes and depth 64. Select `capture_limits` on `start_frida_session` or `instrument_with_frida` before observing: `session_bytes`, `message_type_bytes`, `message_text_bytes`, `json_nodes`, and `json_depth`. Omitted fields keep these defaults. The session allowance includes JSON/base64 expansion, the envelope and metadata; a budget too small for one complete event retains no event. Depth is bounded to 992 levels, reserving eight enclosing message/status/Evidence containers within REA's shared JSON representation limit. These are retention budgets, independent of target authority, observation duration and MCP/client delivery limits. The selected policy is fixed for that session and returned as `capture_limits` in observations; no REA restart is required. The CLI exposes `--capture-session-bytes`, `--capture-message-type-bytes`, `--capture-message-text-bytes`, `--capture-json-nodes`, and `--capture-json-depth`. Evidence names the effective control when that observation dropped data. Changing a budget requires choosing a new session or experiment and cannot recover already discarded live observations. Script source is not stored in Evidence; its selected path and SHA-256 digest are. For a file, `source_sha256` identifies the original selected bytes. Invalid UTF-8 continues to use replacement decoding; Evidence then records that limitation and `raw_result.source_representation` with the encoding, decoding mode, and `loaded_source_sha256` of the UTF-8 string passed to Frida. Valid UTF-8 files and inline scripts do not need this additional representation fact. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. A cancelled script load on an existing session releases only its new script before queued operations can run; the session and previously loaded scripts remain available. If cancellation is observed when the native call returns, partial Evidence retains the source identity and captured messages even if that call rejects, including messages emitted during awaited cleanup. The caller's cancellation reason remains primary and a native rejection is retained as a sanitized diagnostic. A failure already observed before cleanup stays primary if cancellation arrives during cleanup. The cancellation cutoff is REA's observation of native completion, rather than a claim about unobservable native event order. `script_id` is REA's load-attempt identity. When creation rejects without returning a handle, `script_cleanup: "no_handle"` means REA acquired no handle to unload; it does not prove that the engine performed no work. Once the provider commits a successful load, later request cancellation does not unload that script; use `unload_frida_script` to release it. If script cleanup fails, the script remains owned and a paused target cannot resume until the pending instrumentation is released. Queued script loads, resumes, and unloads check cancellation before dispatching native effects. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
The [Frida Core API](https://frida.re/docs/frida-core-api/) documents device management, sessions, and script loading; the [JavaScript API](https://frida.re/docs/javascript-api/) documents target-side instrumentation. [Luma](https://luma.frida.re/) is Frida's standalone GUI; REA does not automate or control it. `npm run verify:frida` requires a Linux host with the optional Frida binding and uses a short-lived local Node.js fixture created by the verifier. Add `-- --address HOST:PORT --pid PID` to run the separate remote attach lane against a caller-selected target. Mock tests do not establish real device/server compatibility.
+20 -3
View File
@@ -103,10 +103,10 @@ failure; bounded post-run integrity checks finish before honoring a late abort.
never passed to sigrok. Caller-supplied tools and their libraries/decoder modules
are trusted, and hashes identify bytes rather than publisher authenticity.
The input copy is bounded at 32 MiB before reading, each command has a 30-second
deadline, and combined stdout/stderr is bounded at 4 MiB during collection.
By default, the input copy is bounded at 32 MiB before reading, each command has
a 30-second deadline, and combined stdout/stderr is bounded at 4 MiB during collection.
Oversized output fails rather than becoming truncated success. These limits do
not form an OS sandbox. Before execution, `prlimit` enforces **1 GiB address
not form an OS sandbox. Before execution, `prlimit` defaults to **1 GiB address
space per process**, **64 MiB per file**, **30 CPU seconds** and no core dumps.
Stricter inherited soft/hard limits are preserved; Evidence records the effective
ceilings. Both limits are set before execution, so decoder allocations and compressed
@@ -114,6 +114,23 @@ recording expansion cannot exceed the per-process address-space cap. These are
not aggregate process-memory or filesystem quotas. The selected limiter is
trusted and its path, digest and arguments are retained in Evidence.
Each capture/recording operation accepts `producer_limits` with positive safe
integer overrides: `input_bytes`, `output_bytes`, `command_timeout_ms`,
`address_space_bytes`, `file_bytes`, and `cpu_seconds`. The wall-clock timeout
is at most 2,147,483,647 milliseconds, matching Node timers. Omitted fields keep
the defaults above. Discovery accepts the same controls except `input_bytes`,
since it does not read a capture or recording. Packet field discovery defaults
to 32 MiB output rather than 4 MiB.
CLI supplies this object as JSON with `--producer-limits`, for example
`--producer-limits '{"input_bytes":67108864,"output_bytes":8388608,"cpu_seconds":60}'`.
Values apply to the next call without restarting REA. Effective native ceilings
still preserve any tighter inherited soft and hard limit. Errors identify the
exhausted producer control and retain partial output and cleanup status; follow
that repair after cleanup settles. Raising producer budgets does not raise
separate parser representations, transport buffers or external client deadlines.
Never truncate the selected artifact to fit an input budget.
## Verification
```sh
+51 -10
View File
@@ -64,10 +64,11 @@ Equivalent MCP input:
```
Each selected regular file is read through the existing stable artifact reader;
symlinks, changing files and files over 8 MiB fail closed. An oversized input
returns `resource_constraint` with resource `file-size` and its fixed byte
allowance in `reported_limits.maximum_input_bytes`; retrying the unchanged file
does not raise that allowance. WASM bytes are copied
symlinks, changing files and files over the selected `max_input_bytes` (default
8 MiB) fail closed. An oversized input returns `resource_constraint` with
resource `file-size` and the effective allowance in
`reported_limits.maximum_input_bytes`. Its recovery names `max_input_bytes`;
raise that per-call value before retrying the same complete file. WASM bytes are copied
into a private workspace as read-only `module.wasm`; its digest is rechecked
before and after each tool. WABT validates with
`wasm-validate --enable-all --ignore-custom-section-errors`, reports sections
@@ -137,12 +138,30 @@ the equivalent MCP input is `{"path":"/artifacts/module.wasm",
"command_timeout_ms":60000,"detail":"summary"}`. Summary delivery retains the
complete Evidence but does not reduce WABT analysis work.
The 8 MiB input allowance and 32 MiB combined command-output budget remain fixed;
increasing `command_timeout_ms`, host memory or the client deadline does not raise
them. Do not truncate required module bytes to fit. Select another suitable
complete artifact only if it answers the question, otherwise report the limit.
Inspection output past the budget fails with `resource_constraint`; an oversized
`--version` probe still reports `unreadable_output`. Both streams share the
The default input, executable, and combined command-output allowances remain
8 MiB, 64 MiB, and 32 MiB respectively. Select `max_input_bytes`,
`max_tool_bytes`, or `max_output_bytes` (CLI `--max-input-bytes`,
`--max-tool-bytes`, `--max-output-bytes`) to change one allowance for the next
inspection. Values are positive integer bytes; the advertised schema maximum
follows the runtime's single-string or Buffer representation ceiling. Raising a
budget does not reserve host memory or raise another budget, the command
deadline, or the client's receive buffer. No REA restart is required. Unset
values preserve the defaults, and explicit values remain in Evidence parameters.
The same effective input/tool limit is used for initial and integrity rereads.
Before cloning and committing complete Evidence, REA accounts for the actual
escaped JSON representation and authentication copies. A large command budget
does not guarantee that the complete report fits the runtime's single-string
ceiling or available heap. Those refusals name the distinct representation or
heap constraint; increasing `max_output_bytes` cannot repair them. WABT reports
use no redundant raw-result mirror, but exact import/export forms and derived
interface facts can still expand beyond their source WAT size.
A limit failure names its effective byte value and exact per-call control.
Raise that control, resolve incomplete cleanup, then retry the same complete
artifact. Do not truncate required module bytes. If already at the runtime
maximum, report the limit or explicitly select another suitable complete input.
Output past the budget fails with `resource_constraint`, including a `--version`
probe; no truncated output is claimed as a successful inspection. Both streams share the
budget, so a stream that starts after the other has filled it retains nothing.
The captured output keeps up to the first 4,096 bytes that each stream retained,
each stream's retained byte count, and the exit code and signal. Retained text
@@ -238,3 +257,25 @@ Glue parsing accepts Node-supported WASM source-phase imports, including
`await import.source("./module.wasm")`. Their string literals retain the same
coordinates and local candidate associations as ordinary import literals; these
are static source observations, without module instantiation or execution.
Typed interface decoding separately charges `max_interface_bytes` (default
32 MiB; CLI `--max-interface-bytes`). The selected allowance applies to both
nested WAT parsing and retained typed entries, so increasing it is not blocked
by an independent fixed nested-parser cap. A partial interface preserves the
original WAT, admitted entries, exact exhausted allowance and retry guidance.
Inspect the same complete module again with a larger value to recover omitted
typed facts.
`compare_wasm_interfaces` accepts its own `max_interface_bytes` in the JSON
input, also default 32 MiB. It admits selected entries and group metadata before
building the comparison inventory. A larger comparison budget cannot upgrade
an already partial inspection: first recover the producer Evidence, then compare
those exact replacement records. Both allowances retain independent runtime
serialization and heap boundaries.
Auxiliary `candidate_paths` and `glue_paths` each retain a default admission of
16 paths. Set `max_artifact_paths` (CLI `--max-artifact-paths`) to admit a larger
explicit selection. Admission happens before any provider acquisition or command,
and errors report the selected list, requested count and required control.
Per-file byte budgets still apply to each admitted path; increasing the path
count does not raise those limits or reserve host memory.
+1 -1
View File
@@ -635,7 +635,7 @@ rejects it as unknown; other command rejections retain their reported failure.
Network and console coverage starts only after REA attaches and enables the relevant CDP domains. `prior_activity_available` is always `false`; absence from these arrays is not evidence that an event never occurred. Scripts are different: scripts the page parsed before REA attached are still reported under `scripts`. A page that loaded before attach can therefore list its scripts while `network.requests` lacks the requests that fetched them. If CDP reuses a request ID without supplying redirect metadata, REA retains the predecessor observation and marks network coverage incomplete.
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation uses `source_map_fetch_timeout_ms` (default 30000) and a 64 MiB aggregate streamed-response budget across redirects. The byte budget has no runtime override. A fetch overrun is an item limitation, not a thrown analysis timeout. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding also bounds raw resolved identities to 64 MiB and aggregate inline evidence to 32 MiB. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation uses `source_map_fetch_timeout_ms` (default 30000) and a default 64 MiB aggregate streamed-response budget across redirects. Select `source_map_limits.fetch_bytes` to change byte admission for the next call. A fetch overrun is an item limitation, not a thrown analysis timeout. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding defaults to 64 MiB for raw resolved identities and 32 MiB for aggregate inline evidence; `source_map_limits.fetch_bytes` and `source_map_limits.output_bytes` own those independent admissions. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
A fully read response that fails UTF-8, JSON, mapping, or structural validation
retains `artifact: null`, empty original-source, module-edge, and mapping
+15 -38
View File
@@ -1114,44 +1114,21 @@ for the hint per reason.
## Resource and lifecycle boundaries
Complete evidence has a 32 MiB input, 96 MiB decoder reply and 64-level nesting
budget. Those budgets have no runtime override. `capture_timeout_ms` does not
raise them. Exceeding a budget returns no partial success.
Oversized selected captures return an input error identifying `capture_path`
and the byte budget; owned reply/output failures remain output or provider errors.
An explicit complete-reply refusal names the 100663296-byte allowance and its
lack of a runtime override. Record selection, paging, delivery mode and worker
memory do not increase that allowance; a smaller producer export is a narrower
observation, and the refusal leaves no completed capture Evidence to retrieve.
Cancellation during reply-file finalization retains validated completed capture
facts as partial observations. Malformed or unvalidated replies are not promoted.
HAR, SAZ and qlog run in an owned Node old-generation heap with one V8 worker.
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` is a decimal integer of mebibytes from
8 to 16384. Unset uses 192, the previous constant. Invalid values fail
configuration and name the setting; they do not fall back to 192. The setting
seeds the launch value. In MCP, set `capture.decoder_old_generation_mib`
through `update_analysis_settings` for decoders belonging to newly accepted
operations, without restarting REA. Changing the launch environment still
requires a new process. A memory failure names the effective mebibytes, known
source, default, floor, and maximum. The decoder
passes that value as `--max-old-space-size` and removes inherited
`NODE_OPTIONS`, so additional host memory and `NODE_OPTIONS` do not raise the
heap. The 8 MiB semi-space stays fixed; a smaller old generation aborts Node
during startup instead of enforcing a limit. Mitmproxy decoding applies
Linux limits of 768 MiB address space, 30 seconds CPU and 96 MiB file output;
both commands use `capture_timeout_ms` (default 30000) and independently supervised
process cleanup. Owned decoder diagnostics retain at most 1 MiB across stdout
and stderr; the collector drops overflow as it arrives, reports the limit
failure and verifies cleanup. Heap limits alone do not establish aggregate RSS limits.
SAZ and qlog refuse decoding when neither a Node heap limit nor a Windows Job Object bound can be enforced. Existing HAR availability is preserved.
When REA runs on Bun, the HAR/SAZ/qlog decoder runs on Bun, which ignores these V8 heap
flags; its limitations and resource failures then omit the V8 heap sizes. On
Windows, the decoder's owned Job Object instead limits its committed memory to
the effective `REA_CAPTURE_DECODER_OLD_GENERATION_MIB` value plus a 512 MiB
reserve for Bun itself. The default 192 MiB setting therefore yields 704 MiB.
The limitations state that limit, and reaching it returns `resource_constraint`
with `process_memory_limit_bytes`, whatever exit status Bun reports. Other Bun
hosts enforce no decoder heap limit, so raising the setting does not bound them.
Complete evidence defaults to a 32 MiB input, 96 MiB decoder reply and
64-level nesting allowance. Set `max_input_bytes`, `max_reply_bytes` or
`max_nesting_depth` on the next `inspect_web_network_capture` call to change
that operation's policy; CLI equivalents are `--max-input-bytes`,
`--max-reply-bytes` and `--max-nesting-depth`. No REA restart is required.
Byte allowances are positive safe integers; nesting is bounded by REA's
advertised maximum of 996 levels, derived by reserving four enclosing
reply/Evidence levels within the 1,000-level JSON representation. Input admission also covers expanded SAZ
members and protobuf descriptor/body reads. Reply admission covers the complete
child reply and protobuf interpretation work. These choices do not raise the
separately owned worker heap, CPU limits or client deadline/receive buffer.
Record selection takes place after complete decoding, so it cannot bypass
whole-capture admission. A refusal names the exhausted control; increase that
control and retry the same unchanged capture after cleanup. No completed capture
Evidence exists for a refused decode.
Observed decoder memory exhaustion returns `resource_constraint`, with the
selected capture, applicable limits and retained process diagnostics. Raising
+135 -8
View File
@@ -82,13 +82,13 @@ implement every current source-map extension.
One owned Node subprocess decodes the map. Its private request directory is
removed before success is returned. Only this child's inherited `NODE_OPTIONS`
is removed; explicit V8 flags set a 192 MiB old-generation and 8 MiB semi-space
budget, one V8 worker and `decode_timeout_ms` (default 20000). The codec heap, map budget, and reply budget have no runtime override. Reported actual V8 heap limits
budget, one V8 worker and `decode_timeout_ms` (default 20000). These are the defaults: select `source_map_limits.codec_heap_mib` for a different heap request. Reported actual V8 heap limits
are checked. These are heap limits, not an aggregate process RSS limit. When
REA runs on Bun, the subprocess is Bun, which ignores these V8 flags: `runtime`
records `id: "bun"`, the Bun version, its Node.js compatibility version and a
null `v8_heap_limit_bytes`. On Windows, the codec's owned Job Object then
limits its committed memory to 704 MiB, the 192 MiB budget plus a 512 MiB
reserve for Bun itself; `process_memory_limit_bytes` records that limit, the
limits its committed memory to the selected codec heap request plus a 512 MiB
reserve for Bun itself (704 MiB with the default request); `process_memory_limit_bytes` records that limit, the
limitations state it, and reaching it returns `resource_constraint` with
`codec_process_memory_limit_bytes`. Elsewhere Bun has no enforced codec limit:
`process_memory_limit_bytes` is null and the limitations state that no codec
@@ -102,16 +102,143 @@ run token while process release is unconfirmed, and the directory until removal
succeeds. A later successful retry settles every error bound to that command;
historical failure diagnostics remain available.
Complete evidence is bounded by a 4 MiB map, 32 MiB codec reply and a shared
Complete evidence defaults to an 8 MiB map, 32 MiB codec reply and a shared
262144-record decoder budget. Generated rows, mapping segments, leaf maps,
source and name declarations, `sourcesContent` entries and indexed sections all
consume this budget. Generated rows and mapping segments also each have a
262144 limit; indexed maps allow at most 64 nested section levels. Those budgets and the
192 MiB codec heap have no runtime override. Exceeding a budget returns an explicit
262144 limit; indexed maps default to 64 nested section levels. Select per-call
`source_map_limits` to change these admissions. Exceeding a budget returns an explicit
failure, without partial results. The existing 32 MiB manifest and 16 MiB selected
script boundaries also apply. No global toolchain or browser configuration changes
are made.
## Compact point delivery and retained source text
Summary delivery is useful for large point answers. Complete output can answer
a small-map task with fewer calls. The
[delivery qualification](../../verification/source-location-delivery.md)
records measured task costs and first-use discovery tradeoffs.
Complete delivery remains the default and includes raw map text and embedded source
content. Select `detail: "summary"` in MCP, or `--detail summary` with
`--evidence_output /analysis/source-point.json` in CLI, to retain the same complete
Evidence and return body-free point metadata. Summary includes source/map/manifest
identity, requested and matched generated positions, original source positions,
content digests and sizes, match count, runtime qualifications and limitations.
MCP returns the summary Evidence directly; its `normalized_result.parent_evidence_id`
identifies the complete parent. CLI returns `{ summary: <Evidence>, evidence_bundle:
{ path, bytes } }`; read `summary.normalized_result.parent_evidence_id` and use the
published bundle path for later views. The receipt byte count describes the written
complete Evidence bundle.
Delivery selection does not enter that parent's analysis parameters or change its ID.
Summary returns the first metadata page (up to 64 matches and 1 MiB), with explicit
`coverage.total`, `coverage.next_offset` and `coverage.exhausted`. Every equal-position
alternative remains in the parent, including duplicate reported names, distinct
indexed-section/source indexes and generated-only segments. Page exhaustion describes
the retained lookup alternatives; execution and deployment authenticity remain unknown.
For another page, call `inspect_analysis_view` with the same retained parent:
```json
{
"source": {
"kind": "retained-evidence",
"evidence_id": "<parent_evidence_id>"
},
"view": {
"kind": "source-location",
"collection": "matches",
"offset": 64,
"limit": 64
}
}
```
Source text is explicit. Select `collection: "source-text"`, an original
`match_index` from the metadata page, and UTF-16 `offset`/`limit` (default 0/1024).
For a snippet around the mapped point, use its `original_position.offset` when
known. The result preserves content digest/size and total code units, returns only
the selected text, and reports text-page coverage. Unretained or generated-only
content returns `item: null` and unavailable coverage. An offset splitting a
surrogate pair is rejected; a page never splits a complete Unicode character.
Both collections accept `max_bytes` (default 1048576), independently of their
row/code-unit limit and MCP response limits. A byte refusal names the allowance
and retained parent: select a smaller page or increase `max_bytes`, then retry
without decoding again. A large individual identity may require a larger view
allowance or complete export. Complete map text and diagnostics remain in the parent.
An optional `view` can accompany `detail: "summary"` on the initial trace, returning
that selected page immediately. `view` with complete delivery is rejected before
artifact acquisition. Summary also refuses before acquisition when session retention
or the CLI Evidence output is unavailable.
These views read retained facts even after the original map is removed. CLI can read
the saved parent with `inspect-analysis-view` and an `evidence-file` source pointing
to the written bundle; select its `evidence_id` when the bundle has multiple records.
MCP can export the complete session with `export_evidence_bundle`. A complete MCP
response that exceeds transport admission retains its parent and supplies a
`source-location` matches continuation.
## Selecting budgets and recovering the same map
Both `trace_web_source_location` and `analyze_web_bundle` accept an optional
`source_map_limits` object. Omitted fields retain the defaults above and stay
omitted in Evidence parameters. An update applies on the next call without a
REA restart. Failure text names the exact field, effective value, supported
range and next action; increasing a deadline does not change any of these
budgets.
| Field | Default | Scope |
| ------------------ | -------- | ------------------------------------------------------------------------------------- |
| `map_bytes` | 8388608 | Local selected map admission, enforced before reading and again by the codec |
| `output_bytes` | 33554432 | Complete local request/reply or aggregate fetched-map representation |
| `decoded_rows` | 262144 | Generated rows before upstream allocation |
| `decoded_segments` | 262144 | Mapping segments before upstream allocation |
| `decoded_records` | 262144 | Shared structural work across leaves, declarations, rows, segments and sections |
| `section_depth` | 64 | Indexed-section nesting |
| `codec_heap_mib` | 192 | Local codec old-generation request; process-memory enforcement remains host dependent |
| `fetch_bytes` | 67108864 | Aggregate response bytes and, independently, resolved-identity bytes |
The local 8 MiB admission covers the pinned 4,284,329-byte main map and 5,529,840-byte
DateSelect map from Actual Budget without an admission retry. This is an artifact
compatibility allowance, not a context or latency optimization: complete Evidence
retains raw map text, and those two measured replies estimate 1.59 and 2.23 million
tokens. A smaller admission refuses the map; it does not compact successful
Evidence. MCP retains complete Evidence before encoding; an oversized response
returns its retained reference for `export_evidence_bundle`, or callers can use
complete CLI JSON. Select opt-in compact delivery above to keep complete Evidence accessible while returning only point metadata or an explicitly selected text page.
Browser collection keeps its separate conservative defaults; see the
[calibration record](../../verification/source-map-budget-calibration.md).
Local tracing accepts all listed fields except `fetch_bytes`. Bundle fetching
accepts all except `map_bytes` and `codec_heap_mib`: it uses its existing
inspection process rather than launching the local codec. Unsupported fields
are rejected rather than silently ignored. Byte fields are bounded below
Node's single-string representation ceiling, record fields by the signed
32-bit decoder coordinate range, section depth by the same supported count range and shared structural admission,
and heap requests by the existing 128..16384 MiB worker envelope. These bounds
remain independent; a larger map can require a larger encoded request budget
and codec heap, and the outer MCP client still owns its receive buffer/deadline.
For example, a valid 10 MiB selected local map can be retried with:
```json
{
"source_map_limits": { "map_bytes": 16777216 }
}
```
Keep the original manifest/map/point selection in that retry. The CLI spelling
is `--source-map-bytes 8388608`; the other local flags are
`--source-map-output-bytes`, `--source-map-decoded-rows`,
`--source-map-decoded-segments`, `--source-map-decoded-records`,
`--source-map-section-depth` and `--source-map-codec-heap-mib`.
`analyze-web-bundle` instead offers `--source-map-fetch-bytes` and the shared
output/record/depth flags. Raising only `decoded_records` does not raise the
independent row or segment budgets. Failed local decoding returns no partial
point evidence; completed browser map observations retain their existing
partial/unknown semantics. Wait for retained cleanup to settle before retrying.
## Verification
Browser bundle collection uses absolute section offsets for nested inline maps.
@@ -130,10 +257,10 @@ its `original_sources` declaration, including repeated source URLs across indexe
sections. Imports are deduplicated within each declaration. Older observations
may omit the index; their edge declaration identity remains unknown.
Browser collection accepts up to 64 MiB of map responses per inspection and
Browser collection defaults to 64 MiB of map responses per inspection and
preflights indexed sections, sources, names, generated rows and mapping segments
against a 262144-record decode budget before upstream flattening. Indexed maps
are limited to 64 nested levels. These structural limits bound decoded object
default to 64 nested levels. These structural limits bound decoded object
growth while preserving large embedded `sourcesContent` strings within the
response-byte budget. It also bounds the expanded inline evidence to 32 MiB
across the inspection, checking resolved source identities and mapping rows
+16 -3
View File
@@ -83,9 +83,22 @@ that `struct module`, symbol-table records, or certificate chains were decoded.
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
8192 `.modinfo` and `__versions` entries plus listed `__ksymtab*` sections are
examined by default (65536 maximum); `coverage.examined_entries` counts all
three. Exceeding that budget fails with no partial success. Keys longer
than 256 bytes and values longer than 4096 bytes stay malformed per entry;
the field that fits is still copied. A `__ksymtab*` section name longer than
three. Exceeding that budget fails with no partial success. `.modinfo` keys
retain at most 256 bytes and values at most 4096 bytes by default. A larger
field becomes `null` and is named in the entry's `omitted_fields`; the field
that fits is still copied. Structural `status` and `reason` remain independent:
a terminated, nonempty `key=value` entry stays `observed` when its text is
omitted, while overall coverage becomes `partial`. Source range and digest
remain available.
Raise `max_modinfo_key_bytes` or `max_modinfo_value_bytes` on the next MCP call
to retain the omitted text. The returned limitation names the required byte
budget. CLI flags are `--max-modinfo-key-bytes` and `--max-modinfo-value-bytes`.
Each accepts an integer from 1 through 536870912 bytes, matching the maximum
input size. No restart is required; file, entry and raw-hex budgets do not
change. These are per-call controls, not runtime settings.
A `__ksymtab*` section name longer than
256 bytes keeps its range with `name: null` and `ksymtab_name_exceeds_limit`. Inline raw hex stops at 4096 bytes and
the SHA-256 remains.
+53 -9
View File
@@ -72,16 +72,60 @@ Carrier byte order is independent of blob byte order.
Malformed magic, truncation, unknown flags/extensions, invalid UTF-8, duplicate
raw asset keys and conflicting blob declarations fail closed with named reasons.
Zero padding after a complete blob is allowed. The blob read ceiling is 256 MiB;
PE carriers also use the shared 512 MiB input, 65,536 directory-entry and 8 MiB
resource-metadata ceilings. The decoder admits at most 100,000 assets whose
names total at most 8 MiB, and 4,096 exec arguments totaling at most 1 MiB of
text. These are fixed resource bounds, not Node format limits, checked before
decoding names or creating records; a hit fails as a limit with no runtime
override. Asset and argument decoding, padding scans, and inventory construction
Zero padding after a complete blob is allowed. Producer admission defaults to
a 256 MiB blob, 100,000 assets whose names total at most 8 MiB, and 4,096 exec
arguments totaling at most 1 MiB of text. These are resource policies, not Node
format limits. Select `artifact_limits.node_sea` on inspection or extraction to
change them before decoding names or creating records. Asset and argument
decoding, padding scans, and inventory construction
yield in bounded batches and honor caller cancellation. Asset count and name
budgets bound retained metadata independently of stored member body sizes. The PE
name probe reads headers from the first 64 KiB of the carrier; a PE whose
budgets bound retained metadata independently of stored member body sizes.
| `artifact_limits.node_sea` field | Default | Unit and admission |
| -------------------------------- | --------: | ------------------------------------------------------ |
| `blob_bytes` | 268435456 | Bytes in one blob, checked before reading and decoding |
| `asset_count` | 100000 | Asset records |
| `asset_name_bytes` | 8388608 | Aggregate raw UTF-8 asset-name bytes |
| `argument_count` | 4096 | Exec-argument records |
| `argument_bytes` | 1048576 | Aggregate raw UTF-8 exec-argument bytes |
| `recognition_header_count` | 4096 | ELF header rows admitted for recognition |
| `recognition_load_command_bytes` | 8388608 | Mach-O load-command bytes admitted for recognition |
Exhausted recognition remains unknown, not examined absence. Inventory preserves
native-root facts or another positively recognized pack and records the exact
recognition limit inline. If extraction cannot establish a supported pack, it
refuses with that admission cause. Confirmed SEA blob admission still returns a
typed resource failure. ELF note-owner matching reads only the prefix needed to distinguish
`NODE_SEA_BLOB`; an unrelated long owner does not consume retained string space
or conceal a later SEA note. This does not change malformed framing checks.
For example, retain the same active carrier and retry an argument-count refusal:
```json
{ "artifact_limits": { "node_sea": { "argument_count": 8192 } } }
```
The CLI spelling is:
```sh
rea inspect-artifact ./application --artifact-limits '{"node_sea":{"argument_count":8192}}' --json
```
Failures report `reader_limit`, including the exact parameter, effective value,
unit, supported range and required value when known. Changes apply on the next
call without restarting REA. Delivery summary, paging and extraction selection
cannot bypass whole-manifest admission. Omitted selections stay omitted in
Evidence parameters; selecting `artifact_limits` records the accepted selection
and `effective_limits`. Those are conditional policies for encountered readers,
not a claim that every listed format was examined. The independent aggregate
member-work override is `artifact_limits.expanded_bytes`; omission keeps the
encoded-size-derived work allowance. Runtime Buffer/string representation limits
and host memory remain independent of larger selected budgets.
PE carriers additionally use the shared 512 MiB input, 65,536 directory-entry
and 8 MiB resource-metadata ceilings; the Node SEA selections do not raise those
shared PE-parser boundaries. The PE name probe reads headers from the first
64 KiB of the carrier; a PE whose
headers extend beyond that is left to native classification. ELF note tables
are read for at most one note per 12 carrier bytes in total, the most that
disjoint tables can hold, so program headers that repeat the same notes do not
+34
View File
@@ -96,6 +96,40 @@ and trace matches likewise retain Ghidra's external and thunk classification.
The Ghidra analysis profile identifies this search projection, so older
snapshots with unqualified search rows cannot replay under the new profile.
## Decompiler and value-flow budgets
Use `inspect_analysis_settings` or a result's named recovery to inspect
`ghidra.value_flow_max_operations` (default 3000),
`ghidra.value_flow_max_inputs_per_operation` (default 64), and
`ghidra.value_flow_max_edges` (default 12000). Each accepts a positive Java
integer count. `update_analysis_settings` applies changes to the next operation
on the same Program without restarting REA. Launch seeds use the corresponding
`REA_GHIDRA_VALUE_FLOW_MAX_OPERATIONS`,
`REA_GHIDRA_VALUE_FLOW_MAX_INPUTS_PER_OPERATION`, and
`REA_GHIDRA_VALUE_FLOW_MAX_EDGES` environment settings.
For a one-shot CLI retry, set the named seed in the environment that launches
the next `rea` invocation; the completed invocation's Program is no longer
active. Runtime MCP changes preserve the current Program. A changed launch seed
only affects a running MCP server after that server restarts.
The returned `native_value_flow.limits` and `limits_reached` distinguish accepted
budgets from actual exhaustion. Truncation keeps omitted counts and names the
setting to adjust. The edge budget applies independently to def-use and
parameter-use collections. New function dossiers are reobserved rather than
replayed from snapshots; completed Evidence remains available for historical
inspection. Other observation caches keep their existing policy.
`ghidra.decompile_max_payload_mib` controls the native decompiler's response
payload, separately from p-code projection and decompile time. Its launch seed
is `REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB`; explicit values are 1..2047 MiB to
avoid overflow in Ghidra's signed integer byte conversion. Omission or runtime
`null` preserves the installed provider's default. Changes apply before the
next native decompile, and native option changes clear its old cached function
state. A failed decompile preserves the original diagnostic and reports the
effective payload allowance; payload recovery applies only when that diagnostic
identifies payload exhaustion. Raising these controls does not raise the MCP
client's deadline or receive buffer.
## PE debug-symbol coverage
The post-analysis handshake reports Ghidra's PDB Program Information attributes:
+4 -1
View File
@@ -148,7 +148,10 @@ returned: `pseudocode`, `native_api` and `native_value_flow` are unavailable
with the decompiler's failure as the reason, while the listing-backed facets
are collected as usual. These failed decompiler observations and workflows
derived from them retain Evidence but skip snapshot reuse, so a later request
can observe backend recovery; healthy dossiers keep their existing cache policy.
can observe backend recovery. Ghidra function dossiers also skip snapshot reuse
so each new operation honors its accepted value-flow budgets; completed source
Evidence remains accessible through retained views and bundle export. Other
healthy provider observations keep their existing cache policy.
`procedure_pseudo_code` still fails for that function. Hopper's unavailable reference-kind metadata and reported unresolved
calls still prevent equality conclusions for those dimensions. Analysis profiles
version these declarations so older snapshots are not reused as current results.
+11 -3
View File
@@ -43,14 +43,22 @@ rea inspect-with-objdump ./program dwarf
rea inspect-with-objdump ./program dwarf --follow-debug-links
```
Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_truncated` marks output beyond that limit. That cap has no runtime override.
Captured stdout and stderr share `max_output_bytes` (CLI: `--max-output-bytes`), default 16777216 bytes (16 MiB). Select an integer from 1 to 44651859 bytes. The maximum leaves room for worst-case JSON escaping of both Evidence output mirrors under Node's single-string ceiling. The selected budget applies on the next call without a restart; `output_budget_bytes` reports it and `output_budget_source` distinguishes a default from an explicit request. On exhaustion REA retains a prefix, requests provider termination, and marks `output_truncated` with unknown completion. Narrow or page the native command, or raise the selected budget. Verify command effects and outstanding cleanup before deciding whether repeating it is safe. Client deadlines and receive buffers remain separate. Concurrent one-shot captures reserve their selected budgets within a 128 MiB aggregate admission and eight-operation capacity; wait for active work or select a smaller output budget when that capacity is occupied.
`inspect_with_objdump`, `rea list-objdump-architectures`, and one-shot `execute_rizin_command` accept `command_timeout_ms` (CLI: `--command-timeout-ms`). Omitted means 120000. The integer range is 1 to 2147483647. Target preflight shares one budget across stat and digest. The one-shot command receives that budget again. It applies on the next call; no restart is required. It does not raise the 16 MiB cap, a Ghidra or Hopper startup deadline, or a debugger session deadline.
`inspect_with_objdump`, `rea list-objdump-architectures`, and one-shot `execute_rizin_command` accept `command_timeout_ms` (CLI: `--command-timeout-ms`). Omitted means 120000. The integer range is 1 to 2147483647. Target preflight shares one budget across stat and digest. The one-shot command receives that budget again. It applies on the next call; no restart is required. It does not raise the separately selected `max_output_bytes`, a Ghidra or Hopper startup deadline, or a debugger session deadline.
## GDB
Persistent GDB sessions, including the MCP workflow and one-shot `rea debug-with-gdb` CLI, are available only on Linux. REA verifies a live inferior's output descriptors through `/proc` before detaching it; it does not claim equivalent descriptor verification on macOS. Windows is also unsupported because owned Job Object cleanup can terminate debugger inferiors. On supported Linux hosts, `start_gdb_session`, `gdb_console`, `gdb_session_status`, and `close_gdb_session` manage a persistent GDB/MI session in MCP. The one-shot CLI requires a caller-selected durable TTY: `rea debug-with-gdb --inferior-tty /dev/pts/7 'file ./program'`. The TTY must remain available after GDB exits so a detached inferior can continue writing. MCP starts GDB with `--nx --quiet --interpreter=mi3`, retries initialization with MI2 if MI3 initialization returns an error and cleanup succeeds, waits for its prompt, and disables auto-loading. Pass optional `inferior_tty` to select the terminal that future inferior runs use; REA configures it before the session becomes ready and checks the exact value with GDB/MI. `gdb_session_status` reports the selected path, whether REA still considers the configuration verified, and whether invalid UTF-8 has been observed in MI bytes (`mi_invalid_utf8`) or stderr (`stderr_invalid_utf8`). Commands that can change the inferior TTY or redirect a run revoke that verification permanently for the session, so close retains GDB ownership if a live inferior remains. The complete built-in inspections `show inferior-tty` and `help inferior-tty` preserve verification; compound commands still use the conservative mutation checks. Closing a session with one live inferior and a verified TTY automatically detaches the target, which can resume execution; MCP clients receive destructive-operation annotations for this behavior. The console remains unrestricted and can run shell commands, change target state, access files, and use network in the REA process user's security context. Raw MI records and console/target/log streams are returned with Evidence; command output is retained up to the configured budget (16 MiB by default) and explicitly marked when truncated. A single console, target or log record over the 16 MiB MI line limit is dropped and the command is marked truncated, while an oversized result or async record still stops the session. GDB startup waits 10000 ms with no runtime override. Each MI command defaults to 120000 ms; its deadline, command-output budget, session capacity and pending-command queue use the [shared debugger launch settings](#debugger-launch-settings). `command_timeout_ms` does not change a persistent debugger session.
A truncated GDB command capture includes `output_limits` with the exact causes
and effective maxima: `command_bytes`, `command_records`, or `mi_line_bytes`.
Only byte-budget exhaustion recommends raising
`REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES`. The 16384-record retention ceiling and
16 MiB single-line protocol ceiling stay fixed; select a command with fewer or
smaller records when either is reached. These cause facts survive timeout,
cancellation and process-exit captures as well as successful commands.
With multiple live inferiors, or a live inferior without a verified TTY, manual
close requests an explicit disposition and keeps the session ready. Use
`gdb_console` to select and explicitly detach or terminate each remaining
@@ -125,7 +133,7 @@ rea debug-with-rizin --inferior-tty /dev/pts/7 ./program dr
rea execute-rizin-command ./program iI
```
Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_truncated` marks output beyond that limit. One-shot commands use the `command_timeout_ms` budget described above. That 16 MiB cap has no runtime override.
Captured stdout and stderr use the same `max_output_bytes` (CLI: `--max-output-bytes`) admission and recovery described above, default 16 MiB. One-shot commands independently use `command_timeout_ms`. Raising output admission cannot recover an earlier capture; command effects and cleanup must be checked before another invocation.
### Decoder and map inputs
+18 -2
View File
@@ -173,10 +173,26 @@ present. `architecture` on any other image is rejected rather than ignored.
Package and dependency counts are checked against the minimum required JSON
representation before graph allocation or schema cloning. Oversized graphs
report the output byte budget; the compiler-path capacity reports
`maximum_rustc_hashes` in hashes, independently of byte limits.
report the output byte budget.
The inferred compiler-path facet keeps at most `max_rustc_hashes` distinct
hashes by default (64). Reaching that selection leaves the dependency report
available and adds `rustc_commit.coverage` with partial status and the effective
limit. Increase `max_rustc_hashes` (CLI `--max-rustc-hashes`) on the next call
to retain a larger prefix; no restart is required. The complete-report byte
allowance still applies. Optional compiler paths are shortened before they can
displace independently observed dependency facts.
Reader architecture names remain verbatim in the report. Evidence subjects use
the recognized canonical family: `386` maps to `x86`, `amd64` to `x86_64`, and
`ppc` to `powerpc`. Unknown families such as `ppc64` remain unknown rather than
being inferred from a related spelling.
Input and complete-report admission are caller choices through `producer_limits`:
`input_bytes` defaults to 256 MiB and `output_bytes` to 8 MiB. Both accept positive
safe integers and apply to the next call without restarting REA. On CLI, use
`--producer-limits '{"input_bytes":536870912,"output_bytes":16777216}'`.
Graph expansion and compiler-path retention consume the same selected report
allowance. The independent compressed dependency limit (1 MiB), inflated JSON
limits (8 MiB and 64 times the consumed stream), parser nesting and transport
budgets remain enforced; increasing report admission does not raise those.
@@ -44,7 +44,9 @@ are outside admitted signature semantics and keep unknown mappings.
## Resource admission
Typed producer rows, names and declaration tokenization are admitted before
allocation against the existing 32 MiB WABT output representation capacity.
allocation against the selected `max_interface_bytes` allowance (default 32 MiB).
Inspection and comparison select this allowance independently; comparison cannot
restore producer facts omitted under a smaller inspection allowance.
Comparison uses linear grouping and cooperative transport yields; it never
builds Cartesian duplicate pairings. CLI JSON input retains the shared file
admission gate. Token usage and peak process-family memory remain unmeasured.
@@ -0,0 +1,27 @@
# Implementation plan
Use the existing named domain contracts and runtime settings owner. Prefer
per-call selection or cohesive resource policies where they express analyst
intent. Extend the runtime registry for ASAR listing admission, which already
flows through the shared archive admission owner.
Implement independent owner slices: kernel metadata; GDB/Ghidra; desktop and
generated Python; Apktool; JavaScript precision; source maps; historical
captures; artifacts/managed/Rust; live capture; and producer limits. Coordinate
shared schema/configuration files centrally and review cross-slice changes
before broad checks.
Remove superseded literals, representations and misleading recovery rather
than adding downstream prose repairs. Use the repository's Result/error algebra,
existing process supervision, and existing CLI/MCP registration paths.
Validate focused boundaries first, then build, type/lint checks and generated
documentation consistency. Document unavailable native/provider lanes. No
performance claim or benchmark is required for correctness and policy ownership.
For default calibration, compare representative completed tasks, including
recovery calls and complete replies. Record artifact identity, an independent
coverage oracle, estimated tokens, elapsed/CPU time and sampled process-family
memory. Separate artifact admission from delivery size; do not raise defaults
merely to make a large response fit. Keep unmeasured defaults explicitly
provisional rather than presenting inherited numbers as optimal.
@@ -0,0 +1,69 @@
# Recoverable analysis budgets
## Problem
Several analysis workflows discard selected facts at fixed retention boundaries.
Existing runtime settings control only some resources. Increasing a deadline,
heap, or header allowance cannot repair an independent count or representation
limit. Desktop readers also duplicate policy in generated Python.
Related discussions: [#6176](https://github.com/morluto/rea/issues/6176),
[#8434](https://github.com/morluto/rea/issues/8434),
[#5808](https://github.com/morluto/rea/issues/5808), and
[#8978](https://github.com/morluto/rea/issues/8978).
## Requirements
1. Preserve defaults unless representative completed-task measurements justify
a calibration; preserve caller selections, Evidence identity, omission,
unknown coverage, and cleanup authority. Changes must not grant new target
execution, injection, mutation, installation, or network authority.
2. Keep source validity independent of retention completeness. In particular,
an overlong but terminated kernel modinfo value is not malformed merely
because REA omitted its text.
3. Preserve exact exhaustion causes and give a next action addressing that
resource. GDB byte, record-count, and single-line limits must remain distinct.
4. Offer caller selection, continuation, or a bounded policy override for the
reviewed desktop projections, Apktool string tables, JavaScript semantic
precision, source maps, historical captures, provider capture retention,
artifact/managed representations, and producer admission/output limits.
5. Interpret each policy at its owner. Parent and child process checks consume
the same accepted values; generated Python consumes the desktop policy rather
than maintaining independent numeric defaults. Reject invalid values rather
than clamping or silently falling back.
6. Retain justified format, runtime, protocol, and ownership constraints.
Distinguish them from adjustable resource policy and external client budgets.
A larger delivery page cannot recover facts already discarded by a producer.
7. A runtime default update affects new accepted operations only. Explicit
per-call choices win. Report effective value, units, applicable control and
retry/restart scope without claiming active-worker telemetry.
8. Artifact recognition that cannot examine the selected carrier within its
accepted resource policy remains limited/unknown, never examined absence.
Preserve the exhausted cause through classification and retained probe facts.
Add cohesive per-call Bun, .NET bundle, Node SEA and Godot admission controls
at their producing owners, including preallocation and expansion checks.
9. Desktop task checks must admit the actual shipped seed representation under
the same selected view policy as the generated reader. Acquisition/export
budgets remain separate from runtime view admission.
10. Local source-location tracing offers opt-in compact retained delivery and
bounded access to mapping alternatives and embedded text. Preserve complete
Evidence, source identity, selected positions, unknowns and every equal-point
mapping; compact output must not repeat the raw map or entire source text.
11. Desktop SQLite projections share a configurable per-table encoded-view
allowance with generated readers and check it before retaining rows. Keep
the independent native SQLite value/record and worker-reply envelopes
explicit; a projected-view override does not raise those constraints.
## Acceptance
Exercise the exhausted boundary and its advertised repair through the strongest
practical public or production workflow. Use small lowered-budget fixtures when
they prove the same boundary without large allocations. Preserve independent
malformed-input, cancellation, identity, cleanup and fixed-protocol controls.
Record real-provider coverage separately from simulated process/protocol tests.
## Non-goals
Unbounded analysis, changing all numeric constants, treating format bit widths as
preferences, raising another application's client buffer/deadline, recovering
lost live observations retroactively, or adding new execution authority.
@@ -0,0 +1,137 @@
# Tasks
- [x] Separate source validity from retained modinfo fields.
- [x] Preserve GDB exhaustion causes and improve Ghidra value-flow recovery.
- [x] Consolidate desktop policy with emitted Python and expose caller controls.
- [x] Make Apktool string omissions reachable through caller selection.
- [x] Thread JavaScript semantic precision policy through actual evaluators.
- [x] Coordinate source-map parent, codec and fetch budgets.
- [x] Coordinate historical-capture parent and decoder budgets.
- [x] Expose independent artifact/managed admission and preserve partial Rust facts.
- [x] Expose capture retention policy without changing target authority.
- [x] Expose producer resource/output controls with exact recovery.
- [x] Integrate ASAR runtime setting, launch validation and setup forwarding.
- [x] Complete focused and integrated checks, update product docs and report gaps.
- [x] Repair reviewed managed CLI decimal parsing and selected-budget provenance.
- [x] Preserve Go/Rust owner-supplied exhaustion facts through summary views.
- [x] Preserve selected desktop policy separately from effective policy and bound
the emitted reader's initial probe during file growth.
- [x] Compare representative completed Flutter and source-map tasks before
calibrating defaults; retain reproducible inputs, oracles and receipts.
- [x] Preserve limited Bun recognition through inventory and probe facts.
- [x] Add per-call artifact admission policies and exact same-artifact recovery.
- [x] Align shipped desktop seed task admission with generated reader policy.
- [x] Add retained source-location summaries, paged matches and bounded text.
- [x] Verify compact point tracing on the pinned public maps and refresh native
provider recovery coverage; complete coordinated integration checks.
## Validation
Executed with the pinned Node 24.22.1 / npm 11.16.0 toolchain:
- Runtime build and packaged skill generation through the focused-test
prerequisite.
- `npm run check:fast`: typecheck, lint and module boundaries pass.
- `npm run format:check`: passes.
- `npm run test:python`: bridge and analysis-efficiency suites pass.
- `npm run verify:test-discovery`: all 3,218 test files discovered exactly once.
- `npm run knip:production`: passes; advisory candidates were inspected rather
than removed without runtime/verifier ownership evidence.
- `npm run docs:generate` and `npm run docs:check`: pass, including the managed
conformance generator and README consistency checks. Removed obsolete ignored
managed-verifier argument bags instead of adding permissive budget fallbacks.
Focused tests ran in batches across CLI, MCP, worker, parser, lifecycle and
provider boundaries. The primary 28-file run passed 726 tests, with six failures
and one host skip. Those failures were repaired and every affected file passed
its targeted rerun. The supplementary producer run passed 512 tests with five
failures and 120 platform skips; every affected file then passed its rerun.
The final capture-only run passed all 45 tests. This is a record of separate
executed runs, not an aggregate unique-test count or a claim that `check:pr` ran.
Recovery checks include the same retained target after raised admission,
explicit selection reaching an omitted item, lower-budget cache re-admission,
worker-policy changes without stale results, generated Python parity, exact
exhaustion causes, and malformed/cancellation/cleanup controls. Integration
review also found and fixed XML/JSON view and historical-capture nesting budgets
that initially failed to reserve complete Evidence wrapper depth.
The subsequent review fixes passed all 50 tests across six compiled acceptance,
CLI, MCP and emitted-Python boundary files. Typecheck and lint also passed after
those fixes. Default calibration records its separate measurements and
limitations in the [Flutter](../../../verification/flutter-budget-calibration.md)
and [source-map](../../../verification/source-map-budget-calibration.md) reports;
those measurements are not native-provider verification.
The calibrated source-map default and explicit smaller-budget recovery passed
all six compiled source-location CLI tests. The final formatting, type/lint,
generated-documentation and README consistency checks passed. The observer's
completed, timeout, surviving-child and signal-refusal controls retained
receipts and confirmed empty process groups.
The final follow-up adds per-call artifact admission, limited recognition,
shipped-seed policy agreement, bounded SQLite views and retained source-location
delivery. The 12-file compiled CLI/MCP run passed 56 tests and exposed four stale
fixed-limit expectations; those owner expectations were updated to structured
resource facts and all five rerun files passed 60 tests, including the shared
analysis-view integration suite. The SEA MCP matrix separately passed both full
and compact schema profiles with same-session lower/raise/lower admission.
The final SQLite worker/emitted-reader checks passed all four scenarios. These
are separate executed runs, not an aggregate unique-test count.
The [source-location delivery qualification](../../../verification/source-location-delivery.md)
retains 18 successful public MCP measurements with matching parent IDs, exact
mapping/snippet oracles and process-tree samples. The
[JavaScript precision calibration](../../../verification/javascript-precision-calibration.md)
retains 12 real CLI runs supporting unchanged numeric defaults for that narrow
corpus. Native capture qualification and the live same-Program Ghidra receipt
are linked from the [runtime verification report](../../../verification/runtime-analysis-settings.md).
The Python gate passed all 102 tests; architecture guards passed across 2,571
source files. Formatting, type/lint, generated docs, README consistency and
production reachability checks passed. Reachability advisory candidates were
reviewed without deleting runtime/verifier-owned exports. The complete
`check:pr` gate was not run.
## Integration onto current main for the PR
Integrated onto main `8dfc47b107afc0cf4b53322a10b94c666fe9e18b`, preserving its
new desktop secret isolation, custom task readers, gzip support, Deno recognition,
signed bundle validation and chunked I/O. Incomplete Bun/SEA recognition now
keeps known native or other confirmed-pack observations with an explicit unknown
cause; it never caches unexamined data as absence. Main's malformed-directory
validation continues to take precedence over adjustable Godot retention.
The fresh 11-file compiled integration run passed 118 tests with one host skip,
covering artifact admission, retained source locations, desktop seed/SQLite/CLI,
Go/Rust snapshots, runtime settings and setup forwarding. Separate owner checks
passed for new main's desktop secret/custom-reader/gzip and JavaScript
enumerable-copy/require-drain behavior. Source-map fetch-budget omission, empty
selection and partial selection now also remain distinct in browser Evidence.
On this integrated branch, Python passed 102 tests; discovery found all 3,343
test files exactly once; architecture guards passed across 2,696 source files.
The runtime/skill build, type/lint and generated-documentation checks passed.
The retained native Ghidra lane was rerun against the integrated code and again
confirmed live budget recovery, unchanged target bytes, Program continuity and
cleanup; see its [integration receipt](https://github.com/morluto/rea/blob/main/docs/verification/runtime-analysis-settings-ghidra-integration-2026-11-09.json).
Historical calibration and earlier receipts retain their original runtime pins;
they are not performance measurements of the integrated head. `check:pr` was
not run.
## Verification limits
The follow-up verified all packaged Java sources against installed Ghidra 12.1.4
and exercised live operation/input/edge, native payload and deadline recovery on
the same Program with unchanged target bytes. The target was not executed.
The [dated native receipt](../../../verification/runtime-analysis-settings.md)
records exact versions, observations and verified cleanup; this does not qualify
Windows or every public transport/provider combination. Real local Frida and
LLDB producer-boundary budget checks also passed with explicitly controlled test
processes and confirmed cleanup. Full AgentEnv execution was not exercised;
desktop checks ran real SQLite workers and emitted reader helpers. Linux-only
native lanes and real Apktool, WABT, pwntools, pwndbg, sigrok and TShark remain
unverified. Source-owned codec/worker and simulated checks are labeled separately.
No native-provider performance improvement is claimed. Source-owned workflow
calibration measures a bounded public-artifact corpus, including complete
replies and recovery calls; it does not establish universally optimal defaults.
Ghidra function dossiers now reobserve accepted budgets while retaining the
native decompiler cache; the native latency cost of that change is unmeasured.
+6
View File
@@ -0,0 +1,6 @@
# Plan
1. Give EVM defaults and per-call policy one domain owner using the shared producer schema.
2. Resolve the accepted policy before resource selection and carry byte limits to the worker request; retain tighter inherited soft and hard bounds.
3. Preserve exact budgets through read/reply/diagnostic/resource recovery, CLI and Evidence. Replace the failure owner's positional argument list with a typed context.
4. Verify policy recovery and existing malformed/cancellation/cleanup behavior with focused checks.
+7
View File
@@ -0,0 +1,7 @@
# Per-call EVM producer budgets
An analyst can raise or lower carrier, reply, diagnostic, address-space, file-output and CPU budgets without changing the selected artifact or restarting REA. Defaults stay 4 MiB carrier, 16 MiB reply/file, 1 MiB diagnostics, 3 GiB address space and 30 CPU seconds. `timeout_ms` remains the separate worker deadline.
Use the shared typed `producer_limits` contract, advertising only fields enforced by EVM. If `file_bytes` is omitted, it follows the selected `output_bytes`; an explicit file budget stays independent. Linux soft/hard limits retain the tighter inherited value, and failures distinguish configured from unknown effective limits and signal causes. Resource guidance identifies the exhausted field, its selected value and whether inherited restrictions can prevent repair. The 256 MiB worker heap stays explicit and independent.
Preserve carrier/decoded identities, metadata, target-free offline authority, cancellation, final cleanup and partial facts. Verify same-target recovery with a small selected allowance and narrower inherited-limit controls; report unavailable real Linux coverage.
+6
View File
@@ -0,0 +1,6 @@
# Tasks
- [x] Declare defaults and exact per-call policy.
- [x] Wire parent, worker and inherited resource enforcement.
- [x] Update exact recovery, CLI and Evidence.
- [x] Run focused regressions and report platform gaps.
+10 -3
View File
@@ -4,8 +4,8 @@
Extend `goBinarySchema` in `src/domain/go/goBinary.ts` with optional
`string_literals` (`evidence_kind: "inferred"`, `items`, `discovered_count`,
`aggregate_bytes`, `max_items`, `truncated`), plus `include_string_literals` and
`max_string_literals` on `src/contracts/go/goToolContracts.ts` and
`aggregate_bytes`, `max_items`, `truncated`, optional `limits_reached`), plus
`include_string_literals` and the Go-owned per-call budget controls on `src/contracts/go/goToolContracts.ts` and
`src/cli/goCommands.ts`. Add the `string_literals` view projection to
`src/domain/analysisView/goBinaryView.ts` so callers can page recovered literals
from retained Evidence without repeating file parsing.
@@ -18,6 +18,9 @@ pointer alignment phase, merge overlaps, and sort by ascending file offset so
`reference_count` counts each physical header once and `first_header_offset` is
minimal. Deduplicate target slices in memory with bounded candidate and rejection
caches before sorting deterministically by `(location.offset, location.bytes)`.
Use exact offset/length keys, so configurable string lengths cannot collide in
an arithmetic key encoding. Resolve budget defaults once in the image reader
and pass that policy to string recovery.
## Incremental work and validation
@@ -37,4 +40,8 @@ caches before sorting deterministically by `(location.offset, location.bytes)`.
Default `inspect_go_binary` calls omit `string_literals` and preserve historical
Evidence compatibility. No external tool or target execution is introduced; only
bounded offline byte slicing over the already read `64 MiB` image buffer runs.
bounded offline byte slicing over the admitted stable image buffer runs. The
input default remains `256 MiB`; callers can select byte and work budgets within
the advertised runtime representability. Count normalized report bytes before
allocating its JSON serialization. Every exhausted limit carries its own next
request repair, independently of transport delivery.
+24 -7
View File
@@ -17,15 +17,30 @@ images so static string slices are recovered at their exact boundaries.
`O(log R)` readable segment checks on Mach-O and sectionless/unnamed-ELF
load-region fallbacks) in deduplicated ascending file-offset order.
- Admit candidate `[ptr, ptr + len)` slices only inside single read-only
non-executable mapped spans with `1 <= len <= 4096`, non-overlapping with the
non-executable mapped spans with `1 <= len <= max_string_literal_bytes`
(default `4096`), non-overlapping with the
header itself, and valid printable UTF-8 (allowing `\t`, `\n`, `\r`, `U+200C`
and `U+200D` while rejecting control, surrogate, noncharacter, private-use and
invisible/bidi `\p{Cf}` code points).
- Bound header-scan work (`64 MiB`), tracked candidates (`100,000`), negative
slice cache (`4,096`), returned items (`max_string_literals`, `1..10,000`,
default `2,000`), and aggregate returned UTF-8 bytes (`512 KiB`), reporting
`evidence_kind: "inferred"`, `discovered_count`, `aggregate_bytes` and
`truncated` honestly.
- Keep the existing defaults for header-scan work (`64 MiB`), tracked candidates
(`100,000`), returned items (`2,000`), and aggregate returned UTF-8 bytes
(`512 KiB`). Expose each independent boundary as the positive-safe-integer
per-call controls `max_string_header_scan_bytes`, `max_string_candidates`,
`max_string_literals`, `max_string_literal_bytes` and `max_string_bytes`.
String-byte maxima follow the runtime's string representability; there is no
separate fixed 10,000-item ceiling. The bounded negative-slice cache (`4,096`)
affects reuse only and must not discard valid later candidates.
- Report `evidence_kind: "inferred"`, `discovered_count`, `aggregate_bytes`,
`truncated` and the exact exhausted `limits_reached` controls honestly.
Increasing a named control and inspecting the same complete artifact can
recover omitted facts; output paging cannot recover facts never retained.
- Deduplicate candidates by their complete offset and length independently of
the selected literal-length allowance; raising that allowance cannot merge
distinct slices or inflate their reference counts.
- Share one Go analysis budget owner with build-info input, representation and
search controls. Omitted selections keep defaults and remain omitted from
request Evidence. Representation maxima account for base64 expansion; no
setting changes the separate shared native structural decoder or MCP budget.
- Record limitation notes when Mach-O `LC_DYLD_CHAINED_FIXUPS` encodes rebases in
pointer slots or when truncation occurs, and expose a focused `string_literals`
projection in `inspect_analysis_view`.
@@ -35,7 +50,9 @@ images so static string slices are recovered at their exact boundaries.
Unit, boundary and CLI acceptance tests cover ELF/PE/Mach-O (32/64-bit, little
and big endian), sectionless and unreadable-`.shstrtab` ELF images, overlapping
spans, `U+200C`/`U+200D` versus disallowed `\p{Cf}` controls, negative-cache and
output caps, and 25,000-region Mach-O indexing/cancellation. The real Go binary
output defaults, named budget recovery, long-literal candidate identity, and
25,000-region Mach-O indexing/cancellation. Service and provider tests cover
malformed numeric controls and same-artifact retries with larger budgets. The real Go binary
verifier validates CLI and advertised-schema MCP parity across compiled targets.
## Out of scope
@@ -6,3 +6,8 @@
- [x] Add unit, provider, MCP view and CLI acceptance tests for multi-format recovery, Unicode `\p{Cf}` / ZWNJ / ZWJ filtering, caps, negative-cache bounds and 25,000-section Mach-O scaling.
- [x] Verify CLI and `artifactMcpCall` advertised-schema MCP parity across compiled targets in `scripts/verify-go-binary.mjs`.
- [x] Update the Go binary guide, skill reference and generated catalogs; pass documentation route and static checks.
- [x] Consolidate Go-owned budgets and expose per-call input/report/build-info/search and inferred-string controls in CLI/MCP without changing defaults.
- [x] Preserve source validity and exact exhausted controls, remove the fixed 10,000-item schema ceiling, and deduplicate variable-length candidates with complete offset/length identity.
- [x] Run focused source string/service tests and production service budget-recovery checks over temporary offline fixture files.
- [ ] Run the compiled CLI/MCP boundary lanes and real Go verifier for the new budget selections; source checks do not establish those workflows.
@@ -0,0 +1,5 @@
# Implementation plan
1. Declare caller budgets in the domain input and derive one immutable provider policy with existing defaults.
2. Carry that policy in private child requests and scope all parser/read/reply consumers to it without global mutable state.
3. Update exact recovery, CLI controls and Evidence parameters; verify selected-budget recovery and default regressions.
@@ -0,0 +1,9 @@
# Historical capture admission budgets
Allow an analyst to inspect the same retained HAR, SAZ, qlog or native mitmproxy capture after explicitly increasing an exhausted input, reply or nesting budget. Keep existing defaults, producer identity, sensitive-value handling and complete-or-failure semantics.
The public contract accepts positive safe-integer `max_input_bytes` and `max_reply_bytes`, and `max_nesting_depth` up to REA's JSON representation depth minus the four enclosing record/reply/Evidence levels (996). Selected values apply to the admitted operation and its child decoder, source and reply readers, archive expansion and protobuf interpretation. They do not raise the separately owned worker heap, CPU or client transport limits.
Failures name the exhausted parameter and value and the next-call repair. Omitted values retain 32 MiB input, 96 MiB reply and 64 nesting levels. Evidence preserves explicit selections and omission.
Acceptance: the same valid capture rejected by a deliberately small selected budget succeeds after changing only that budget. A malformed control remains malformed. The child and parent enforce the same selected facts, and parallel operations keep distinct policies.
@@ -0,0 +1,6 @@
# Tasks
- [x] Add input contracts and canonical policy selection.
- [x] Connect parent, Node/Python decoder, archive and protobuf boundaries.
- [x] Expose CLI controls and preserve request provenance.
- [x] Verify same-input recovery, malformed controls and parser regressions.
@@ -0,0 +1,9 @@
# Plan
Use one domain policy and schema for defaults, selection and recovery. Publish
local and fetch projections that reject fields their path does not consume.
Thread selected values through existing acquisition/codec/process/format owners,
including independently validated child request budgets. CLI exposes named
numeric options and MCP derives its inputs from canonical source contracts.
Existing guides own product behavior. Verify finite budget recovery through
production boundaries and the public CLI; keep resource/lifecycle regressions.
@@ -0,0 +1,35 @@
# Per-call source-map budget recovery
## Problem
Local tracing and bundle source-map collection discard or refuse useful facts
at independent fixed byte, structural and codec-heap ceilings. A longer timeout
cannot admit the same map. Related: #6176, #8434, #8978.
## Requirements
- Preserve defaults unless representative calibration justifies a change;
preserve selected artifact identities, source declarations,
unknown coverage and cleanup ownership.
- Admit explicit bounded per-call budgets at the producing owners, and propagate
the same policy through acquisition, structural preflight, codec child and
complete request/reply checks.
- Reject inapplicable fields; preserve omission in Evidence parameters.
- Explain the exact exhausted control, effective value, range, application point
and recovery. Retain real representation and client constraints.
- Keep upstream decoding unchanged and preflight rows/segments before allocation.
## Acceptance
A valid local map fails at an explicitly lowered admission, names map_bytes,
and succeeds for the same point through the public CLI after increasing that
budget. The calibrated 8 MiB default admits a complete map above 4 MiB while
preserving an explicit smaller selection; see the
[calibration record](../../../verification/source-map-budget-calibration.md).
A finite fetched map similarly recovers after increasing decoded_records.
Malformed maps, cancellation and failed cleanup retain their existing semantics.
## Non-goals
No source fetching beyond existing origin authority, application execution,
implicit retries, unbounded allocation or change to client deadlines.
@@ -0,0 +1,10 @@
# Tasks
- [x] Define shared source-map policy with existing defaults and typed selections.
- [x] Propagate local budgets through acquisition, service, codec and process owner.
- [x] Propagate fetched response, record and representation budgets.
- [x] Expose exact CLI/MCP contracts and actionable recovery.
- [x] Add finite structural/fetch and public large-map recovery regressions.
- [x] Update source-location and browser guides.
- [x] Run compiled CLI/codec recovery and coordinated type/lint/documentation gates.
- [x] Calibrate local map admission against shipped maps; retain other budgets.
@@ -0,0 +1,135 @@
# Flutter projection budget calibration
Keep the default `pool_policy.uri_limit=500`. Two pinned real APKs show that
requesting 2048 immediately benefits a full bounded package-URI inventory, but
increases complete overview delivery when the task only needs identity and
snapshot facts. This is a task-specific selection, not evidence for a universal
2048 default. No production default changed in this experiment.
## Tasks and correctness
The inventory task retrieves every distinct complete printable ASCII run that
starts `package:` and has length 5–1024 in arm64
`_kDartIsolateSnapshotData`. Both workflows select `contains:"package:"`.
The baseline starts at the default 500 and retries with 2048 when the returned
array is shorter than its count lower bound; the candidate starts at 2048.
Both final arrays exactly match an independent Python ZIP/ELF dynamic-symbol and
section-table parser plus byte-regex oracle. Gallery has 756 matches, LocalSend
1407; 1024 is insufficient for the latter.
The overview control requires target/library identity, ABI and snapshot section
observations, without individual strings. It compares unfiltered complete
Evidence with URI 500 and URI 2048. All calls preserve `run_bytes=1024`,
`token_bytes=128`, `token_limit=2000` and existing admission budgets. Strings
remain byte-pattern observations, not recovered Dart types. Gallery has eight
printable runs over 1024 bytes and LocalSend one. Recovering every bounded
package URI does not make the overall string pool or snapshot coverage complete.
## Measurements
Three serial trials per case, 24 successful fresh source-service processes,
Node 24.22.1, macOS 26.3 arm64. Each starts `FlutterBuildAnalysisService` and closes
its provider. No app installation or execution occurred. OS caches were not
flushed; these are neither cold-cache nor warm-runtime-cache claims. No small
real Flutter artifact was available as a setup control. Eight task variants
across two APKs do not establish universal workload fit.
Values below are medians of three runs; tokens were identical within each case.
The numeric receipts, artifact manifest and portable reproduction tools are in
[`scripts/verify/analysis/flutter-budget-calibration/`](https://github.com/morluto/rea/tree/main/scripts/verify/analysis/flutter-budget-calibration).
| APK / completed task | Calls | cl100k tokens | Wall s | CPU s | Peak group RSS MiB |
| --------------------------------------- | ----: | ------------: | -----: | ----: | -----------------: |
| Gallery inventory, default + recovery | 2 | 14,488 | 0.836 | 1.028 | 258.656 |
| Gallery inventory, 2048 first | 1 | 8,631 | 0.480 | 0.601 | 238.859 |
| Gallery overview, default | 1 | 15,995 | 0.448 | 0.639 | 241.922 |
| Gallery overview, 2048 | 1 | 18,764 | 0.560 | 0.639 | 243.328 |
| LocalSend inventory, default + recovery | 2 | 22,360 | 0.405 | 0.519 | 221.328 |
| LocalSend inventory, 2048 first | 1 | 16,317 | 0.296 | 0.365 | 184.781 |
| LocalSend overview, default | 1 | 16,885 | 0.292 | 0.403 | 189.766 |
| LocalSend overview, 2048 | 1 | 27,158 | 0.293 | 0.388 | 189.641 |
Tokens use tiktoken 0.12.0 `cl100k_base`, separately encoding compact Unicode JSON
for every logical request and complete reply, including retries. They are exact
counts for that encoding and those records, not model usage. CLI/MCP discovery,
adapter delivery, retained-resource paging, chat framing and model reasoning
are unmeasured. Path spelling can change replay counts. The driver also emits
separately labeled tokenx 1.3.0 estimates; the table uses cl100k counts.
Wall time includes observer overhead. CPU is OS `wait4` user+system usage of the
waited subtree; it excludes sampler work and descendants not waited for. RSS is
the simultaneous process-group sum sampled every 20 ms: short peaks can be
missed, shared pages can be counted twice and descendants leaving the group
are excluded. The 1536 MiB observer stop is not a kernel hard limit. All 24 runs
exited 0 without a resource stop and left their groups empty. No timing or memory
claim is made for an unmeasured MCP summary workflow; the separate retained
local historical summary study (`experiments/analysis-efficiency/flutter-aot-summary.md`) measured that workflow historically.
The frozen baseline is HEAD `e2a2ede5440df396e44b3406ef511a31a680290e` plus
tracked patch SHA256 `038b69c19bbd79a303c9ecd8d9d22a1251637e3886543ca4f5a4c1b70c724eec`
and copied untracked policy sources. HEAD alone does not identify the measured
runtime. Original driver/oracle hashes and per-run values are retained in
`measurements.json`; retained portable scripts change path resolution without
changing task semantics. Later observer cleanup fixes do not change the sampled
RSS algorithm or these successful, empty-group receipts.
## Reproduce
Use the pinned Node toolchain and installed locked dependencies. Download the two
URLs in `manifest.json` to an external input directory under their declared
filenames. `oracle.py` verifies APK sizes/digests, libapp digests, section offsets
and expected counts before generating exact arrays. Do not commit APKs or raw
reply records. Run comparisons serially; replace the paths below with absolute
paths to the selected source snapshot, Node 24 and external artifacts.
```sh
REA_CAL_REPO=/absolute/path/to/rea
REA_CAL_NODE=/absolute/path/to/node24
REA_CAL_INPUTS=/absolute/path/to/apks
REA_CAL_OUTPUT=/absolute/path/to/receipts
REA_CAL_ASSETS="$REA_CAL_REPO/scripts/verify/analysis/flutter-budget-calibration"
for trial in 1 2 3; do
python3 "$REA_CAL_ASSETS/oracle.py" "$REA_CAL_INPUTS" "$REA_CAL_OUTPUT/r$trial"
python3 "$REA_CAL_ASSETS/configs.py" "$REA_CAL_INPUTS" "$REA_CAL_OUTPUT/r$trial"
for config in "$REA_CAL_OUTPUT/r$trial/"*.config.json; do
python3 "$REA_CAL_REPO/scripts/verify/analysis/budget-calibration-run.py" \
--output "${config%.config.json}.metrics.json" --cwd "$REA_CAL_REPO" \
--timeout-seconds 90 --memory-mib 1536 -- \
"$REA_CAL_NODE" --import "$REA_CAL_REPO/node_modules/tsx/dist/loader.mjs" \
"$REA_CAL_ASSETS/task.mjs" "$REA_CAL_REPO" "$config"
done
done
python3 "$REA_CAL_ASSETS/aggregate.py" "$REA_CAL_OUTPUT" "$REA_CAL_OUTPUT/aggregate.json"
```
`aggregate.py` requires tiktoken 0.12.0. Install it separately if needed; the
calibration scripts do not change the project's dependencies or host software.
Replays on a later checkout are new measurements, not reproduction of the
frozen runtime merely because artifact hashes match.
## Other defaults remain evidence-limited
Keep Apktool strings 2000/table and 10000 aggregate: the retained real
three-APK native-decoder study (`experiments/analysis-efficiency/apktool-string-tables.md`, historical local output) has at most 1126 valid
strings in one table, and three selected locale tables fit the aggregate.
That does not calibrate all APKs or all-locales tasks. No fresh Apktool run was
performed here; the current host lacks the executable.
Keep desktop sampling defaults pending a representative complete state-view
corpus. The pinned official QGIS GPKG used in the historical local query study
(`experiments/analysis-efficiency/sqlite-existing-controls.md`) has 26 declared tables,
240 countries and 4556 states/provinces according to an independent read-only
SQLite count. A 500-row sample covers the former and omits the latter. One
all-column geometry row can expand to 485277 bytes; selected tables/columns are
more appropriate than globally multiplying retained rows. Counts alone do not
calibrate CPU/memory or text/XML defaults.
Keep JavaScript semantic candidate/depth/getter/alias defaults. They bound
analysis work and precision, rather than merely display projection. No
independent real-artifact sensitivity study crossing these defaults was found.
Synthetic recovery regressions establish behavior, not representative workload
fit. This calibration gap remains explicit.
The supporting QGIS input is pinned in `manifest.json`; the row counts were
rechecked with Python SQLite in read-only mode. Historical local studies are
context, not new provider measurements or checked-in raw receipts.
@@ -0,0 +1,127 @@
# JavaScript primitive-budget calibration — 2026-11-09
Keep the existing semantic precision defaults. A narrow real-source comparison
found no additional required facts when doubling `primitive_bytes`; it does not
justify a global increase or replacing the existing conservative admission rule.
The input contract now explains that rule, and its list of defaults derives from
the policy owner instead of repeating independent literals.
## What was checked
Two inert directories went through the actual compiled
`analyze-javascript-application` CLI, including its isolated source worker,
complete Evidence serialization and normal CLI cleanup:
- An original `theme.ts` embedded in the pinned Actual Budget Theme source map.
The required task recovers all **237** literal CSS property/value pairs and
their source identity. A separate Python parser accepts only the complete
literal-only declaration and checks every name/value without using REA's
evaluator. A small package descriptor constructs the analysis directory; this
is one captured module, not the complete application or an authenticated
browser capture.
- The installed locked **ms 2.1.3** package's original `index.js` and package
descriptor. The requested small control recovers its `s = 1000` millisecond
base unit. Multiplication-derived coefficients remain unsupported and unknown;
larger byte admission does not implement arithmetic or prove those values.
Input URLs/digests, a portable preparation/oracle script, CLI task driver and
all twelve compact numeric receipts are in
[the calibration assets](https://github.com/morluto/rea/tree/main/scripts/verify/analysis/javascript-precision-calibration).
No downloaded code or complete large replies are committed. The theme original
source SHA-256 is `05307dc6f927705a8c76e43579c910bf6415ce28b375eea19251dc51dd2b1726`.
## Results
Three fresh CLI/worker trials per case compare omitted precision with explicit
`semantic_precision: {"primitive_bytes": 2097152}`. Both requests hold the worker
and CLI host old-space allowance at 512 MiB. Each task makes one analysis call,
with no retries. All twelve exit successfully, verify the required facts and
leave the observer's process group empty. Theme has no semantic unknowns; ms
keeps the same 133 unrelated unknowns in both variants. This is complete
coverage of the requested facts, not complete semantic understanding of ms.
| Task / allowance | Required facts | Estimated CLI task tokens | Wall s | CPU s | Sampled host-group RSS MiB |
| ----------------------------- | -------------: | ------------------------: | -----: | ----: | -------------------------: |
| Theme / default 1 MiB | 237 | 259,396 | 0.937 | 1.226 | 330.6 |
| Theme / explicit 2 MiB | 237 | 259,475 | 0.928 | 1.229 | 330.8 |
| ms base unit / default 1 MiB | 1 | 234,613 | 0.899 | 1.238 | 322.2 |
| ms base unit / explicit 2 MiB | 1 | 234,668 | 0.905 | 1.225 | 330.5 |
Values are medians of three serial trials, in fixed default-then-double order.
Node 24.22.1 ran on macOS 26.3 arm64; OS caches were not flushed. Mixed small
timing/memory differences do not establish a performance improvement.
Token estimates use tiktoken 0.12.0 `cl100k_base`: compact Unicode JSON of the
actual argv and complete parsed stdout Evidence, plus literal progress stderr.
The full complete replies are about 734 KB for Theme and 624 KB for ms. This
measurement includes the CLI entrypoint, worker, serialized output, and the
driver's correctness checks. It excludes artifact acquisition, one-off oracle
preparation, tokenizer computation, MCP discovery/protocol, chat framing and
model reasoning. These are not billed model tokens or a claim that full output
is an efficient agent workflow; existing retained summary/views can change
delivery independently of evaluator precision. Replays with other path spellings
can have different tokens and identities.
CPU is OS `wait4` user+system usage of the waited subtree; sampler work and
unwaited descendants are excluded. RSS sums the host process group every 20 ms:
short peaks may be missed and shared pages counted more than once. **The isolated
JavaScript worker launches into another process group, so its peak memory is
unmeasured here.** The table is not total process-family peak memory. The
observer's 1536 MiB early-stop guard is not a kernel hard limit; its 90-second
deadline was not reached. Worker old-space flags are also not whole-process
memory limits. Compiled entrypoint and owner hashes are retained because HEAD
`e2a2ede5440df396e44b3406ef511a31a680290e` alone does not identify the measured
uncommitted runtime. Portable scripts revise path handling after measurement
without changing the requested facts; original driver/oracle hashes are retained.
## Why retain the conservative rule
`primitive_bytes` charges six estimated JSON bytes per UTF-16 code unit plus
quotes, before creating concatenations. At the 1 MiB default, 174,762 ASCII units
fit and one additional unit exhausts that estimate, even though actual ASCII
JSON occupies far fewer bytes. Control characters and lone surrogates can need
six escaped bytes, so this is a conservative precision/resource policy rather
than an assertion about observed encoded bytes. Existing regressions retain the
boundary and exact exhausted-control recovery.
A bounded Babel inventory of the pinned generated Theme, main and DateSelect
scripts found their longest static strings/templates to be 152, 936 and 13,120
UTF-16 units respectively. This inventory is not full semantic analysis and
does not bound dynamically composed values or candidate cross-products. It
nevertheless supplies no representative case requiring a higher string default.
Exact preallocation accounting would need to inspect string contents across
every candidate concatenation, including pairs formed across piece boundaries,
while preserving work bounds. Without an observed task failing this estimate,
that added work and complexity are unjustified. The 256-candidate, 512-state and
other depth/work defaults remain provisional: these two tasks do not establish
their suitability for large unions, getters, aliases or adversarial sources.
## Reproduce
Download the pinned Theme map URL from `manifest.json` to external storage, use
the pinned Node toolchain and installed locked dependencies, then run:
```sh
REA_JS_REPO=/absolute/path/to/rea
REA_JS_NODE=/absolute/path/to/node24
REA_JS_DATA=/absolute/path/to/external-receipts
REA_JS_ASSETS="$REA_JS_REPO/scripts/verify/analysis/javascript-precision-calibration"
python3 "$REA_JS_ASSETS/prepare.py" "$REA_JS_REPO" /absolute/path/to/theme.js.map "$REA_JS_DATA"
for trial in 1 2 3; do
for task in theme ms; do
for budget in default double; do
python3 "$REA_JS_REPO/scripts/verify/analysis/budget-calibration-run.py" \
--output "$REA_JS_DATA/r${trial}-${task}-${budget}.metrics.json" \
--cwd "$REA_JS_REPO" --timeout-seconds 90 --memory-mib 1536 -- \
python3 "$REA_JS_ASSETS/task.py" "$REA_JS_REPO" "$REA_JS_NODE" \
"$REA_JS_DATA" "$task" "$budget" "$REA_JS_DATA/r${trial}-${task}-${budget}.replies.json"
done
done
done
python3 "$REA_JS_ASSETS/aggregate.py" "$REA_JS_DATA" "$REA_JS_DATA/aggregate.json"
```
`aggregate.py` needs the separately installed tokenizer version above. These
scripts neither install dependencies nor change host/provider configuration.
Later checkouts are new measurements even when target hashes still match.
@@ -0,0 +1,168 @@
{
"schema": "rea-native-budget-qualification/v1",
"date": "2026-11-09",
"host": "macOS 26.3 arm64",
"node": "24.22.1",
"scope": "Real application service/provider boundaries, not public MCP/CLI recovery. Controlled owned fixtures only.",
"frida": {
"package_version": "17.23.2",
"cases": [
{
"session_bytes": 1,
"messages": [],
"messages_truncated": true,
"effective_limits": {
"session_bytes": 1,
"message_type_bytes": 4096,
"message_text_bytes": 32768,
"json_nodes": 250000,
"json_depth": 64
},
"limitations": [
"Artifact identity is unavailable for this observation.",
"The target operating system and architecture were not reported by this observation.",
"Frida capture reached capture_limits.session_bytes (1 bytes including JSON/base64 expansion and metadata). Select a larger capture_limits.session_bytes before starting a new explicitly chosen session or instrument_with_frida experiment; no REA restart is required. Already discarded observations cannot be recovered. Session bytes and MCP/client delivery budgets remain independent."
],
"cleanup_error": null
},
{
"session_bytes": 4096,
"messages": [
{
"script_id": "2c901ff4-07b7-4a97-9784-bdc9f676c911",
"type": "send",
"payload": null,
"payload_truncated": true,
"payload_capture_limit": "payload_bytes"
}
],
"messages_truncated": false,
"effective_limits": {
"session_bytes": 4096,
"message_type_bytes": 4096,
"message_text_bytes": 32768,
"json_nodes": 250000,
"json_depth": 64
},
"limitations": [
"Artifact identity is unavailable for this observation.",
"The target operating system and architecture were not reported by this observation.",
"Frida capture reached capture_limits.session_bytes (4096 bytes including JSON/base64 expansion and metadata). Select a larger capture_limits.session_bytes before starting a new explicitly chosen session or instrument_with_frida experiment; no REA restart is required. Already discarded observations cannot be recovered. Session bytes and MCP/client delivery budgets remain independent."
],
"cleanup_error": null
},
{
"session_bytes": 262144,
"messages": [
{
"script_id": "6283235c-f8c4-40db-ad61-3375003e4362",
"type": "send",
"payload": {
"rea_budget_probe": "ok"
}
}
],
"messages_truncated": false,
"effective_limits": {
"session_bytes": 262144,
"message_type_bytes": 4096,
"message_text_bytes": 32768,
"json_nodes": 250000,
"json_depth": 64
},
"limitations": [
"Artifact identity is unavailable for this observation.",
"The target operating system and architecture were not reported by this observation."
],
"cleanup_error": null
}
]
},
"lldb": {
"version": "lldb-2100.0.17.108",
"cases": [
{
"output_bytes": 2,
"target_sha256": "819c94d6cdf0aff72ff5cea9769a7caf900d223af23ef11d66a13baa8baf71aa",
"outcome": "exited",
"events": 1,
"stdout": {
"text": "re",
"bytes": 20,
"truncated": true,
"complete": true
},
"stderr": {
"text": "",
"bytes": 0,
"truncated": false,
"complete": true
},
"terminated": true
},
{
"output_bytes": 64,
"target_sha256": "819c94d6cdf0aff72ff5cea9769a7caf900d223af23ef11d66a13baa8baf71aa",
"outcome": "exited",
"events": 1,
"stdout": {
"text": "rea_budget_probe_ok\n",
"bytes": 20,
"truncated": false,
"complete": true
},
"stderr": {
"text": "",
"bytes": 0,
"truncated": false,
"complete": true
},
"terminated": true
}
],
"cleanup": {
"ok": true
}
},
"working_controls": {
"frida_existing_smoke": {
"ok": true,
"provider": "frida",
"host_platform": "darwin",
"target_mode": "local_spawn",
"target_pid": 47641,
"target_name": "node",
"script_message_observed": true,
"cleanup_error": null
},
"ghidra_java_compile": {
"ghidra_version": "12.1.4",
"javac": "javac 21.0.12.1",
"class_path_jars": 195,
"sources": [
"bridge/ghidra/ReaGhidraAnalysisOptions.java",
"bridge/ghidra/ReaGhidraApplySeeds.java",
"bridge/ghidra/ReaGhidraBridge.java",
"bridge/ghidra/ReaGhidraBundleActivator.java",
"bridge/ghidra/ReaGhidraExtensions.java",
"bridge/ghidra/ReaGhidraMipsSoftFloat.java",
"bridge/ghidra/ReaGhidraNoReturnFix.java",
"bridge/ghidra/ReaGhidraPrepareCom.java",
"bridge/ghidra/ReaGhidraPrepareDol.java",
"bridge/ghidra/ReaGhidraPrepareRaw.java",
"bridge/ghidra/ReaGhidraRiscvCallingConvention.java"
]
}
},
"probe_source_sha256": {
"frida-budget-probe.mjs": "43dd424e0750bc8f60468210d99caedaa1e0fa16148db3c2290a8ff593254ccb",
"lldb-budget-probe.mjs": "f11e899698d6c219078b1e4a6b52d97bfd308ab9ee8fade6902cb2bdfd1826af"
},
"limitations": [
"Each raised capture budget starts a new controlled process; discarded live observations are not recovered retroactively.",
"Initial 4 KiB Frida marker assertion failed because a fixed 128 KiB metadata reserve leaves no payload allowance; tagged truncation is preserved.",
"Initial 1-second LLDB observation elapsed before any event; a 10-second-window rerun isolated output retention.",
"Successful observer receipts showed empty owned process groups; this is not a kernel process-family memory or performance qualification.",
"Windows/Linux, full AgentEnv, remote Frida and public native-tool transport recovery are unverified."
]
}
@@ -0,0 +1,227 @@
{
"status": "passed",
"platform": "darwin",
"architecture": "arm64",
"node": "v24.22.1",
"fixture": {
"kind": "volatile-straight-line",
"statements": 5400,
"source_bytes": 297679
},
"target_sha256": "4004126020cc5517d75c47cacabc68422a12b3260ccd19c105f9a7df087c69d1",
"source_sha256": "8bc279d5a4881ca87d1629fc26d73d191986d2a2fb014f77a8d711ea0eca9426",
"compiler": {
"command": "cc",
"arguments": [
"-dynamiclib",
"-fPIC",
"-O0",
"/var/folders/xl/60j5sd4j6b99tn8q082l9t8r0000gn/T/rea-ghidra-decompile-timeout-c0NNHy/slow-decompile.c",
"-o",
"/var/folders/xl/60j5sd4j6b99tn8q082l9t8r0000gn/T/rea-ghidra-decompile-timeout-c0NNHy/slow-decompile.dylib"
]
},
"provider": {
"id": "ghidra",
"name": "Ghidra",
"version": "12.1.4"
},
"analysis_profile_digest": "43b27fd852aa4e8b9b08c60928f06e9961e04f17410d0246ca771c3d526248c4",
"configured_jvm_max_heap": "1G",
"elapsed_seconds": 16.066160042,
"stages": [
{
"stage": "operations_low",
"available": true,
"operations": 1,
"largest_input_count": 2,
"def_use_edges": 0,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 1,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": ["max_operations"],
"omitted_operations_lower_bound": 1,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "operations_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "inputs_low",
"available": true,
"operations": 8,
"largest_input_count": 1,
"def_use_edges": 4,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 1,
"max_edges": 12000
},
"limits_reached": ["max_inputs_per_operation"],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 5,
"known_omitted_edges": 0
},
{
"stage": "inputs_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "edges_low",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 1,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 1
},
"limits_reached": ["max_edges"],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 5
},
{
"stage": "edges_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "operations_lowered",
"available": true,
"operations": 2,
"largest_input_count": 2,
"def_use_edges": 1,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 2,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": ["max_operations"],
"omitted_operations_lower_bound": 1,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "payload_1_mib",
"seconds": 1.7718680000000004,
"code": "execution_failure",
"message": "Ghidra decompilation failed: Exception while decompiling 00000414: Response buffer size exceeded for: target-4004126020cc.dylib . The effective native decompiler payload allowance is 1 MiB. If the diagnostic reports payload exhaustion, in MCP raise ghidra.decompile_max_payload_mib with update_analysis_settings (integer MiB from 1 to 2047) and repeat the selected operation on this Program without restarting REA. For a one-shot CLI retry, set REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB to a larger MiB value in the environment that starts the next rea invocation, then repeat the selected operation; the previous invocation's Program is no longer active. Runtime null or an omitted launch seed restores the installed Ghidra default. This does not raise the decompile deadline, high-pcode projection budgets or MCP client limits. Only the decompiler failed for this procedure. The Ghidra session remains usable."
},
{
"stage": "payload_64_mib_recovered",
"seconds": 2.11943175,
"pseudocode_bytes": 261131
},
{
"stage": "deadline_partial_dossier",
"seconds": 2.3652059170000013,
"pseudocode": {
"available": false,
"reason": "Ghidra decompilation of 0x414 exceeded the 1-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS)"
},
"assembly_lines": 49912,
"basic_blocks": 1,
"cache_policy": "live"
},
{
"stage": "deadline_pseudocode",
"seconds": 1.2178966249999994,
"code": "provider_timeout",
"message": "Ghidra decompilation of 0x414 exceeded the 1-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS). Only this procedure's decompilation was stopped. The Ghidra session remains usable.",
"limit": {
"setting": "ghidra.decompile_timeout_seconds",
"effective_value": 1,
"unit": "seconds",
"minimum": 1,
"maximum": 2147483,
"applies_at": "next_operation",
"agent_editable": true,
"scope": "rea-runtime",
"rea_restart_required": false
},
"remediation": {
"action": "Use analyze_function (CLI: `rea function`) for listing facts. Call update_analysis_settings with setting ghidra.decompile_timeout_seconds and action set to raise the per-function deadline for the next operation on this Program. For CLI or future REA processes, set REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Set the MCP tool-call deadline to cover startup and the full call; earlier cancellation closes the session. `rea setup` estimates deadlines for new Codex/Kimi registrations and preserves existing positive values; edit existing deadlines yourself."
}
},
{
"stage": "deadline_recovered_payload_null",
"seconds": 2.0052460419999987,
"selected_timeout_seconds": 20,
"selected_payload_mib": null,
"pseudocode_bytes": 261131
}
],
"initial_session_run_id": "c03f277d-d679-4e85-965b-690474ec4e9c",
"final_session_run_id": "c03f277d-d679-4e85-965b-690474ec4e9c",
"same_session_next_decompile": "succeeded",
"verifier_run": {
"run_id": "7fea6f67-fb82-4c6a-b129-8bbe3de8b5b2",
"verifier_pid": 52515,
"parent_pid": 2850,
"process_lineage": {
"status": "verified",
"observed_at": "2026-11-09T16:50:38.401Z",
"launcher_pid": 52515,
"launcher_parent_pid": 2850,
"process_group_id": 52515,
"descendants": []
}
},
"scope": "Real Ghidra analysis of generated Mach-O/ELF through authenticated loopback: independent value-flow budgets, lowered admission, native payload exhaustion and recovery, natural decompile timeout with listing-backed partial facts, and deadline recovery on the same Program. No target execution. Windows and public CLI/MCP transport recovery are unverified. Timings are observations, not performance comparisons; CPU, peak process-family memory and agent tokens are unmeasured.",
"cleanup": "complete"
}
@@ -0,0 +1,232 @@
{
"integration_base": "8dfc47b107afc0cf4b53322a10b94c666fe9e18b",
"tested_feature_head": "329df8b331ece4201e7ea0849643c4481611ac86",
"qualification": "Post-main-integration correctness run; not a performance comparison.",
"receipt": {
"status": "passed",
"platform": "darwin",
"architecture": "arm64",
"node": "v24.22.1",
"fixture": {
"kind": "volatile-straight-line",
"statements": 5400,
"source_bytes": 297679
},
"target_sha256": "0eabbda46fcb6bbb33a84df09f8a2f1b2e4621aa5c7b8c7216921188e3b55ad8",
"source_sha256": "8bc279d5a4881ca87d1629fc26d73d191986d2a2fb014f77a8d711ea0eca9426",
"compiler": {
"command": "cc",
"arguments": [
"-dynamiclib",
"-fPIC",
"-O0",
"/var/folders/xl/60j5sd4j6b99tn8q082l9t8r0000gn/T/rea-ghidra-decompile-timeout-8ppfRJ/slow-decompile.c",
"-o",
"/var/folders/xl/60j5sd4j6b99tn8q082l9t8r0000gn/T/rea-ghidra-decompile-timeout-8ppfRJ/slow-decompile.dylib"
]
},
"provider": {
"id": "ghidra",
"name": "Ghidra",
"version": "12.1.4"
},
"analysis_profile_digest": "a70b06ba1d73ceb5b6e7ee2d9f7587583c17e076134ebb96f0169f6a8b95956a",
"configured_jvm_max_heap": "1G",
"elapsed_seconds": 16.838551541999998,
"stages": [
{
"stage": "operations_low",
"available": true,
"operations": 1,
"largest_input_count": 2,
"def_use_edges": 0,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 1,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": ["max_operations"],
"omitted_operations_lower_bound": 1,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "operations_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "inputs_low",
"available": true,
"operations": 8,
"largest_input_count": 1,
"def_use_edges": 4,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 1,
"max_edges": 12000
},
"limits_reached": ["max_inputs_per_operation"],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 5,
"known_omitted_edges": 0
},
{
"stage": "inputs_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "edges_low",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 1,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 1
},
"limits_reached": ["max_edges"],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 5
},
{
"stage": "edges_recovered",
"available": true,
"operations": 8,
"largest_input_count": 2,
"def_use_edges": 6,
"parameter_uses": 1,
"truncated": false,
"limits": {
"max_operations": 3000,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": [],
"omitted_operations_lower_bound": 0,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "operations_lowered",
"available": true,
"operations": 2,
"largest_input_count": 2,
"def_use_edges": 1,
"parameter_uses": 1,
"truncated": true,
"limits": {
"max_operations": 2,
"max_inputs_per_operation": 64,
"max_edges": 12000
},
"limits_reached": ["max_operations"],
"omitted_operations_lower_bound": 1,
"known_omitted_inputs": 0,
"known_omitted_edges": 0
},
{
"stage": "payload_1_mib",
"seconds": 1.6774305839999988,
"code": "execution_failure",
"message": "Ghidra decompilation failed: Exception while decompiling 00000414: Response buffer size exceeded for: target-0eabbda46fcb.dylib . The effective native decompiler payload allowance is 1 MiB. If the diagnostic reports payload exhaustion, in MCP raise ghidra.decompile_max_payload_mib with update_analysis_settings (integer MiB from 1 to 2047) and repeat the selected operation on this Program without restarting REA. For a one-shot CLI retry, set REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB to a larger MiB value in the environment that starts the next rea invocation, then repeat the selected operation; the previous invocation's Program is no longer active. Runtime null or an omitted launch seed restores the installed Ghidra default. This does not raise the decompile deadline, high-pcode projection budgets or MCP client limits. Only the decompiler failed for this procedure. The Ghidra session remains usable."
},
{
"stage": "payload_64_mib_recovered",
"seconds": 2.1426570829999982,
"pseudocode_bytes": 261131
},
{
"stage": "deadline_partial_dossier",
"seconds": 2.4800411670000013,
"pseudocode": {
"available": false,
"reason": "Ghidra decompilation of 0x414 exceeded the 1-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS)"
},
"assembly_lines": 49912,
"basic_blocks": 1,
"cache_policy": "live"
},
{
"stage": "deadline_pseudocode",
"seconds": 1.2235721249999987,
"code": "provider_timeout",
"message": "Ghidra decompilation of 0x414 exceeded the 1-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS). Only this procedure's decompilation was stopped. The Ghidra session remains usable.",
"limit": {
"setting": "ghidra.decompile_timeout_seconds",
"effective_value": 1,
"unit": "seconds",
"minimum": 1,
"maximum": 2147483,
"applies_at": "next_operation",
"agent_editable": true,
"scope": "rea-runtime",
"rea_restart_required": false
},
"remediation": {
"action": "Use analyze_function (CLI: `rea function`) for listing facts. Call update_analysis_settings with setting ghidra.decompile_timeout_seconds and action set to raise the per-function deadline for the next operation on this Program. For CLI or future REA processes, set REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Set the MCP tool-call deadline to cover startup and the full call; earlier cancellation closes the session. `rea setup` estimates deadlines for new Codex/Kimi registrations and preserves existing positive values; edit existing deadlines yourself."
}
},
{
"stage": "deadline_recovered_payload_null",
"seconds": 2.361492542,
"selected_timeout_seconds": 20,
"selected_payload_mib": null,
"pseudocode_bytes": 261131
}
],
"initial_session_run_id": "89aac416-1a46-4eeb-9ddf-9652ba17262b",
"final_session_run_id": "89aac416-1a46-4eeb-9ddf-9652ba17262b",
"same_session_next_decompile": "succeeded",
"verifier_run": {
"run_id": "1c4699a5-d090-4cad-8271-961cdd41d23e",
"verifier_pid": 9490,
"parent_pid": 2850,
"process_lineage": {
"status": "verified",
"observed_at": "2026-11-09T21:55:25.629Z",
"launcher_pid": 9490,
"launcher_parent_pid": 2850,
"process_group_id": 9490,
"descendants": []
}
},
"scope": "Real Ghidra analysis of generated Mach-O/ELF through authenticated loopback: independent value-flow budgets, lowered admission, native payload exhaustion and recovery, natural decompile timeout with listing-backed partial facts, and deadline recovery on the same Program. No target execution. Windows and public CLI/MCP transport recovery are unverified. Timings are observations, not performance comparisons; CPU, peak process-family memory and agent tokens are unmeasured.",
"cleanup": "complete"
}
}
+107 -4
View File
@@ -2,11 +2,16 @@
Historical report added in
[commit f3ed1657f82188fb90914f745a08849537342a23](https://github.com/morluto/rea/commit/f3ed1657f82188fb90914f745a08849537342a23)
on 2026-10-25. The exact source revisions used for the checks and measurements
below were not recorded here. This record does not establish current verification.
on 2026-10-25. The exact source revisions used for the initial checks and
measurements were not recorded here. Those results do not establish current
verification. The initial checks describe the runtime-settings introduction;
dated follow-ups below preserve their own receipts and coverage limitations.
Rebasing the implementation does not requalify a later runtime against these
historical measurements.
Verified on macOS with Node 24.22.1, Java 21.0.12.1 and Ghidra 12.1.4.
This change adds two MCP tools and 11 runtime defaults without a new dependency.
The initial verification used macOS with Node 24.22.1, Java 21.0.12.1 and
Ghidra 12.1.4. That implementation added two MCP tools and 11 runtime defaults
without a new dependency.
## Behavioral checks
@@ -86,3 +91,101 @@ and regex setting integration have source/boundary coverage, not a new real
provider run for every setting. Linux/Windows engines and Bun are unverified
for this change. Client deadlines and receive buffers remain externally owned;
active worker values are explicitly unobserved in the settings inspection.
## Ghidra budget recovery — 2026-11-09
The existing
[`verify-real-ghidra-decompile-timeout.mjs`](https://github.com/morluto/rea/blob/main/scripts/verify-real-ghidra-decompile-timeout.mjs)
now covers p-code projection budgets and native payload admission in the same
real Program as deadline recovery. It replaces the branch fixture in that lane
with 5,400 straight-line volatile statements. A preliminary 1,800-statement
calibration completed under one second, so it did not reproduce deadline
exhaustion; this was fixture calibration, not a failed provider result.
The retained lane ran once on macOS arm64 with Node 24.22.1, Ghidra 12.1.4 and
Homebrew OpenJDK 21.0.12.1. It compiled its own Mach-O dylib using
`cc -dynamiclib -fPIC -O0`; the target was never executed. The lane requested
a 1 GiB JVM heap, bounded compiler execution to 30 seconds, native recovery
decompiles to 20 seconds, and the overall run to 180 seconds. The JVM request
does not bound the separate native decompiler's memory.
Reproduce after the runtime build with an existing Ghidra/JDK installation:
```sh
GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC \
JAVA_HOME=/path/to/jdk-21 \
node scripts/verify-real-ghidra-decompile-timeout.mjs
```
`REA_DECOMPILE_TIMEOUT_STATEMENTS` selects another source-bounded fixture size
when calibrating a different host. The verifier refuses a scenario that does
not naturally exhaust the selected allowance instead of claiming a reproduced
timeout or payload failure.
The [compact receipt](https://github.com/morluto/rea/blob/main/docs/verification/runtime-analysis-settings-ghidra-2026-11-09.json) retains
the source/target hashes, profile digest, exact setting facts, native failure
diagnostics, timings and cleanup lineage. Observed results were:
| Selected control | Observed failure or omission | Recovery in the same Program |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| P-code operations: 1 | One operation retained, omitted-operation lower bound 1 | Raising to 3000 returned eight operations without truncation; lowering to 2 subsequently returned two |
| Inputs per operation: 1 | Five known inputs omitted | Restoring 64 returned complete input coverage for the selected function |
| Def-use edges: 1 | One edge retained, five omitted | Restoring 12000 returned six edges without truncation |
| Native payload: 1 MiB | Ghidra reported `Response buffer size exceeded` | Raising to 64 MiB returned 261131 bytes of pseudocode |
| Native deadline: 1 second | The dossier retained 49912 assembly lines and one basic block with unavailable decompiler facets; direct pseudocode returned typed `provider_timeout` | Raising to 20 seconds and selecting payload `null` returned 261131 bytes of pseudocode; the next small function also decompiled |
The complete lane took 16.07 seconds. Initial and final authenticated session
run IDs both equal `c03f277d-d679-4e85-965b-690474ec4e9c`; the target SHA-256
remained `4004126020cc5517d75c47cacabc68422a12b3260ccd19c105f9a7df087c69d1`.
Provider close succeeded, token-verified lineage reported no live descendants,
and the fixture workspace was removed. Ghidra may replace its native decompiler
after a timeout; this check establishes Program/session continuity, not native
child-process continuity.
This lane uses the real provider and authenticated loopback bridge with the
shared runtime setting store. It does not exercise public CLI or MCP transport
recovery, Windows, another Ghidra release, or every parameter-use edge boundary.
The successful payload-null call does not measure the installed default's
effective ceiling. Timings are observations, not a before/after performance
comparison. CPU time, peak process-family memory and agent token usage were not
measured in the retained run.
## Native capture qualification — 2026-11-09
The installed Frida Node SDK and system LLDB also exercised the new capture
controls through their real application service/provider boundaries on macOS
arm64. The [compact receipt](https://github.com/morluto/rea/blob/main/docs/verification/native-capture-budget-qualification-2026-11-09.json)
records the selected/effective policies, observations and cleanup.
Frida's existing local-spawn verifier observed its marker and reported no cleanup
error. Additional controlled runs selected session allowances of 1 byte, 4 KiB
and 256 KiB. They respectively retained no event, a tagged payload-truncated
event, and the complete marker. The 4 KiB case exposes conservative accounting:
the existing fixed 128 KiB metadata reservation leaves no payload allowance.
This is explicit truncation, not evidence that the nominal 4 KiB is filled with
retained bytes. Its first marker assertion failed for that explained reason;
the subsequent larger-budget experiment does not recover discarded observations.
The production LLDB tracer ran a newly compiled arm64 C fixture with one known
function entry and a 20-byte stdout line. With `output_bytes: 2`, it retained
`re`, reported 20 drained bytes and truncation. A new run of the same unchanged
binary with `output_bytes: 64` returned the complete line, one expected event and
normal exit. Both targets were confirmed terminated and tracer cleanup succeeded.
An initial one-second observation elapsed during startup before any event; the
ten-second-window rerun isolated output retention. No caller target was run.
These checks establish real local producer behavior and cleanup, not public
MCP/CLI recovery, full AgentEnv execution, remote Frida, Windows/Linux behavior,
or optimal default sizes. Group cleanup observations are not whole-family peak
memory measurements. The live Ghidra lane above separately establishes same-Program
recovery for read-only static analysis.
## Main integration qualification — 2026-11-09
After integration onto main `8dfc47b107afc0cf4b53322a10b94c666fe9e18b`, the
retained Ghidra lane ran again against the integrated feature code. Its
[integration receipt](https://github.com/morluto/rea/blob/main/docs/verification/runtime-analysis-settings-ghidra-integration-2026-11-09.json)
records the new source/target identities and observations. Payload and deadline
recovery again used one Program with unchanged target bytes, and owned cleanup
completed. This is a fresh correctness qualification, not a new performance
comparison or a replacement for the historical measurements above.
@@ -0,0 +1,206 @@
# Source-location delivery qualification — 2026-11-09
Opt-in summary delivery substantially reduces the measured main-map task's
serialized transcript and its median elapsed time, CPU and sampled memory. The
small Theme control exposes a tradeoff: summary adds a call and becomes more
expensive when a client must first load the complete `inspect_analysis_view`
definition. Complete delivery remains the compatibility default. These results
support explicit summary selection for large point answers or already available
views; they do not establish a uniform whole-task token or latency improvement.
The [product guide](../guides/web/web-source-location.md#compact-point-delivery-and-retained-source-text)
explains summary, matches pages, explicit UTF-16 source text selection and retained
complete Evidence. This record measures the public compiled stdio MCP workflow.
CLI publication performance is unmeasured here.
## Task, oracle and execution
The task returns every alternative at one generated point, preserving source/map
identity and original source positions, plus a 512 UTF-16-code-unit snippet around
each mapped point. Inputs are the pinned public Actual Budget Theme, main
`index.CMrE7aQb`, and `DateSelect.BVYaCNUe` maps and generated scripts in the existing
[artifact manifest](https://github.com/morluto/rea/blob/main/docs/verification/source-map-budgets-2026-11-09/artifacts.json). Acquisition used
verified cached public bytes and the existing acquisition helper's explicitly
incomplete analyst-manifest construction. No application or browser was executed.
The independent [VLQ oracle](https://github.com/morluto/rea/blob/main/docs/verification/source-map-budgets-2026-11-09/oracle.py) supplies the
expected source declarations, positions, alternatives and content digests.
Both variants use the same compiled runtime, files and portable driver:
- **Complete:** request complete Evidence. Theme and main return it inline; the
driver selects the required snippet locally from the returned source content.
DateSelect exceeds the default 10 MiB transport allowance, then follows its
retained matches continuation and requests the selected source text: three tool
calls, including the failed delivery.
- **Summary:** request `detail: summary`, then explicitly request the selected
source-text page: two tool calls. Match paging remains part of the driver when
required. All three selected real points have one alternative.
This compares current **complete delivery with bounded retained-view recovery**
against opt-in summary. It does not compare against the former export-only fallback.
The measurement includes driver artifact hashing/reference parsing, initialization,
full catalog discovery, trace, necessary continuation/text calls, answer validation
and shutdown. Export is not needed to answer this task and was not included.
Three fresh-process runs per case/variant completed. Trial two reversed variant
order; OS caches were not flushed. All 18 runs exited zero, with empty owned groups
and no resource stop. Aggregation independently verifies all snippets and pinned
map/generated-script/content hashes, identical answers and parent Evidence IDs
across variants/trials, and exact raw-map SHA-256 whenever the initial complete
response includes raw text. Runtime execution and deployment authenticity remain
unknown.
## Serialized token estimates
Counts use tiktoken 0.12.0 `cl100k_base` on compact Unicode JSON-RPC request/reply
lines, including initialization, the initialized notification, `tools/list`, failed
calls, continuations and selected text. Replies include both MCP text and structured
content. These are serialized-transcript estimates, **not actual model usage or
billing**; client presentation can omit or transform those representations.
The observed full catalog uses compact input schemas and full output schemas.
`tools/list` returns 4,487,850 wire bytes. Initialization, notification and catalog
account for **1,145,053 estimated tokens** in every run. Do not add selected
contracts again to the catalog-included totals. Post-discovery counts exclude only
that discovery prefix, so they include all task retries and subsequent calls.
| Map / delivery | Tool calls | Catalog-included task tokens | Post-discovery tokens |
| ----------------------------------- | ---------: | ---------------------------: | --------------------: |
| Theme / complete | 1 | 1,196,400 | 51,347 |
| Theme / summary | 2 | 1,154,878 | 9,825 |
| Main / complete | 1 | 4,354,894 | 3,209,841 |
| Main / summary | 2 | 1,154,851 | 9,798 |
| DateSelect / complete with recovery | 3 | 1,155,932 | 10,879 |
| DateSelect / summary | 2 | 1,154,974 | 9,921 |
Selected complete wired definitions cost **8,408 tokens** for
`trace_web_source_location` and **180,576** for `inspect_analysis_view`. When those
definitions are uncached and a client loads only the tools it uses, Theme complete
costs 8,408 + 51,347 = **59,755**, whereas summary costs
8,408 + 180,576 + 9,825 = **198,809**. Summary loses in this first-use accounting
scenario. The measured full-catalog scenario already loads the view definition,
so its Theme total instead falls from 1,196,400 to 1,154,878. Main still improves
with selected complete definitions: 3,218,249 to 198,782.
As a separate **hypothetical client projection**, retaining only each definition's
`{name, description, inputSchema}` costs 548 and 3,533 tokens respectively. Adding
those selected projected definitions to the same post-discovery transcripts gives:
| Case | Complete → summary, selected complete wired definitions | Complete → summary, hypothetical input-only definitions |
| ---------- | ------------------------------------------------------: | ------------------------------------------------------: |
| Theme | 59,755 → 198,809 | 51,895 → 13,906 |
| Main | 3,218,249 → 198,782 | 3,210,389 → 13,879 |
| DateSelect | 199,863 → 198,905 | 14,960 → 14,002 |
This projection is an offline accounting calculation, not an observed client/model
transformation or an alternate measured discovery workflow. Cached or deferred tool
loading, output-schema profiles and model-facing reply presentation can change the
costs. No actual model run or billing comparison was performed.
## Elapsed time, CPU and sampled memory
Values are medians of three runs on macOS 26.3 arm64 with Node v24.22.1. The small
control has a slight elapsed/CPU increase; DateSelect memory is similar across
variants. Three runs do not establish general performance guarantees.
| Map / delivery | Wall seconds | CPU seconds, waited subtree | Sampled group peak MiB | Sampled tree peak MiB |
| ----------------------------------- | -----------: | --------------------------: | ---------------------: | --------------------: |
| Theme / complete | 0.972 | 1.128 | 498.0 | 555.8 |
| Theme / summary | 0.985 | 1.143 | 483.9 | 542.1 |
| Main / complete | 1.625 | 1.815 | 776.5 | 776.5 |
| Main / summary | 1.134 | 1.294 | 549.7 | 661.4 |
| DateSelect / complete with recovery | 1.199 | 1.351 | 580.6 | 695.6 |
| DateSelect / summary | 1.150 | 1.326 | 588.6 | 694.2 |
The observer samples simultaneous `ps` RSS sums at nominal 20 ms intervals, plus
sampling work. **Tree RSS includes
codec descendants outside the root PGID while their PPID ancestry remains linked.**
Short peaks, exited/reparented/orphaned descendants can be missed; shared pages can
be counted repeatedly. These are sampled sums, not kernel peak or private-memory
measurements. CPU is OS `wait4` user + system for the waited subtree, excluding
sampler CPU and unwaited/orphaned descendants. Wall time includes observer overhead.
The 90-second deadline, 1536 MiB observed memory early stop and cleanup signals apply
only to the **owned process group**. Observational tree membership grants no signal
authority. The group guard does not enforce a tree-wide or kernel memory hard cap;
empty-group cleanup does not prove that an unobserved orphan cannot exist.
A preliminary 18-run completed-task matrix used group-only RSS and a temporary
driver that decoded UTF-8 separately per incoming chunk. It is superseded: codec
processes can change PGID, and chunk boundaries can split UTF-8. Its local method
receipts are preserved; **none of its result statistics appear in these tables**.
All final runs use the retained driver's UTF-8 stream decoder, raw-map digest
check and the observer's additional read-only tree RSS measurement. The earlier
[map-admission calibration](source-map-budget-calibration.md) is a separate study.
## Retained evidence and reproduction
The compact [measurement record](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/source-location-delivery/measurements.json)
contains 18 numeric records, per-call bytes/token estimates, oracle/answer checks,
full SHA-256 pins and the compiled-tree digest. Large replies, artifacts and model
transcripts are not committed. Runtime pins include `dist/mcp/main.js`
(`9bc4c070f63adc9963f276da20b36ffbbcc77b4079585cadf90d792dd2cc04dd`)
and the 2,921-file compiled `dist` tree
(`a8a510499dabc07b3cdf6b164d02e55f2fc6917b696dcc4d23449510e91e80fa`).
The recorded CLI digest is separately bound to `dist/cli.js`. These pins identify
the measured build and selected source-location owners. A later unrelated generated
desktop SQLite diagnostic change and rebuild are outside this snapshot; later
whole-tree hashes do not replace the measured one.
The [portable driver](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/source-location-delivery/task.mjs)
has SHA-256 `b21ec9d5315682337eb55a4ad208e2145a7397d8d567cdb6653facd3f0bec90e`.
The [observer](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/budget-calibration-run.py)
has SHA-256 `c18a06c44715c97ec9cbc76248dfbcd862d0e5ca3cd0c159d868bc7fa1fa9e9f`.
Use the repository's built runtime and pinned Node. Acquisition refuses changed
publisher bytes; the existing acquisition helper creates `artifacts.json` and
analyst manifests under the selected artifact directory.
Create a fresh receipt directory and its `runtime.json` before measuring. Keep
source files and the compiled runtime unchanged throughout all 18 runs and
aggregation; do not build or edit between them. The compiled-tree hash collected
afterward identifies the measured runtime under that condition. Aggregate before
any subsequent rebuild.
```sh
python3 docs/verification/source-map-budgets-2026-11-09/acquire.py \
docs/verification/source-map-budgets-2026-11-09/artifacts.json /tmp/rea-point-artifacts
python3 - <<'PY'
import hashlib, json
from pathlib import Path
receipts = Path('/tmp/rea-source-location-measurements')
receipts.mkdir()
paths = {
'driver_sha256': Path('scripts/verify/analysis/source-location-delivery/task.mjs'),
'observer_sha256': Path('scripts/verify/analysis/budget-calibration-run.py'),
'cli_sha256': Path('dist/cli.js'),
}
metadata = {key: hashlib.sha256(path.read_bytes()).hexdigest()
for key, path in paths.items() if key != 'cli_sha256'}
cli = paths['cli_sha256']
metadata['cli_sha256'] = hashlib.sha256(cli.read_bytes()).hexdigest() if cli.is_file() else None
(receipts / 'runtime.json').write_text(json.dumps(metadata, indent=2) + '\n')
PY
python3 scripts/verify/analysis/budget-calibration-run.py \
--output /tmp/rea-source-location-measurements/r1-index-summary.metrics.json --cwd "$PWD" \
--timeout-seconds 90 --memory-mib 1536 -- \
node scripts/verify/analysis/source-location-delivery/task.mjs "$PWD" \
'{"artifactDir":"/tmp/rea-point-artifacts","select":"index.","detail":"summary","resultPath":"/tmp/rea-source-location-measurements/r1-index-summary.replies.json"}'
```
Run Theme, main and DateSelect with both detail modes serially, three times, reversing
variant order for trial two. Use unique receipt/result paths named
`r1-index-summary.metrics.json` / `.replies.json` (and the corresponding case/trial
names), preserving the runtime metadata. After measurements, the reusable
[aggregator](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/source-location-delivery/aggregate.py)
requires Python with tiktoken 0.12.0 and regenerates the compact record:
```sh
python3 scripts/verify/analysis/source-location-delivery/aggregate.py \
/tmp/rea-source-location-measurements --repo-root "$PWD" --output /tmp/point-measurements.json
```
The aggregator hashes the compiled tree after performance runs, verifies every
answer and successful complete raw-map response, and excludes large payloads from
its output. Analysis still decodes and retains complete Evidence; this study does
not qualify arbitrary larger maps, batch source-map fetching, browser execution or
disk-cold behavior.
@@ -0,0 +1,109 @@
# Source-map admission calibration — 2026-11-09
The local map default changes from 4 to **8 MiB** to admit the pinned Actual
Budget main and DateSelect maps on the first call. Other source-map defaults
remain unchanged. This is a compatibility improvement for the measured artifacts,
not an established latency, memory or context optimization or a universal optimum.
[Issue #8978](https://github.com/morluto/rea/issues/8978) identifies Actual Budget
v26.11.0. We acquired eight public shipped maps from
[the public demo](https://demo.actualbudget.org/) without running the application.
These eight preload-selected maps total 18,319,814 bytes and 834,659 structural
records; they are not the complete eleven-map scope of the issue. Pinned URLs,
SHA-256 values, counts and independently decoded lookup expectations are in
[artifacts.json](https://github.com/morluto/rea/blob/main/docs/verification/source-map-budgets-2026-11-09/artifacts.json).
The completed point task reads and verifies a selected generated script, analyst
manifest and map, runs the actual owned source codec, checks every equal-position
match and embedded-source digest, and creates and serializes complete Evidence.
The acquisition manifest is constructed and explicitly incomplete: it establishes
no browser capture, runtime execution or deployment authenticity. The independent
[Python VLQ oracle](https://github.com/morluto/rea/blob/main/docs/verification/source-map-budgets-2026-11-09/oracle.py) derives mapping positions
and counts directly from artifact bytes, separately from REA's decoder.
## Admission-only measurements before compact delivery
Each cell below is the median of three fresh-process runs. A baseline call uses
4 MiB admission and, when refused, retries the same complete map with 8 MiB. The
candidate selects 8 MiB on its first call. Other policies are identical. All
18 point runs exit successfully, verify the oracle and leave their process groups
empty. The baseline source snapshot and individual measurements/digests are in
[measurements.json](https://github.com/morluto/rea/blob/main/docs/verification/source-map-budgets-2026-11-09/measurements.json).
| Map / workflow | Calls | Estimated task tokens | Wall s | CPU s | Sampled host-group RSS MiB |
| ---------------------------- | ----: | --------------------: | -----: | ----: | -------------------------: |
| Theme / default | 1 | 25,103 | 0.368 | 0.359 | 98.6 |
| Theme / 8 MiB | 1 | 25,126 | 0.367 | 0.391 | 99.3 |
| Main / default + retry | 2 | 1,590,278 | 0.553 | 0.573 | 174.0 |
| Main / 8 MiB | 1 | 1,589,746 | 0.539 | 0.559 | 181.5 |
| DateSelect / default + retry | 2 | 2,232,370 | 0.582 | 0.586 | 205.0 |
| DateSelect / 8 MiB | 1 | 2,231,835 | 0.599 | 0.617 | 192.4 |
Tokens are tiktoken 0.12.0 `cl100k_base` estimates of compact UTF-8 JSON arguments
and complete replies, including failed calls and retries; they are not actual
model usage. Catalog discovery was not performed. Catalog/tool schemas, JSON-RPC
envelopes, network acquisition and actual model billing are unmeasured. Complete
Evidence retains raw map text: eliminating a retry saves only about 532–535
estimated tokens against replies exceeding 1.5–2.2 million tokens. A map admission
cap is therefore not a suitable agent context budget. These measurements do not
qualify a smaller public reply projection or a live browser investigation.
Source inspection confirms that the production MCP handler records complete
Evidence before response encoding. Overflow recovery names its retained
reference and offers session export or complete CLI JSON; facts are not lost.
At the time of this admission-only experiment, local tracing had no summary,
paged matches view or native analysis-view continuation. This experiment did not
exercise a large reply through a live MCP client. The subsequent
[compact-delivery qualification](source-location-delivery.md) measures real
public MCP summaries, continuations and source-text selection separately.
Node v24.22.1 ran on macOS 26.3 arm64. Workers/processes were fresh; OS caches were
not flushed, and no cold-cache comparison is claimed. Wall time includes the
observer. CPU is OS `wait4` user +
system usage for the waited subtree; orphaned/unwaited descendants and sampler
CPU are excluded. RSS is a simultaneous process-group sum sampled every 20 ms;
short peaks and descendants leaving the group can be missed and shared pages can
be counted repeatedly. In particular, the owned codec launches in a separate
group, so its peak is unmeasured in this older table; this is not whole-family
memory. The 1536 MiB observer threshold is an early-stop guard,
not a kernel memory limit. Mixed timing/memory differences do not establish a
performance improvement.
## Unsuccessful full-fetch sizing
An eight-map sizing attempt selected 128 MiB expanded output and 1,048,576
records/segments. It generated a 123 MB full-Evidence file, but the observer's
signal race failed while the sampled process group exceeded its 1536 MiB guard.
No valid complete measurement receipt was produced; this is an unsuccessful,
unmeasured sizing attempt, not evidence of a safe completed task or a calibrated
fetch default. The observer was subsequently repaired. A small Theme fetch
control completed, but one control does not justify raising collection defaults.
Full-fetch output, records, heap and response-byte defaults stay conservative and
configurable. Local tracing retains its existing 262,144-record budget; the
largest measured individual map consumes 260,561 records, so broader map coverage
remains unqualified.
## Reproduction
No downloaded artifacts or complete large replies are committed. Publisher URLs
may change; acquisition refuses a digest mismatch. Use a separate baseline source
snapshot identified in `measurements.json`, then select the same explicit policies.
The retained driver uses production source through the pinned tsx loader and runs
the real source-owned codec. Fetch mode replays pinned HTTP artifact bytes through
the fetcher and includes schema/Evidence/serialization costs, while excluding
browser capture and generated-script analysis.
```sh
python3 docs/verification/source-map-budgets-2026-11-09/acquire.py \
docs/verification/source-map-budgets-2026-11-09/artifacts.json /tmp/rea-map-calibration
python3 docs/verification/source-map-budgets-2026-11-09/oracle.py \
/tmp/rea-map-calibration/artifacts.json
node --import ./node_modules/tsx/dist/loader.mjs \
docs/verification/source-map-budgets-2026-11-09/workload.mjs "$PWD" \
'{"artifactDir":"/tmp/rea-map-calibration","mode":"point","select":"index.","strategy":"recover","policy":{"map_bytes":8388608},"resultPath":"/tmp/map-replies.json"}'
```
For candidate-first, omit `strategy`. Use `select: "theme."` or `"DateSelect."`
for the other point cases. Wrap each invocation with
[the process-group observer](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/budget-calibration-run.py) for bounded process-group
observation; keep runs serial. After the default change, reconstruct the baseline
snapshot or explicitly select `map_bytes: 4194304` for the initial baseline call.
@@ -0,0 +1,44 @@
"""Acquire pinned public artifacts read-only and construct explicitly incomplete analyst manifests."""
import hashlib
import json
from pathlib import Path
import sys
import urllib.request
manifest_path, destination = map(Path, sys.argv[1:])
destination.mkdir(parents=True, exist_ok=True)
manifest_bytes = manifest_path.read_bytes()
manifest = json.loads(manifest_bytes)
(destination / 'artifacts.json').write_bytes(manifest_bytes)
for item in manifest:
for artifact in [item, *([item['generated_script']] if 'generated_script' in item else [])]:
with urllib.request.urlopen(artifact['url'], timeout=30) as response:
data = response.read(16 * 1024 * 1024)
assert len(data) == artifact['bytes']
assert hashlib.sha256(data).hexdigest() == artifact['sha256'], artifact['url']
(destination / artifact['path']).write_bytes(data)
if 'generated_script' not in item:
continue
script = item['generated_script']
(destination / 'files').mkdir(exist_ok=True)
(destination / 'files' / script['path']).write_bytes((destination / script['path']).read_bytes())
context = {
'capture_path': str((destination / 'artifacts.json').resolve()),
'capture_sha256': hashlib.sha256(manifest_bytes).hexdigest(),
'capture_kind': 'browser-scenario',
'source_evidence_id': None,
'capture_completeness': {
'status': 'incomplete', 'equality_eligible': False,
'missing_sections': ['action', 'screenshot', 'dom', 'accessibility', 'url', 'history', 'storage', 'events', 'frames', 'workers', 'popups', 'websockets'],
'truncated_sections': [],
},
'output_directory': str(destination.resolve()),
'analysis_input': {'input_path': str(destination.resolve()), 'format': 'directory'},
'limitations': ['Constructed analyst manifest over pinned public HTTP bytes. capture_path identifies the artifact manifest, not a browser capture. No capture, deployment authenticity or execution is asserted.'],
'scripts': [{
'url': script['url'],
'source': {'kind': 'scenario-response', 'transaction_id': 'read-only-public-http', 'request_sequence': 1, 'response_sequence': 2, 'status': 200},
'content': {'state': 'exported', 'relative_path': script['path'], 'layout': 'isolated', 'layout_reason': 'explicit-public-artifact-selection', 'sha256': script['sha256'], 'bytes': script['bytes'], 'media_type': 'text/javascript', 'redacted': False, 'representation': 'browser-decoded-response-bytes'},
}],
}
(destination / (script['path'] + '.manifest.json')).write_text(json.dumps(context))
@@ -0,0 +1,252 @@
[
{
"url": "https://demo.actualbudget.org/static/js/index.CMrE7aQb.js.map",
"path": "index.CMrE7aQb.js.map",
"bytes": 4284329,
"sha256": "f0de6886f6a49758f53c7f2de0b77fbff6d72c719a7c32ac23d13453ebe7b3e8",
"sources": 790,
"names": 1050,
"generated_lines": 97,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"e9cabe1eb63edd2868b94f9e315c2b15-ssl\""
},
"segment_count": 201344,
"record_count": 204072,
"point": {
"line": 2,
"column": 15579
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/absurd-sql/dist/indexeddb-main-thread.js",
"original_line": 10,
"original_column": 0,
"content_sha256": "d6bf964929e31ceeb301a174d85f4f58de4d4a73f65336488aac7b50fce0a1cd"
}
],
"mapped_count": 201344,
"generated_script": {
"path": "index.CMrE7aQb.js",
"url": "https://demo.actualbudget.org/static/js/index.CMrE7aQb.js",
"bytes": 2078745,
"sha256": "937f792d1849b69a6e0741234428b22b4884ec0157ea31dd6949dd1bacc46456"
}
},
{
"url": "https://demo.actualbudget.org/static/js/theme.EvD-JhP6.chunk.js.map",
"path": "theme.EvD-JhP6.chunk.js.map",
"bytes": 49264,
"sha256": "ceef8194eaca339e55f35cf2decf7ceffa121c7744ee1fffd976a07d39477b32",
"sources": 7,
"names": 0,
"generated_lines": 1,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"f9829244cb0a7c333e7bbe83b110c2ab-ssl\""
},
"segment_count": 2339,
"record_count": 2355,
"point": {
"line": 1,
"column": 1643
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/react/cjs/react.production.js",
"original_line": 12,
"original_column": 0,
"content_sha256": "6b28feb9dbe9781ab1e1b64dea7b29a7c70f03d2dc10eaa966363053d8a9b4e5"
}
],
"mapped_count": 2339,
"generated_script": {
"path": "theme.EvD-JhP6.chunk.js",
"url": "https://demo.actualbudget.org/static/js/theme.EvD-JhP6.chunk.js",
"bytes": 29718,
"sha256": "10ecf4c66ae103222a7c146ad9b945dc83e78b6c78ce05021296e3c2f799bd5b"
}
},
{
"url": "https://demo.actualbudget.org/static/js/months.CGmeIa7I.chunk.js.map",
"path": "months.CGmeIa7I.chunk.js.map",
"bytes": 1630058,
"sha256": "f18a2a2f4d6360b944a8c57432e3f3898314f1cf39d69ba5d049233ab2f39e8d",
"sources": 267,
"names": 208,
"generated_lines": 7,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"8e5c8614ccacde6bd659f89916deba34-ssl\""
},
"segment_count": 65705,
"record_count": 66455,
"point": {
"line": 1,
"column": 230
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../src/build-shims.js",
"original_line": 1,
"original_column": 6,
"content_sha256": "1039d33f95ace766ed5731b2775e957198f2c9ad102989ca09cbd297d94b2863"
}
],
"mapped_count": 65705
},
{
"url": "https://demo.actualbudget.org/static/js/DateSelect.BVYaCNUe.chunk.js.map",
"path": "DateSelect.BVYaCNUe.chunk.js.map",
"bytes": 5529840,
"sha256": "e1939ef36214ff46263d3bdaeb99d60a46c75714a6b6220ed27c9be2d24d6cc8",
"sources": 1001,
"names": 3893,
"generated_lines": 116,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"407e8912b56411e3b2b9a55824a6bb7b-ssl\""
},
"segment_count": 254549,
"record_count": 260561,
"point": {
"line": 1,
"column": 3866
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/date-fns/isSameWeek.js",
"original_line": 40,
"original_column": 0,
"content_sha256": "436cf6fa9365c1cbf783a422c8da3a3b82e55edeac248f987cca22c0ba950902"
}
],
"mapped_count": 254549,
"generated_script": {
"path": "DateSelect.BVYaCNUe.chunk.js",
"url": "https://demo.actualbudget.org/static/js/DateSelect.BVYaCNUe.chunk.js",
"bytes": 2191517,
"sha256": "9e45139dd0b510ad7014188a5e9d11b307c2ac15dfdc761050388e76e94759bf"
}
},
{
"url": "https://demo.actualbudget.org/static/js/useLocalPref.CMSDl-Rj.chunk.js.map",
"path": "useLocalPref.CMSDl-Rj.chunk.js.map",
"bytes": 443520,
"sha256": "341c7d72797abdda122b935c0a5fc80718a79457e02d315fed2cf32f4bbd6f38",
"sources": 32,
"names": 9,
"generated_lines": 1,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"ffaaea8a6a08415032b4cba4f266624f-ssl\""
},
"segment_count": 8976,
"record_count": 9051,
"point": {
"line": 1,
"column": 352
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/react-router/dist/production/lib/router/url.js",
"original_line": 12,
"original_column": 0,
"content_sha256": "493e1c3475d74a528af0c1aaae7574e841fe21019fb9e801dc8fe60f212570c7"
}
],
"mapped_count": 8976
},
{
"url": "https://demo.actualbudget.org/static/js/Value.FdYRYOLz.chunk.js.map",
"path": "Value.FdYRYOLz.chunk.js.map",
"bytes": 4367424,
"sha256": "8428453a8a2aa4366a719e37f6aeb93e75f70aea2e8713529ec6f898d4cddcfa",
"sources": 538,
"names": 717,
"generated_lines": 52,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"6adb2ff5f177a9236937e770097bbeef-ssl\""
},
"segment_count": 181768,
"record_count": 183614,
"point": {
"line": 2,
"column": 7624
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../loot-core/src/shared/query.ts",
"original_line": 22,
"original_column": 13,
"content_sha256": "e4b93dea21ab9d73af8eeffcee818935c00c9a10427549a643ac133be0dfc685"
}
],
"mapped_count": 181768
},
{
"url": "https://demo.actualbudget.org/static/js/columns.BR9M2Oqz.chunk.js.map",
"path": "columns.BR9M2Oqz.chunk.js.map",
"bytes": 1549581,
"sha256": "39f0c008302908e9648f492f073570e76f03a978db25c7b527192e4936cc255f",
"sources": 346,
"names": 626,
"generated_lines": 53,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"c23d08378a207ac086f12075af6703b4-ssl\""
},
"segment_count": 83166,
"record_count": 84538,
"point": {
"line": 1,
"column": 843
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/reselect/dist/reselect.mjs",
"original_line": 63,
"original_column": 0,
"content_sha256": "ce215e8758151f87b03d651c8ebdd097a8b658ffbe56e0162e0154bd00b92f12"
}
],
"mapped_count": 83166
},
{
"url": "https://demo.actualbudget.org/static/js/TransactionEdit.C3L723rq.chunk.js.map",
"path": "TransactionEdit.C3L723rq.chunk.js.map",
"bytes": 465798,
"sha256": "cf04088c6542a020fadc284264cb3cb52763de9f352b2e630fbe10278ceab4c8",
"sources": 69,
"names": 227,
"generated_lines": 1,
"headers": {
"Content-Type": "application/octet-stream",
"Etag": "\"35e992601467cfecec49b2eeb6925d92-ssl\""
},
"segment_count": 23646,
"record_count": 24013,
"point": {
"line": 1,
"column": 6411
},
"expected_matches": [
{
"source_index": 0,
"source": "../../../../../node_modules/react-aria-components/dist/private/Checkbox.mjs",
"original_line": 46,
"original_column": 6,
"content_sha256": "8319abfad740fe6665fc33f1ca8790251fb58d144497b856f7073c10eef62974"
}
],
"mapped_count": 23646
}
]
@@ -0,0 +1,370 @@
{
"tokenizer": "tiktoken0.12.0 cl100k_base; compact UTF8 JSON args and complete replies; estimates not model usage; catalog and protocol unmeasured",
"rows": [
{
"name": "point-r1-DateSelect-default-retry",
"elapsed_s": 0.5818042080063606,
"cpu_s": 0.586226,
"rss_bytes": 214908928,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 193,
"replies": 2232177
},
"reply_bytes": 5775790,
"measurement_receipt_sha256": "5f7c3a7b1bbcacb046f0caf136d41b6afa43a805d14f85365f265df9ac0015ae",
"reply_trace_sha256": "04b9aa60f9843ce5441ea30abf0c4d2e803a2764949ca9cf01136d5d720b424d"
},
{
"name": "point-r1-DateSelect-map8",
"elapsed_s": 0.5986265420069685,
"cpu_s": 0.616543,
"rss_bytes": 203620352,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 102,
"replies": 2231733
},
"reply_bytes": 5774116,
"measurement_receipt_sha256": "85b6a677a2c542ca55ef156ed2cfb9a1af92fc6783921d22c118d0a28b2c4866",
"reply_trace_sha256": "ec9abc70ca71f5e91911147879f97df2d999ad2c4370e6c975b081577fdd5969"
},
{
"name": "point-r1-index-default-retry",
"elapsed_s": 0.5493043329915963,
"cpu_s": 0.573376,
"rss_bytes": 184139776,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 181,
"replies": 1590097
},
"reply_bytes": 4428697,
"measurement_receipt_sha256": "cab96c90a933d2ccf8a854308b2de0b2593a678413afd5d95dd9d1628df277b0",
"reply_trace_sha256": "d613c618414f970b94b17ffee783768cedf45e3c414162dc4c408943522ac585"
},
{
"name": "point-r1-index-map8",
"elapsed_s": 0.5393435839941958,
"cpu_s": 0.559231,
"rss_bytes": 190300160,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 96,
"replies": 1589650
},
"reply_bytes": 4427067,
"measurement_receipt_sha256": "abf76eb747e00c26eab5807944224ed55d5fd3b08bc4c8b147732e70180dceca",
"reply_trace_sha256": "abc7c820c6d627553f3e2d3549899d4ff7160271a0b6a937a5719d87420190bf"
},
{
"name": "point-r1-theme-default-retry",
"elapsed_s": 0.6525920830026735,
"cpu_s": 0.72032,
"rss_bytes": 167591936,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 88,
"replies": 25015
},
"reply_bytes": 77129,
"measurement_receipt_sha256": "c108ab41544816f7706e6343584264bf3db938a97dfd51b52d4495f579efeb30",
"reply_trace_sha256": "d086b3579fbd099df1a32c7d1e54e2af6ea6a5b244747d1a012fa19ed541d4e5"
},
{
"name": "point-r1-theme-map8",
"elapsed_s": 0.3669995829986874,
"cpu_s": 0.391014,
"rss_bytes": 104103936,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 99,
"replies": 25027
},
"reply_bytes": 77171,
"measurement_receipt_sha256": "146b3d877d36fea192e0442f3ae06d9e05cfbaf8c75902d8b1db914cea818cf2",
"reply_trace_sha256": "6a905573af8e55b34bbbd1618b48aec7d3cc12860f59643f28024002e81fcf00"
},
{
"name": "point-r2-DateSelect-default-retry",
"elapsed_s": 0.5365884999919217,
"cpu_s": 0.5738759999999999,
"rss_bytes": 206454784,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 193,
"replies": 2232177
},
"reply_bytes": 5775790,
"measurement_receipt_sha256": "415deab64b1490e8300b63fa926a517f8630f68117c5735c126f7a8f8f02e635",
"reply_trace_sha256": "ae1055e47e9b2e3ef6ded1aa124950d220ba0bbe7a27afe68b9f46039299ce1e"
},
{
"name": "point-r2-DateSelect-map8",
"elapsed_s": 0.5848246670066146,
"cpu_s": 0.5841609999999999,
"rss_bytes": 196968448,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 102,
"replies": 2231733
},
"reply_bytes": 5774116,
"measurement_receipt_sha256": "18c256638988915c3029f1cf6c19b1ce6cde5e2b7e046a93d47d0afbf5c74c6f",
"reply_trace_sha256": "ee27a03f5b3580d54ad8ad33bc7f22f552c724c9e1674f8c39aac4dd7b8fffab"
},
{
"name": "point-r2-index-default-retry",
"elapsed_s": 0.5531810840038816,
"cpu_s": 0.5698139999999999,
"rss_bytes": 182484992,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 181,
"replies": 1590097
},
"reply_bytes": 4428697,
"measurement_receipt_sha256": "e97c8b561949f6e6615f9684e8187c7eb2a47833e836532891631b56ac26526a",
"reply_trace_sha256": "35a4f9f5f868083cd1c45767282e5d7555f07a2962fbdf647de538a07452ae02"
},
{
"name": "point-r2-index-map8",
"elapsed_s": 0.4954192500008503,
"cpu_s": 0.53103,
"rss_bytes": 176865280,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 96,
"replies": 1589650
},
"reply_bytes": 4427067,
"measurement_receipt_sha256": "d9f3c788ee7de0508ba42f3a37fea9df282d195a634e4e523b673eed6f813b3a",
"reply_trace_sha256": "e19bbc0e4534d60d9577c8c5871243604439f1e84f6e6e1c5a763cf5001665f1"
},
{
"name": "point-r2-theme-default-retry",
"elapsed_s": 0.36803100000543054,
"cpu_s": 0.358402,
"rss_bytes": 103415808,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 88,
"replies": 25015
},
"reply_bytes": 77129,
"measurement_receipt_sha256": "ad7ff37773f7a383a2ba1376863358451959a535d802c3300495dba4d8c0ef2e",
"reply_trace_sha256": "4b9afba56e44a75d3e1f674b70ddd2b030ace630ad8bf8bde2950cf866890880"
},
{
"name": "point-r2-theme-map8",
"elapsed_s": 0.38679324999975506,
"cpu_s": 0.40970799999999996,
"rss_bytes": 104267776,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 99,
"replies": 25027
},
"reply_bytes": 77171,
"measurement_receipt_sha256": "3958afd848f3cc5529bb1a56f8ba8ea82eea34818713c8b045d327ea0aae9f65",
"reply_trace_sha256": "241cf2e478a29f069c1a17fdb5b14f35e3430ee9c276d818189a7de789608c89"
},
{
"name": "point-r3-DateSelect-default-retry",
"elapsed_s": 0.6513657079922268,
"cpu_s": 0.613527,
"rss_bytes": 220790784,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 193,
"replies": 2232177
},
"reply_bytes": 5775790,
"measurement_receipt_sha256": "19e779011951101b141f48e84b427161bf86f41998e79a2fb368d9e129aa6b00",
"reply_trace_sha256": "7d1916049662bf0a278ef0bf341874e35a317182846d4ac4eca1a09a166a3e58"
},
{
"name": "point-r3-DateSelect-map8",
"elapsed_s": 0.6560826670029201,
"cpu_s": 0.686476,
"rss_bytes": 201785344,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 102,
"replies": 2231733
},
"reply_bytes": 5774116,
"measurement_receipt_sha256": "32af3c6720329c991dc434844878aba283e0c6001087ed0f93a1bfaabbdecc67",
"reply_trace_sha256": "5feb55a1ff8c433f5dee38a7c3ae877b9260d1fbd0d5ef8501ed38d8e5d46d60"
},
{
"name": "point-r3-index-default-retry",
"elapsed_s": 0.5955084579909453,
"cpu_s": 0.6005269999999999,
"rss_bytes": 182140928,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 2,
"tokens": {
"arguments": 181,
"replies": 1590097
},
"reply_bytes": 4428697,
"measurement_receipt_sha256": "ab049ddfe685398068198b13ed317eb6c51aff104d88d9d11a0cc7dab6fab900",
"reply_trace_sha256": "7fd9f40f0e16debbffb65c9b5bff8043e586b7aa493daf376eefb0348676fd1e"
},
{
"name": "point-r3-index-map8",
"elapsed_s": 0.6677014579909155,
"cpu_s": 0.6370119999999999,
"rss_bytes": 194920448,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 96,
"replies": 1589650
},
"reply_bytes": 4427067,
"measurement_receipt_sha256": "fa78f3707dda0387d07a8cfee9f4591d64d5a79b49f097c4501de143e32f89e5",
"reply_trace_sha256": "184a5536859edd8e990e89a785048200847aff85de28008fb6f972ec3455e491"
},
{
"name": "point-r3-theme-default-retry",
"elapsed_s": 0.3368863329960732,
"cpu_s": 0.358882,
"rss_bytes": 102940672,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 88,
"replies": 25015
},
"reply_bytes": 77129,
"measurement_receipt_sha256": "ab076949d4ba9ffc11e1f677e6af805d8dae976ee642a1344fdf252c710c5ec6",
"reply_trace_sha256": "ae3742c091752393a0fae881f1203287fdbb2bde08838edf989e809ae278e246"
},
{
"name": "point-r3-theme-map8",
"elapsed_s": 0.3349004170013359,
"cpu_s": 0.36136099999999993,
"rss_bytes": 103153664,
"exit_code": 0,
"cleanup": "group_empty",
"calls": 1,
"tokens": {
"arguments": 99,
"replies": 25027
},
"reply_bytes": 77171,
"measurement_receipt_sha256": "eff22ab422018535d88a0e0d3c135139a6930a18a24a3d32f9c68defaeae8dce",
"reply_trace_sha256": "4af542e52f3377ad3410a069a60c64cf4db76771a0bc686075ebea31c2733032"
}
],
"medians": {
"DateSelect-default-retry": {
"runs": 3,
"elapsed_s": 0.5818042080063606,
"cpu_s": 0.586226,
"rss_mib": 204.953125,
"calls": 2,
"argument_tokens": 193,
"reply_tokens": 2232177,
"reply_bytes": 5775790
},
"DateSelect-map8": {
"runs": 3,
"elapsed_s": 0.5986265420069685,
"cpu_s": 0.616543,
"rss_mib": 192.4375,
"calls": 1,
"argument_tokens": 102,
"reply_tokens": 2231733,
"reply_bytes": 5774116
},
"index-default-retry": {
"runs": 3,
"elapsed_s": 0.5531810840038816,
"cpu_s": 0.573376,
"rss_mib": 174.03125,
"calls": 2,
"argument_tokens": 181,
"reply_tokens": 1590097,
"reply_bytes": 4428697
},
"index-map8": {
"runs": 3,
"elapsed_s": 0.5393435839941958,
"cpu_s": 0.559231,
"rss_mib": 181.484375,
"calls": 1,
"argument_tokens": 96,
"reply_tokens": 1589650,
"reply_bytes": 4427067
},
"theme-default-retry": {
"runs": 3,
"elapsed_s": 0.36803100000543054,
"cpu_s": 0.358882,
"rss_mib": 98.625,
"calls": 1,
"argument_tokens": 88,
"reply_tokens": 25015,
"reply_bytes": 77129
},
"theme-map8": {
"runs": 3,
"elapsed_s": 0.3669995829986874,
"cpu_s": 0.391014,
"rss_mib": 99.28125,
"calls": 1,
"argument_tokens": 99,
"reply_tokens": 25027,
"reply_bytes": 77171
}
},
"baseline": {
"baseline_worktree": "/var/folders/xl/60j5sd4j6b99tn8q082l9t8r0000gn/T/rea-budget-calibration-baseline-2rg1qcde",
"head": "e2a2ede5440df396e44b3406ef511a31a680290e",
"tracked_patch_sha256": "038b69c19bbd79a303c9ecd8d9d22a1251637e3886543ca4f5a4c1b70c724eec",
"source_map_policy_sha256": "6d28cba1ff7936dee640474a4d5e1e456a3acb3c63432ea6ab4698181a53ca05"
},
"conditions": {
"host": "macOS26.3 arm64",
"node": "v24.22.1",
"workers": "fresh process and owned codec each run; OS caches warm, not flushed",
"rss": "sampled simultaneous process-group RSS sum at20ms, shared pages may count twice; short peaks and descendants leaving group excluded",
"cpu": "OS wait4 user+system waited subtree; orphaned or unwaited descendants excluded; sampler excluded",
"guard": "1536MiB observed early stop, not kernel hard limit",
"catalog": "not counted; no catalog discovery in replay; tool schemas, JSON-RPC envelopes, actual billed tokens, network, browser capture, and generated-script analysis unmeasured"
}
}
@@ -0,0 +1,62 @@
"""Independent source-map VLQ artifact oracle; standard library only."""
import hashlib
import json
from pathlib import Path
ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def values(segment):
output, value, shift = [], 0, 0
for character in segment:
digit = ALPHABET.index(character)
value += (digit & 31) << shift
if digit & 32:
shift += 5
else:
output.append(-(value >> 1) if value & 1 else value >> 1)
value, shift = 0, 0
assert shift == 0
return output
def inspect(path):
data = Path(path).read_bytes()
raw = json.loads(data)
source = original_line = original_column = 0
count, mapped_count, first, matches = 0, 0, None, []
for line, row in enumerate(raw['mappings'].split(';'), 1):
column = 0
for segment in row.split(','):
if not segment:
continue
count += 1
fields = values(segment)
column += fields[0]
if len(fields) > 1:
assert len(fields) in (4, 5)
mapped_count += 1
source += fields[1]
original_line += fields[2]
original_column += fields[3]
if first is None and len(fields) > 1:
first = {'line': line, 'column': column}
if first == {'line': line, 'column': column}:
if len(fields) == 1:
matches.append({'generated_only': True})
else:
text = raw['sourcesContent'][source]
matches.append({'source_index':source,'source':raw['sources'][source],
'original_line':original_line+1,'original_column':original_column,
'content_sha256':hashlib.sha256(text.encode()).hexdigest() if text is not None else None})
lines = raw['mappings'].count(';')+1
return {'sha256':hashlib.sha256(data).hexdigest(), 'bytes':len(data),
'segment_count':count, 'mapped_count':mapped_count, 'record_count':1+len(raw['sources'])+len(raw['names'])+len(raw['sourcesContent'])+lines+count,
'point':first,'expected_matches':matches}
if __name__ == '__main__':
import sys
manifest = json.loads(Path(sys.argv[1]).read_text())
for source in manifest:
observed = inspect(Path(sys.argv[1]).resolve().parent / source['path'])
for key, value in observed.items():
assert source[key] == value, (source['path'], key, source[key], value)
print(json.dumps({'verified_maps':len(manifest), 'sha256s':[source['sha256'] for source in manifest]}))
@@ -0,0 +1,288 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const [projectRoot, configText] = process.argv.slice(2);
const config = JSON.parse(configText);
const require = createRequire(join(projectRoot, "package.json"));
assert(
config.artifactDir,
"Select artifactDir containing the acquired artifacts.json",
);
const manifest = JSON.parse(
await readFile(join(config.artifactDir, "artifacts.json"), "utf8"),
).map((source) => ({ ...source, path: join(config.artifactDir, source.path) }));
const selected =
config.select === "all"
? manifest
: manifest.filter((x) => x.path.includes(config.select));
assert(selected.length > 0);
const load = (path) => import(pathToFileURL(join(projectRoot, path)).href);
const { projectAnalysisError } = await load(
"src/domain/analysisErrorProjection.ts",
);
const calls = [];
let finalResult;
const digest = (value) => createHash("sha256").update(value).digest("hex");
const start = performance.now();
if (config.mode === "point") {
const { SourceMapDecoder } = await load(
"src/javascript/sourceMaps/SourceMapDecoder.ts",
);
const { WebSourceLocationService } = await load(
"src/application/WebSourceLocationService.ts",
);
const { LocalWebSourceLocationArtifacts } = await load(
"src/browser/assets/WebSourceLocationArtifacts.ts",
);
const { spawnOwnedProviderProcess } = await load(
"src/process/ProviderProcess.ts",
);
const decoder = new SourceMapDecoder({
launcher: (options) =>
spawnOwnedProviderProcess({
...options,
arguments: [
"--import",
require.resolve("tsx"),
...options.arguments.map((x) =>
x.endsWith("/SourceMapCodecProcess.js")
? join(
projectRoot,
"src/javascript/sourceMaps/SourceMapCodecProcess.ts",
)
: x,
),
],
}),
});
const service = new WebSourceLocationService(
new LocalWebSourceLocationArtifacts(),
decoder,
);
try {
for (const source of selected) {
const text = await readFile(source.path, "utf8");
const policies =
config.strategy === "recover"
? [{}, config.policy]
: [config.policy ?? {}];
let completed = false;
for (const policy of policies) {
const args = {
manifest_path: source.path.slice(0, -4) + ".manifest.json",
script_index: 0,
source_map: { path: source.path, url: source.url },
generated_position: source.point,
...(Object.keys(policy).length ? { source_map_limits: policy } : {}),
};
const began = performance.now();
const result = await service.trace(args);
calls.push({
operation: "trace_web_source_location",
args,
reply: result.ok ? result.value : projectAnalysisError(result.error),
elapsed_ms: performance.now() - began,
});
if (!result.ok) continue;
const report = result.value.normalized_result;
assert.equal(report.source_map_sha256, source.sha256);
assert.equal(report.matches.length, source.expected_matches.length);
for (let i = 0; i < source.expected_matches.length; i++) {
const expected = source.expected_matches[i],
actual = report.matches[i];
assert.equal(
actual.state,
expected.generated_only ? "generated-only" : "mapped",
);
if (expected.generated_only) continue;
assert.equal(actual.source_index, expected.source_index);
assert.equal(actual.reported_source, expected.source);
assert.equal(actual.original_position.line, expected.original_line);
assert.equal(
actual.original_position.column,
expected.original_column,
);
assert.equal(actual.content.state, "embedded");
assert.equal(actual.content.utf8_sha256, expected.content_sha256);
}
finalResult = result.value;
completed = true;
break;
}
assert(
completed,
`point incomplete for ${source.path}: ${JSON.stringify(calls.at(-1)?.reply)}`,
);
}
} finally {
await service.close();
}
} else {
const { fetchWebSourceMaps } = await load(
"src/browser/WebSourceMapFetcher.ts",
);
const { analyzeWebBundleInputSchema, webBundleAnalysisSchema } = await load(
"src/domain/webBundleAnalysis.ts",
);
const { jsonValueSchema } = await load("src/domain/jsonValue.ts");
const { createBrowserEvidence } = await load(
"src/application/BrowserEvidence.ts",
);
const requests = selected.map((s, i) => ({
scriptKey: `scr_${String(i + 1).padStart(64, "0")}`,
declaredUrl: s.url,
fetchUrl: s.url,
}));
const policies =
config.strategy === "recover" ? [{}, config.policy] : [config.policy ?? {}];
const byUrl = new Map(selected.map((s) => [s.url, s]));
let completed = false;
for (const policy of policies) {
const input = analyzeWebBundleInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://demo.actualbudget.org"],
target_id: "calibration-artifact-replay",
fetch_source_maps: true,
...(Object.keys(policy).length ? { source_map_limits: policy } : {}),
});
const began = performance.now();
const result = await fetchWebSourceMaps(requests, input, undefined, {
fetch: async (url) =>
new Response(await readFile(byUrl.get(String(url)).path), {
status: 200,
headers: { "content-type": "application/source-map+json" },
}),
});
let reply = result.ok ? result.value : projectAnalysisError(result.error);
if (result.ok && config.evidence !== false) {
// Isolate source-map retention cost: script capture/analysis are explicitly outside this replay.
const bundle = webBundleAnalysisSchema.parse({
capture: {
target_url: "https://demo.actualbudget.org/",
target_origin: "https://demo.actualbudget.org",
scripts_observed: 0,
scripts_analyzed: 0,
source_artifacts: [],
},
observations: {
chunks: { nodes: [], edges: [] },
routes: [],
endpoints: [],
webmcp_declarations: [],
source_maps: result.value,
},
inferences: [],
unknowns: [],
completeness: {
status: result.value.status === "included" ? "complete" : "partial",
parsed_scripts: 0,
parse_failures: 0,
visited_ast_nodes: 0,
},
limitations: [
"Pinned public source-map transport replay. Browser capture and generated-script static analysis were not performed.",
],
});
jsonValueSchema.parse(bundle);
const evidence = createBrowserEvidence(
"analyze_web_bundle",
input,
bundle,
{
id: "source-map-calibration-replay",
name: "Pinned public source-map replay",
version: "1",
},
);
// Include actual Evidence envelope cost rather than only the producer report.
reply = evidence;
}
calls.push({
operation: "analyze_web_bundle.source_maps",
args: {
source_urls: selected.map((s) => s.url),
source_sha256s: selected.map((s) => s.sha256),
...(Object.keys(policy).length ? { source_map_limits: policy } : {}),
},
reply,
elapsed_ms: performance.now() - began,
});
if (
!result.ok ||
result.value.items.length !== selected.length ||
result.value.items.some(
(item) => !["included", "partial"].includes(item.status),
)
)
continue;
for (let i = 0; i < selected.length; i++) {
const original = selected[i],
item = result.value.items[i],
raw = JSON.parse(await readFile(original.path, "utf8"));
assert(["included", "partial"].includes(item.status));
assert.equal(item.mappings.length, original.mapped_count);
assert.equal(item.artifact.sha256, original.sha256);
assert.equal(item.original_sources.length, raw.sources.length);
const expected = original.expected_matches.filter(
(x) => !x.generated_only,
);
const actual = item.mappings.filter(
(x) =>
x.generated_line === original.point.line &&
x.generated_column === original.point.column,
);
assert.equal(actual.length, expected.length);
for (let j = 0; j < expected.length; j++) {
assert.equal(actual[j].source_index, expected[j].source_index);
assert.equal(actual[j].original_line, expected[j].original_line);
assert.equal(actual[j].original_column, expected[j].original_column);
}
for (let j = 0; j < raw.sources.length; j++) {
assert.equal(item.original_sources[j].source_index, j);
if (raw.sourcesContent[j] !== null)
assert.equal(
item.original_sources[j].artifact.sha256,
digest(raw.sourcesContent[j]),
);
}
}
finalResult = result.value;
completed = true;
break;
}
if (!completed && config.allowIncomplete !== true)
throw Error(
`fetch incomplete: ${JSON.stringify(calls.at(-1)?.reply?.items?.map((i) => ({ status: i.status, limitation: i.limitation })))}`,
);
}
if (config.resultPath)
await writeFile(config.resultPath, JSON.stringify({ config, calls }));
const rows = calls.map((c) => {
const report =
c.reply.normalized_result?.observations?.source_maps ?? c.reply;
return {
operation: c.operation,
elapsed_ms: c.elapsed_ms,
reply_bytes: Buffer.byteLength(JSON.stringify(c.reply)),
state: report.status ?? (c.reply.code ? "error" : "complete"),
error: c.reply.code ? c.reply.message : undefined,
items: report.items?.map((i) => ({
status: i.status,
sources: i.original_sources?.length,
mappings: i.mappings?.length,
limitation: i.limitation,
})),
};
});
console.log(
JSON.stringify({
config,
operation_ms: performance.now() - start,
calls: rows,
result_sha256: finalResult ? digest(JSON.stringify(finalResult)) : null,
resource_usage: process.resourceUsage(),
}),
);