Compare commits

..
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 c6cda443c1 docs(javascript): state that duplicate unmapped content stays ambiguous
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 03:33:36 +00:00
Kaoru0822andClaude Opus 5.5 e308f35c43 fix(javascript): keep unmapped captured runtime scripts unknown
A captured script whose bytes matched no static digest was reported as
high-confidence unmatched even when no automatic or caller-declared
mapping applied to its location in any static layer. The matcher now
computes mapping presence once while locating candidates and shares one
no-candidate classification between the captured-source and source-free
branches, so an unmapped location stays unknown with
no-authorized-location-mapping and is not counted as unmatched.

Fixes #6061.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:20:21 +00:00
morluto 719d56ebfe fix(evidence): preserve provider execution provenance across workflows 2026-10-22 10:02:31 +08:00
morluto 87c8086fc1 refactor(managed): reuse canonical JSON digest owner 2026-10-22 09:52:58 +08:00
morluto eab4e47916 refactor(ghidra): share session startup observation checks 2026-10-22 09:52:49 +08:00
morluto db87917daa refactor(native): use canonical deadline defaults 2026-10-22 09:52:40 +08:00
morluto 183b4d4e7e refactor(managed): share metadata token schema 2026-10-22 09:33:58 +08:00
morluto 00819258a7 refactor(browser): separate bundle state from AST inspection 2026-10-22 09:33:58 +08:00
morluto c465ff5a81 refactor(evidence): share canonical ASCII ordering rules 2026-10-22 09:21:54 +08:00
morluto 1ac7ce3f3b refactor(native): share call observation outcome schema 2026-10-22 09:21:54 +08:00
morluto 55f5c8db69 refactor(kuna): centralize installation cancellation projection 2026-10-22 09:21:54 +08:00
morluto 5b0373db11 refactor(browser): map artifact failures at one boundary 2026-10-22 09:21:54 +08:00
morluto 73be86c39e fix(android): decode guardian output as UTF-8 streams 2026-10-22 09:11:44 +08:00
morluto 8dbe92a4a3 refactor(reference): centralize source inventory rules 2026-10-22 09:11:44 +08:00
morluto 5a28a147f1 fix(hopper): preserve streamed Unicode lease owner identities 2026-10-22 09:11:44 +08:00
morluto 13a8640204 fix(browser): observe fetch settlement after synchronous abort 2026-10-22 09:03:51 +08:00
morluto 09b29436f4 refactor(snapshot): share persisted execution projection 2026-10-22 09:03:51 +08:00
morluto 421d527bfe Merge remote-tracking branch 'origin/main' into codex/legacy-cleanup-followup-20261022 2026-10-22 08:52:56 +08:00
morluto 24bd2b989a refactor(browser): derive comparability once per capture pair 2026-10-22 08:52:56 +08:00
morluto b2fad362f2 fix(browser): stop expired work and release deadline resources 2026-10-22 08:52:56 +08:00
morluto 247c8f4eab refactor(javascript): own property capture translation 2026-10-22 08:52:56 +08:00
morluto adfde012ec refactor(readiness): share canonical stage validation 2026-10-22 08:52:56 +08:00
Jay-Alexander Elliot 57fd3ea5d8 feat(cli): summarize managed members with durable Evidence paging (#5167)
Fixes #5148.

Add opt-in durable managed member summaries and authenticated paging while preserving complete default output, original Evidence, cleanup ownership and queued cancellation.

Verified with bounded real Newtonsoft/Redux/Rust tasks, independent oracles, 556 focused tests and the expanded domain gate; paired token estimates and latency tradeoffs are recorded in experiments/analysis-efficiency/managed-cli.md.
2026-10-22 00:43:30 +00:00
morluto d8948efb81 refactor(mcp): bind Electron provider failures at registration 2026-10-22 08:40:36 +08:00
morluto 3966c92c71 refactor(ghidra): derive reference facts from shared schema 2026-10-22 08:40:36 +08:00
morluto 1fbfbf74ef refactor(gdb): own command record snapshots and stream groups 2026-10-22 08:40:36 +08:00
morluto 2604059961 refactor(process): share decoded NDJSON framing 2026-10-22 08:35:20 +08:00
morluto 6b44e35bec refactor(managed): own successful PE inspection identity 2026-10-22 08:35:20 +08:00
morluto 20bf7d957f refactor(setup): reuse desired registration per document snapshot 2026-10-22 08:35:20 +08:00
morluto c9f95eaf90 refactor(javascript): own graph Evidence envelope validation 2026-10-22 08:27:39 +08:00
morluto a2805bc934 refactor(managed): share artifact and token identity schemas 2026-10-22 08:27:39 +08:00
morluto b7529304f0 refactor(browser): centralize shared CDP observation facts 2026-10-22 08:27:39 +08:00
morluto 22c4b528d2 Merge remote-tracking branch 'origin/main' into codex/legacy-cleanup-followup-20261022 2026-10-22 08:20:30 +08:00
morluto 1158dc2a94 refactor(process): own worker frame wait lifecycle 2026-10-22 08:20:30 +08:00
morluto 2765fc12bb refactor(browser): share composed artifact cleanup ownership 2026-10-22 08:20:30 +08:00
gensokyo-dev 18669641e9 test(browser): select a bounded service-worker setup deadline (#6186) 2026-10-22 08:16:34 +08:00
morluto cdd718ffb0 refactor(inputs): own absolute path validation in shared contracts 2026-10-22 08:13:46 +08:00
morluto c5c5ff45aa refactor: import shared limits and projection helpers from their owners 2026-10-22 08:13:46 +08:00
morluto 5da3b7c393 refactor(asar): centralize read failure and cancellation semantics 2026-10-22 08:13:46 +08:00
morluto 2451e187be test(prompts): isolate capture admission regression 2026-10-22 07:51:42 +08:00
morluto 5e6529521c Merge remote-tracking branch 'origin/main' into codex/legacy-cleanup-followup-20261022 2026-10-22 07:49:01 +08:00
morluto 7cec547ecc fix(prompts): suggest only admitted process capture evidence 2026-10-22 07:49:00 +08:00
morluto c1a0561bf3 refactor(mobile): retain inventory sources at the projection boundary 2026-10-22 07:48:39 +08:00
morluto fafc1881b5 refactor(managed): return singular admitted evidence records 2026-10-22 07:48:39 +08:00
morluto a59abe932e refactor(evidence): centralize comparison identities and source recording 2026-10-22 07:45:47 +08:00
morluto 452e3ccad1 refactor(javascript): own source range validation once 2026-10-22 07:45:47 +08:00
8413ec08f7 fix(process): report the effective regex heap and uncertain signal retry (#6099)
* fix(process): report the effective regex worker heap

Node gives --max-old-space-size, including NODE_OPTIONS, precedence over
the worker's 128 MiB resourceLimits request. Name that effective
old-generation heap and stop saying the request has no runtime override.
The matching deadline stays a fixed cap.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): keep SIGXCPU and SIGXFSZ retries uncertain

The signal does not prove the retained rlimit was exhausted, so an
unchanged retry may succeed. Project that retry as possible instead of
saying it fails the same way.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave percentage heap overrides unestablished

--max-old-space-size-percentage takes precedence over the absolute flag
in either order. Node computes that MiB from startup memory, which a
later read does not recover, so the effective heap stays unknown instead
of the 128 MiB request or an absolute flag.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): read launch NODE_OPTIONS, not process.env

A preload can rewrite process.env.NODE_OPTIONS after V8 has applied the
original flags, and those flags are not copied into execArgv. Use the
initial environment block when it can be read, and leave the effective
heap unestablished when that provenance cannot be recovered.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave env-file heap overrides unestablished

--env-file and --env-file-if-exists can supply NODE_OPTIONS when that
variable was absent from the initial environment. The file is not in
/proc/self/environ, so a missing entry is not the 128 MiB request.
An initial NODE_OPTIONS is still parsed, because Node does not let the
file replace it.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave --max-heap-size old generation unestablished

A positive --max-heap-size or --max_heap_size sets the total heap, not the
old generation, and can stay below the 128 MiB worker request when an
old-space flag is also present. The effective old generation stays
unestablished while that value is in effect. Zero or empty clears it.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave config-file heap options unestablished

--experimental-config-file and --experimental-default-config-file can
apply a heap percentage from a file that is not launch provenance. The
effective old generation stays unestablished when either flag is present,
including beside an absolute --max-old-space-size.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): read launch exec argv from the command line

process.execArgv can be cleared after V8 applies --max-old-space-size.
Linux recovers those tokens from /proc/self/cmdline and leaves the heap
unestablished when that vector cannot be separated from the program entry.
Heap probes compare pinned absolute limits instead of a percentage of host memory.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave a title-overwritten command line unestablished

process.title and --title replace /proc/self/cmdline with the title.
A buffer without a program entry is not an empty option list, so the
heap stays unknown instead of the 128 MiB request. The command-line
probe does not read /proc off Linux.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 07:40:14 +08:00
morluto 603a7d022c refactor(process): share capture evidence admission 2026-10-22 07:37:21 +08:00
morluto b2dc45006d refactor(cli): share registered command traversal 2026-10-22 07:37:21 +08:00
morluto 1828cf87a0 refactor: remove redundant contract and syntax definitions 2026-10-22 07:37:21 +08:00
morluto a73eac190b refactor: remove obsolete forwarding APIs and unify CI contracts (#6181)
* refactor: remove obsolete internal forwarding APIs

* refactor(ci): centralize failure classification contracts
2026-10-22 07:30:54 +08:00
0e5c511c01 fix(electron): follow absolute symlinks that stay inside the framework (#3587)
* fix(electron): follow absolute symlinks that stay inside the framework

An absolute symlink target is now passed to relativeInside(root,
target), and a null result still ends the walk as an escape. When the
target lies inside the root, resolved is cleared and the root-relative
pieces are queued on pending, which replaces the path walked so far with
that absolute target. Later hops stay under the hop limit and the
existing lstat containment checks. Provider tests now cover an absolute
Versions/Current that stays on in-framework version B and matches the
relative bundle's resolved path and SHA-256, an absolute Current outside
the framework, and an absolute in-framework link whose next hop leaves
the framework; those last two still surface an escape error.

Fixes #3380

* test(electron): cover absolute framework symlink cycles

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-22 07:21:37 +08:00
2e6b38cef0 feat(environment): render more read tools from the state and describe responses (#5352)
* feat(environment): render more read tools from the state and describe responses

Keep main's environment serving and fold in coverage for non-entity reads,
response summaries, and MCP tool annotations.

Squashed rebase of #5352 onto main.

* fix(environment): keep rendered documents and quoted samples inside their budgets

A recorded body of exponential numbers can render past 256 KiB and turn a
working read into a state size error, so that document stays a recorded replay.
Description samples are measured after JSON quoting, and the replay-fallback
catalog check uses a tool count the longer response descriptions still separate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(environment): pin the replay card this branch serves

The replay-only card test pinned main's 11,131-byte card. This branch's descriptions name each tool's recorded responses, so the card it serves is 12,818 bytes; the pin and its name follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:08:44 +08:00
d7ca93cc2c fix(artifacts): name the ASAR header admission budget (#6172)
Keep the 16 MiB ASAR header/index ceiling fixed and report that budget,
the lack of a runtime override, and that source selection does not bypass
header admission.

Refs #5910

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 06:33:09 +08:00
Afloat 82e4e032df fix(artifacts): complete positional ZIP reads (#4414)
fix(artifacts): complete ZIP positional reads
2026-10-22 06:13:24 +08:00
Abdul Samad Siddiquiandmorluto ca2ca16146 fix(dotnet): reject unfinished CIL prefix chains (#4416)
* fix(dotnet): reject unfinished CIL prefix chains

* test(dotnet): cover unfinished prefixes in fat method bodies

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-22 06:13:10 +08:00
xjh 14c4c2a1d7 fix(managed): preserve observed method header differences (#3814) 2026-10-22 06:12:55 +08:00
Hemang VarshneyandHemang Varshney 4842f23765 fix(source): retain CommonJS requires spelled as exact no-substitution templates (#4511)
* fix(source): retain CommonJS requires spelled as exact no-substitution templates

Admit expression-free template arguments at the CommonJS require gate and
read their single cooked quasi through the shared exact-literal reader.
Scope is the require path only: static ESM template declarations keep their
parse failures and dynamic import() keeps the explicit unknown/partial
<dynamic-import> relationship with no invented target.

Refs: #4382

* fix(source): admit TypeScript-wrapped exact require arguments via the shared literal reader

Address review: the require gate matched the outer AST node, so
runtime-transparent TS wrappers (as const, !, satisfies) around exact
strings or expression-free templates were still silently omitted even
though readExactJavaScriptLiteral unwraps them. Remove the gate and let
the shared exact-literal reader own argument exactness; computed
arguments remain silent and dynamic import() behavior is unchanged.

Refs: #4382

---------

Co-authored-by: Hemang Varshney <218273197+hemv-857@users.noreply.github.com>
2026-10-22 06:12:42 +08:00
7721d59e1a fix(adb): normalize absent installer package sentinels (#4073)
* fix(adb): report a null installer declaration as an absent installer

Package Manager prints its String fields by concatenation, so a package
without a recorded installer shows `installerPackageName=null` in
`dumpsys package`. The projection treated only an empty value as absent
and forwarded the text "null" as a present installer identity, which a
consumer cannot tell apart from a package literally named "null" (#3943).
`versionName` is printed the same way and had the same gap.

The parser now maps the empty value and the `null` sentinel of both
String fields to the nullable result's null at the producing boundary;
a reported installer such as com.android.vending is kept unchanged.

Validation: parser unit tests for the sentinel, empty and missing
installer lines and the absent version name, plus a provider-level
regression through the inspect_adb_package operation; three of them fail
on main and pass here. The issue's four CLI controls through
`rea inspect-adb-package` against a stub adb return JSON null for the
sentinel, empty and missing cases and keep com.android.vending. Typecheck,
lint and module boundaries pass.

Closes #3943

* fix(adb): keep the null sentinel handling to the installer field

Review feedback on #4073: a manifest can declare the literal string
"null" as android:versionName, and dumpsys prints it exactly like an
absent value, so mapping versionName to null would turn a real version
into absence. Only installerPackageName, where no package can carry that
name, treats the printed null as the absent installer. The test for the
version name now pins the retained text.

* test(adb): establish permission inventory in null-installer fixture

Main now reports partial coverage when the requested-permission
inventory is not established (9534e42ec). Give the null-installer
fixture a requested permissions section so it still checks a
complete projection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:11:49 +08:00
76dbc32f39 fix(environment): bound state reads and retain cleanup ownership (#6158)
* feat(environment): extract the initial state a capture's reads observed

Add the environment-state analysis view. From the parents the environment
manifest accepts, it extracts the data the capture's reads observed before
the first related write: entity types with an identity field (proved by a
path parameter or inferred from an identity name), entities with their
recorded values and provenance, documents for written non-entity paths, and
a render plan per read tool that is kept only when it reproduces the tool's
latest initial recordings from the state; every other read tool is a
recorded view with the reason. The Evidence is always inferred.

Response-body decoding moves to a domain owner shared with replay, exact
JSON parsing moves to the owner of the exact-number policy, the manifest and
state views share parent admission and record grouping, and
projectAnalysisView takes its accumulators as one object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): bound state at redacted writes; infer identity only from items

A scenario write whose URL was redacted has no request line, so it was
dropped and reads after it counted as initial state. Such a successful
write now joins the timeline with an empty path, whose root relates to
every type, so it ends every type's state; a limitation counts them.

A list whose recordings were all empty passed the "every listed item
carries it" check vacuously and published an identity field nothing
showed; it now needs at least one item, else the view stays recorded
as no-identity.

A value that differed between two request lines of one item tool, such
as a query variant, was called volatile, and the gate then substituted
each recording's own value; only repeats of one request line now show
volatility, so differing lines go through fields-differ.

List membership is compared only while a list can still be the whole
collection, and merged entities are indexed by type and collection once,
so paged captures no longer cost pages times entities (8,000 pages of
40,000 entities: 5.7 s to 0.23 s).

Docs and the tool contract say scenario state keeps the capture's
redaction markers and how redacted writes bound it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep first sightings, one list pointer and linear field unions

Entities took the order of the first plan observed, and item plans run
before list plans, so a complete list read before its detail reads came
out reordered and lost "all" membership; each entity now keeps its
earliest recorded sighting.

A list plan reused the first recording's pointer for later recordings,
so a later response that was itself an entity wrapping a list was read
as that list; every recording must now locate the same list.

The field union scanned its ordered list for every key, quadratic in an
object's width; a set now answers membership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep post-write values and partial lists out of the initial state

A value that changed between repeats of one request line was named
volatile whenever no write under the type's own root lay between, so a
write under another root (for example POST /api/dashboards/db before a
second GET /api/search) left its effect in the initial state. Any
recorded write between the two observations now leaves every tool of the
type recorded with reason fields-differ instead.

A list was published with membership "all" even when a total or count
envelope member exceeded the number it listed. Such a counter now makes
the membership "recorded".

Also drop a stale doc comment on ObservedEntity.order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(environment): scope item-shaped and deleted written paths out of documents

State documents are held only for list-shaped paths a POST, PUT or PATCH
tool writes. Reads of item-shaped written paths and of paths that are only
deleted stay recorded views; the guide now says so and the spec defers them
to a later increment of #4933.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): count writes in flight when a value changes between reads

The volatility check compared write start positions with the two reads,
so a write already in flight when the earlier read started was missed and
its effect was named volatile. A write now counts when its interval
overlaps the span from the earlier read's start to the later read's end.

The check also ran once per changed field and scanned every write. It now
runs once per pair of observations, answered by a binary search over the
writes in timeline order and the latest end among each prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): catch cross-tool changes across writes; read only item totals

A value that changed across a write was caught only between repeats of
one tool's request line, so a list read before a write and an item read
after it put the post-write value into the initial state. Changes are
now classified per entity in timeline order after observation: across
any recorded write, a change between repeats of a line or between two
lines of the type rewrites the type (every tool recorded as
fields-differ); with no write between, a repeat's change is volatile.
The classification lives in environmentStateMerge.ts.

Any numeric member whose name contained "total" or "count" counted as
an item total, so total_pages, account_id or page_count made complete
lists partial. Item totals are now members whose name tokens include
total or count and none of page, pages or per; a string of digits counts
as a number; and a set has_more, next or next-page token member also
marks the list partial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep post-write observations out of initial state

* fix(environment): retain field provenance and bound state inference work

* feat(environment): serve tool calls from the environment state

`environment-serve --state <state.json>` serves an environment from the
initial state an environment-state view extracted. Read tools whose views
are state-backed answer from a live state store: item views render the held
entity into the recorded envelope, list views render the collection or the
recorded members, and document views return the held value; a missing entity
or one lacking a required field is refused, and requests the state cannot
answer fall back to their recordings with the reason. The /agentenv data
plane implements data/reset, data/add (a rea-state/v1 document as a data
part, base64 bytes, a file:// path or an https URL, validated and replaced
atomically) and data/get, and the card advertises those operations and an
intake declaration. Stage 1 is read-only: write tools are not served in
state mode. Without --state nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(environment): record verification after merging the state view's review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(environment)!: serve the environment state by default

environment-serve derives the environment state at startup by running the
environment-state view in-process on the capture with the manifest's
template rules, so an environment comes with its state and a user supplies
nothing extra. The derived Evidence is the same one inspect-analysis-view
produces by hand. --state is removed; --replay-only serves exactly the
stateless replay of main. When the state cannot be derived, or its catalog
does not fit the discovery budget, the server starts in replay mode, logs
the reason and reports it in the stop summary's `state`. Exported bundles
serve the state by default too: export derives it the same way, so the
verifier's tool count and checked answer match what is served, and the
README describes the served mode.

BREAKING CHANGE: environment-serve answers read tools from the derived
state and does not serve write tools unless --replay-only is given; the
stop summary reports `state` instead of `state_evidence_id`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): bound data-plane replies, keep batch order, cancel intake on stop

- One /agentenv response is held to 64 MiB: replies are measured as they
  are produced and one past the budget becomes a -32000 error stating it.
- Every message of a batch enters the arrival-order queue on arrival, so
  another request's reset never lands between them.
- Stopping the server, or a caller leaving, cancels the active download
  and answers queued calls `cancelled` without running them.
- file:// URIs name only this host: another host or a network share is
  refused before anything is opened.
- A loaded view value may not give an entity another identity.
- List answers refuse a member lacking a field every recording carried,
  as item answers do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep bodies too deep for the state's nesting out of the state

A recorded body at the JSON depth limit was accepted by extraction, then
nested a few levels deeper as a document or entity value, so sealing the
state Evidence threw and environment-serve and environment-export stopped
instead of falling back. stateBodyJson, the one rule extraction and
serving share, now treats a body nesting deeper than the limit minus 16
levels as unusable, so its tool stays a recorded view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): measure data-plane replies before building them

83fcc739 bounded one /agentenv response to 64 MiB, but it still
serialized each reply whole before measuring it, so a batch of data/get
calls on a large state built every refused reply in full only to drop
it. Each reply is now measured in bounded JSON parts (the shared
jsonParts encoder), its parts kept only while it fits; a reply past the
budget is counted for its exact reply_bytes but never assembled. A test
checks that a refused read is never handed to JSON.stringify whole and
that its reply_bytes is exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): release batch replies as they are encoded; judge exact numbers by value

answerBatch kept every settled reply in its pending array until the
batch ended, so a batch alternating data/add and data/get held each
loaded state alive; each reply now leaves the queue as it is encoded.

The exact-number policy judged a number by its recorded spelling, so a
state loaded with 1e20 came back from data/get as
100000000000000000000, which data/add refused, breaking the promise
that data/get output loads back unchanged. A number is exact only when
both its recorded spelling and the one serialization writes are safe,
so integers from 2^53 below 1e21 are refused in any spelling; replay
keeps such bodies as recorded text. A domain test checks that every
accepted number is accepted again once written back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep room for refusals and framing in the reply budget

The response budget counted accepted replies only: a budget error that
replaced a reply, and a batch's brackets and commas, were not charged,
so near 64 MiB later refusals pushed the body past the advertised
limit. Every reply still to come now keeps room for its own budget
error, and a batch keeps room for its framing, so a refusal always fits
and the body never passes the budget. Reply ids and which messages are
answered come from one place shared by the replies and the reserve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): refuse a loaded state that data/get would write past the intake limit

data/add measured only the document it was given, while data/get
writes the state back with the members a document may omit (types, the
state Evidence id, view fields and record citations). A document just
under 32 MiB could therefore load and be reported larger than any intake
accepts, breaking the data/get then data/add round trip. replace now
measures the state as data/get writes it, part by part, and refuses it
past the limit, leaving the state unchanged. The limit moves to the
state document module, so the application store can read it; the
intake re-exports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep merged export checks on the shared test graph

Main's export tests installed module mocks for the whole services
project, which reuses one module graph. A tarball read failure then
missed its importer, and a one-shot heap mock could be taken by another
file. Read those through bindings the tests replace for one case.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(environment): keep a state file whose close failed owned until it closes

A file:// data/add that could not close its descriptor dropped the handle,
and an admission failure whose cleanup close failed dropped its owner, so
nothing could retry the close or report it.

The read now runs through withOwnedRegularFile, and a failed close is
retained in a server-lifetime ArtifactResourceScope. The next file read
retries it first and answers -32000 while it is still open; inline and
https documents still load. Closing the served environment retries it again
and rejects with the unfinished close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): attempt all state cleanup on close

* fix(environment): bound wide state JSON traversal

* fix(environment): scope cached state reply sizes to requests

* fix(domain): preserve JSON depth diagnostic guidance

---------

Co-authored-by: Edgar Arakelyan <edgar.arakelyan@scale.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 06:06:36 +08:00
Mohith DK f23066f0da fix(reconstruction): fall back to a kind-based title for blank graph labels (#6137)
Fall back to a kind-based obligation title when a graph label is blank

A whitespace-only application observation or semantic node label passed through ?? as the obligation title, and the ledger schema (trimmed, min length 1) then rejected the whole ledger. Use trim() || so a blank label takes the generated title.

Closes #6126
2026-10-22 05:53:42 +08:00
Abel YagubyanandAbelo9996 cbbd317fb5 fix(process): index late scenario events in validation issues (#6160)
Co-authored-by: Abelo9996 <Abelo9996@users.noreply.github.com>
2026-10-22 05:53:22 +08:00
Abel YagubyanandAbelo9996 d54c4345d3 fix(browser): report zero decoded bytes for undecodable gRPC-web text (#6159)
Co-authored-by: Abelo9996 <Abelo9996@users.noreply.github.com>
2026-10-22 05:53:13 +08:00
bdf0be87c6 fix(mcp): name response-byte and startup ceilings in recovery (#6005)
* fix(mcp): name response-byte and startup ceilings in recovery

Name REA_MCP_MAX_RESPONSE_BYTES, its effective value, the 10485760 byte
floor, and process restart when the stdio response budget is hit, and
state that the setting does not raise Node's string ceiling. Name fixed
MCP startup phase budgets when those deadlines fire, and keep Codex's
30 second startup figure as client guidance.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(mcp): distinguish string ceilings and keep dossier recovery

Name REA's reserved string limit separately from Node's ceiling, restore
detail:summary discovery for an oversized complete evidence bundle, and
apply the doctor deadline sentence when binary_session itself times out.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(mcp): keep retained SDK errors inside the text budget

Quoted SDK validation diagnostics expand when recovery JSON is escaped
into MCP text. Shorten only that preview so the retained-evidence
reference still arrives as parseable error content.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(mcp): keep export advice when a summary exceeds the response budget

An oversized get_evidence_bundle summary no longer repeats detail: summary.
export_evidence_bundle still writes the retained bundle outside one MCP frame,
including when a larger byte budget cannot raise the string ceiling.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* style(mcp): format the SDK diagnostic preview return

oxfmt rejected the fitted-preview ternary in EvidenceMcpServer.ts, which failed Static checks and the CI required gate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 05:48:56 +08:00
0155b89953 fix(browser): name web-bundle budgets and recover web deadlines (#6036)
* fix(browser): name web-bundle budgets and recover web deadlines

Name the existing heap, per-script, and total web-bundle phases, and reject a per-script deadline above the total. Promote capture, CDP command, source-map decode, and source-map fetch deadlines at today's defaults. Name DevTools timeoutMs, and state that discovery, cleanup grace, byte budgets, and Electron hook retention have no runtime override.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): apply selected web deadlines to the owning phase

Keep HTTP and browser-socket discovery on the fixed discovery deadline so a
short command_timeout_ms starts at the handshake. Apply scenario
command_timeout_ms to launch startup and the primary connect session, and
keep service-worker restoration on the fixed 5000 ms grace. Omit
command_timeout_ms from recovery text when the operation does not accept it.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): retain scenario commands that finish after their deadline

A timed-out scenario command stays owned so cleanup can close a late
browser or context and restore a mutation that lands after the deadline.
Child storage replies use that deadline, scenario recovery names
command_timeout_ms in the capture JSON, and the session test connection
implements beginCleanupGrace.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): keep child command timeouts as the public failure

A nested command such as Page.enable that is delivered but never answered
was stored as protocol_error, hiding the command name, selected deadline,
and recovery. Store the typed timeout and assert that public error.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): keep runtime cleanup on the fixed grace

Switch runtime restoration to the cleanup grace before the first send so a
short command_timeout_ms cannot fail an otherwise successful observation.
Stop advertising command_timeout_ms on list_browser_targets, which never
bounds a post-discovery command.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(browser): prove a short command deadline cannot fail runtime cleanup

Drive restoration through the runtime fixture. A cleanup command that
outlives command_timeout_ms still succeeds, and a stalled restoration
names the fixed 5000 ms grace.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): name the effective source-map decode deadline

codecBudgetLimitation reported a fixed 20-second deadline after a
caller selected decode_timeout_ms. Result and Evidence limitations
now name that effective millisecond deadline.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): separate handshake and target-attach deadlines

A browser-level Target.attachToTarget no longer shares the WebSocket
handshake budget. Blocked service-worker attachment keeps discovery on
its fixed deadline and applies command_timeout_ms to the later handshake
and attach.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): name the web-bundle resource each runtime exhausts

Bun ignores the V8 old-space flag. A Windows Job reports the process
commit limit, and other Bun hosts no longer suggest raising max_heap_mb.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): cite the browser-socket discovery message budget

WebSocket discovery enforces the 64 MiB protocol budget. Timeout
recovery no longer advertises the 8 MiB HTTP document budget.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): bound the blocked service-worker check before discovery

The first registration check has its own fixed 10s deadline. Discovery,
handshake, and Target.attachToTarget stay on their separate deadlines.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(browser): name the operand that stopped web-bundle admission

Host heap pressure no longer claims the fixed result budget is exhausted.
A Windows Bun allocation before the commit limit still names that limit.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 05:47:38 +08:00
166fb0820f feat(environment)!: serve the environment state by default (#5351)
* feat(environment): extract the initial state a capture's reads observed

Add the environment-state analysis view. From the parents the environment
manifest accepts, it extracts the data the capture's reads observed before
the first related write: entity types with an identity field (proved by a
path parameter or inferred from an identity name), entities with their
recorded values and provenance, documents for written non-entity paths, and
a render plan per read tool that is kept only when it reproduces the tool's
latest initial recordings from the state; every other read tool is a
recorded view with the reason. The Evidence is always inferred.

Response-body decoding moves to a domain owner shared with replay, exact
JSON parsing moves to the owner of the exact-number policy, the manifest and
state views share parent admission and record grouping, and
projectAnalysisView takes its accumulators as one object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): bound state at redacted writes; infer identity only from items

A scenario write whose URL was redacted has no request line, so it was
dropped and reads after it counted as initial state. Such a successful
write now joins the timeline with an empty path, whose root relates to
every type, so it ends every type's state; a limitation counts them.

A list whose recordings were all empty passed the "every listed item
carries it" check vacuously and published an identity field nothing
showed; it now needs at least one item, else the view stays recorded
as no-identity.

A value that differed between two request lines of one item tool, such
as a query variant, was called volatile, and the gate then substituted
each recording's own value; only repeats of one request line now show
volatility, so differing lines go through fields-differ.

List membership is compared only while a list can still be the whole
collection, and merged entities are indexed by type and collection once,
so paged captures no longer cost pages times entities (8,000 pages of
40,000 entities: 5.7 s to 0.23 s).

Docs and the tool contract say scenario state keeps the capture's
redaction markers and how redacted writes bound it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep first sightings, one list pointer and linear field unions

Entities took the order of the first plan observed, and item plans run
before list plans, so a complete list read before its detail reads came
out reordered and lost "all" membership; each entity now keeps its
earliest recorded sighting.

A list plan reused the first recording's pointer for later recordings,
so a later response that was itself an entity wrapping a list was read
as that list; every recording must now locate the same list.

The field union scanned its ordered list for every key, quadratic in an
object's width; a set now answers membership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep post-write values and partial lists out of the initial state

A value that changed between repeats of one request line was named
volatile whenever no write under the type's own root lay between, so a
write under another root (for example POST /api/dashboards/db before a
second GET /api/search) left its effect in the initial state. Any
recorded write between the two observations now leaves every tool of the
type recorded with reason fields-differ instead.

A list was published with membership "all" even when a total or count
envelope member exceeded the number it listed. Such a counter now makes
the membership "recorded".

Also drop a stale doc comment on ObservedEntity.order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(environment): scope item-shaped and deleted written paths out of documents

State documents are held only for list-shaped paths a POST, PUT or PATCH
tool writes. Reads of item-shaped written paths and of paths that are only
deleted stay recorded views; the guide now says so and the spec defers them
to a later increment of #4933.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): count writes in flight when a value changes between reads

The volatility check compared write start positions with the two reads,
so a write already in flight when the earlier read started was missed and
its effect was named volatile. A write now counts when its interval
overlaps the span from the earlier read's start to the later read's end.

The check also ran once per changed field and scanned every write. It now
runs once per pair of observations, answered by a binary search over the
writes in timeline order and the latest end among each prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): catch cross-tool changes across writes; read only item totals

A value that changed across a write was caught only between repeats of
one tool's request line, so a list read before a write and an item read
after it put the post-write value into the initial state. Changes are
now classified per entity in timeline order after observation: across
any recorded write, a change between repeats of a line or between two
lines of the type rewrites the type (every tool recorded as
fields-differ); with no write between, a repeat's change is volatile.
The classification lives in environmentStateMerge.ts.

Any numeric member whose name contained "total" or "count" counted as
an item total, so total_pages, account_id or page_count made complete
lists partial. Item totals are now members whose name tokens include
total or count and none of page, pages or per; a string of digits counts
as a number; and a set has_more, next or next-page token member also
marks the list partial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep post-write observations out of initial state

* fix(environment): retain field provenance and bound state inference work

* feat(environment): serve tool calls from the environment state

`environment-serve --state <state.json>` serves an environment from the
initial state an environment-state view extracted. Read tools whose views
are state-backed answer from a live state store: item views render the held
entity into the recorded envelope, list views render the collection or the
recorded members, and document views return the held value; a missing entity
or one lacking a required field is refused, and requests the state cannot
answer fall back to their recordings with the reason. The /agentenv data
plane implements data/reset, data/add (a rea-state/v1 document as a data
part, base64 bytes, a file:// path or an https URL, validated and replaced
atomically) and data/get, and the card advertises those operations and an
intake declaration. Stage 1 is read-only: write tools are not served in
state mode. Without --state nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(environment): record verification after merging the state view's review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(environment)!: serve the environment state by default

environment-serve derives the environment state at startup by running the
environment-state view in-process on the capture with the manifest's
template rules, so an environment comes with its state and a user supplies
nothing extra. The derived Evidence is the same one inspect-analysis-view
produces by hand. --state is removed; --replay-only serves exactly the
stateless replay of main. When the state cannot be derived, or its catalog
does not fit the discovery budget, the server starts in replay mode, logs
the reason and reports it in the stop summary's `state`. Exported bundles
serve the state by default too: export derives it the same way, so the
verifier's tool count and checked answer match what is served, and the
README describes the served mode.

BREAKING CHANGE: environment-serve answers read tools from the derived
state and does not serve write tools unless --replay-only is given; the
stop summary reports `state` instead of `state_evidence_id`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): bound data-plane replies, keep batch order, cancel intake on stop

- One /agentenv response is held to 64 MiB: replies are measured as they
  are produced and one past the budget becomes a -32000 error stating it.
- Every message of a batch enters the arrival-order queue on arrival, so
  another request's reset never lands between them.
- Stopping the server, or a caller leaving, cancels the active download
  and answers queued calls `cancelled` without running them.
- file:// URIs name only this host: another host or a network share is
  refused before anything is opened.
- A loaded view value may not give an entity another identity.
- List answers refuse a member lacking a field every recording carried,
  as item answers do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep bodies too deep for the state's nesting out of the state

A recorded body at the JSON depth limit was accepted by extraction, then
nested a few levels deeper as a document or entity value, so sealing the
state Evidence threw and environment-serve and environment-export stopped
instead of falling back. stateBodyJson, the one rule extraction and
serving share, now treats a body nesting deeper than the limit minus 16
levels as unusable, so its tool stays a recorded view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): measure data-plane replies before building them

83fcc739 bounded one /agentenv response to 64 MiB, but it still
serialized each reply whole before measuring it, so a batch of data/get
calls on a large state built every refused reply in full only to drop
it. Each reply is now measured in bounded JSON parts (the shared
jsonParts encoder), its parts kept only while it fits; a reply past the
budget is counted for its exact reply_bytes but never assembled. A test
checks that a refused read is never handed to JSON.stringify whole and
that its reply_bytes is exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): release batch replies as they are encoded; judge exact numbers by value

answerBatch kept every settled reply in its pending array until the
batch ended, so a batch alternating data/add and data/get held each
loaded state alive; each reply now leaves the queue as it is encoded.

The exact-number policy judged a number by its recorded spelling, so a
state loaded with 1e20 came back from data/get as
100000000000000000000, which data/add refused, breaking the promise
that data/get output loads back unchanged. A number is exact only when
both its recorded spelling and the one serialization writes are safe,
so integers from 2^53 below 1e21 are refused in any spelling; replay
keeps such bodies as recorded text. A domain test checks that every
accepted number is accepted again once written back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep room for refusals and framing in the reply budget

The response budget counted accepted replies only: a budget error that
replaced a reply, and a batch's brackets and commas, were not charged,
so near 64 MiB later refusals pushed the body past the advertised
limit. Every reply still to come now keeps room for its own budget
error, and a batch keeps room for its framing, so a refusal always fits
and the body never passes the budget. Reply ids and which messages are
answered come from one place shared by the replies and the reserve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): refuse a loaded state that data/get would write past the intake limit

data/add measured only the document it was given, while data/get
writes the state back with the members a document may omit (types, the
state Evidence id, view fields and record citations). A document just
under 32 MiB could therefore load and be reported larger than any intake
accepts, breaking the data/get then data/add round trip. replace now
measures the state as data/get writes it, part by part, and refuses it
past the limit, leaving the state unchanged. The limit moves to the
state document module, so the application store can read it; the
intake re-exports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): keep merged export checks on the shared test graph

Main's export tests installed module mocks for the whole services
project, which reuses one module graph. A tarball read failure then
missed its importer, and a one-shot heap mock could be taken by another
file. Read those through bindings the tests replace for one case.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(environment): keep a state file whose close failed owned until it closes

A file:// data/add that could not close its descriptor dropped the handle,
and an admission failure whose cleanup close failed dropped its owner, so
nothing could retry the close or report it.

The read now runs through withOwnedRegularFile, and a failed close is
retained in a server-lifetime ArtifactResourceScope. The next file read
retries it first and answers -32000 while it is still open; inline and
https documents still load. Closing the served environment retries it again
and rejects with the unfinished close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-22 05:04:57 +08:00
Diya 3dbe314ada fix(javascript): preserve source-map preparation capability errors (#6149) 2026-10-22 04:24:31 +08:00
xiaofeilovecodingandxiaofeilovecoding f5acf82a78 fix(native): retain decoded zero-address Swift class references (#6069)
Co-authored-by: xiaofeilovecoding <fxy850328059@gmail.com>
2026-10-22 04:18:25 +08:00
morluto a721c42c07 fix(cancellation): bind retained errors to cleanup owners
Carry the capture handle cleanup lease through retry results and bind composed export failures to it. Bind aborted binary retirement errors to the failed close so successful later cleanup reveals the original cancellation.

Validation: 46 focused tests passed; npm run check passed. Four regression assertions fail on the original source. Full suite and real-provider workflows were not run.
2026-10-22 04:17:14 +08:00
087813fb88 fix(cancellation): preserve caller abort reasons through late finalization (#6143)
* fix(cancellation): preserve caller abort reasons through late finalization

Caller aborts that arrived during final provider, cleanup, or protocol
handling were dropped or replaced by I/O, cleanup, or success results.
Each affected path now samples the caller signal after its owner has
settled and returns AnalysisCancelledError carrying the exact
signal.reason, keeping any completed partial Evidence or cleanup state.

- Web script export: keep the exact reason for an interrupted output
  write, and honor a cancellation fact recorded before a retried
  capture close succeeds.
- Native value tracing and Hopper regex search: check the signal after
  the final provider reply or regex worker settles.
- Apple asset catalog: stop assetutil with SIGTERM so cancellation
  settles only after the helper exits.
- CDP observation: end an armed window on a fatal protocol failure.
- ADB bugreport: prefer cancellation over the staging-removal output
  error when the caller aborted.
- JavaScript recovery: check the signal after provider success.
- export-web-scripts CLI: run under command cancellation and forward
  the signal into the export workflow.
- Objdump and Rizin CLI: report cancellation after cleanup completes.
- Binary session: keep cancellation primary when previous-client
  retirement fails after abort.

Fixes morluto/rea#6042
Fixes morluto/rea#6043
Fixes morluto/rea#6048
Fixes morluto/rea#6049
Fixes morluto/rea#6057
Fixes morluto/rea#6060
Fixes morluto/rea#6078
Fixes morluto/rea#6080
Fixes morluto/rea#6108
Fixes morluto/rea#6111
Fixes morluto/rea#6115

* fix(cancellation): keep caller reasons through export rollback and commit

- Rollback failure after a caller abort now returns AnalysisCancelledError
  with the exact signal.reason, attaching the rollback failure as cleanup
  and retaining any published result as partial observation.
- A capture close retry that succeeds after abort returns cancellation
  instead of the stale cleanup diagnostic.
- An abort that lands during the final output commit returns cancellation
  carrying the completed manifest as partial observation.
- Apple assetutil stop test: abort and await the run in finally so a
  timed-out wait does not leave the child running, and skip on Windows
  where the JavaScript SIGTERM handler cannot run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5

* fix(cancellation): bind export cancellations to cleanup and caller reasons

- A rollback-failure cancellation is bound to the retained cleanup lease,
  so a later successful close clears cleanup_incomplete instead of
  reporting a residual directory that no longer exists.
- mapExportFailure wraps the caller's own AnalysisCancelledError reason
  under export_web_scripts rather than returning it as the export's error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5
2026-10-22 04:15:16 +08:00
Alex GorevskiandCopilot 4e28166626 fix(cancellation): preserve caller abort reason in cancellation errors (#6144)
* fix(cancellation): preserve caller abort reason in cancellation errors

Several application, provider, and MCP boundaries constructed
AnalysisCancelledError, ArtifactReaderFailure, or
DetachedPgpSignatureError for an already-observed or newly-observed abort
without passing the caller's signal.reason as the error cause. The typed
result then reported cancellation with an undefined reason even though the
exact object supplied to AbortController.abort(reason) was still available.

Pass signal.reason as the cause at each producing cancellation site:
JavaScript artifact reconstruction and file abort checks, derived MCP
operation checkpoints, detached PGP verification, ASAR listing admission,
Dart AOT inspection and its provider guard, evidence-file analysis views,
binary layout and recorded crash admission, WASM inspection admission,
and the Kuna shared assembly-index wait.

Cancellation classification and cleanup behavior are unchanged.

Fixes morluto/rea#5935
Fixes morluto/rea#5944
Fixes morluto/rea#6100
Fixes morluto/rea#6101
Fixes morluto/rea#6110
Fixes morluto/rea#6113
Fixes morluto/rea#6121
Fixes morluto/rea#6131
Fixes morluto/rea#6132

* test(cancellation): assert the exact caller reason identity

Copied reasons satisfied toMatchObject, so the tests would pass even if the
caller's abort reason were replaced by a structurally equal copy. Capture the
rejection and assert the cause is the same object.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5
2026-10-22 04:11:15 +08:00
JohnandClaude Opus 5.5 7ffae5a791 test(artifacts): cover fixed input-size recovery for Rust and IL2CPP (#6148)
#5633 gave Rust and IL2CPP input-size refusals input-allowance recovery
through fixedAllowanceOptions, but no public CLI check covered either
command, and IL2CPP had no remediation assertion at all. Add both
commands to the CLI input-limit matrix.

Go and WASM still carried a duplicated hand-written copy of the same
advice. They now use the shared "input" allowance recovery, so all four
commands name their fixed allowance from one owner.

The matrix also asserted not.toContain("evidence reply"), which the new
"evidence-reply" wording passes only because of the hyphen. Assert the
absence of the old advice text instead.

Fixes #5405

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:10:46 +08:00
KIEU TRONG KHANG 2aadcf7d04 fix(electron): report rejected CLI arguments with issue details (#5036)
* fix(electron): report rejected CLI arguments with issue details

Pass schema validation issues and submitted input to inputError in list-electron-targets and inspect-electron-page commands, matching the structured diagnostics returned by browser commands.

Fixes #3865

* test(electron): assert exit code 1 on rejected CLI arguments
2026-10-22 04:08:52 +08:00
amyanger b0a554cc3a fix(browser): read SAZ archives with zero-byte HTTP members (#5453)
Fiddler writes a zero-byte raw/NN_s.txt (or _c.txt) for a session that
never got that message. The decoder passed it to the HTTP parser, which
found no header/body separator and rejected the whole archive, so no
session could be read even with --record.

Report a zero-byte request or response member as null, like a missing
member, keep it in location.members, and add a per-record limitation
that says only that the stored member is empty. Non-empty malformed
members are still rejected.

Fixes #5411
2026-10-22 04:08:42 +08:00
lyly 414ad52b20 fix(objc): validate direct protocol enumeration input (#4405) 2026-10-22 04:08:31 +08:00
Peter 3cfdc585b4 fix(mcp): keep analyze_function progress strictly increasing (#3460) 2026-10-22 04:08:20 +08:00
The one -NiuandKaoru0822 4fe88a7e3f fix(browser): reserve the initial scenario step id from action inputs (#3493)
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-22 04:08:11 +08:00
jayinversandmaxazure f4cabe535f fix(wasm): pass glue filenames to the JavaScript parser (#4485)
associateWasmGlue selected the unnamed-source parser mode even though
the selected glue filename was already an adapter argument, so valid
JavaScript such as `n===0?H=>({start:H.startY}):H=>({start:H.startX})`
was read with the TypeScript grammar (conditional arrows as a return
type) and the whole glue file failed parsing, losing every .wasm
literal reference after it. Pass the filename so .js glue parses with
the JavaScript grammar, matching the general filename-aware parser
boundary from #3782. The unnamed-source fallback policy itself is
unchanged.

Adds a regression test asserting the conditional-arrow fixture parses
completely and retains the ./module.wasm reference (line 2, column 6,
local-path-candidate, static-literal) while the plain fetch control
stays working; reverting the fix fails the new test.

Fixes #4474

Co-authored-by: maxazure <maxazure@gmail.com>
2026-10-22 04:08:00 +08:00
keeponlightandkeeponlight 6bc4ec3a74 fix(electron): block moveToApplicationsFolder during active capture (#3471)
Co-authored-by: keeponlight <20103260+keeponlight@users.noreply.github.com>
2026-10-22 04:07:48 +08:00
The one -Niu 1dcaa77693 fix(browser): retain the null console argument in selected console text (#3479) 2026-10-22 04:07:37 +08:00
ShuaixinHuang 0da36910dc docs(artifacts): clarify unsupported DMG extraction (#5555)
* docs(artifacts): clarify unsupported DMG extraction

* fix(cli): keep the WASM command timeout optional

* fix(cli): preserve WASM timeout help metadata

* test(pe): reuse compiled CLI startup for resource acceptance

* fix(cli): use upstream exact-optional timeout semantics
2026-10-22 04:07:28 +08:00
Danish Rafie Ekaputra 55c3129224 docs(browser): reconcile HTTP discovery wording with browser sockets (#3484) 2026-10-22 04:07:18 +08:00
dbd556043c fix(providers): name JEB, IDA, and one-shot command deadlines (#6014)
* fix(providers): name JEB, IDA, and one-shot command deadlines

Wire JEB catalog discovery to REA_JEB_MCP_TIMEOUT_MS, name the IDA
registration timeoutMs, and add command_timeout_ms for one-shot objdump
and rizin. Debugger, Frida, and Cutter caps stay fixed and say so.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(providers): pass remaining deadlines and name the cap that fired

Catalog discovery now sends the JEB budget still remaining to the SDK on
connect and every tools/list page. Objdump and rizin preflight share one
command_timeout_ms across stat and digest; the command still receives that
budget again. Architecture Evidence keeps a selected timeout, and Frida and
Rizin name the capture limit that actually stopped output.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(frida): preserve non-timeout device lookup failures

getDeviceById is a lookup deadline only when Frida rejects with
"Device not found" after the wait. A device-manager or backend failure
stays a capability error. Align the assertions that still expected the
previous capacity and capture sentences.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 04:01:02 +08:00
Peter G b28f6c0953 fix(reference): retain inventories with empty module specifiers (#3497) 2026-10-22 03:57:38 +08:00
Jiwon | 지원 3220e17b8b fix(snapshot): count newly retained workflow bindings on import (#4428)
AnalysisSnapshotCache.stage counted new direct entries but never
incremented for new workflow entries, so a snapshot carrying only a
trace_feature binding reported 0 new entries while retaining it.

Count a workflow binding when its query ID is new, matching direct
entries; replacements and capacity-refused bindings stay uncounted.

Fixes #4377
2026-10-22 03:51:32 +08:00
Vinícius Andrade cc4ed5b904 fix(ida): keep omitted callee truncation markers unknown (#4286)
* fix(ida): keep omitted callee truncation markers unknown

Modern headless callees pages may omit more. The runner treated every
page that was not explicitly capped as complete coverage, so
trace_call_path could return not_reached when callee completeness was
unproven. Map omitted more to unknown coverage and keep the observed
callee rows. Explicit more: false remains complete.

Fixes #3988

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>

* test(ida): keep omitted callee truncation from becoming not_reached

Assert that an x86 callees page without more reports unknown
relationship coverage and that trace_call_path to an unlisted goal
stays unknown instead of not_reached.

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>

---------

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>
2026-10-22 03:51:22 +08:00
Vinícius Andrade 126b6e20d4 fix(ida): retain analysis profile in partial operation evidence (#4645)
* fix(ida): retain analysis profile in partial operation evidence

Pass the session analysis profile commitment to createEvidence when IdaSessionClient.#failure retains partial observations after a failed operation.

Refs #3992

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>

* test(ida): cover analysis profile retention in partial operation evidence

Verify that failed IDA operations retain a supplied AnalysisProfileCommitment on partialObservation and projected error details while leaving unprofiled sessions null.

Refs #3992

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>

* test(ida): align analysis profile provider with IDA adapter identity

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>

---------

Signed-off-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>
2026-10-22 03:51:12 +08:00
Wade 8fe8622e09 fix(evm): reject repeated byte-string keys in Solidity metadata (#4024)
cborg's rejectDuplicateMapKeys compares Map keys by identity, and each byte-string key decodes to its own Uint8Array, so a terminal metadata map with the same byte key twice was reported as decoded. Compare the root map's scalar keys by kind and value and report a repeat as not-decoded, like repeated text keys.
2026-10-22 03:51:02 +08:00
Vishal Tambi 1a945ce9ff fix(cli): reject an empty JavaScript application path (#5265) 2026-10-22 03:50:52 +08:00
Wade d8199ab6af fix(javascript): keep Vite dependency slots after a spread unknown (#4023)
The __vite__mapDeps table indexer mapped a spread element to one unknown slot, so every later entry kept a definite index even though the spread can add any number of elements. A call such as __vite__mapDeps([0, 2]) over ["./first.js", ...["./middle-a.js", "./middle-b.js"], "./last.js"] then resolved index 2 to last.js instead of middle-b.js. Keep only the entries before the first spread; indexes from the spread onward stay unknown.
2026-10-22 03:50:42 +08:00
ShuaixinHuang f6e4e0296f fix(native): remove completed signing capture limitation (#5548)
* fix(native): remove completed signing capture limitation

* fix(cli): keep the WASM command timeout optional

* fix(cli): preserve WASM timeout help metadata

* test(pe): reuse compiled CLI startup for resource acceptance

* fix(cli): use upstream exact-optional timeout semantics
2026-10-22 03:50:31 +08:00
4c20744f1c test(update): isolate CLI subprocess deadlines (#5019)
test(update): isolate CLI command deadlines

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 03:50:18 +08:00
Prasad Navale 3dce8326c9 docs(errors): include Omarchy and current host-support messages in error catalogue (#5078) (#5278) 2026-10-22 03:50:08 +08:00
Prasad Navale fd64b1c240 docs(cli): include Kuna in supported doctor providers (#4624) (#5011) 2026-10-22 03:49:57 +08:00
Prasad Navale d2015e94f3 docs(setup): clarify prebuild-install warning from optional dependencies (#4881) (#5012) 2026-10-22 03:18:41 +08:00
7fdf7a5b79 docs(go): clarify cumulative build-info search budget (#2731)
* fix(go): retain build info in later ELF data segments

* test(javascript): retain unknown shadowed parameter provenance

* fix(go): bound overlapping build-info searches

* test(release): select the runnable preflight job

* fix(ci): isolate Linux Hopper process fixture inventory

* fix(comparison): recheck historical calls-only changes (#2223)

* fix(comparison): preserve incomplete function call evidence

Keep missing, unknown, partial and unresolved call relationships unknown in function comparisons and reconstruction claims. Aggregate IDA caller and callee coverage without overwriting uncertainty.

Validate with 144 focused tests, typecheck/lint, docs checks and test discovery. Real providers were not verified; the full headless IDA session suite requires unavailable Windows native controls.

* fix(comparison): recheck historical calls-only changes

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>

* docs(go): expose cumulative build-info scan budget

* chore: retain upstream MCP test formatting

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: Lucien <164857476+dakjdakd@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-22 03:18:31 +08:00
Iven Koo 7cdf8bc5cf test(native): assert failed-callee call identities and edge caps (#2888)
* fix(native): attempt failed value trace selectors once per invocation

* test(native): assert failed-callee call identities and edge caps
2026-10-22 03:18:21 +08:00
Vishal Tambi 465e1a1f36 test(sqlite): keep incomplete cleanup primary over late cancellation (#5365) 2026-10-22 03:18:10 +08:00
Christopher Pruijsen f3537db34f fix(setup): treat empty COPILOT_HOME as unset (#5047)
`copilotDirectory` used nullish coalescing, so `COPILOT_HOME=""` produced
the relative path `mcp-config.json`. Setup then planned and wrote the
GitHub Copilot CLI registration into the current working directory, not
`~/.copilot/mcp-config.json`, and doctor looked for it in the same wrong
place.

Use `||` like the sibling client homes (CODEX_HOME, KIMI_CODE_HOME,
GROK_HOME, XDG_CONFIG_HOME), and as #2686 did for DSH_HOME.
2026-10-22 03:18:01 +08:00
Dragoloneandgensokyo-dev 48e7470041 fix(evm): trim hex carrier whitespace in linear time (#5200)
The hex carrier trimmed outer ASCII whitespace with a global
start|end-anchored regex. For an invalid carrier with a long interior
whitespace run, the end branch was retried at every position of the run,
so rejection took quadratic time (about 18 s for a 200 KB run) and a few
hundred KB could exhaust the worker deadline instead of returning the
documented invalid-format error. Trim with index scans over the same
ASCII whitespace set.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-22 03:17:50 +08:00
2947a77507 fix(engines): keep format ceilings fixed and recover the EVMole deadline (#6088)
* fix(engines): keep format ceilings fixed and recover the EVMole deadline

Promote the owned EVMole worker wall clock to timeout_ms and name it in
recovery. Fixed CPU, byte, and address-space refusals in this theme say
the adjustable deadline does not raise them.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(engines): recover the allowance that refused the request

Model capacity recovery follows a structured limit instead of message text,
and the shared header ceiling no longer names Safetensors. Firmware signal
errors keep captured tool output, oversized reports do not suggest an
intermittent rerun, and NativeAOT hydration budget exhaustion explains the
fixed parser allowance on the partial report.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 03:06:59 +08:00
ae09e3fe70 fix(native): recover macOS command deadlines (#6059)
* fix(native): recover macOS command deadlines

Promote native_command_timeout_ms (default 60000), native_ui_deadline_ms
(default 180000), and pwntools_timeout_ms (default 30000) as validated
per-call settings. Keep cpuSeconds, byte allowances, LLDB grace, and plist
probes fixed, and say those caps have no runtime override.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(native): report the deadline that actually expired

Pass the selected native command timeout through xcrun resolution, thread
the pwntools deadline into pwndbg failures, and state the selected UI
deadline apart from the fixed wait-step cap. SIGXFSZ and EFBIG recovery
names the file-size allowance.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(native): honor the selected deadline on helper stages

The UI helper compile and capture subprocesses used fixed 60s and 30s
exec timeouts, so a longer native_ui_deadline_ms still failed as a generic
provider error. Preparation timeouts reported the 60000 ms default instead
of the selected native_command_timeout_ms.

Memory resource-constraint recovery names memory again, and the Darwin
plist CLI expectation matches the byte-counted input allowance.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 02:59:27 +08:00
Ulises Millán 67a6477f01 fix(cli): label reconciliation input failures as application workflow errors (#5668)
Closes #5436
2026-10-22 02:53:27 +08:00
Oke Iyanuoluwa EnochandClaude Opus 5.5 3dde9a5e1c ci(windows): run remaining Windows-only path admission cases in the lane (#5807)
Three test files have cases that run only on Windows
(skipIf(process.platform !== "win32") / describe.runIf(win32)), but the
windows-curated job runs an explicit file list that did not name them,
so CI never executed those cases:

- src/contracts/sessionPathInputs.test.ts (Windows absolute path forms)
- tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
  (snapshot replay through a junction and before native admission)
- tests/boundary/mcp/rawBinaryMcp.test.ts (saved reads across a
  junction and a noncanonical Windows path)

Add them to the lane and the package change scope, and guard the
entries in curatedWindowsLane.test.ts.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:52:11 +08:00
Shakti 07fd426e9a fix(apple): preserve asset catalog caller cancellation reason (#6055)
* fix(apple): preserve asset catalog caller cancellation reason

* test(apple): cover asset catalog cancellation checkpoints
2026-10-22 02:51:11 +08:00
gensokyo-dev 0583fc69be perf(javascript): query selected source-to-bundle facts (#6128)
Select completed source-to-bundle mappings and candidates through the shared view owner, preserve reference-file provenance, and retain full Evidence. Reuse bounded JSON reading, heap admission, and cancellation before session writes.

Verified public Redux Toolkit source/map facts with an independent oracle, equivalent CLI/MCP workflows, focused negative controls, and complete CI/review. Historical paired SDK context estimates fall about 64%; timing/resource tradeoffs and final verification receipts are recorded without a general speedup or actual model-token claim.

Fixes #6112.
2026-10-21 18:45:50 +00:00
a0214e0e0e fix(javascript): name heap and semantic ceilings (#6022)
* fix(javascript): name heap and semantic ceilings

Refs #5910. Heap exhaustion names max_heap_mb with its effective value,
range, and next-analysis worker restart. Semantic ceiling coverage names
the four max_semantic_* arguments. Wakaru and transfer ceilings say this
release has no runtime override.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): distinguish adjustable ceilings from fixed limits

The effective transfer file ceiling and the parent result budget follow
the CLI/MCP process heap, so recovery names NODE_OPTIONS instead of
denying an override. A provenance report over 8 MiB keeps the fixed
allowance guidance, and the Wakaru deadline says an unchanged retry may
hit it again.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): keep Bun OOM and checkpoint heap controls distinct

Bun heap-exhaustion exits no longer claim the ignored max_heap_mb V8
old-space limit. Checkpoint decode names the parent heap and Node
NODE_OPTIONS, the same control as the parent result budget.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): name semantic ceilings without claiming both were reached

Truncated semantic retention names the total and per-file controls as
effective ceilings. Callers only know that retention was truncated, so
the limitation no longer says both ceilings were reached.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): name only the semantic ceiling that stopped retention

Semantic ceiling limitations claimed both the application total and the
per-file control whenever either one truncated the graph. Record which
offered budget stopped node and unknown retention, and name only those
controls. Coverage limits still list both effective values.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): require a value in the NODE_OPTIONS heap example

A bare --max-old-space-size is an illegal V8 flag and prevents Node from
starting. Parent-heap and transfer-ceiling recovery now share
--max-old-space-size=<MiB>, and the Kuna heap remediation uses the same
complete form.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): limit the old-space placeholder to NODE_OPTIONS recovery

The review covers the CLI/MCP NODE_OPTIONS example and the transfer-ceiling
copy. The Kuna heap-headroom sentence stays unchanged.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 02:44:14 +08:00
01788bac86 fix(android): name the JADX deadline and recover apktool decode (#6024)
* fix(android): name the JADX deadline and recover apktool decode

JADX operation timeouts now name REA_JADX_OPERATION_TIMEOUT_MS, the
effective milliseconds, the 1..2147483647 range, and that a change applies
on the next process start. Apktool decode accepts decode_timeout_ms,
omitted means 600000, and a timeout names that key. ADB deadlines and
apktool and Java version probes stay fixed and say there is no runtime
override.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(android): derive capture recovery from the fixed allowance

Main now stores a fixed allowance instead of a prebuilt remediation
string. Apktool and adb capture refusals keep the no-runtime-override
sentence in that allowance name, including that decode_timeout_ms does
not raise the apktool output cap.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(apktool): observe a real decode deadline and process cleanup

A hanging interpreter launcher and its pipe-holding child stay alive
until decode_timeout_ms elapses. The provider then reports
provider_timeout with completed cleanup, and both processes are gone.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 00:31:43 +08:00
gensokyo-dev aa51e43439 perf(mcp)!: default to compact input schema advertisements (#5975)
Use compact MCP input advertisements by default while preserving explicit full, canonical validation, output contracts and complete Evidence. Require an explicit selected catalog for runtime identity and honor both profiles in CLI status.

Real ripgrep/Zellij SDK tasks, independent ELF oracle, actual client trials, profile parity workflows and CI are recorded in experiments/analysis-efficiency/mcp-input-catalog.md. SDK token estimates improve; actual Codex token savings and general latency/memory gains are not established.

Refs #5861

BREAKING CHANGE: MCP input advertisements default to compact. Set REA_MCP_INPUT_SCHEMA_PROFILE=full for complete advertised guidance and nested constraints.
2026-10-22 00:17:11 +08:00
gensokyo-dev 01ee6551b4 fix(ci): use one heap snapshot for JavaScript admission (#6081)
Read the V8 limit and used heap together for initial admission, checkpoint
decode and remaining result capacity. Remove the duplicated second heap/RSS
observation while preserving every reserve and ceiling.

Make the controlled parent-capacity fixture internally consistent and test
both unchanged usage and a later one-MiB collection. Real worker/transfer
coverage still retains static/module facts and declines only the large graph.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-22 00:15:25 +08:00
d8a9673fc4 fix(process): name the Swift compile deadline (#6071)
Name REA_SWIFTC_COMPILE_TIMEOUT_MS, its effective milliseconds, the
1..2147483647 range, and the omitted 120000 ms default when a Darwin
ownership-helper compile times out. A change applies after REA restarts,
on the next ownership-helper compile.

Producer SIGXCPU and SIGXFSZ recovery says REA retains the tighter
inherited limit. Agent-visible fixed caps in this theme say this release
has no runtime override. Numbers are unchanged.

Refs #5910

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 23:46:29 +08:00
gensokyo-dev f86206535f fix(ci): validate artifact MCP replies against named schemas (#6021)
Bind artifact lifecycle and analysis calls to their advertised named SDK
output schemas, sharing discovery per connection and rebinding on reconnect.
Reject schema overrides and ambiguous catalogs, preserve request options,
and retain primary failures while attempting all owned cleanup.

Add real-stdio boundary regressions for malformed output, discovery reuse,
reconnection, schema ambiguity, override rejection and timeout preservation.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-21 23:28:12 +08:00
Kaoru0822andClaude Opus 5.5 8e3bf56e11 fix(electron): record actions skipped after a failed action (#6023)
An Electron scenario stopped at the first failed or cancelled action and
returned no record for the remaining declared actions, so
normalized_result.actions could not account for the whole scenario.

Record every remaining action as cancelled with a not-executed error, the
representation browser scenarios already use. Both runners now share one
helper for that error, which also distinguishes a stop caused by request
cancellation from one caused by an action failure.

Fixes #5943

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:06:25 -07:00
Kaoru0822andClaude Opus 5.5 dc53c11f6d fix(process): keep structural capture issues alongside semantic ones (#6020)
parseProcessCapture re-threw only the custom (semantic) schema issues as a
TypeError whenever any existed, discarding structural issues found in the
same parse. Throw the schema's ZodError instead so every issue is retained
and describeValidationFailure reports the first issue with the remaining
count. Reconstruction obligation candidates now use that same formatter
instead of the raw error message.

Fixes #5957

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:05:30 -07:00
Kaoru0822andClaude Opus 5.5 d4c637f4cf fix(browser)!: keep malformed console argument inventories unknown (#6016)
* fix(browser)!: keep malformed console argument inventories unknown

An omitted, null, or non-array Runtime.consoleAPICalled args inventory now
reports argument_types as null, text capture as unavailable, and
console_events as invalid_protocol_value instead of a complete empty
inventory. Malformed argument elements keep their CDP position as
"unknown", so retained text values keep their original argument_index.

Call-site attribution now uses the frame at position zero: a malformed top
stack frame is no longer replaced by its caller. Console events are
excluded and request initiator locations are null, both reported as
invalid_protocol_value.

BREAKING CHANGE: console event argument_types may now be null, and
console text_capture.status may now be "unavailable", when the CDP
producer omits or malforms the argument inventory.

Fixes #5934

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(browser): report malformed WebMCP registration stacks as incomplete

topCallFrame now owns the omitted-versus-malformed decision for the whole
stack value: an omitted stack has no call site, while a non-object stack,
non-array callFrames, or a malformed top frame is malformed. WebMCP
discovery keeps the tool with a null registration_source but records
webmcp_tools invalid_protocol_value, so capture comparisons stay unknown
instead of confirming a changed source. Console and initiator stacks use
the same rule.

Refs #5934

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:01:45 -07:00
Kaoru0822andClaude Opus 5.5 4dfa1c67dc fix(capture): keep excluded MIME protobuf applicability partial (#6012)
* fix(capture): keep excluded MIME protobuf applicability partial

Automatic protobuf framing treated an explicitly excluded, conflicting or
unavailable Content-Type the same as a known-absent one, so a selected body
was skipped and coverage still upgraded to complete. Body metadata now
reports whether MIME applicability is established; an unresolved non-empty
body adds a record limitation and keeps protobuf_coverage partial. HAR uses
its required mimeType, falling back only to a visible header value.

Fixes #5926

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(capture): compare repeated Content-Type declarations by essence

Repeated Content-Type headers that differ only by case, whitespace or
parameters declare the same media type, yet the raw-string comparison
collapsed them to an unknown value and the new applicability check
reported partial coverage. Body metadata now compares media-type
essences and owns automatic framing selection, so the consumer no longer
re-parses the raw value; framing lookup ignores inherited properties.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:58:31 -07:00
Kaoru0822andClaude Opus 5.5 1f92c77398 fix(adb): report an unparsed push checksum as unverified (#6010)
fix(adb): derive push digest source from the parsed device digest

A zero-exit sha256sum whose output held no recognizable digest produced
device_sha256: null with device_digest_source: "device_sha256sum" and
dropped the not-device-side-verified limitation. The push receipt now
takes one tagged device digest, so the source can only name sha256sum
when a digest was parsed; unrecognized output reports "unavailable",
the unverified limitation, and the raw command output. The sha256sum
parser moves to AdbShellOutput and accepts GNU's escaped-name prefix.

Fixes #5924

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:56:38 -07:00
Kaoru0822andClaude Opus 5.5 2319166c2e fix(reconstruction): keep complete contradictions beside incomplete entries (#6009)
The obligation ledger flattened every contradiction entry for an obligation
into one Evidence list, so one entry naming absent Evidence suppressed a
sibling entry whose Evidence was all present. Group whole manifest entries
and evaluate each one: any complete entry still reports `contradiction`,
while absent references from any entry stay visible as
`contradiction-evidence-missing`.

Share the ready packaged-process request fixture with the CLI boundary test,
which now covers both entry orders through the public command.

Fixes #5925

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:54:37 -07:00
Kaoru0822andClaude Opus 5.5 63b43b69bb fix(javascript): refuse Webpack loaders that can fall off the end (#6007)
* fix(javascript): refuse Webpack loaders that can fall off the end

The bootstrap integrity proof accepted any loader whose explicit returns
all yielded the module's exports. A loader with no returns, or with only
the cache-hit return, passed vacuously even though its miss path returns
undefined, so a bundled `n(45840).BrowserWindow` became an exact Electron
window.

Require that the loader body cannot complete normally before the proof
checks its returns, so the bundled-call identity stays unresolved while
the literal require("electron") reference is preserved.

Fixes #5958

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(javascript): cover finally completion in Webpack loader proofs

Group the loader completion-path cases in their own suite and add
focused finally cases: a completing finally keeps a falling-through
catch unproven, a finally returning another value stays unresolved, and
a finally returning the exports proves the loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(javascript): keep IPC checks for unresolved Webpack loader cases

Share the unresolved-identity assertion so the completion-path cases
also reject directly attributed IPC observations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:53:42 -07:00
Kaoru0822andClaude Opus 5.5 c8d3f8f8ec fix(firmware): keep a missing producer report distinct after exit 0 (#6008)
* fix(firmware): keep a missing producer report distinct after exit 0

A firmware engine that exits 0 without writing its requested report now
fails with the observed report state (missing, empty, invalid_json,
oversized, not_regular_file), the command, arguments, exit status and
captured output, plus report-specific recovery instead of generic
`rea doctor` advice. REA still does not retry or substitute an empty
result.

Fixes #5876

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(firmware): keep missing-report recovery from replaying removed paths

The recorded command arguments name REA's private workspace, which is
removed after the attempt. Recovery now presents them as provenance and
tells callers to replace every private path when reproducing the run.
Cover the oversized and not_regular_file report states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:52:40 -07:00
Kaoru0822andClaude Opus 5.5 4423b6fff2 fix(process): reject finite trace variants that only reorder unordered members (#6006)
Duplicate-variant validation keyed each finite trace by its JSON form, which
preserves the member order of unordered tokens that matching treats as a
multiset. Key traces by the observations they accept instead: sort unordered
members and expand a token that repeats one event into that event's sequence,
keeping token positions and multiplicities significant.

Fixes #5916

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:52:06 -07:00
Kaoru0822andClaude Opus 5.5 5df59ee1fd fix(errors): name fixed plist decode allowances instead of host memory in recovery (#5860)
fix(errors): derive fixed-allowance recovery from a typed refusal field

inspect_plist refused a binary plist above its fixed 64 MiB decode
estimate, or nested deeper than 128 levels, with resource "memory" and no
recovery, so the shared presentation told the caller to add worker or
host memory. Neither raises that allowance. The 64 MiB source-size refusal
likewise got the worker file-size advice for evidence replies.

AnalysisResourceConstraintError now carries a typed fixedAllowance
{ name, kind }. fixedAllowanceOptions sets it instead of a prebuilt
string, and the presentation derives the recovery from it, so every
existing fixed-allowance producer moves to the same field. Operation
re-attribution, IDA startup re-attribution and explicit redaction copy the
field through one resourceConstraintRecovery helper; redaction still
applies to the allowance name.

The plist decode, nesting and input refusals use the field. A nesting
refusal now reports maximum_nesting_depth instead of the decode byte
limit it did not exceed.

Fixes #5847

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:51:46 -07:00
Kaoru0822andClaude Opus 5.5 6d356b8434 test(ci): keep module mocks in isolated test projects (#5886)
* test(ci): keep module mocks in isolated test projects

A module mock registered after another file in the same worker loaded the
module never applies, so a mocking test in a project that shares one module
graph passes or fails with file scheduling. That is how the environment
export source tests failed on hosted runs until c324d31b4 removed their
mocks.

Guard the rule: every tracked test using vi.mock or vi.doMock must belong
to an isolated project. Move the last shared-graph case,
DirectAnalysisStatFailure, which mocks node:fs/promises, into process-global
beside the other module-mocking workflow tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(pe): share one compiled CLI process across PE resource inspections

The acceptance test started a fresh CLI process for each of its fixtures
inside one 30 s test deadline. Hosted runs took 24-27 s and c324d31b4 timed
out. Every inspection there reads no stdin and sets no deadline, so use the
fixture's file-scoped compiled CLI process. Local time drops from 8.5 s to
about 3.9 s with the same assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:51:24 -07:00
gensokyo-dev c538a843cb fix(ci): keep Windows ACL observation in SID form (#5941)
Observe private-runtime ACL ownership, trustees and rule count from one SID
rule collection. Guard the account-name presentation member through the owning
PowerShell type and verify that guard is installed. Keep the original15s probe
deadline and every ACL/readonly assertion.

Record bounded phase diagnostics and document the observed failure boundary.
The repeated historical timeout cause is unconfirmed; no retry or deadline
increase is presented as a fix.

Validation: complete current-head CI passed40 jobs with one source-only skip;
real Windows typecheck, curated/native controls, package and Node24/22 checks
passed. PowerShell7.5.2 API/parser experiment and278 routing regressions passed.
Full evidence: https://github.com/morluto/rea/actions/runs/39599008194

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
2026-10-21 20:43:56 +08:00
Kaoru0822andClaude Opus 5.5 25e1a24a3e fix(reconstruction)!: summarize obligation ledgers by cited Evidence authority (#5875)
summary.by_evidence_authority bucketed each obligation's source_state, so a
shipped-artifact and a controlled-replay ledger returned the same summary and
the promised authority breakdown was unanswerable.

Derive the buckets from authority_references[].authority using the Evidence
authority vocabulary owned by the Evidence schema. An obligation counts once
under each distinct authority it cites, so counts can exceed total. Keep the
source-state histogram under its own name, by_source_state. The readiness
example now derives its summary from the same owner function instead of
hand-writing buckets.

BREAKING CHANGE: summary.by_evidence_authority keys are now Evidence
authorities (shipped-artifact, controlled-replay, historical-reference,
external-service, analyst-inference) instead of source states. The previous
state counts move to the new required summary.by_source_state. Ledger closure
digests change accordingly.

Fixes #5838

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:12:55 -07:00
Kaoru0822andClaude Opus 5.5 705058f3e2 fix(javascript): count one IPC observation per site-resolved channel (#5810)
* fix(javascript): count one IPC observation per site-resolved channel

A send with a local constant channel was recorded twice: once unresolved
by the syntax pass and once resolved by wrapper substitution, because
duplicates were keyed on the channel. A channel the call site determines
now resolves that observation in place. A channel supplied through a
wrapper argument stays a candidate beside the unresolved observation.
Invocation identity now follows the call that supplies the wrapper's
literal arguments, so one wrapper call reached through several entries
is recorded once.

Fixes #5710

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): keep wrapper invocations distinct while removing repeats

Taking the nearest literal call as invocation identity collapsed separate
outer calls that pass different handlers through one wrapper call. Entry
identity is restored, and a finding is a repeat only when an identical
finding was reached through the same trailing local calls. A registration
replaces its unresolved source observation only when the site also
determines its handler, so a parameter handler keeps the generic source
registration beside per-invocation candidates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): compare site-determined IPC handlers by value

In-place resolution compared handler representations, so a constant
channel with a named local handler (recorded as an identifier by the
syntax pass and as its function by substitution) stayed counted twice.
The handler is now site-determined when it evaluates to the same function
in the site's lexical environment. A replaced observation also removes
the syntax pass's unknown-channel contribution, so a fully resolved file
is no longer reported as partial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): keep closures over invocation values as distinct IPC handlers

A handler node created in different wrapper activations may capture
different values, so the same function node alone does not prove the call
site determines the registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(javascript): guard optional call paths in closure handler test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): decide IPC handler identity from activation captures

A closure is one site-determined handler only when it reads no binding of a
function this trace activated, such as a wrapper parameter or factory local.
Unused bindings in the activation no longer split a known listener, and
closures over outer-call values stay distinct when a shorter entry traced
the same wrapper call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): treat implicit captures and bundle modules precisely in IPC handler identity

An arrow handler reading its enclosing function's arguments or this captures
that activation. A bundle module factory runs once per module load, so its
bindings are shared by every trace rather than per-invocation captures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): attribute an arrow handler's inherited context to its owner

An arrow inherits this and arguments from the nearest non-arrow function, so
an activated arrow inside that owner does not make the handler depend on
the traced call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): skip suffix scans for per-invocation IPC handlers

Check activation capture before scanning recorded call chains, keep the
chains of capture-dependent handlers out of the index, append chains in
place, and treat an arrow's inherited super and new.target as captures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): supersede nested-entry IPC records and share module-init factories

A shorter entry traced alone no longer swallows the outer calls that invoke
the same wrapper sequence: the first fuller path takes over its record and
separate outer calls stay distinct invocations. A factory activation called
at module scope runs once per load, so its closure is shared unless the
trace itself makes that call. The resolver now owns bundle module-factory
recognition instead of an environment marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:06:50 -07:00
Kaoru0822andClaude Opus 5.5 35d6d5bb0a fix(artifacts): admit ZIP archives after unclaimed leading bytes (#5803)
* fix(artifacts): admit ZIP archives after unclaimed leading bytes

ZIP recognition required the archive at byte 0, so a ZIP behind a
script or self-extractor stub was refused as an unsupported binary
format although standard readers accept it. Target resolution and root
inventory now locate a trailing single-disk ZIP from its end record,
central directory and first local header, whether member offsets include
the leading bytes or not. Leading bytes any executable header family
claims keep their executable route.

Fixes #5732

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): share the root ZIP family across recognition paths

The trailing-ZIP fallback named a stub-prefixed root `.app` archive
`zip`, while root inventory and the leading-signature route name it
`app-pack`. The root mapping now lives once in `zipRootPackageFormat`
and both target resolution routes and root inventory use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:06:28 -07:00
Kaoru0822andClaude Opus 5.5 5b014a739e fix(adb): report unrecognized package rows from resolve-adb-packages (#5874)
resolve_adb_packages marked coverage partial when pm list packages printed
unrecognized rows, but dropped the rows themselves and could still emit a
limitation describing an unparsed_lines field the result never carried.

ADB parsers now own unrecognized-line facts as a bounded sample (first ten)
plus the exact total, and a single provider projection derives
unparsed_lines, coverage, and the truncation limitation from those facts for
every parsed listing and the package search. The resolver result schema now
carries unparsed_lines, matching list_adb_packages through CLI and MCP.

Fixes #5843

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:06:11 -07:00
Kaoru0822andClaude Opus 5.5 ce1f4b1bba fix(artifacts): report unknown inventory traversal in inspection coverage (#5867)
fix(artifacts): keep unknown inventory traversal out of complete inspection coverage

Artifact inspection derived its coverage status only from always-zero
omission counts, so an inventory substep with unknown container-entry
traversal (for example, an omitted FIFO) yielded
complete-within-substeps. Analysis-view summaries also exposed the
inventory's typed source_coverage only for PCK and PyInstaller roots.

Derive the inspection status from the inventory substep's coverage
(unknown traversal is partial), and carry the producer's source_coverage
plus the traversal-unknown caveat in every artifact summary. Hash-count
fields keep their existing format gating.

Fixes #5837

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:59:09 -07:00
Kaoru0822andClaude Opus 5.5 d124809950 fix(android): report a rejected relative JADX JAR instead of an unset one (#5862)
A supplied non-absolute REA_JADX_MCP_JAR was folded into the unset case,
emitting not_configured with jar_configured: false and dropping the
caller-selected value. The JAR parser now keeps the two outcomes distinct:
unset stays not_configured, while a supplied non-absolute value (including
an empty string) fails with open_options_invalid, jar_configured: true, the
exact rejected jar_path and the absolute-path requirement. CLI, MCP and
doctor derive their output from that one boundary result.

Fixes #5844

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:57:55 -07:00
Kaoru0822andClaude Opus 5.5 7f9a492a8d fix(signals): report unknown decoder help IDs as invalid input (#5858)
inspect_signal_decoders turned sigrok's explicit missing-decoder refusal
into execution_failure with rea doctor advice, while decode_signal_recording
already reported the same refusal as invalid_request on decoders.

The sigrok selector classifier now owns the unknown-ID match for both
selections: help names the decoder field and points to catalog discovery,
decoding keeps decoders/annotations. The provider passes each producer
command's caller selection to that one owner instead of per-call closures.
Installed decoders that fail to load and refusals naming other IDs remain
provider failures. The real-sigrok verifier now asserts the typed refusal
for both operations.

Fixes #5849

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:57:28 -07:00
Kaoru0822andClaude Opus 5.5 c2bee63e99 fix(evidence): clear staged write cleanup state after a confirmed retry (#5852)
The atomic JSON writer reported a failed staging-directory release as
cleanup state on the returned EvidenceFileError or AnalysisCancelledError,
but never bound that error to the retained staging lease. A later scope
retry that removed the stage could not clear the error, so
`rea evidence-export` (and other writers sharing the scope, such as
--evidence-output delivery) kept reporting cleanup_incomplete and a
retained resource after cleanup had completed.

Bind each staged-output cleanup failure, and each admission retry
failure, to its exact leases so confirmed release restores the primary
outcome. Export recovery now reads that bound state instead of a local
close flag. A retry that still fails keeps the error incomplete with the
same retained stage.

Fixes #5842

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:57:08 -07:00
gensokyo-dev 905cbaf239 perf(ci): use tsrs and reduce PE acceptance startup (#5795)
Use pinned tsrs on supported development hosts while retaining stable official
TypeScript on Windows and unsupported platforms. Partition compile/typecheck
caches by actual compiler and qualify fallback hosts for compiler inputs and
lifecycle hooks.

Reduce PE acceptance startup through the existing bounded compiled CLI. Retain
upstream named MCP definitions, every fixture/assertion and the original 30s
deadline. Verify that browser transport loss preserves the observed target.

Validation: full qualification and real host lanes in
https://github.com/morluto/rea/actions/runs/39590450889 ; 450 focused regressions
passed with two documented conditional skips. All 1724 production emit files
match SHA-256 and size across both compilers. Current-head Codex/Greptile review
found no actionable issues. Measurements and limitations are in the PR.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
2026-10-21 19:47:52 +08:00
gensokyo-dev 33f912919d perf(apple): reuse retained Interface Builder graph validation (#5885)
Reuse successful owning-schema validation only for exact immutable Evidence snapshots, preserve current parent binding and complete source facts, and collect only requested document pages.

Verify real XLD CLI/MCP workflows and archived connector UIDs, including complete pre-change baselines for both admission sizes and a wrong-baseline negative control.

Fixes #5855.
2026-10-21 19:44:05 +08:00
gensokyo-dev aa53c06538 feat(firmware): make analysis deadlines recoverable per call
Expose per-call firmware analysis budgets with exact recovery guidance, shared
CLI/MCP validation and retained actual settings. Keep publication and fixed
resource policy independent; preserve typed interruption and one captured-output
owner through command and one-shot cleanup.

Fixes #5851. Executed verification, real-provider limits, known baseline issues
and authorized readiness override are recorded in #5863.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-21 19:25:22 +08:00
Kaoru0822andClaude Opus 5.5 bcfcd8b6e2 fix(sqlite): attribute generated-column evaluation failures to the selection (#5879)
* fix(sqlite): attribute generated-column evaluation failures to the selection

A selected virtual generated column whose expression needs a function the
read-only connection lacks (or raises an SQL error for a sampled row) was
classified by the worker's catch-all as a malformed database and projected as
an invalid_format issue on `path`.

Row reads now attribute SQLITE_ERROR from selected cells by probing each
selected virtual generated column over the sampled rows, and report a
typed capability_unavailable refusal naming the table, each failing column
with SQLite's message, and the remaining columns to select. Other causes,
including corruption, TOOBIG and NOMEM, keep their classification.

Fixes #5719

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(sqlite): recommend only probed-readable columns after evaluation failures

Record every per-column probe failure with its own reason, discover stored
columns outside an inventory-scope selection without decoding unrelated
malformed names, and present recovery as the structured columns selection
instead of shell-quoted command text built from database-supplied names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:08:49 -07:00
gensokyo-dev efab044f30 feat(gpu): inspect static CUDA and HIP code objects (#5813) 2026-10-21 19:06:44 +08:00
647717c050 perf(javascript): reuse the source-analysis worker within a process (#5819)
* perf(javascript): reuse the source-analysis worker within a process

Each analyze-javascript-application call started a new Node worker.
That startup, not fixture IO or the shared CLI session, dominated the
slow loader-review and factory-completion files.

Park one healthy worker on the artifact resource scope and on the CLI
process. Stop it when the scope, session, or one-shot process ends.
Do not reuse after failure, cancellation, or a different heap or timeout.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(cli): close the JavaScript worker when dispatch throws

Minimal dispatcher runtimes now export a no-op closeCliJavaScriptResources,
and CLI dispatch plus output rendering always await that cleanup. A dispatch
failure and a cleanup failure are both reported.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(artifacts): report close-hook failures with retained cleanup

When retained cleanup and the scope close hook both fail, the close error
keeps both causes and both resource labels. The pending owners and the hook
stay registered so the next close retries each path.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): stop a replaced analysis worker before the next one starts

A heap or timeout change now closes the parked worker before constructing
its replacement. The scope retains that process only when the close fails.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* style: format worker-reuse close and launch helpers

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(cli): report a failed JavaScript worker shutdown before exit

Share one in-flight close and drop the CLI scope only after it succeeds,
so a later caller retries a worker that is still owned. The one-shot
exit callback writes that failure and exits non-zero, while a closed
stdout pipe still exits 141.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): keep a worker parked during an overlapping limit change

Claim the scope slot only when it is still empty after the replaced
worker closes. A concurrent analysis that parked another healthy worker
keeps that process, and the extra worker is closed through its own port.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(artifacts): run later close hooks when an earlier hook fails

Run every registered close hook on each close. One failure is returned
unchanged, and several failures are aggregated so a later hook is not
skipped.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): fail a limit change when the previous worker stays alive

A failed close used to be retained and then ignored, so MCP could return
a successful analysis while the old process and its replacement were
both owned. The request now fails before the replacement is constructed,
and the still-live worker stays owned for a later close.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 19:01:20 +08:00
morluto c76edbebbe test(pe): bind acceptance calls to advertised MCP definitions
Pass the exact named tool descriptors to SDK callTool for PE resource inspection and session inventory. Avoid full-catalog validator compilation while retaining SDK output validation, every CLI/MCP scenario, cleanup, and the unchanged 30-second deadline.

The complete focused test passed locally in 7.1 seconds versus 11.0 seconds before this change; host load and CPU/memory costs were not controlled or measured. check:ci passes.
2026-10-21 18:58:50 +08:00
morluto c324d31b45 test(environment): verify export failures through the real CLI
Remove shared-module mocks for input reads, heap statistics, and tarball streaming. Exercise actual file changes and real V8 heap admission in fresh CLI processes; keep only selected-descriptor syscall fault injection for the pathless EIO case.

Reproduced the four hosted source failures with a preloaded module graph and found the same flaw in the adjacent tarball test. The preloaded control now passes 17 cases; 32 focused tests, all 1,739 service tests (2 skips), and check:ci pass. Snapshot, cleanup, identity, and error projection coverage is retained.
2026-10-21 18:26:58 +08:00
Cursor Agentandmorluto 5eebe2fdf8 test(cli): treat omitted exactOptional inputs as optional
The advertised-schema check parsed undefined on each field.
exactOptional rejects that value and still accepts a missing key, so
inspect-wasm-artifact looked required for commandTimeoutMs.

Judge omission with an object parse that leaves the key out. The
advertised required list already matched that parse for every other
primary command.

Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 18:14:39 +08:00
Edgar ArakelyanandClaude Opus 5.5 ea3534759f feat(analysis-view): share templated positions across manifest tools (#5664)
* docs(specs): plan a template rule that shares templated positions

Record the outcome, requirements, non-goals, plan and tasks for
prefix-variance, an opt-in environment-manifest rule that templates a
literal segment with the parameter another tool already uses at that
position, so a session's writes to one entity share the read's template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(analysis-view): share templated positions across manifest tools

Add the opt-in prefix-variance template rule. It runs after the other
opt-in rules and templates a literal at a position that another tool,
of any method or segment count, templates by one of those rules after
the same preceding parts, when the literal is one of that parameter's
observed values or carries a digit and is spelled like one. The
parameter takes the shared parameter's name, tools that now match merge,
and a merged position keeps a tool's own rule when one templated it.

A write to the one entity a session edited, such as
PATCH /api/v1/collections/0RZ576QZQA5KG, becomes
patch_collections_by_collection_id on the read's template instead of a
literal single-entity tool.

Tool naming moves to its own module, and the three comparing passes
share the 32-segment split. Manifests that do not list the rule are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(analysis-view): drop the unused ManifestRule alias

Parameters now type their rule as a format rule, a shared opt-in rule
or prefix-variance, so nothing reads the union alias.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(specs): record the prefix-variance checks and corpus results

Record the gates, and the 50 showcase captures with and without the
rule: identical output without it, 25 write and 34 read tools templated
with it, and the write tools the write-plan branch serves on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(analysis-view): share positions with digit-free generated identifiers

prefix-variance accepted an unobserved literal only when it carried a
digit, so a generated identifier that happened to hold none stayed a
literal tool: an n8n workflow id such as FkMwxmXNGaGVsXFG, whose reads
then never joined the item view. A literal now also qualifies when every
value observed at the position has one length of eight or more characters
and the literal has that length and uses only their character classes.

Across the 50 showcase captures this templates 10 more read tools in
Memos and n8n and no words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(specs): record five environments rebuilt end to end with the rule

Five randomly drawn environments rebuilt on VMs with the rule and the
write-plan branch: smoke, retrieval and write tasks for two models, and
the same calls against the live applications. Record the results, the
misses and their causes, and the corpus numbers after the fixed-length
clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(analysis-view): share a position with the parameter that observed the literal

A shared position folded every donor's values into the earliest donor's
parameter. With an opaque-token read before sibling-variance reads at
the same position, a PATCH of /api/items/b2 qualified on the sibling's
evidence but became /api/items/{uid} instead of joining the item read's
/api/items/{item_id}. Each position now keeps one donor per naming rule
with its own values, and a literal takes the donor that observed it,
else the earliest one it is spelled like.

Each donor's spelling (length and character classes of its values, and
its fixed-length family) is summarised once per position, so a literal
is checked in time independent of how many values were observed, rather
than copying and rescanning them for every candidate.

A family now forms from one observed value, as the requirement says, so
a digit-free id of the same length shares a position that only one
opaque-token or after-uid-literal tool supplied. The 50 showcase
captures give the same tools as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(specs): record the prefix-variance review round

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(analysis-view): bound the spelling summary of shared positions

spellingSummary classified every observed value with three full-size
replaceAll copies and a set per distinct character, so a donor holding
a multi-megabyte segment cost unaccounted memory and time in finish().
Characters are now classified in one pass, and a value longer than 128
characters takes no part in spelling comparisons: it shares a position
only as an exactly observed value. The 50 showcase captures give the
same tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:11:51 +08:00
morluto 19f2969c56 fix(ci): align omission and browser transport assertions
Check property absence in an object instead of treating explicit undefined as omission, preserving Zod exactOptional semantics and WASM timeout bounds. Update real-browser disconnect verification to require transport_disconnected and reject an invented crash event.

Reproduced the CLI catalog failure on d9767eb48; 37 focused tests and check:ci pass. Real-browser verification runs on hosted CI; Chrome was not launched locally.
2026-10-21 18:07:21 +08:00
d9767eb48f test: assert Result reasons instead of bare ok checks (#5821)
Malformed DOS MZ, raw-binary entry, EVM limit, and Ghidra handshake
cases only checked Result.ok. Name the producer error, or the admitted
language and compiler, so a different failure cannot keep the test green.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 18:00:31 +08:00
999 changed files with 57826 additions and 7018 deletions

No files matched your search

@@ -167,7 +167,9 @@ explicit path or endpoint and do not need it.
ambiguity. No implicit fetch or target execution; WAT is not original source.
See the [WASM guide](https://github.com/morluto/rea/blob/main/docs/wasm-artifacts.md).
- Explicit local EVM bytecode carrier: `inspect_evm_interface` with caller-selected
`raw` or `hex` encoding. Preserve carrier/decoded digests and treat selectors,
`raw` or `hex` encoding. The owned worker deadline is `timeout_ms` (default
30000 ms, range 1..2147483647, next call). It does not raise the fixed byte,
address-space, or CPU allowances. Preserve carrier/decoded digests and treat selectors,
argument strings and mutability as inferred candidates; this performs no chain
lookup or target execution. For the complete inferred interface without hex or
duplicate stdout, discover support and select `detail: "summary"`; complete
@@ -181,6 +183,8 @@ explicit path or endpoint and do not need it.
absent metadata does not prove a binary is not Go, and unparsed text records
remain explicit. See the
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/go-binaries.md).
- Embedded CUDA/HIP code objects: `inspect_gpu_code_objects`; inventory targets
first, then decode selected payloads and query retained GPU Evidence.
- Bundled GGUF or safetensors model declarations: `inspect_model_artifact`
with the explicit local file. Default header hashing leaves full-file identity
unknown; `hash: "full"` additionally reads all weights for SHA-256. MCP returns
@@ -25,8 +25,9 @@ stdio. A full JDK, including `jdk.compiler`, is required on every host; Windows
`JAVA_HOME` selects `bin\java.exe`. Consult the Android guide for actual host
verification coverage. Calls use one worker and provider deadlines; optional
`REA_JADX_HEAP_MIB` selects the JVM heap and `REA_JADX_OPERATION_TIMEOUT_MS`
the per-operation deadline (default 120000 ms; the MCP client's tool timeout
must also allow it). The APK is never launched,
the per-operation deadline (integer milliseconds from 1 to 2147483647, default
120000; a change applies on the next process start; the MCP client's tool
timeout must also allow it). The APK is never launched,
and no emulator is required.
Manifest entries and decompiled text are static observations, not execution or
@@ -94,3 +94,9 @@ extract the data those reads observed instead: entity types and identity
fields, entities with recorded values, documents, and per-tool render plans
checked against the recordings. The state comes only from reads before the
first related write; types, identity and list membership are inferences.
`rea environment-serve manifest.json --evidence capture.json` derives that
state at startup, answers read tools from it and serves `data/reset`,
`data/add` and `data/get` on `/agentenv`; write tools are not served. It
replays the capture instead with `--replay-only` or when the state is
unavailable (not derivable, or a catalog over the discovery budget), and its
stopped document's `state` says which applied.
@@ -22,6 +22,11 @@ requires complete application analysis; `source_paths: []` is invalid, not an
unfiltered request. `detail: "summary"` reduces delivery
and does not by itself reduce analysis work.
ASAR header/index admission is fixed at 16 MiB / 16777216 bytes for this
release and has no runtime override. `source_paths`, `detail`, and
`max_heap_mb` do not bypass or raise it. Analyze an extracted directory, or
supply an archive whose header fits.
Exact `#alias` specifiers resolve through the importer's
enclosing package `imports` map to package-local files; nested `node_modules`
packages use their own map. Wildcard, recursive, and external-package import
@@ -345,8 +345,8 @@ every executable in the bundle. It also lists missing or weak loads and earlier
`architecture`. System paths stay undetermined because the dyld shared cache
provides them.
Use `extract_artifact` when materialized files are needed. It extracts the active
target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md).
Use `extract_artifact` when materialized files from a supported format are needed.
It extracts the active target and accepts optional `integrity_policy`; see the [MCP contracts](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md).
It materializes all regular files into a fresh temporary directory chosen by
REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
@@ -355,8 +355,11 @@ destination. No separate permission grant is needed.
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
claim child inventory when only root identity is available. Extraction is a
separate filesystem-writing operation.
claim child inventory when only root identity is available. DMG extraction is
unavailable through `extract_artifact`. To access DMG files on macOS, create a
read-only mount with your own tools, select files from that mount for analysis,
and detach the mount you created when finished. REA's inspection mount is
temporary, not a retained export of the image's contents.
For headerless DOS COM, explicitly open with `format: "dos-com"` (CLI
`--target-format dos-com`). Inspect the load image before trusting function
+9
View File
@@ -819,6 +819,11 @@ jobs:
with:
save: "true"
- run: npm ci
- name: Typecheck with the official Windows compiler
env:
GOMAXPROCS: "2"
GOMEMLIMIT: "3GiB"
run: npm run typecheck -- --checkers 2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: rea-build-cache
@@ -849,7 +854,11 @@ jobs:
npx vitest run --coverage.enabled=false
src/process/capture/FilesystemSnapshot.test.ts
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
tests/boundary/filesystem/typescriptCompiler.test.ts
tests/boundary/filesystem/binaryTarget.test.ts
src/contracts/sessionPathInputs.test.ts
tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
tests/boundary/mcp/rawBinaryMcp.test.ts
tests/boundary/filesystem/safeOutputTree.test.ts
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
+7
View File
@@ -90,6 +90,12 @@ Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
MCP uses compact input schemas by default to reduce context overhead. Set
`REA_MCP_INPUT_SCHEMA_PROFILE=full` in your MCP registration when you need the
complete advertised field guidance and nested validation details. Both profiles
keep the full tool set and canonical server validation. See
"Compact input schema profile" in the MCP contracts guide.
### Ask your agent
```text
@@ -180,6 +186,7 @@ Additional tools and host support depend on the target:
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](docs/native-investigation.md) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
| GPU code objects | CUDA/HIP targets, selected kernel parameters and resources, retained PTX text | Built-in static ELF/PE/fatbin/CUBIN/PTX inspection; [GPU code objects](docs/gpu-code-objects.md) |
| Model artifacts | GGUF / safetensors metadata, tensor declarations and derived totals | Built-in header inspection; optional full-file hash; [model files](docs/model-artifacts.md) |
| IL2CPP metadata | Unity global-metadata.dat assembly images, type and method names and index ranges | Built-in static decoding of versions 24, 27, 29, 31 and 39; no Unity required |
| Godot PCK | Archive inventory and extraction, original header/directory facts and selected content identities | Built-in static formats 2, 3 and 4; no Godot required |
+11 -6
View File
@@ -162,8 +162,9 @@ and during shutdown.
APKs keep other names report the `unknown` role rather than guessing.
- **Partial results keep facts.** Failed individual pulls return successful
artifacts with `coverage: "partial"` and per-file `failures`. Listings
that contain unrecognized lines report them as `unparsed_lines` and mark
partial coverage instead of dropping rows.
and package searches that contain unrecognized lines report the first ten
as `unparsed_lines`, state the exact total in a limitation when more were
seen, and mark partial coverage instead of dropping rows.
- **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and
`uninstall_adb_package` declare `mutatesTarget` and never run implicitly:
pushes check for existing device files before transfer and refuse them
@@ -171,7 +172,9 @@ and during shutdown.
transfer even with `overwrite`; only a clean `test -e` exit of 1 with no
output is interpreted as absence. The check is not atomic, so a different
process can create the path before `adb push` writes it. When available, the device's
`sha256sum` verifies the transfer; installs preserve the
`sha256sum` verifies the transfer. A missing command or output without a
recognizable digest reports `device_digest_source: "unavailable"` with an
unverified-transfer limitation; installs preserve the
device's `Failure [...]` reason verbatim.
- **Producer identity stays attached.** Every result embeds the producing
adb client (`binary_path`, `path_source`, `version`, `installed_path`).
@@ -183,9 +186,11 @@ and during shutdown.
The app/activity lifecycle tools use `am start -W` and `am force-stop`.
- Per-invocation budgets: client/devices 10-20 s, shell reads 20-60 s with
64 KiB-8 MiB capture caps (logcat 4 MiB, screen 32 MiB), transfers and
bugreports 600 s. Output capture overflow reports `resource_constraint`;
deadline expiration, including the client-version probe, reports
`provider_timeout`. Command cancellation preserves the caller's exact reason;
bugreports 600 s. Output capture overflow reports `resource_constraint`
and names the byte allowance. Deadline expiration, including the
client-version probe, reports `provider_timeout` and names the effective
milliseconds. This release has no runtime override for those deadlines or
capture caps. Command cancellation preserves the caller's exact reason;
a later abort does not replace an already completed command failure.
- Pull and capture outputs are additive: the destination must not already
exist. Re-running a completed pull refuses the existing directory instead
+325 -51
View File
@@ -1,11 +1,15 @@
# Agent environments from recorded captures
Use `rea environment-serve` to serve an environment manifest as an agent
environment: an MCP server whose tools answer from the records of one retained
web network capture, plus the environment card the agentenv framework reads to
discover it. Nothing is fetched from the recorded application; every answer is
retained Evidence. `rea environment-export` writes the same pair as a bundle
folder `agent-env run` builds and runs; see [Export a bundle](#export-a-bundle).
environment: an MCP server whose tools answer from what one retained web
network capture observed, plus the environment card the agentenv framework
reads to discover it. By default the server derives the
[state the capture observed](#the-state-a-capture-observed) at startup and
answers read tools from it; `--replay-only` replays the capture's records
instead. Nothing is fetched from the recorded application; every answer comes
from retained Evidence. `rea environment-export` writes the same pair as a
bundle folder `agent-env run` builds and runs; see
[Export a bundle](#export-a-bundle).
```sh
rea inspect-web-network-capture /absolute/session.har har --format json > capture.json
@@ -27,9 +31,10 @@ it, where `view.json` wraps the capture Evidence as the inline source:
}
```
The third command serves that manifest. Both served files are complete REA
Evidence JSON as the commands write it with `--format json`; the `evidence`
member of `view.json` is the whole of `capture.json`.
The third command serves that manifest from the state it derives from the
capture; see [Serve from the state](#serve-from-the-state). Both served files
are complete REA Evidence JSON as the commands write it with `--format json`;
the `evidence` member of `view.json` is the whole of `capture.json`.
## From a browser scenario
@@ -110,7 +115,9 @@ record's ordinal is its zero-based position; a transaction whose URL carried a
redacted secret is left unrepresented rather than templated, and one the
capture cannot attribute to a single request and response is left
unrepresented too. Tools are chosen by response media type, so a scenario that
selected neither response bodies nor header values is refused. The browser
selected neither response bodies nor header values is refused; one that
selected header values but no response bodies yields a manifest but no state,
so it is served by replay. The browser
scenario redacts credential headers and the secrets it declares; values the
application returns in response bodies, such as session tokens, are kept, and
the replayed body is the bytes the browser exposes for each response, with any
@@ -144,10 +151,16 @@ This was calibrated with 1500 query fields and eight concurrent SDK discovery
requests (183,798-byte card, 507.27 MiB sampled process-family RSS). Over-capacity
catalogs return `resource_constraint`, retain complete input Evidence, and require
a smaller capture or manifest. Fields and tools are never silently omitted.
Served from the state, each description is longer and the card declares the
data plane, so a catalog replay admits can exceed the reservation; the state
is then unavailable and the server serves replay instead (see
[Serve from the state](#serve-from-the-state)).
## What replay means and does not mean
Each MCP tool call selects one of the records its manifest tool cites and
Replay answers every tool with `--replay-only` or when the state is unavailable,
and, served from the state, each read tool whose view the state does not back.
Each such MCP tool call selects one of the records its manifest tool cites and
returns that record's recorded response. Selection compares the supplied path
parameters against the record's path segments at the parameter indexes, then
the supplied query values against the record's query string, and keeps the
@@ -202,7 +215,8 @@ degrades into an omitted one.
Replay is not emulation. A served environment does not establish the
application's authorization model, response semantics or state transitions,
which the manifest lists as unknowns; it does not validate supplied values; and
it does not answer requests the capture never recorded. Replayed content is
it answers only from the capture's recordings and the state they observed or a
caller loaded through `data/add`. Replayed content is
target content: every tool result, including the argument errors the MCP server
returns before a replay runs, carries `_meta.provenance.untrusted_content: true`
with `source: "analysis_target"`. The server advertises REA's untrusted-content
@@ -220,17 +234,23 @@ moment of the capture recorded them. The
[environment state](web-network-captures.md#environment-state) view extracts
instead the data the capture's reads observed before the first related write:
entity types with an identity field, entities with their recorded values,
documents for written paths, and a render plan per read tool, kept only where
it reproduces that tool's recordings from the state. It is the starting state
an environment can hold, load and report; serving from it is a later increment
and does not change replay.
documents for reads that are not entities, and a render plan per read tool,
kept only where it reproduces that tool's recordings from the state. It is the
starting state an environment holds, loads and reports: `environment-serve`
derives it from the capture at startup; see
[Serve from the state](#serve-from-the-state). With `--replay-only`, replay is
unchanged.
## Serve a manifest
```sh
rea environment-serve manifest.json --evidence capture.json [--host 127.0.0.1] [--port 0]
rea environment-serve manifest.json --evidence capture.json [--replay-only] [--host 127.0.0.1] [--port 0]
```
The server derives the state from the capture before it binds and serves from
it, or serves replay with `--replay-only` or when the state is unavailable;
see [Serve from the state](#serve-from-the-state).
`--host` and `--port` default to `MCP_HOST` and `MCP_PORT` from the
environment, then to `127.0.0.1` and an ephemeral port, exactly as
[`rea mcp --http`](cli.md#serve-mcp-over-streamable-http) resolves them. The
@@ -242,7 +262,8 @@ manifest's `name` is used, and a set but blank value is refused as
listener serves one MCP client at a time with the same session, `Host` header
and shutdown contract as [transports](mcp-contracts.md#transports) describes.
It runs until SIGINT or SIGTERM, then closes the listener, prints one
`{"status":"stopped", …}` document and exits `130` or `143`.
`{"status":"stopped", …}` document whose `state` member says whether it served
from the state, and exits `130` or `143`.
Signals during input loading or DNS/bind cancel the owning reader/listener before
a listening announcement;
that path returns a structured `cancelled` result instead of a stopped-listener
@@ -250,19 +271,19 @@ document, with the same signal exit status.
Three routes are served:
| Route | Answer |
| --------------------------------- | ---------------------------------------------------------------------- |
| `POST /mcp` | The MCP server: one tool per manifest tool, replaying retained records |
| `GET /.well-known/agent-env.json` | The environment card |
| `POST /agentenv` | JSON-RPC `-32601 method not found: <method>` for every method |
| Route | Answer |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /mcp` | The MCP server: from the state, one tool per read tool, answering from the state where it backs them; in replay, one tool per manifest tool, replaying records |
| `GET /.well-known/agent-env.json` | The environment card |
| `POST /agentenv` | From the state, `data/reset`, `data/add` and `data/get`; in replay, JSON-RPC `-32601 method not found: <method>` for every method |
Numeric JSON-RPC IDs outside JavaScript's safe range or whose precision would
be lost are refused as invalid requests with a null ID. They are never echoed
as a different number.
The data plane at `/agentenv` is advertised by the card but carries no
operations in this release; the error names the requested method so a caller
can see that the method, not the transport, is unsupported. Its request body is
In replay the data plane at `/agentenv` is advertised by the card but carries
no operations; the error names the requested method so a caller can see that
the method, not the transport, is unsupported. Its request body is
read within the same 4 MiB bound the MCP endpoint applies: a larger body is
answered `413` with a JSON-RPC `-32000` error before any of it is parsed, and a
body that is not JSON is answered `400` with `-32700`. A notification (no `id`)
@@ -274,7 +295,7 @@ that are not an object or array) gets `-32600 invalid request`.
## The environment card
The card is the discovery document the agentenv framework reads before
connecting to `/mcp`:
connecting to `/mcp`. A replay environment's card reads:
```json
{
@@ -288,7 +309,7 @@ connecting to `/mcp`:
"tools": [
{
"name": "get_users_by_id",
"description": "GET /api/users/{numeric}; replays retained records [5, 6]; evidence ev_…",
"description": "Untrusted captured API catalog: … GET /api/users/{numeric}; returns a JSON object with fields \"id\", \"uid\", \"login\", \"email\"; recorded statuses 200; replays retained records [5, 6]; evidence ev_…",
"inputSchema": {
"type": "object",
"properties": {
@@ -309,7 +330,38 @@ connecting to `/mcp`:
```
`capabilities.tools` lists the same names, descriptions and input schemas that
`tools/list` advertises over MCP. Path parameters are required strings whose
`tools/list` advertises over MCP. After the catalog warning, each description
names the method and path template, then what the tool returns, one level deep:
`returns a JSON object with fields …`, `returns a JSON array of objects with
fields …` or `returns a JSON object whose <member> lists objects with fields …`,
where a list is an array of objects found by the
[environment state](web-network-captures.md#environment-state)'s list rule (the
root array, or the one array of objects an envelope carries). Field and member
names are written as JSON strings, so a captured name cannot read as more of
the description; a member name too long to show whole is a quoted prefix marked
as truncated. The summary comes
from the first cited `2xx` record that replay answers as JSON; only when none
did, the first cited JSON record of another status gives it, labelled with that
status (`status 401 returns …`). It describes one recorded response, not a
contract. Field names are sampled within the same budget as other captured
values, and the manifest's `statuses` for the tool follow; a tool with no JSON
record states only its statuses. The description then lists the replayed
records and the evidence ID, and the whole catalog stays within its
serialization reservation, which the summaries and MCP annotations are charged
to.
Over MCP, every tool also carries annotations: `readOnlyHint` is true for the
safe methods (`GET`, `HEAD`, `OPTIONS` and `TRACE`) and for query `POST`s (a
path segment such as `search` or `query`); `destructiveHint` is true for `DELETE` and left unset for other
writes, so the MCP default (destructive) applies to them, and read-only tools
omit it because clients ignore it there; `idempotentHint` is true for the safe
methods, `PUT` and `DELETE`; and `openWorldHint` is false for every tool,
because answers come from the capture rather than the captured application. The
hints describe the captured method; replay itself changes nothing. The card
keeps the `name`, `description` and `inputSchema` of the echo example's tool
shape and carries no annotations.
Path parameters are required strings whose
description names the segment index, the templating rule and the observed
values (sampled within the list budget above; the manifest keeps them all);
query parameter names are optional strings; write methods accept an
@@ -320,9 +372,207 @@ advertised as `query_<name>`, a query name longer than 64 characters as
and the write body as `request_body` when a query parameter is named `body`. Every
schema closes with `additionalProperties: false`, a key the echo example's
schema does not carry.
`extensions`, `operations` and `children_environments` are fixed to `null`,
`[]` and `null`: a replay environment declares no extensions, no data-plane
operations and no children.
In replay, `extensions`, `operations` and `children_environments` are fixed to
`null`, `[]` and `null`: a replay environment declares no extensions, no
data-plane operations and no children. Served
[from the state](#serve-from-the-state), the card declares the three data-plane
operations and one intake extension instead, leaves write tools out and
describes each tool as answering from the state where the state backs it.
## Serve from the state
```sh
rea environment-serve manifest.json --evidence capture.json
```
Before it binds, the server derives the
[environment state](web-network-captures.md#environment-state) the capture
observed: it runs the `environment-state` view on the capture in-process with
the manifest's `template_rules` in the same order, exactly as
`inspect-analysis-view` projects it from the same capture, so the state
Evidence id it reports is the one that projection produces. The view's own
budget and refusals apply, and the result is checked like the view's Evidence
for this manifest: extracted from the served capture, with the manifest's
rules, naming only manifest tools. The server keeps one live state, shared by
every MCP session and the data plane, starting from the derived state.
A state that is unavailable does not stop the server: it logs a warning naming
the error `code` and message before it binds, then serves replay. The state is
unavailable when it cannot be derived, such as from a
`capture_browser_scenario` capture that selected header values but no response
bodies or over the view's budget, and when its catalog under the served name
exceeds the [discovery reservation](#what-a-manifest-is). That check runs
first, so no state is derived for it, and replay is then admitted exactly as
with `--replay-only`. `--replay-only`
serves replay without deriving anything; the card, tools and `/agentenv`
answers are then exactly those of replay. The stopped document's `state`
member reports which applied:
- `{"served": true, "evidence_id": "ev_…"}`: served from the derived state.
- `{"served": false, "reason": "replay-only"}`: `--replay-only` was given.
- `{"served": false, "reason": "unavailable", "code": "unsupported_target", "message": "…"}`:
the state is unavailable; `code` and `message` are those of the refusal
(`resource_constraint` for a catalog over the reservation), as the warning
logged them.
Each read tool answers according to its view in the state:
| View | Answer |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `item` | The entity the path arguments name, rendered into the recorded envelope of `envelope_ordinal`. Arguments match as replay matches them: each collection segment equals the held collection's segment as written or after percent-decoding, and the identity is the last argument percent-decoded, then as written; values are never re-split on `/`. Any entity the state holds answers, including one `data/add` loaded; query arguments only decide `replay.exact_match`. An entity the state does not hold, or one lacking a field every recording carried, is a tool error; a body is never invented. |
| `list` | When the arguments name one of the lines the view checked exactly (its latest recordings before the first related write; every path and query value equal to the recording's, and every recorded query parameter supplied), that recorded envelope with its array rendered from the state: every entity of the collection in state order for `membership: "all"`, or the recorded members still held. A member lacking a field every recording carried makes the call a tool error rather than a partial list. |
| `document` | When the arguments name the document's checked line exactly, the state's value for it; a tool error when the state holds none. |
| `recorded` | The recording, as replay answers it. |
A list or document call that names no checked line, including a line recorded
after a write or with other query values, answers from the recording. Every
answer has the replay shape plus `state`: `{"source": "state", "view": …}`
naming the entity, collection or document path, or `{"source": "recording",
"reason": …}` with the view's reason, `no-view` or `unrecorded-request`. A
state answer carries the base record's status, content type and `replay`
provenance with `truncated: false`; one whose JSON would exceed 256 KiB is
refused as a `resource_constraint` tool error rather than cut. Extraction does
not state-back a document whose rendered JSON exceeds 256 KiB; that read stays
recorded (`rendered-oversize`) and replays the captured body. Serving is
read-only: `POST`, `PUT`, `PATCH` and `DELETE` tools, other than query `POST`s,
are not served, so nothing a call does changes the state. Query `POST`s stay
served and answer from their recordings with the reason `query-body`.
The data plane serves three JSON-RPC methods:
| Method | Result |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| `data/reset` | `{}` after restoring the state the Evidence holds |
| `data/add` | `{}` after replacing the whole state with one valid `rea-state/v1` document |
| `data/get` | `{"parts": [{"kind": "data", "data": <rea-state/v1 document>}]}` with `state_evidence_id` and the `types` |
Calls run one at a time in arrival order across requests. Every message of a
batch is queued when the batch arrives, so another request's call never lands
between them and each message sees the changes of those before it; a reset
sent while a download is loading applies after that load. `data/add`
takes exactly one part: a `data` part whose `data` is the document, or a
`file` part whose `file` carries `bytes` (base64 of UTF-8 JSON) or a `uri`,
either a `file://` URL of a local regular file, whose host is empty or
`localhost`, or an `https://` URL fetched without following redirects and
within 30 seconds, including reading the body; `mimeType`, when given, is
`application/json`. A `file://` URL naming another host, or a UNC path such as
`file:////server/share/state.json`, is refused before anything is opened, so a
load never reaches a network share. A loaded document is at most 32
MiB, and so is the state as `data/get` writes it back with the members a
document may omit (`types`, `state_evidence_id`, record citations), so a
reported state always loads again; an inline part also stays within the 4 MiB
request body bound, so larger states arrive by `uri`. Anything else, including `{"parts": []}`, is `-32602
invalid params`; a download that fails or answers a non-2xx status is
`-32000`. Errors never echo the uri or the content; a file or download failure
names its category in `error.data.reason`: `not-found`, `permission`,
`not-regular-file`, `changed` or `read-failed` for files, `timeout`,
`http-status` or `network` (connection, TLS or a refused redirect) for
downloads, and `cancelled` for a download the server's stop or its caller's
departure ended.
Stopping the server cancels every pending call, and a caller that leaves before
its reply cancels its own: a download in progress ends, and calls not yet run
are answered `-32000` with `error.data.reason` `cancelled` without running. A
cancelled call leaves the state unchanged, and shutdown never waits for a
download.
A `file://` state file whose close fails stays owned by the server, and
the `data/add` gets `-32000`. The next `file://` load retries that close first
and, while it still fails, answers `-32000` without reading another file;
inline and `https://` documents still load. Closing the server retries it once
more and fails with the unfinished close rather than dropping the descriptor.
One `/agentenv` response carries at most 64 MiB of replies, twice the 32 MiB
a loaded document may hold: room for a `data/get` of a loaded state, while a
batch of reads cannot multiply a large state into one body. Replies are
measured in order as they are produced, in bounded parts and before they are
assembled, and room is kept for every later reply's budget error and the
batch's brackets and commas, so one that would take the response past the
budget is never built whole and is answered instead with `-32000` whose `error.data` gives
`max_response_bytes` and the `reply_bytes` it would have taken. That call still
ran, so repeat such a read in another request. A reply no larger than that
error, such as a write's `{}`, is always kept.
```json
{
"rea_state": {
"version": 1,
"entities": [
{
"type": "/api/users",
"collection": "/api/users",
"id": "2",
"fields": { "id": 2, "login": "probe" }
}
],
"documents": []
}
}
```
Each entity's `type` is a state type's `collection_template`, its `collection`
matches that template segment by segment (a template parameter matches any one
segment), its identity field's value is its `id`, and its type, collection and
id are unique; `view_fields` is optional and names item or list views of its
type. Views render their values over the entity's fields, so a view's values
must not set the type's identity field to a value whose text differs from the
entity's `id`. Spell a collection as requests spell it, as `data/get` reports it: list
calls find entities under the checked line's recorded path. Each document
names a `document` view once, at its initial document's `path`. The `version`,
and the `state_evidence_id` and `types` that `data/get` reports, must match
the served state, so a `data/get` result loads back unchanged. A number without
an exact native value, or whose serialized spelling would not be read as exact
(an integer from 2^53 below 1e21, such as `1e20`, in any spelling), or a
`__proto__` member is refused rather than altered.
An invalid document is `-32602` naming the first problem by JSON pointer, as in
`/rea_state/entities/3/collection` (also in `error.data.path`); a problem
inside caller data (`fields`, `value`, `types`) is located at that member, so
the pointer never names the caller's keys. A refused document changes nothing;
a valid one replaces the whole state at once. The order of `entities` is the
state order lists use.
Record citations stay Evidence provenance. An entity's `record_ordinals` may
be omitted or empty and a document's `record_ordinal` omitted; otherwise they
must be the served Evidence's citations for that entity or document, or the
document is refused. After loading, an entity or document whose values equal the Evidence's cites
the Evidence's records, and one whose values were edited or added cites none
(`record_ordinals: []`, no `record_ordinal`), so `data/get` never presents a
loaded value as recorded.
The card declares the operations and what `data/add` accepts:
```json
{
"extensions": [
{
"uri": "urn:agentenv:intake/v1",
"description": "data/add replaces the whole environment state with one rea-state/v1 document; data/get returns the current state in the same format.",
"params": {
"add": [
{ "part": "data", "format": "rea-state/v1", "load": "replace" },
{
"part": "file",
"format": "rea-state/v1",
"mimeTypes": ["application/json"],
"load": "replace"
}
],
"get": [{ "part": "data", "format": "rea-state/v1" }]
}
}
],
"operations": ["data/reset", "data/add", "data/get"]
}
```
The state is a projection of recorded reads. List and document calls answer
from the state only for the lines their views checked, and every recorded view
answers from recordings; item calls answer for any held entity but render only
the envelope one recording carried. Values copied outside entity objects,
derived values and counters other than those that count a list stay as
recorded; no write has an effect. An exported bundle carries no state file:
its server derives the state at startup, as the export does to predict what it
serves.
## Export a bundle
@@ -336,12 +586,12 @@ agent-env run ./grafana-example
```text
grafana-example/
README.md provenance, replay limits, run command, rights note
README.md provenance, what it serves, run command, rights note
envs/grafana-example/Dockerfile pinned image: node:24.18.0-bookworm-slim + rea-agents@<this version>
envs/grafana-example/env.toml environment_name = "grafana-example"
envs/grafana-example/manifest.json byte-exact copy of manifest.json
envs/grafana-example/evidence.json byte-exact copy of capture.json
artifacts/check_grafana-example/verify.py the replay verifier
artifacts/check_grafana-example/verify.py the verifier
tasks/smoke.json deploy_env (local sandbox), then env_outcome_verifier
```
@@ -352,8 +602,12 @@ pair as `environment-serve` does, so a manifest projected from another capture
is refused with the same `invalid_request` before anything is written, and the
bytes it copies are the bytes it authenticated. The bundle directory must be
absent or empty; an occupied directory is refused and nothing in it changes.
The complete tool catalog must also fit the server's advertised catalog budget
before any files are written. Privately parsed JSON is sealed once so authentication
After checking the bundle name, the export prepares the state from the capture
exactly as the bundle's server will at startup under that name, to predict what
the bundle serves; an unavailable state, including one whose catalog exceeds
the discovery reservation, does not stop the export, and the bundle then
serves replay. The complete tool catalog the bundle serves must also fit the
server's advertised catalog budget before any files are written. Privately parsed JSON is sealed once so authentication
and replay share immutable response trees rather than copying the capture repeatedly.
`--name` replaces the manifest's portable kebab-case name as the env folder and env id
(Windows device names such as `con` and `lpt1` are refused);
@@ -361,7 +615,9 @@ and replay share immutable response trees rather than copying the capture repeat
folder and makes the Dockerfile install it instead of the registry package and
remove it in the same layer, which is how a checkout rather than a release is
run. The command prints the
written files with their sizes and the pinned package source.
written files with their sizes, the pinned package source, the number of tools
the bundle serves as `tool_count`, and in `state` whether it serves from the
state, in the shape of the serve command's stopped document.
Export uses the shared output-tree owner to check directory identities around
exclusive creates, verify written bytes, and reject unowned entries before
@@ -383,21 +639,31 @@ user, so a private host umask cannot leave them unreadable, runs as that user an
name from Python sources the image does not have.
`verify.py` is the framework's `async def verify(mcp_url)` shape over its
Python MCP client: it lists the tools and checks the count equals the
manifest's, calls the first `GET` tool without path parameters with no
arguments and checks the call did not fail and its replayed `status` equals the
recorded status of that tool's first cited record (an unknown recorded status
stays unknown; it is not borrowed from another record), and returns one
`replay` result whose description
reports both checks. A manifest with no such tool yields a verifier that checks
the count only and says so. `tasks/smoke.json` deploys the env on the local
Python MCP client: it lists the tools and checks the count equals the number
the bundle serves (the manifest's read tools from the state, every manifest
tool in replay), calls one tool with no arguments and checks the call did not
fail and its `status` is the expected one, and returns one `serve` result
whose description reports both checks. The tool is the first served `GET` tool
without path parameters whose answer to no arguments is not an error, and the
expected status is the status of that answer: the export calls the same answer
function the server uses, from the derived state or by replay, so a state
answer carries its base record's status and a replayed one its record's (an
unknown recorded status stays unknown; it is not borrowed from another
record). A bundle with no such tool yields a verifier that checks the count
only and says so. `tasks/smoke.json` deploys the env on the local
sandbox and runs that verifier against the env's MCP endpoint; the first run
also builds the framework's own infrastructure, and the run tears everything
down when it ends.
The README written into the bundle records the capture artifact's SHA-256, the
capture and manifest Evidence ids, the manifest view digest, the tool count and
the package source, and states that the recorded content belongs to the
capture and manifest Evidence ids, the manifest view digest, the served tool
count, the derived state's Evidence id or that no state is served, and the
package source. It says what the environment serves: read tools from the state
the capture observed, the `/agentenv` data plane on it and no write tools yet,
replay when the state is unavailable, and `--replay-only` for pure replay; it
says which applies to this bundle, with the error code and message when the
state is unavailable, and that neither is emulation and served
content is untrusted data. It states that the recorded content belongs to the
captured application and must not be published without the rights to it.
## Compatibility verification
@@ -414,13 +680,21 @@ framework versions.
## Resource and lifecycle boundaries
Both Evidence files are read once at startup through the bounded JSON input
The Evidence files are read once at startup through the bounded JSON input
reader and authenticated as Evidence before the listener binds; a file that is
not Evidence, a manifest of another view kind, a capture of another format, or
a manifest projected from a different capture is refused before any port is
opened. Because authentication covers the capture's records, a manifest whose
opened. The state is derived once, in-process and before binding, from the
already authenticated capture: it is projected as retained Evidence, so the
capture's strings are not charged again as inline view input, and the view's
96 MiB budget bounds the derivation. Because authentication covers the capture's records, a manifest whose
parent check passes cites only records the capture retains. The server keeps
only the cited records' request lines and response text in memory. No outbound
network request is made, no process is launched and nothing is written to
disk. A listener that fails after binding is logged and ends the command with
only the cited records' request lines and response text in memory, plus the
live state when it serves from one: the derived initial state, or one
validated document whose JSON was at most 32 MiB. Data-plane calls run one at a time, so
at most one loaded document is being read and parsed at once; `data/get`
serializes the held state whole. No process is launched and nothing is
written to disk. No outbound network request is made unless a `data/add` call names an
`https://` uri, which is fetched once within the limits above; a `file://` uri
is read once. A listener that fails after binding is logged and ends the command with
a `resource_constraint` error and exit `1` instead of serving nothing.
+6 -3
View File
@@ -335,9 +335,12 @@ export REA_JADX_OPERATION_TIMEOUT_MS=600000
```
The value must be a decimal millisecond count from 1 to 2147483647; anything
else is refused before Java starts. The same deadline bounds each upstream
call, a timeout reports the selected value, and a passing JADX configuration
check in `rea doctor` reports the effective `operation_timeout_ms`. Explicit
else is refused before Java starts. REA reads it from the environment captured
at process start. A change applies on the next process start; an already
running server keeps the deadline it started with. The same deadline bounds
each upstream call. A timeout names `REA_JADX_OPERATION_TIMEOUT_MS`, the
effective milliseconds, and that range. A passing JADX configuration check in
`rea doctor` reports the effective `operation_timeout_ms`. Explicit
cancellation still ends the operation immediately, and partial observations
and owned-process cleanup are unchanged. Raising the deadline does not extend
the bridge's 30-second startup handshake or the upstream **90-second**
+9 -3
View File
@@ -65,6 +65,7 @@ rea decode-android-resources /targets/Example.apk --locale de
rea decode-android-resources /targets/Example.apk --locales de --locales pt-BR
rea decode-android-resources /targets/Example.apk --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
```
Equivalent MCP methods are `inspect_apktool_client` and
@@ -133,9 +134,14 @@ still suppresses all string projection, including in this mode.
## Resource and lifecycle limits
- The decode runs with a 600 s deadline and a 4 MiB output capture budget;
a stopped decode reports `provider_timeout` and exceeded budgets report
`resource_constraint`.
- The decode wall clock is `decode_timeout_ms` (CLI `--decode-timeout-ms`).
Omitted means 600000 ms. The integer range is 1 to 2147483647. A change
applies on the next decode. It does not raise the 4194304-byte (4 MiB)
output capture budget. That capture budget has no runtime override. A
stopped decode reports `provider_timeout` and names the effective
milliseconds; an exceeded capture budget reports `resource_constraint`.
- 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.
+50 -30
View File
@@ -18,7 +18,7 @@ Inspect native SAZ or HAR 1.2 offline with REA, or connect Everywhere's separate
- `inspect_web_page` / `rea inspect-web-page` captures DOM structure, accessibility nodes, scripts, resources, safe response/DOM metadata, attach-window network and console metadata, WebSocket frame sizes, workers, quota, and optionally selected storage key names, redacted storage-content fingerprints, or script sources.
- `analyze_web_bundle` / `rea analyze-web-bundle` parses selected script artifacts without execution and derives chunk edges, route and endpoint candidates, vendor fingerprints, static WebMCP declarations, and optionally source-map/original-source evidence.
- `observe_web_session` / `rea observe-web-session` arms an observation window of the requested duration for an external user action and records ordered reload, SPA navigation, redirect, failure, lifecycle, and target-termination metadata.
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. Declared `readOnly`, `untrustedContent`, `consequential`, and `autosubmit` annotation hints are reported per registration: `true`/`false` are declared values, `null` means the declaration did not provide the hint in that capture, and a missing `consequential` key means the hint was not recorded, either in a historical capture or because the observed annotation was invalid. Consult discovery completeness and exclusions to distinguish these cases. Malformed annotation values retain the registration with incomplete coverage and an `invalid_protocol_value` exclusion. Other malformed protocol payloads can fail discovery explicitly. REA never exposes `WebMCP.invokeTool`.
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. Declared `readOnly`, `untrustedContent`, `consequential`, and `autosubmit` annotation hints are reported per registration: `true`/`false` are declared values, `null` means the declaration did not provide the hint in that capture, and a missing `consequential` key means the hint was not recorded, either in a historical capture or because the observed annotation was invalid. Consult discovery completeness and exclusions to distinguish these cases. Malformed annotation values or registration stacks retain the registration with incomplete coverage and an `invalid_protocol_value` exclusion. Other malformed protocol payloads can fail discovery explicitly. REA never exposes `WebMCP.invokeTool`.
- `compare_web_captures` / `rea compare-web-captures` compares passive captures or exact-step-aligned browser scenarios. Scenario results commit reproducible literal normalization and expose alignment failures plus artifact-level action, screenshot, DOM, accessibility, URL, history, storage, and event differences. Missing or truncated evidence is never treated as equivalence.
@@ -125,14 +125,11 @@ bundle, for example
`"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"`. Fedora installs
Chromium as `chromium-browser`.
REA needs the browser's HTTP discovery endpoint on a loopback port. A browser
driven over `--remote-debugging-pipe` or a private channel, for example by an
automation SDK, exposes no such endpoint, so `list_browser_targets` has nothing
to attach to. Connection failures report `endpoint_unreachable`; a reachable
HTTP service that returns a non-200 or non-JSON discovery response reports
`invalid_endpoint_response`.
Reproduce that flow in a Chromium-family browser started with
`--remote-debugging-port`.
REA attaches through the browser's HTTP discovery endpoint on a loopback port,
or, for some passive tools, through a
[browser socket](#browsers-without-http-discovery). Connection failures report
`endpoint_unreachable`; a reachable HTTP service that returns a non-200 or
non-JSON discovery response reports `invalid_endpoint_response`.
In a minimal Linux container, headed Chromium under Xvfb can keep running while
its DevTools port never opens. Install the GLES, EGL, and Mesa DRI libraries
@@ -184,7 +181,9 @@ file for you.
the origins you are investigating.
A browser driven only through `--remote-debugging-pipe` or a vendor's private
automation channel has neither endpoint, and REA cannot attach to it.
automation channel has neither endpoint, and REA cannot attach to it. To
inspect such a page, reproduce it in a Chromium-family browser started with
`--remote-debugging-port`.
## Request boundary
@@ -361,7 +360,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
WebSocket stream inventories and connection counts likewise require complete
WebSocket coverage. Redirect resource types use only shared known metadata:
an omitted type leaves equality unknown rather than proving a modification.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- WebSocket observations retain direction, opcode, and payload byte length. A missing or malformed CDP opcode is returned as `null`, with opcode-dependent payload byte length and shape also `null`, and marks `websocket_frames` incomplete. Missing or wrong-typed payload data is not treated as empty; its byte length is `null`, and a text payload shape is unknown. Numeric opcode `0` and an observed empty payload remain distinct from missing data. Opcode is nullable in the public inspection schema, so clients must handle unknown protocol values. When shape capture is selected, text messages are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw payloads are never retained.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache fingerprints hash all decoded response bytes, including bodies above 64 KiB, subject to the [64 MiB per-message CDP limit](#request-boundary). Missing or undecodable cache bodies and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
@@ -386,7 +385,8 @@ section explicitly truncated.
Disallowed-origin entries are filtered before retention.
Selected script-source collection shares a 32 MiB aggregate UTF-8 retention
budget and a 20-second deadline across the capture. An oversized source is
budget and the `command_timeout_ms` deadline (default 20000 ms) across the
capture. The byte budget has no runtime override. An oversized source is
omitted while later smaller sources may still fit. When the deadline expires,
remaining source reads are skipped. Script metadata, per-item omission reasons,
and `script_sources` completeness remain available. A single CDP response can
@@ -394,19 +394,25 @@ still be materialized up to the separate 64 MiB transport limit before source
admission; the retention budget does not bound that transient allocation.
Bundle parsing and semantic inspection run sequentially in an owned child,
without executing captured code. `max_heap_mb` defaults to 1024 MiB V8
old-space; `analysis_timeout_ms` defaults to 30000 per script, including startup
and transfer. A failed or timed-out script becomes an explicit unknown while
completed findings and later scripts remain available. `total_analysis_timeout_ms`
defaults to 300000 across script analysis; expiry returns a typed timeout with
partial Evidence and identifies unstarted scripts. These controls are also
available as CLI flags with hyphenated names. Capture and optional source-map
fetching have separate budgets.
without executing captured code. These are separate phases. `max_heap_mb`
defaults to 1024 MiB V8 old-space (range 128..16384). `analysis_timeout_ms`
defaults to 30000 per script, including startup and transfer.
`total_analysis_timeout_ms` defaults to 300000 across script analysis. A
per-script deadline above the effective total is rejected. A failed or
timed-out script becomes an explicit unknown while completed findings and later
scripts remain available. Total-deadline expiry returns a typed timeout with
partial Evidence and identifies unstarted scripts. `source_map_fetch_timeout_ms`
defaults to 30000 and is an item limitation, not that analysis timeout. These
controls are also available as CLI flags with hyphenated names. Worker protocol
and result byte budgets have no runtime override.
One web-bundle capture/analysis is admitted per REA process at a time. Completed
script results share an 8 MiB serialized transfer budget, with host heap
reservation checked before decoding and validating another result.
Exhausted result capacity stops further script launches and returns a typed
When the host reservation is what reaches zero, the failure names that heap
headroom and the remaining fixed budget. When the 8 MiB allowance itself is
exhausted, the failure names that fixed budget and says it has no runtime
override. Either stop ends further script launches and returns a typed
resource failure with completed facts and explicit unstarted-script unknowns.
Worker control frames are limited to 64 KiB. Cleanup must confirm child termination
before releasing its private files; failed cleanup retains its exact owner and
@@ -416,8 +422,10 @@ facts, including cancellation during final cleanup.
The worker heap setting is not a process-family RSS limit. Node enforces V8
old-space flags; Bun ignores them. Windows Bun instead uses an owned process
commit limit with a 512 MiB runtime reserve; other Bun hosts have no enforced
memory limit. These bounds do not establish an aggregate host-memory limit
across other REA operations or external processes.
memory limit. An allocation failure before that Windows limit is reported as
reached still names the configured limit and says it was not reached. These
bounds do not establish an aggregate host-memory limit across other REA
operations or external processes.
Resource-tree inventories distinguish a valid empty `resources` array from a
missing or malformed collection. Invalid resource entries are omitted with
@@ -431,12 +439,24 @@ an invalid supplied size remains unknown with incomplete resource coverage while
the resource's other facts are retained. Zero and nonnegative fractional sizes
are preserved.
CDP discovery has a 10-second deadline. Each WebSocket handshake and command
response has its own 20-second deadline, separate from the caller-selected
observation window. A stalled handshake or command reports `provider_timeout`;
caller cancellation still reports cancellation. WebSocket messages remain subject
to the [64 MiB limit](#request-boundary). REA does not impose a message-count or
pending-command quota. On cancellation, it closes only its own WebSocket,
CDP discovery has a 10-second response deadline. HTTP discovery documents
use an 8 MiB budget. Browser-socket discovery (`Browser.getVersion`,
`Target.getTargets`) uses the 64 MiB protocol message budget. Neither budget
nor the discovery deadline has a runtime override. `list_browser_targets` accepts no
`command_timeout_ms`: HTTP discovery and browser-socket discovery
(`Browser.getVersion`, `Target.getTargets`) stay on that fixed deadline, and
the socket closes without a later command. Other CDP tools use
`command_timeout_ms` (default 20000) for each WebSocket handshake and command
response, separate from the caller-selected observation window, discovery, and
the 5000 ms cleanup grace. A browser-level socket's handshake and the following
`Target.attachToTarget` each receive that full deadline; they do not share one
budget. A stalled handshake or command reports
`provider_timeout` and names `command_timeout_ms` only when that operation
accepts it; caller cancellation still reports cancellation. WebSocket messages
remain subject to the [64 MiB limit](#request-boundary), which has no runtime
override. REA does
not impose a message-count or pending-command quota. On cancellation, it closes
only its own WebSocket,
waiting up to one second before terminating it. Malformed unsolicited events
and malformed replies to pending commands poison the connection so later
commands fail closed. A correlated reply's session ID must match the command's
@@ -445,7 +465,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 has a 30-second deadline and a 64 MiB aggregate streamed-response budget across redirects. 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 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-map items retain the script's `declared_url` separately from the observed
`effective_url`. successful maps use the latter as their source-resolution base.
+18 -4
View File
@@ -23,9 +23,21 @@ that rejection does not roll back browser effects. REA removes its owned
future-document scripts, restores live frame methods, then disconnects.
Add secret declarations, storage seeds, and additional artifact or event
capture only when needed. Action, secret, and storage counts are not capped.
Duration, action, and navigation timeouts remain fixed provider-owned liveness
limits. `capture.limits` controls retained event and network-content budgets;
it does not change those liveness deadlines.
Duration, action, and navigation timeouts remain separate from CDP command
deadlines. `command_timeout_ms` (default 20000; omit it to keep that default
and leave it out of Evidence) bounds launch startup, the connect-over-CDP
handshake, target lookup, environment commands, and storage CDP commands,
including replies from attached child targets. Set it in the scenario JSON.
`capture-browser-scenario` has no `--command-timeout-ms` option.
It does not change action `timeout_ms`, `wait_for_timeout.duration_ms`, or
navigation. Service-worker restoration keeps a fixed 5000 ms grace and does
not inherit a raised `command_timeout_ms`. CDP discovery stays on its own
fixed deadline. When service workers are blocked, the registration check
before discovery has its own fixed 10000 ms deadline; `command_timeout_ms`
does not apply because discovery has not started. The discovery deadline
covers endpoint discovery only. The following WebSocket handshake and
`Target.attachToTarget` each use `command_timeout_ms`. `capture.limits` controls retained event and network-content
budgets; it does not change those liveness deadlines.
## Minimal launch example
@@ -212,7 +224,9 @@ statements remain under the attached debugger client's control.
The result is Evidence with an initial state followed by one record per
declared action. Each step reports action status, elapsed time, sanitized URLs,
event bounds, and independently typed capture states for screenshot, DOM,
accessibility, URL, history, and storage. Console, page-error, network,
accessibility, URL, history, and storage. Actions after a failed or cancelled
action are not run and are recorded as `cancelled` with a not-executed error.
Console, page-error, network,
WebSocket, frame, worker, popup, and cancelled-download events are attributed to
steps. Retained events and requested artifacts are returned inline; omitted
events are counted by kind and identify affected network transactions.
+51 -21
View File
@@ -63,6 +63,16 @@ or repeat it to select several types.
Selection limits decoding to the selected types; member references remain an
assembly-wide inventory, so the output can still be large.
Select `--detail summary --evidence-bundle /absolute/path/members.json` to
receive identity, selection, coverage, limitations and collection counts while
saving the complete original Evidence in a new bundle. Summary requires that
explicit destination; existing files are preserved. Later
`inspect-analysis-view` calls can page the exact parent from the saved bundle
without reopening the assembly. See
[CLI summary and saved member rows](managed-code-analysis.md#cli-summary-and-saved-member-rows)
for the complete command sequence. Omitted or `--detail complete` retains the
full inline response.
Choose the workflow from the artifact's contents rather than its `.exe` or
`.dll` extension. The managed parser requires PE/CLI metadata; it does not
unpack .NET single-file hosts or decode IL2CPP. For the image, type and method
@@ -204,7 +214,7 @@ The selected provider remains bound to the session until an explicit switch
or close. Provider failures are returned with their original reason. For an
`ambiguous` selection error, choose from `details.candidate_ids`; for
`provider_unavailable`, follow `message`, `remediation.action`, and the original
reasons in `details.rejections`. For Hopper, Ghidra, IDA, and JADX, run
reasons in `details.rejections`. For Hopper, Ghidra, IDA, JADX, and Kuna, run
`rea doctor --provider ID --json` to diagnose the selected engine. Other adapters,
such as `pwntools-elf`, report their configuration requirements directly and are
not accepted by `doctor --provider`. See
@@ -279,7 +289,12 @@ Exports preserve an existing destination unless `--overwrite` is explicit.
For application traces and version comparisons, see
[JavaScript application workflows](javascript-application-workflows.md).
MCP [retained Evidence references](mcp-contracts.md#retained-application-evidence-inputs)
belong to one connection. Separate CLI calls consume full saved records.
belong to one connection. Separate CLI calls can consume full saved records.
`inspect-analysis-view REQUEST.json --evidence-bundle BUNDLE.json` resolves
the request's `retained-evidence` source ID from that authenticated bundle,
including bundles exported from an MCP session. The same view JSON works for
every supported parent kind; no target is reopened and no record is selected
by file order. Omit `--evidence-bundle` when the request supplies inline Evidence.
## Import historical source
@@ -390,14 +405,25 @@ entry keep stdio.
## Serve an environment manifest
`rea environment-serve <manifest.json> --evidence <capture.json> [--host H]
[--port N]` serves an environment manifest projected by `inspect-analysis-view`
as an agent environment: an MCP server at `/mcp` with one tool per manifest
tool, each replaying the retained capture records it cites, the environment
card at `/.well-known/agent-env.json`, and a `/agentenv` data plane that
answers every method with JSON-RPC `-32601`. The manifest's
`parent_evidence_id` must equal the evidence file's `evidence_id`; otherwise
the command refuses with `invalid_request` before binding.
`rea environment-serve <manifest.json> --evidence <capture.json>
[--replay-only] [--host H] [--port N]` serves an environment manifest
projected by `inspect-analysis-view` as an agent environment: an MCP server at
`/mcp`, the environment card at `/.well-known/agent-env.json`, and a
`/agentenv` data plane. The manifest's `parent_evidence_id` must equal the
evidence file's `evidence_id`; otherwise the command refuses with
`invalid_request` before binding. Before binding it derives the
`environment-state` view of the capture with the manifest's `template_rules`,
as `inspect-analysis-view` would, and serves from it: read tools whose views
are state-backed answer from a live state, write tools are not served, and
`/agentenv` serves `data/reset`, `data/add` and `data/get`. When the state
cannot be derived, or its catalog exceeds the discovery reservation, it logs a
warning with the error code and message and serves replay; `--replay-only` serves replay without deriving. In replay every
manifest tool replays the retained capture records it cites and `/agentenv`
answers every method with JSON-RPC `-32601`. The stopped document's `state`
member is `{"served": true, "evidence_id": …}`,
`{"served": false, "reason": "replay-only"}` or
`{"served": false, "reason": "unavailable", "code": …, "message": …}`; see
[serve from the state](agent-environments.md#serve-from-the-state).
```sh
rea environment-serve manifest.json --evidence capture.json --port 8765
@@ -408,8 +434,8 @@ rea environment-serve manifest.json --evidence capture.json --port 8765
the served environment; set but blank, it is refused as `configuration_invalid`
like a blank `MCP_HOST`. The bound URL is written to stderr as one JSON log line
(`"msg":"MCP transport listening"`). See
[agent environments](agent-environments.md) for what replay returns and does
not establish.
[agent environments](agent-environments.md) for what the state and replay
return and do not establish.
## Export an environment bundle
@@ -417,9 +443,11 @@ not establish.
[--package-tarball <path>] [--name <override>]` writes the same manifest and
capture pair as a bundle folder the agentenv framework runs with
`agent-env run <dir>`. It reads each file once, authenticates and cross-checks
the pair with the same checks as `environment-serve`, copies the bytes it
authenticated, refuses to write into a directory that exists and is not empty,
and never overwrites a file.
the pair with the same checks as `environment-serve`, checks the bundle name,
then prepares the state under that name as the bundle's server will at startup
to predict what the bundle serves (replay when the state is unavailable),
copies the bytes it authenticated, refuses to write into a directory that
exists and is not empty, and never overwrites a file.
```sh
rea environment-export manifest.json --evidence capture.json ./grafana-example --json
@@ -429,14 +457,16 @@ The folder holds a `README.md` with the bundle's provenance, an env folder
`envs/<name>/` with a `Dockerfile` that installs this exact `rea-agents`
version and serves the copied `manifest.json` and `evidence.json` (written
with mode `0600` on POSIX; Windows retains inherited ACLs) with
`environment-serve`, a Python verifier under `artifacts/check_<name>/` and the
smoke task `tasks/smoke.json`. `--package-tarball` copies a packed `rea-agents`
`environment-serve` (from the state by default), a Python verifier under
`artifacts/check_<name>/` that checks the served tool count and one served
answer, and the smoke task `tasks/smoke.json`. `--package-tarball` copies a packed `rea-agents`
tarball into the env folder and installs it instead of the registry package,
which is how the hosted lane proves a checkout rather than a release. `--name`
replaces the manifest's kebab-case name as the env folder and env id; a device
name Windows reserves (`con`, `nul`, `com1` and the like) is refused. The
command prints one JSON document listing the written files with their sizes
and the pinned package source. See
command prints one JSON document listing the written files with their sizes,
the pinned package source, the served `tool_count`, and `state` in the shape
of the serve command's stopped document. See
[agent environments](agent-environments.md#export-a-bundle) for the folder
layout and what the smoke task checks.
@@ -488,8 +518,8 @@ SIGTERM, closes the listener, then prints one structured
`{"status":"stopped", …}` document naming the URL, environment and tool count
and exits `130` or `143` through the shared cancellation handler below.
Refusals before binding (unreadable files, non-Evidence JSON, capture Evidence
in a format other than HAR, a manifest whose parent is not the supplied
evidence, an unbindable port) print a structured
in a format other than HAR, a manifest or state whose parent is not the
supplied evidence, an unbindable port) print a structured
error and exit `1`, as does a listener that fails after binding.
`rea environment-export` is one-shot: it prints the written files and exits
`0`, or prints a structured `invalid_request` error and exits `1` for the same
+2 -2
View File
@@ -153,8 +153,8 @@ headroom. The default is 450000 ms, covering the default 330-second Ghidra
startup; an explicit Ghidra startup timeout of 2700000 ms produces a 2820000 ms
tool timeout. The startup allowance follows an explicitly selected Ghidra or
Hopper provider, and otherwise uses the larger known Ghidra/Hopper deadline.
An explicit `REA_JEB_MCP_TIMEOUT_MS` request budget also contributes to that
maximum. For `ida` or `auto` selection, a valid IDA registration's parsed
An explicit `REA_JEB_MCP_TIMEOUT_MS` deadline, shared by catalog discovery and
each tools/call, also contributes to that maximum. For `ida` or `auto` selection, a valid IDA registration's parsed
`timeoutMs` also contributes to that maximum; its default is 300000 ms.
Setup forwards these explicitly selected REA deadlines and the input schema
profile into the registration; unrelated process values are omitted. An
+2 -2
View File
@@ -17,8 +17,8 @@ shared form would leave later fields unknown instead. The existing
`REA_MCP_INPUT_SCHEMA_PROFILE=compact` profile lets callers stay below
its character limit by advertising reduced nested validation structure, while
REA continues to validate calls against the complete canonical input schema.
Compact applies to the server's advertised inputs and is not an automatic
full-profile fallback; see the [compact input schema profile](mcp-contracts.md#compact-input-schema-profile)
Compact is the default for the server's advertised inputs; an explicit `full`
choice is preserved. See the [compact input schema profile](mcp-contracts.md#compact-input-schema-profile)
for details of its reduced forms.
Custom renderer hosts can raise `inputMaxChars` through `renderToolSignature` or
+10 -2
View File
@@ -116,7 +116,9 @@ The CLI and MCP surfaces accept the same schema. For a JSON request file:
rea capture-electron-scenario scenario.json --json
```
The result records action status and targets, correlated app/window/WebContents,
The result records one status and target per declared action; actions after a
failed or cancelled action are not run and are recorded as `cancelled` with a
not-executed error. It also records correlated app/window/WebContents,
preload, session, navigation, shell, permission, popup, download, protocol,
native-addon, process, and IPC timeline events. IPC channels and argument-shape
metadata retain their observed lengths; payload values are never retained.
@@ -243,4 +245,10 @@ application path and capture outcome to the static graph, while frames, scripts,
workers, and execution claims remain unavailable until a separate passive runtime
capture provides them.
ASAR observation reads the header through one open file handle, validates its declared lengths against the archive size, and limits encoded JSON headers to 16 MiB before parsing. The header cache also retains at most 16 MiB of encoded headers across eight archives. Oversized headers produce a scope refusal.
ASAR observation reads the header through one open file handle and validates its
declared lengths against the archive size. Header/index admission is the same
fixed 16 MiB / 16777216 byte budget used by static ASAR reads. This release has
no runtime override. An oversized header is not parsed; selecting a member path
does not bypass admission, and the page stays unauthorized. The header cache
separately retains at most 16 MiB of already-admitted encoded headers across
eight archives. Evicting that cache does not raise admission.
+5 -1
View File
@@ -106,7 +106,11 @@ CLI keeps its existing complete result.
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, 3 GiB virtual
address-space limit, 30 CPU seconds and a 30-second command deadline. Virtual
address-space limit and 30 CPU seconds. 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. Virtual
address space differs from resident memory. The worker retains inherited tighter
soft/hard limits, reports its configured soft limits inline and leaves hard limits
unchanged. WASM trap reservations are disabled
+15 -1
View File
@@ -158,7 +158,13 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
Supply a stable copy within the allowance. No in-place source modification.
- One queued heavy operation per provider instance; Binwalk one thread and
Unblob one worker. No automatic parallel extraction engines.
- **120-second** analysis deadline through inventory. Publication and final
- Analysis defaults to **120 seconds**, from tool resolution and input snapshot
through inspection and its final private-workspace cleanup, or extraction
through report consumption and inventory, excluding queue waiting and retained
cleanup admission. Inspection has no publication phase. Select `analysis_timeout_ms` in either MCP operation or
`--analysis-timeout-ms` in either CLI command for slower analysis. Current
results retain the actual budget; an absent historical value means unknown.
Extraction publication and its final
cleanup start a separate **120-second** budget; select
`publication_timeout_ms` in MCP or `--publication-timeout-ms` in CLI for a slow
destination. Valid budgets are integers from 1 through 2,147,483,647 ms (the
@@ -168,10 +174,18 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
both phases and final cleanup. Phase handoff, publication commit and final
completion also check monotonic elapsed time, so delayed timer callbacks
cannot revive an expired budget.
- Analysis timeout recovery names the selected budget and the analysis control,
not the later publication control. Keep the client call timeout above analysis,
publication and cleanup headroom; keep the input/range/question unchanged.
Resolve any reported incomplete cleanup before retrying. Raising wall time
does not raise fixed CPU, memory, report, diagnostic or input limits.
- Inherited **1 GiB per-process address-space** limit,
120 CPU seconds and zero core-dump allowance through `prlimit`.
- JSON reports: at most **8 MiB** and valid UTF-8; malformed encoding and
oversized reports fail without repairing or truncating producer data.
Exit status 0 is not an empty result: a missing, empty, malformed or
oversized report fails with `unreadable_output`, its `report_state`, the
command, exit status and captured output. REA does not retry automatically.
Diagnostic output is bounded with exact captured byte counts.
- Default staging budget: **256 MiB** and **10,000 entries**, configurable using
`max_output_bytes` / `max_output_files` (CLI `--max-output-bytes` /
+1 -1
View File
@@ -24,6 +24,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. Message capture is bounded to 16 MiB and reports truncation. 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 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.
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.
+166
View File
@@ -0,0 +1,166 @@
# Static CUDA and HIP code objects
`inspect_gpu_code_objects` answers which GPU targets and kernel declarations a
local artifact carries. It reads ELF/PE sections, standalone CUDA fatbins,
CUBIN/PTX and Clang HIP bundles, including CCOB v1/v2/v3. It neither executes the
artifact nor invokes CUDA/ROCm tools. GPU execution, SASS disassembly and host
launch correlation remain outside coverage.
Start with a target inventory:
```json
{ "path": "/artifacts/library.so", "view": { "collection": "targets" } }
```
Target rows aggregate inventoried payloads and distinguish an unrequested kernel
analysis from a complete or partial one. `objects` pages expose exact object
IDs, compression declarations and encoded/header locations. Fields, exact
filters, batch IDs and stable pagination select the facts delivered to the
caller. IDs are positional within the recorded artifact SHA-256. Selecting `object_ids`
in a new inspection requires `expected_sha256` from that inventory; a changed
file is rejected before parsing selected payloads.
`target` is the inventory declaration used for selection; `payload_target` is
the independently observed inner declaration when available. Object pages keep
both values, their kind, `target_consistency` and inspection coverage even with
selected fields.
Conflicting base architectures retain both values, mark object/report/target
coverage partial and annotate the affected kernels. CUBIN flags establish a base
architecture: `base-only` explicitly leaves feature-profile agreement unknown,
including `a` variants. A standalone declaration is `single-source`, and
unexamined payloads remain `unexamined`. An exact AMDGPU target is normalized at
its note owner for comparison with a Clang bundle declaration; raw values remain
intact. Unresolved feature comparisons stay `unknown`.
`cuda_target_header` retains the OS ABI, normalized raw `e_flags` and exact
header location. CUDA OS ABI `0x33` and the observed `0x41` flags layout use
different architecture fields; they are decoded at one ABI-aware owner.
Unrecognized layouts keep the header and an unknown target instead of
inventing an SM label. Header provenance is validated against its CUBIN object.
Target filters select inventory declarations. An unselected payload's inner
target remains unexamined; an empty filtered result does not establish that
no enclosed code declares that target. Discovering every inner declaration
requires decoding those payloads.
HIP IDs with one or two separators before the architecture retain the declared
target. If a recognized AMDGPU bundle ID does not expose a target, the same
bounded note-discovery owner used for standalone AMDGPU files establishes it
before filtering. Those directory/note reads share the inspection work budget;
successive queries can select the target returned by inventory.
Decode selected targets and return a kernel page in the same call:
```json
{
"path": "/artifacts/library.so",
"analysis": "kernels",
"targets": ["sm_100"],
"view": {
"collection": "kernels",
"names": ["kernel_name"],
"fields": [
"name",
"parameter_count",
"parameter_bytes",
"parameters",
"registers",
"shared_bytes"
]
}
}
```
Only selected payloads are decoded. Other inventory entries remain present with
`not-selected` states, and overall payload coverage remains partial. An empty
kernel page proves no matching retained fact; it does not establish that an
unexamined payload contains no kernel. Missing fields remain null.
Kernel representations have different meanings:
- PTX reports parameter declarations and virtual registers. Shared bytes cover
supported static declarations inside that entry; they do not establish module
shared memory, dynamic allocation or physical device resources.
Scalar register declarations count each bare or percent-prefixed name and
each parameterized range. Unsupported declaration forms or overflowing totals
retain an unknown count and partial decoding coverage. Unsupported parameter
layouts and unknown shared-memory declarations likewise retain their known
facts, explicit limitations and partial coverage through retained pages.
- CUBIN identifies CUDA entry symbols and reads numeric `nv.info` records and
native section declarations. Global register declarations also cover compiler
versions that leave the older text-section register field empty.
- AMDGPU reads v3+ MessagePack notes. Argument counts include hidden ABI arguments
when present; SGPR/VGPR counts and fixed group/private segment sizes retain
their declared meanings. Legacy YAML metadata remains unsupported.
Each selected PTX payload becomes a retained UTF-8 text artifact with a SHA-256,
byte length and source coordinates. It is omitted from compact replies. Read it
with `inspect_analysis_view` and the parent Evidence ID returned by the compact
receipt:
```json
{
"source": { "kind": "retained-evidence", "evidence_id": "ev_..." },
"view": {
"kind": "gpu-code-objects",
"collection": "ptx",
"object_id": "...",
"offset": 0,
"limit": 4096
}
}
```
Text offsets use UTF-16 code units and must fall on Unicode scalar boundaries.
Complete Evidence retains the selected text; `export_evidence_bundle` provides
its portable source record. Historical pages never reopen the input file.
A location with `container_id: null` uses original file offsets. Other locations
use that inventoried object's decoded byte coordinates. Follow its parent,
encoded location and digest to trace decompressed metadata back to a native
section and the original file. Decoded offsets are never presented as physical
file offsets.
The CLI uses the same service and contracts:
```sh
rea inspect-gpu-code-objects /artifacts/library.so --analysis kernels \
--targets '["sm_100"]' --detail summary \
--view '{"collection":"kernels","names":["kernel_name"]}' \
--evidence_output /analysis/library-gpu.evidence.json --json
```
A full-file SHA-256 always streams every byte. Inventory then reads bounded
native directories and bundle headers; CCOB requires decompression to discover
its inner directory. Kernel analysis reads selected payloads. `bytes_read`
includes hashing and subsequent descriptor reads, so this is not a header-only
hashing workflow.
CUDA raw LZ4 blocks and Zstd, and CCOB zlib/Zstd are supported. Unknown versions
or codecs retain declarations with explicit limitations. Lengths are checked
before decoding, and CCOB's truncated MD5 is verified against decoded bytes.
Resource limits are one active inspection, 128 MiB per input/output buffer,
512 MiB of payload input, decoded output and AMD note-read work per inspection,
and 16 MiB retained facts including selected PTX. Payload work is reserved before
reading or allocating; a capacity refusal does not reread an aliased payload.
Native tables and decoded section names each have a 16 MiB structural budget;
AMDGPU ELF note discovery admits at most 8 MiB of source note bytes, with
bounded depth and container/string allocations. Standalone AMD targets are read
from those notes before target selection; selected kernel analysis reuses the
same parsed declarations. Pages have a
1 MiB response budget. Resource refusals and interrupted observations preserve
known facts and explicit uncertainty.
CUDA ELF parsing additionally shares a 262,144 record/symbol visit limit,
128 MiB of cumulative metadata section reads, and a 16 MiB serialized-size
accounting budget for temporary index facts. These limits are checked before
index growth; they are not claims about JavaScript heap size. Equivalent scoped
metadata byte ranges and repeated equal register declarations are coalesced;
conflicting values remain unknown. Parameter sections are parsed once per ELF.
Missing CUBIN symbol tables or unresolved section names produce `partial`
object, report and target coverage even when some kernel facts survive. AMD note
failures preserve earlier successfully parsed declarations and the original
failure reason. Standalone PTX recognition tries a small header first, then
continues within the existing 16 MiB text budget when leading comments or
whitespace obscure its directives; admitted full-file probe bytes are reused.
+1 -1
View File
@@ -142,7 +142,7 @@ Use a package version that actually includes this adapter; repository main can b
{"name":"close_binary","arguments":{}}
```
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration's optional `timeoutMs` defaults to 300000 for upstream requests. An upstream SDK request that reaches this limit returns `provider_timeout` with its operation and actual request budget. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Each health request is capped by the time remaining in that overall wait, so a stalled read cannot extend the readiness deadline by another full request timeout. Expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started after the deadline. That error names the busy tool from the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. If a health request itself consumes the remaining wait budget, the same last-busy timeout is returned with the stalled request's captured output. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration JSON named by `REA_IDA_MCP_CONFIG` has an optional `timeoutMs` integer from 1 to 2147483647. Omitted means 300000. REA applies an edited value on the next IDA connection after it re-reads that file; an already open session keeps the timeout it connected with. Changing the `REA_IDA_MCP_CONFIG` path itself is read at process start. An upstream SDK request that reaches this limit returns `provider_timeout` naming the effective milliseconds and `timeoutMs`. A shorter inner request is the time remaining inside that ceiling, and the error names both when they differ. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Each health request is capped by the time remaining in that overall wait, so a stalled read cannot extend the readiness deadline by another full request timeout. Expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started after the deadline. That error names the busy tool from the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. If a health request itself consumes the remaining wait budget, the same last-busy timeout is returned with the stalled request's captured output. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <client>`. Setup includes that file reference in its reviewed plan and preserves it after approval; it does not copy upstream credentials into the agent configuration. For `ida` or `auto` selection, Codex/Kimi setup and doctor use the effective client registration's parsed `timeoutMs` (default 300000 ms) when recommending the agent's per-tool deadline. Existing positive client deadlines remain caller choices. `rea doctor --provider ida --json` validates the registration without starting IDA and explicitly leaves runtime compatibility and target binding for analysis.
+8 -2
View File
@@ -24,6 +24,10 @@ Restart the agents named in the completion message. Quit Claude Desktop fully
before approving, because a running app can overwrite the registration; on
Windows, see [where Claude Desktop reads its configuration](client-configuration.md#claude-desktop).
During package resolution, npm may print a deprecation warning for `prebuild-install`
from transitive native dependencies (such as Frida). This warning originates from
the upstream dependency tree and does not affect REA or indicate setup failure.
For the latest published release, use `npx rea-agents@latest setup`.
[Package-runner and global-installation options](#package-runners-and-global-installation)
cover explicit versions, Bun, runtime requirements, and the curl wrapper.
@@ -1189,8 +1193,10 @@ truncation markers. Darwin ownership-helper compilation failures also retain
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
compiler remains distinguishable from a compiler that exited with an error.
The helper compile deadline defaults to 120,000 ms; set
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 when
a busy host needs longer.
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 in
the environment that starts REA when a busy host needs longer. Restart REA to
apply a change; the next ownership-helper compile uses the new value. A
timeout names that effective deadline.
On macOS, the Java prerequisite probe waits for this independently bounded
preparation before starting its five-second command deadline. Caller cancellation
still interrupts preparation, and failed helper cleanup remains owned for retry.
+52 -8
View File
@@ -106,6 +106,15 @@ only reduces delivery; source selection reduces text acquisition and analysis.
Explicit native provider or snapshot routing rejects `--source-path` rather
than ignoring it.
ASAR header/index admission is a fixed 16 MiB / 16777216 bytes for this
release. There is no runtime override. The archive index is read before any
member can be selected, so `source_paths`, `--source-path`, `detail:
"summary"`, `max_heap_mb`, and available host memory do not bypass or raise
it. When the header exceeds that allowance, the failure names this budget.
Analyze an extracted application directory, which does not admit an ASAR
header, or supply an archive whose header fits. Repeating the unchanged
request fails the same way.
## Read the analysis
`analyze_javascript_application` returns the complete analysis Evidence by
@@ -172,7 +181,8 @@ for the tool-result contract.
Use MCP `detail: "summary"` for a large application, then read selected views of
the retained analysis. Summary changes delivery only. To reduce source parsing,
select exact [source paths](#select-sources-before-analysis).
select exact [source paths](#select-sources-before-analysis). Source selection
does not bypass ASAR header/index admission.
If a complete response exceeds the transport budget, use its retained Evidence
ID for a summary, module page, or item, or export the bundle. See
@@ -371,7 +381,15 @@ Recognized literal module-table bootstraps also support bounded lexical
substitution through module exports, import helper bodies, local wrapper calls,
returned closures, object spreads, and callback parameters. Recovered Electron
paths retain source ranges, module identities, literal channels and branch
conditions as static candidates. Unknown spreads, reassigned bindings, accessors,
conditions as static candidates. A channel the IPC call site itself
determines, such as a local constant, resolves that call's observation rather
than adding a second one. A channel or registration handler supplied through
a wrapper argument is a candidate beside the call's own unresolved
observation, because callers outside the traced paths may pass other values.
A wrapper call that is both its own entry and reached through another entry
is recorded once when both paths find the same facts; calls that supply
different handlers or channels stay distinct.
Unknown spreads, reassigned bindings, accessors,
dynamic channels and unsupported syntax do not become exact implementations.
This pass neither executes the bundle nor proves that runtime guards pass.
An ambiguous or unresolved handler retains its channel registration but does
@@ -589,8 +607,9 @@ The default semantic capacities are 100000 nodes across the application and
`--max-semantic-nodes-per-file`), each from 1 to 1000000. The per-file offer is
also bounded by the remaining application capacity. Raising these controls does
not bypass worker heap, execution deadline or parent result admission. Truncated
coverage reports both effective node ceilings; omitted relationships remain
unknown.
coverage lists both effective ceilings, `max_semantic_nodes` and
`max_semantic_nodes_per_file`. The limitation names only the ceiling that
stopped retention. Omitted relationships remain unknown.
Unknown records have independent defaults of 100000 across the application and
20000 per source. Select `max_semantic_unknowns` and
@@ -598,8 +617,10 @@ Unknown records have independent defaults of 100000 across the application and
`--max-semantic-unknowns-per-file`), each from 1 to 1000000. The parent offers the
remaining capacity to the worker, and rejects excess records before adoption.
Unknown admission happens before canonical identity and provenance construction.
When capacity is exhausted, coverage is partial and truncated and names both
effective unknown ceilings. `coverage.omitted_unknown_occurrences` counts examined
When capacity is exhausted, coverage is partial and truncated. Coverage lists
both effective unknown ceilings, `max_semantic_unknowns` and
`max_semantic_unknowns_per_file`. The limitation names only the ceiling that
stopped retention. `coverage.omitted_unknown_occurrences` counts examined
emission occurrences by family and reason, including repeated emissions. It does
not claim distinct discarded identities or count frontiers in unexamined source.
Omitted unknown provenance and IDs are not retained. Existing source, node,
@@ -612,12 +633,35 @@ checkpoint at its 8 MiB bound with the same 6x retention expansion used at
admission. Earlier checkpoints are already charged to retained bytes; the
remaining inventory does not multiply the reserve. Streamed file transfers keep the default 512 MiB
allowance, and can grow with the parent's offered retention budget (8x byte
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The parent
expansion), up to 2 GiB. Individual records remain bounded to 8 MiB. The 8 MiB
record ceiling and the 2 GiB file maximum have no runtime override, and raising
`max_heap_mb` does not raise them. The effective file ceiling follows the
parent's offered result budget. On Node, a larger CLI/MCP process heap
(`NODE_OPTIONS`, for example `--max-old-space-size=<MiB>`, after restarting that
process) can raise it up to 2 GiB. Selecting fewer `source_paths` can also
raise that effective allowance, because the reserve for remaining sources and
earlier retained results reduce the offered budget. Neither change raises the
record ceiling or the 2 GiB maximum. The parent
still independently checks actual bytes, record overhead, digests and source
identities before adopting results.
A source that exhausts the analyzer heap does not terminate the CLI/MCP
process. Completed static facts are retained, other files can still be analyzed,
process. On Node, the failure names `max_heap_mb` (CLI `--max-heap-mb`), the
effective mebibytes, the inclusive range 128 to 16384, and the 1024 MiB
default. A changed value applies on the next analysis, which starts a new
worker. It does not change a worker that is already running and does not
require restarting the REA server. On Bun, the same heap-exhaustion diagnostic
is not attributed to that V8 old-space limit, because Bun ignores the flag.
Raising `max_heap_mb` does not raise the parent result budget,
the transfer record ceiling, the 2 GiB transfer file maximum, the CLI/MCP
process heap, or fixed parser limits. The parent result budget follows that
process heap. On Node, set `NODE_OPTIONS` (for example `--max-old-space-size=<MiB>`)
in the environment that starts the CLI or MCP server, then restart that
process, to raise the parent heap and the result budget. A completed static
checkpoint that does not fit the parent's remaining heap names that parent
process heap. Increasing `max_heap_mb` does not raise it. On Node, the same
`NODE_OPTIONS` restart can raise the parent heap and admit the checkpoint.
Completed static facts are retained, other files can still be analyzed,
and unexamined semantic relationships remain explicit unknowns. The parent
also checks transfer expansion and accumulated results against its remaining
heap before accepting semantic facts. A resource-limited result has partial
+9 -4
View File
@@ -129,10 +129,15 @@ equivalence remain unknown. No network fetching occurs.
## Resource policy and verification
Each operation uses one worker, a 1 GiB address-space ceiling and a 120-second
deadline; it accepts up to 64 MiB of total input across all selected files and
128 MiB/10000 entries of staged
output, with 8 MiB per diagnostic/report stream. Exceeding a limit is an explicit
Each operation uses one worker, a 1 GiB address-space ceiling and a 120000 ms
deadline. That deadline has no runtime override: raising `analysis_timeout_ms`
or `max_heap_mb` does not raise it. An unchanged retry may hit the deadline
again. The operation accepts up to 64 MiB of total
input across all selected files and 128 MiB/10000 entries of staged output,
with 8 MiB per diagnostic/report stream. A `provenance.json` over that report
allowance names the fixed limit: this release has no runtime override, and
raising `max_heap_mb` or `analysis_timeout_ms` does not raise it. Malformed
reports stay distinct from that size refusal. Exceeding a limit is an explicit
failure with rollback. Selected inputs beyond the total input allowance return a
non-retryable `resource_constraint` with `resource: "file-size"` and
`reported_limits.maximum_input_bytes`/`remaining_input_bytes`; input that is not
+6 -1
View File
@@ -106,7 +106,12 @@ nodes. REA evaluates compatible static candidates conservatively:
that disagreement. If a mapped candidate's digest is unavailable and no
mapped content match is known, the comparison remains `unknown`.
6. Missing authorization mappings and incomplete or truncated input coverage
remain unknown or truncated rather than becoming absence claims.
remain unknown or truncated rather than becoming absence claims. A runtime
location with no automatic artifact-root or caller-declared mapping into
any layer, and no content match, is `unknown` with
`no-authorized-location-mapping`, with or without captured bytes; several
content matches remain `ambiguous` under rule 4. Only a mapped location can
be `unmatched` with `no-static-content-match`.
Every successful cross-layer `observed_as` edge has
`authority: cross-layer-reconciliation` and `state: inferred`. The underlying
+12 -10
View File
@@ -28,17 +28,19 @@ JEB service is reachable or that a project is open; use the existing
MCP `binary_session` probes the live JEB service and its advertised tool catalog.
Availability is reported per operation: a reachable headless client can support
inspection and decompilation while refusing `open_jeb_project`. Catalog discovery
uses one 60-second deadline across connection and pagination; a responsive server
cannot keep discovery running indefinitely by returning more pages.
is one deadline across connection and pagination; a responsive server cannot keep
discovery running indefinitely by returning more pages.
Each upstream tool call has an absolute 60-second request deadline by default.
Set `REA_JEB_MCP_TIMEOUT_MS` to a positive decimal number of milliseconds to
select a different per-request budget, for example `REA_JEB_MCP_TIMEOUT_MS=300000`
for a five-minute decompilation. This budget starts after discovery and applies
to each individual `tools/call`; it does not extend the separate catalog deadline
or establish one end-to-end deadline across a whole CLI or MCP operation. A
request that reaches its budget returns a typed timeout, while caller
cancellation remains a cancellation.
Catalog discovery and each `tools/call` share `REA_JEB_MCP_TIMEOUT_MS`. Set it
in REA's launch environment to an integer from 1 to 2147483647 milliseconds,
for example `REA_JEB_MCP_TIMEOUT_MS=300000` for a five-minute budget. Omitted
means the pinned MCP client SDK default of 60000 ms (`@modelcontextprotocol/client`
2.3.1). Restart REA and reconnect to apply a change. Raising the setting raises
both phases. It does not make one end-to-end deadline across a whole CLI or MCP
operation: discovery and each `tools/call` each get that budget. A request that
reaches its budget returns a typed timeout naming the phase and the effective
milliseconds, while caller cancellation remains a cancellation. Allow execution,
cleanup, and delivery headroom in the outer MCP client deadline.
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. The official Linux x64 CE build `5.48.0.202610071539` was also verified through real CLI and production stdio MCP native-code workflows; see the [Linux CE audit](../experiments/analysis-efficiency/jeb-linux-native.md) for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
+1 -1
View File
@@ -68,7 +68,7 @@ The background export belongs to the session. `close_binary`, or the end of a CL
The assembly label index is scanned once per export reader. Cancelling a request stops its wait without interrupting other readers; closing the session aborts and drains the shared scan before releasing its files.
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a five-second matching deadline. Literal matching remains the default. Cancellation or a matching deadline leaves the session available.
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a five-second matching deadline and a worker old-generation heap. Literal matching remains the default. Cancellation or a matching deadline leaves the session available. This release has no runtime override for that deadline. The worker requests 128 MiB. A positive Node.js `--max-old-space-size` on the process that starts REA, including `NODE_OPTIONS`, overrides that request and sets the effective heap. `--max-old-space-size-percentage` takes precedence over that flag in either order. REA does not turn the percentage into a MiB, so the effective heap stays unestablished instead of the 128 MiB request or the absolute flag. The reported `NODE_OPTIONS` value is the process initial environment when that block can be read. A later edit to the environment is not the value Node applied, and an unreadable initial value leaves the effective heap unestablished. The launch Node options are the initial command line on Linux when that buffer still has a program entry, not a later edit to `process.execArgv`. A preload can clear that array after V8 has applied the flags. Setting `process.title` or `--title` overwrites the command line, so a buffer without a program entry leaves the effective heap unestablished. `--env-file` or `--env-file-if-exists` can supply `NODE_OPTIONS` when the initial environment has none. That file is not part of the initial environment block, so the effective heap stays unestablished instead of the 128 MiB request. An initial `NODE_OPTIONS` is still used, because Node does not let the file replace it. A positive `--max-heap-size`, including the underscore form and launch `NODE_OPTIONS` on Node versions that allow it, sets the total heap rather than the old generation and can be lower than the 128 MiB request. The effective old generation stays unestablished while that value is in effect. A zero or empty value clears it. `--experimental-config-file` or `--experimental-default-config-file` can set `nodeOptions`, including a heap percentage, from a file that is not read back. The effective heap stays unestablished when either flag is present, including when an absolute `--max-old-space-size` is also on the command line or in the recovered `NODE_OPTIONS`. Node accepts the config path as `=` or the next argument.
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger text remains in the local export and the error identifies the file and range. `analyze_function` initially returns the complete dossier, which can still be large; later retained views do not remove that first transfer. Prefer the individual text tools for a focused question.
+50 -4
View File
@@ -85,7 +85,7 @@ rejects selected summary before analysis; complete delivery remains available.
Complete `inspect_managed_members` delivery always includes the assembly-wide
`member_refs` and `exported_types`, which `type_names` does not narrow, so even
one selected or unmatched type can return megabytes. Select
one selected or unmatched type can return megabytes. In MCP, select
`{"path":"/absolute/path/Assembly.dll","type_names":["Namespace.Type"],"detail":"summary"}`
to keep the complete source in the session and receive identity, metadata
status, the selection with its unmatched names, coverage/issues, the separate
@@ -115,6 +115,49 @@ literal pool's own coverage. Older Evidence that never examined exported types
or user strings reports a null count and refuses those pages rather than
returning an empty one.
#### CLI summary and saved member rows
The CLI accepts the same `complete` and `summary` detail choices. Omitted or
`--detail complete` returns complete Evidence. To receive a summary, choose a
new Evidence bundle destination so its original complete parent remains
available after the command exits:
```bash
rea inspect-managed-members /absolute/path/Assembly.dll --type Namespace.Type \
--detail summary --evidence-bundle /absolute/path/evidence/members.json --json
```
`--evidence-bundle` writes the complete original Evidence in REA's canonical
bundle format. Its parent directory must exist, and it never replaces an
existing path. Relative paths resolve against the command's working directory.
The option also works with complete delivery when a saved bundle is useful.
Summary without a destination is rejected before analysis. A publication failure
returns a typed error with the completed Evidence under
`details.partial_observation`; it does not report successful summary delivery.
Cancellation before publication prevents the new output. Once atomic
publication has started, it can complete despite cancellation; a cancelled
delivery then retains the committed path and byte count under
`details.diagnostics.evidence_bundle`.
Copy `normalized_result.parent_evidence_id` from the summary into the
`source.evidence_id` field of the `managed-members` request above, then save that
small request as `members-page.json` and run:
```bash
rea inspect-analysis-view /absolute/path/evidence/members-page.json \
--evidence-bundle /absolute/path/evidence/members.json --json
```
The view command authenticates the selected bundle and resolves that exact
parent ID. It does not reopen the assembly, so the original target may have
been moved or removed. Subsequent calls can select another collection or use
the returned `coverage.next_offset` to continue the same collection. An inline
source already includes its parent and cannot also select `--evidence-bundle`.
Summary changes delivery, while type selection still controls decoding and the
same inspection resource limits apply. Each separate CLI page reads and
authenticates the saved bundle; the full source is retained on disk.
## Shipped scope
The canonical parser admits PE/CLI bytes and their metadata/CIL without loading
@@ -622,6 +665,9 @@ which managed methods executed or how the CLR resolved them.
Managed member comparisons report a `metadata` dimension when an observed
MethodDef flags or implementation flags value differs, including accessibility
or synchronization changes. This is separate from the CIL/signature matching
tiers: identical instructions do not establish unchanged method metadata. The
reported difference is static metadata evidence, not proof of runtime behavior.
or synchronization changes. Observed method-body `init_locals` and `max_stack`
differences also contribute to `metadata`, even when CIL decoding is incomplete.
An unavailable header value keeps body coverage unknown rather than establishing
equality or a difference. These comparisons are separate from the CIL/signature
matching tiers: identical instructions do not establish unchanged method metadata.
The reported difference is static metadata evidence, not proof of runtime behavior.
+56 -20
View File
@@ -234,11 +234,14 @@ The destination changes only when a complete export is atomically published.
Clients that explicitly configure a larger receive buffer can set the REA
server's `REA_MCP_MAX_RESPONSE_BYTES` environment variable to the same byte
count. This setting must be a safe decimal integer at least 10485760; REA
reserves 1024 bytes for the envelope. Raising it restores complete inline
delivery for responses that fit that buffer and Node's single-string limit.
It does not change the client's buffer, analysis coverage, or retained content.
Ordinary responses keep their existing complete result contract.
count. This setting must be a safe decimal integer at least 10485760 (10 MiB);
REA reserves 1024 bytes for the envelope. The value is read when the REA
process starts, so a change requires a restart and a matching client receive
buffer. Raising it restores complete inline delivery for responses that fit
that buffer. It does not raise REA's reserved single-string limit, which is
1024 UTF-16 code units below Node's single-string ceiling, and it does not
raise that Node ceiling. It does not change analysis coverage or retained
content. Ordinary responses keep their existing complete result contract.
### Error recovery and deadline ownership
@@ -329,7 +332,7 @@ See [environment manifests](web-network-captures.md#environment-manifest).
`inspect_analysis_view` also accepts `environment-state` for the same parents,
extracting the initial state the recorded reads observed: entity types with an
identity field, entities, documents for written non-entity paths, and a render
identity field, entities, documents for non-entity reads, and a render
plan per read tool that reproduces its recordings from the state or names why it
cannot. Types and identity are inferences, so the Evidence is always inferred.
See [environment state](web-network-captures.md#environment-state).
@@ -352,10 +355,41 @@ application and semantic coverage. The result carries a digest of the selected
facts. Actual serialized size determines MCP transport admission: reduce the
page size or export retained Evidence if the view is too large.
`compare_source_to_bundle` accepts the committed historical graph inline or as
`reference: {"kind":"file","path":"/absolute/reference.json"}`. The shared
reader checks resource bounds and the graph's root commitment on every read. File-backed
comparison Evidence records `parameters.reference_source`: the exact
`requested_path` and the reader's absolute selected `path`, preserving POSIX
`..`/symlink semantics. Normalized comparison facts and mapping identities agree
with inline input; Evidence IDs distinguish caller-selected file origins.
Omitting `view` preserves the complete comparison. For a compact initial reply,
select `view: {"kind":"summary"}` or a `source-to-bundle` page; the session retains
the complete comparison before delivery. Later calls use `inspect_analysis_view`
with the returned `parent_evidence_id`.
A `source-to-bundle` view selects `collection: "mappings"` or `"candidates"` with
exact `source_paths`, `mapping_ids`, `statuses`, `fields`, `offset` and `limit`.
Omitted filters select all; empty arrays select none. The default mapping fields
include status, confidence, source digest/language and candidate/mapped counts.
Candidate defaults include kind, score and confidence; request `signals` for exact
supporting values. Mapping identity and `source_index` always remain; candidates
also retain `current_node_id`, `candidate_index` and whether they were actually
mapped. Indices refer to the complete comparison's item array and that item's
candidate array. All classifications are computed before selection. The original
`source_coverage` remains separate from page `coverage`; exhausting a page never
turns unknown correspondence into absence or proves behavioral equivalence.
The CLI accepts the same reference-file alternative (relative paths resolve from
the working directory). Save its complete `--json` Evidence, then use
`inspect-analysis-view` with an `evidence-file` source and the same view. Neither
adapter executes the target or repeats analysis for retained queries.
Completed `inspect_artifact` and atomic `inventory_artifact` Evidence also
support summary and inventory views. A summary reports the manifest's expected
counts separately from available record counts, observed entry kinds, inventory
Evidence IDs and inspection coverage. An inventory view selects `nodes`,
Evidence IDs, the inventory producer's traversal `source_coverage` and
inspection coverage. Inspection coverage is `partial` when that traversal
coverage is unknown. An inventory view selects `nodes`,
`occurrences`, `edges`, or `integrity_contradictions`, with `offset` and `limit`
defaulting to 0 and 64. For occurrences, optional `logical_paths` matches exact
recorded strings: omission selects all available records; `[]` selects none.
@@ -805,13 +839,13 @@ auto-analysis, bridge connection, and health readiness before returning analysis
These deadlines have different owners:
| Deadline | Owner and effect |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
| Darwin swiftc helper | REA allows 120,000 ms by default for ownership-helper compilation; `REA_SWIFTC_COMPILE_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. |
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
| Deadline | Owner and effect |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
| Darwin swiftc helper | REA allows 120,000 ms by default for ownership-helper compilation; `REA_SWIFTC_COMPILE_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Set it in the environment that starts REA and restart REA; the next ownership-helper compile uses the new value. |
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
For [Codex and Kimi registrations](client-configuration.md), setup supplies
missing tool deadlines from the known provider allowance plus 120 seconds of
@@ -819,7 +853,8 @@ operation headroom: 450 seconds with the default Ghidra allowance, or 2820
seconds with `REA_GHIDRA_STARTUP_TIMEOUT_MS=2700000`. Positive caller-selected
deadlines remain unchanged. Doctor reports an informational recommendation
when a configured deadline is shorter than the known provider allowance. An
explicit JEB upstream request budget also contributes to that allowance.
explicit JEB upstream deadline (`REA_JEB_MCP_TIMEOUT_MS`, shared by catalog
discovery and each tools/call) also contributes to that allowance.
For an already connected client using the pinned SDK, request options are the
**second** argument of `callTool`:
@@ -1018,14 +1053,15 @@ verify the compact advertisement through the same client boundary.
Some function-calling providers reject a request while any single tool's
`parameters` schema exceeds a literal serialized size cap; Moonshot clients
such as kimi-cli fail every request while the default catalog is attached
such as kimi-cli have reported failures with the full catalog attached
([#1484](https://github.com/morluto/rea/issues/1484)). Setting the server's
`REA_MCP_INPUT_SCHEMA_PROFILE` environment variable to `compact` renders every
advertised input schema within a 13,312-byte budget, measured to sit below
`REA_MCP_INPUT_SCHEMA_PROFILE` environment variable to `compact` (the default)
renders every advertised input schema within a 13,312-byte budget, measured to sit below
Moonshot's observed cap (community measurements: a 13.7 KB schema is accepted
while a ~15 KB one is rejected). The setting is read at startup; restart the
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. The default
value `full` keeps the complete advertisement unchanged. Compact presentation
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. Explicitly select
`full` to retain complete advertised input guidance and nested validation detail.
Existing explicit profile choices are preserved. Compact presentation
also reduces schemas whose expanded JSON tree exceeds ten levels, the limit
reported by strict-depth MCP clients in [#2813](https://github.com/morluto/rea/issues/2813).
WorkBuddy/CodeBuddy gateways have also rejected full REA catalogs with an
+43 -6
View File
@@ -202,8 +202,38 @@ Function dossiers retain this same string evidence. Native call edges retain
Hopper's partial `CallReference` classification and exact endpoints; detailed
reference flags remain unavailable rather than being invented.
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
five-second matching deadline. Deadline or cancellation stops matching while
leaving the Hopper API available. Literal mode retains Unicode casefold matching.
five-second matching deadline and a worker old-generation heap. Deadline or
cancellation stops matching while leaving the Hopper API available. This
release has no runtime override for that deadline. The worker requests 128
MiB. A positive Node.js `--max-old-space-size` on the process that starts
REA, including `NODE_OPTIONS`, overrides that request and sets the effective
heap. `--max-old-space-size-percentage` takes precedence over that flag in
either order. REA does not turn the percentage into a MiB, so the effective
heap stays unestablished instead of the 128 MiB request or the absolute flag.
The reported `NODE_OPTIONS` value is the process initial environment when that
block can be read. A later edit to the environment is not the value Node
applied, and an unreadable initial value leaves the effective heap
unestablished. The launch Node options are the initial command line on Linux
when that buffer still has a program entry, not a later edit to
`process.execArgv`. A preload can clear that array after V8 has applied the
flags. Setting `process.title` or `--title` overwrites the command line, so a
buffer without a program entry leaves the effective heap unestablished.
`--env-file` or `--env-file-if-exists` can supply `NODE_OPTIONS`
when the initial environment has none. That file is not part of the initial
environment block, so the effective heap stays unestablished instead of the
128 MiB request. An initial `NODE_OPTIONS` is still used, because Node does
not let the file replace it. A positive `--max-heap-size`, including the
underscore form and launch `NODE_OPTIONS` on Node versions that allow it,
sets the total heap rather than the old generation and can be lower than the
128 MiB request. The effective old generation stays unestablished while that
value is in effect. A zero or empty value clears it.
`--experimental-config-file` or `--experimental-default-config-file` can set
`nodeOptions`, including a heap percentage, from a file that is not read
back. The effective heap stays unestablished when either flag is present,
including when an absolute `--max-old-space-size` is also on the command
line or in the recovered `NODE_OPTIONS`. Node accepts the config path as
`=` or the next argument.
Literal mode retains Unicode casefold matching.
## Bounded static traces
@@ -302,8 +332,13 @@ Binary plist inspection reads the original bytes through an owned file descripto
so conversion cannot erase control characters, fractional dates, negative zero,
or the high word of a 128-bit integer. It accepts at most 64 MiB of input and
reserves an estimated 64 MiB decode budget before parsing; this estimate is not
a hard process-memory bound. Binary-source provenance records the `file`
classification without claiming a `plutil` conversion.
a hard process-memory bound. A refused input reports non-retryable
`resource_constraint` with `reported_limits.maximum_input_bytes`,
`maximum_decoded_bytes` or, for nesting deeper than 128 levels,
`maximum_nesting_depth`. These are fixed allowances: worker limits and host
memory do not raise them, and the unchanged request fails the same way.
Binary-source provenance records the `file` classification without claiming a
`plutil` conversion.
A plist explicitly selected through the path input or as the active plist target
reports an unreadable or malformed selection as `invalid_request`. A missing or
@@ -532,8 +567,10 @@ cleanup. A later abort does not replace an earlier helper or output failure.
Cleanup failures retain their runtime owner until a confirmed cleanup retry.
Scenarios accept caller-selected action lists and accessibility node counts.
Individual waits cannot exceed the operation's 180-second deadline, and the
complete scenario result has a 64 MiB output budget. Screenshots are scaled to
One wait step stays capped at 180000 ms. That cap has no runtime override.
The operation deadline is `native_ui_deadline_ms` (omitted means 180000 ms)
and does not raise the wait-step cap. The complete scenario result has a
64 MiB output budget. Screenshots are scaled to
at most 2,048 pixels and captured only for the selected window. REA compiles one
owned helper per scenario, removes its temporary compiler cache and stops its
helper on cancellation. It does not launch or own the selected application. UI
+9
View File
@@ -545,6 +545,15 @@ missing or relative SDK/compiler path fails with an actionable prerequisite
error before compilation. An incompatible installation reports the compiler's
bounded diagnostics; REA does not repair or replace the toolchain.
The compile deadline is `REA_SWIFTC_COMPILE_TIMEOUT_MS`. An omitted value is
120,000 ms, and accepted values are integers from 1 to 2,147,483,647. REA reads
the variable from the environment that starts the process and keeps that
deadline for every helper compile in that process. Restart REA to apply a
change; the next ownership-helper compile uses the new value. A timeout names
the effective milliseconds. Invalid values fail configuration validation at
startup. Embedded compiler diagnostics are cut at 512 bytes, and this release
has no runtime override for that excerpt.
For example, select paths from your existing Xcode or Command Line Tools
installation **before** entering an enforced write boundary:
+1
View File
@@ -267,6 +267,7 @@ npx rea-agents@latest setup
- [التثبيت والإعداد](../installation.md): تسجيل الوكلاء، وتهيئة الموفّرين، والتحديثات، وإلغاء التثبيت.
- [الجاهزية واستكشاف الأخطاء](../installation.md#check-readiness-for-your-task): تشخيص وكيل أو محرك تحليل محدد.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI وEvidence](../cli.md): الأوامر، واختيار الموفّر، واللقطات، والاستيراد/التصدير، وحالات الخروج.
- [عقود MCP](../mcp-contracts.md) و[تعليمات الوكلاء](../mcp-prompts.md): نتائج الأدوات، والجلسات، والاستقصاءات الموجّهة.
+1
View File
@@ -265,6 +265,7 @@ Beginne mit den [praxisnahen Anleitungen](https://rea.tools/de/guides/) auf der
- [Installation und Einrichtung](../installation.md): Agentenregistrierung, Provider-Konfiguration, Updates und Deinstallation.
- [Bereitschaft und Fehlerbehebung](../installation.md#check-readiness-for-your-task): Diagnose eines bestimmten Agenten oder einer Analyse-Engine.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI und Evidence](../cli.md): Befehle, Provider-Auswahl, Snapshots, Import/Export und Exit-Status.
- [MCP-Kontrakte](../mcp-contracts.md) und [Agenten-Prompts](../mcp-prompts.md): Werkzeugergebnisse, Sitzungen und geführte Untersuchungen.
+1
View File
@@ -278,6 +278,7 @@ npx rea-agents@latest setup
- [Εγκατάσταση και ρύθμιση](../installation.md): καταχώριση agents, ρύθμιση παρόχων ανάλυσης, ενημερώσεις και απεγκατάσταση.
- [Έλεγχος ετοιμότητας και αντιμετώπιση προβλημάτων](../installation.md#check-readiness-for-your-task): διάγνωση ενός agent ή μιας μηχανής ανάλυσης.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI και Evidence](../cli.md): εντολές, επιλογή παρόχου, στιγμιότυπα, εισαγωγή/εξαγωγή και κωδικοί εξόδου.
- [Προδιαγραφές MCP](../mcp-contracts.md) και [prompts για agents](../mcp-prompts.md): αποτελέσματα εργαλείων, συνεδρίες και καθοδηγούμενες διερευνήσεις.
+1
View File
@@ -265,6 +265,7 @@ Empieza con las [guías prácticas](https://rea.tools/es/guides/) del sitio web.
- [Instalación y configuración](../installation.md): registro de agentes, configuración de proveedores, actualizaciones y desinstalación.
- [Preparación y resolución de problemas](../installation.md#check-readiness-for-your-task): diagnóstico de un agente o motor de análisis concreto.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI y Evidence](../cli.md): comandos, selección de proveedores, instantáneas, importación/exportación y estados de salida.
- [Contratos de MCP](../mcp-contracts.md) y [prompts para agentes](../mcp-prompts.md): resultados de herramientas, sesiones e investigaciones guiadas.
+1
View File
@@ -250,6 +250,7 @@ npx rea-agents@latest setup
- [نصب و راه‌اندازی](../installation.md): ثبت ایجنت، پیکربندی ارائه‌دهنده، به‌روزرسانی و حذف نصب.
- [آمادگی و رفع اشکال](../installation.md#check-readiness-for-your-task): عیب‌یابی یک ایجنت یا موتور تحلیل.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI و شواهد](../cli.md): دستورات، انتخاب ارائه‌دهنده، snapshotها، ورود/خروج داده و وضعیت‌های خروج.
- [قراردادهای MCP](../mcp-contracts.md) و [پرامپت‌های ایجنت](../mcp-prompts.md): نتایج ابزارها، نشست‌ها و بررسی‌های هدایت‌شده.
+1
View File
@@ -269,6 +269,7 @@ Commencez par les [guides pas à pas](https://rea.tools/fr/guides/) du site web.
- [Installation et configuration](../installation.md) : enregistrement des agents, configuration des fournisseurs, mises à jour et désinstallation.
- [Vérification et dépannage](../installation.md#check-readiness-for-your-task) : diagnostiquer un agent ou un moteur d'analyse donné.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI et Evidence](../cli.md) : commandes, choix du fournisseur, snapshots, import/export et codes de sortie.
- [Contrats MCP](../mcp-contracts.md) et [prompts pour agents](../mcp-prompts.md) : résultats des outils, sessions et investigations guidées.
+1
View File
@@ -290,6 +290,7 @@ Untuk mengetahui opsi, prasyarat, dan kontrak hasil secara tepat:
- [Instalasi dan penyiapan](../installation.md): pendaftaran agen, konfigurasi penyedia, pembaruan, dan penghapusan instalasi.
- [Kesiapan dan pemecahan masalah](../installation.md#check-readiness-for-your-task): mendiagnosis satu agen atau mesin analisis.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI dan Bukti](../cli.md): perintah, pemilihan penyedia, snapshot, impor/ekspor, dan status keluar.
- [Kontrak MCP](../mcp-contracts.md) dan [prompt agen](../mcp-prompts.md): hasil alat, sesi, dan investigasi terpandu.
+1
View File
@@ -269,6 +269,7 @@ Inizia dalle [guide pratiche](https://rea.tools/it/guides/) del sito web. Per op
- [Installazione e configurazione](../installation.md): registrazione agente, configurazione provider, aggiornamenti e disinstallazione.
- [Pronto all'uso e risoluzione dei problemi](../installation.md#check-readiness-for-your-task): diagnostica un agente o un motore di analisi.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI ed Evidence](../cli.md): comandi, selezione provider, snapshot, import/export e stati di uscita.
- [Contratti MCP](../mcp-contracts.md) e [prompt agente](../mcp-prompts.md): risultati degli strumenti, sessioni e indagini guidate.
+1
View File
@@ -267,6 +267,7 @@ npx rea-agents@latest setup
- [インストールとセットアップ](../installation.md):エージェントの登録、プロバイダーの設定、更新、アンインストール。
- [準備状況の確認とトラブルシューティング](../installation.md#check-readiness-for-your-task):特定のエージェントや分析エンジンを診断。
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI と Evidence](../cli.md):コマンド、プロバイダーの選択、スナップショット、インポート/エクスポート、終了ステータス。
- [MCP 契約](../mcp-contracts.md)と[エージェント向けプロンプト](../mcp-prompts.md):ツールの結果、セッション、ガイド付き調査。
+1
View File
@@ -280,6 +280,7 @@ npx rea-agents@latest setup
- [설치 및 설정](../installation.md): 에이전트 등록, 제공자 설정, 업데이트, 제거.
- [준비 상태 확인 및 문제 해결](../installation.md#check-readiness-for-your-task): 특정 에이전트 또는 분석 엔진 진단.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI 및 Evidence](../cli.md): 명령, 제공자 선택, 스냅샷, 가져오기/내보내기, 종료 상태.
- [MCP 계약](../mcp-contracts.md) 및 [에이전트 프롬프트](../mcp-prompts.md): 도구 결과, 세션, 안내에 따른 조사.
+1
View File
@@ -283,6 +283,7 @@ npx rea-agents@latest setup
- [Installation and setup](../installation.md): agent registration, provider configuration, updates, uninstall.
- [Readiness and troubleshooting](../installation.md#check-readiness-for-your-task): ഒരു agent-ന്റെയോ analysis engine-ന്റെയോ പ്രശ്നങ്ങൾ കണ്ടെത്തുക.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI and Evidence](../cli.md): commands, provider തിരഞ്ഞെടുക്കൽ, snapshots, import/export, exit statuses.
- [MCP contracts](../mcp-contracts.md), [agent prompts](../mcp-prompts.md): tool results, sessions, guided investigations.
+1
View File
@@ -265,6 +265,7 @@ Zacznij od [przewodników z przykładami](https://rea.tools/pl/guides/) na stron
- [Instalacja i konfiguracja](../installation.md): rejestracja agentów, konfiguracja dostawców analizy, aktualizacje i odinstalowanie.
- [Gotowość i rozwiązywanie problemów](../installation.md#check-readiness-for-your-task): diagnostyka jednego agenta lub silnika analizy.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI i Evidence](../cli.md): polecenia, wybór dostawcy, migawki, import/eksport i kody zakończenia.
- [Kontrakty MCP](../mcp-contracts.md) i [prompty dla agentów](../mcp-prompts.md): wyniki narzędzi, sesje i prowadzone analizy.
+1
View File
@@ -265,6 +265,7 @@ Comece pelos [guias práticos](https://rea.tools/pt-BR/guides/) do site. Para op
- [Instalação e configuração](../installation.md): registro de agentes, configuração de provedores, atualizações e desinstalação.
- [Prontidão e solução de problemas](../installation.md#check-readiness-for-your-task): diagnóstico de um agente ou motor de análise específico.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI e Evidence](../cli.md): comandos, seleção de provedores, snapshots, importação/exportação e status de saída.
- [Contratos MCP](../mcp-contracts.md) e [prompts para agentes](../mcp-prompts.md): resultados das ferramentas, sessões e investigações guiadas.
+1
View File
@@ -265,6 +265,7 @@ npx rea-agents@latest setup
- [Установка и настройка](../installation.md): регистрация агентов, настройка провайдеров, обновление и удаление.
- [Готовность и устранение неполадок](../installation.md#check-readiness-for-your-task): диагностика конкретного агента или движка анализа.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI и Evidence](../cli.md): команды, выбор провайдера, снимки, импорт/экспорт и коды завершения.
- [Контракты MCP](../mcp-contracts.md) и [промпты агентов](../mcp-prompts.md): результаты инструментов, сеансы и исследования с подсказками.
+1
View File
@@ -265,6 +265,7 @@ npx rea-agents@latest setup
- [การติดตั้งและตั้งค่า](../installation.md): การลงทะเบียนเอเจนต์ การตั้งค่าผู้ให้บริการ การอัปเดต และการถอนการติดตั้ง
- [ความพร้อมใช้งานและการแก้ไขปัญหา](../installation.md#check-readiness-for-your-task): ตรวจวินิจฉัยเอเจนต์หรือเอนจินวิเคราะห์หนึ่งตัว
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI และ Evidence](../cli.md): คำสั่ง การเลือกผู้ให้บริการ สแนปช็อต การนำเข้า/ส่งออก และรหัสการสิ้นสุด
- [สัญญา MCP](../mcp-contracts.md) และ[พรอมป์ต์สำหรับเอเจนต์](../mcp-prompts.md): ผลลัพธ์เครื่องมือ เซสชัน และการตรวจสอบตามคำแนะนำ
+1
View File
@@ -270,6 +270,7 @@ Web sitesindeki [uygulamalı rehberlerden](https://rea.tools/tr/guides/) başlay
- [Yükleme ve kurulum](../installation.md): ajan kaydı, sağlayıcı yapılandırması, güncellemeler ve kaldırma.
- [Hazırlık ve sorun giderme](../installation.md#check-readiness-for-your-task): belirli bir ajanı veya analiz motorunu tanılama.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI ve Evidence](../cli.md): komutlar, sağlayıcı seçimi, anlık görüntüler, içe/dışa aktarma ve çıkış durumları.
- [MCP sözleşmeleri](../mcp-contracts.md) ve [ajan istemleri](../mcp-prompts.md): araç sonuçları, oturumlar ve yönlendirmeli araştırmalar.
+1
View File
@@ -265,6 +265,7 @@ npx rea-agents@latest setup
- [Встановлення й налаштування](../installation.md): реєстрація агентів, конфігурація провайдерів, оновлення та видалення.
- [Готовність та усунення неполадок](../installation.md#check-readiness-for-your-task): діагностика конкретного агента або рушія аналізу.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI та Evidence](../cli.md): команди, вибір провайдера, знімки, імпорт/експорт і статуси завершення.
- [Контракти MCP](../mcp-contracts.md) і [промпти агентів](../mcp-prompts.md): результати інструментів, сеанси й керовані дослідження.
+1
View File
@@ -265,6 +265,7 @@ Bắt đầu với [hướng dẫn thực hành](https://rea.tools/vi/guides/) t
- [Cài đặt và thiết lập](../installation.md): đăng ký tác nhân, cấu hình trình phân tích, cập nhật và gỡ cài đặt.
- [Kiểm tra mức sẵn sàng và khắc phục sự cố](../installation.md#check-readiness-for-your-task): chẩn đoán một tác nhân hoặc bộ máy phân tích cụ thể.
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI và Evidence](../cli.md): lệnh, lựa chọn trình phân tích, ảnh chụp trạng thái, nhập/xuất và trạng thái thoát.
- [Hợp đồng MCP](../mcp-contracts.md) và [prompt cho tác nhân](../mcp-prompts.md): kết quả công cụ, phiên làm việc và điều tra có hướng dẫn.
+1
View File
@@ -271,6 +271,7 @@ npx rea-agents@latest setup
- [安裝與設定](../installation.md):AI 代理註冊、提供者組態、更新和卸載。
- [就緒檢查與故障排查](../installation.md#check-readiness-for-your-task):診斷特定 AI 代理或分析引擎。
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI 與 Evidence](../cli.md):命令、提供者選擇、快照、匯入/匯出和退出狀態。
- [MCP 契約](../mcp-contracts.md)和[AI 代理提示詞](../mcp-prompts.md):工具結果、會話和引導式調查。
+1
View File
@@ -271,6 +271,7 @@ npx rea-agents@latest setup
- [安装与设置](../installation.md):智能体注册、提供方配置、更新和卸载。
- [就绪检查与故障排查](../installation.md#check-readiness-for-your-task):诊断特定智能体或分析引擎。
- [CUDA / HIP](../gpu-code-objects.md)
- [GGUF / safetensors](../model-artifacts.md)
- [CLI 与 Evidence](../cli.md):命令、提供方选择、快照、导入/导出和退出状态。
- [MCP 契约](../mcp-contracts.md)和[智能体提示词](../mcp-prompts.md):工具结果、会话和引导式调查。
@@ -320,6 +320,13 @@ close a `packaged-process` obligation. A static-only source cannot close a
runtime or process claim. Marking a claim `blocked` or `out-of-scope` records the
disposition but does not silently count it as verified.
The `summary` counts obligations. `by_status`, `by_application_layer`, and
`by_source_state` place each obligation in exactly one bucket.
`by_evidence_authority` groups obligations by the Evidence `authority` of their
`authority_references`: an obligation counts once under each distinct authority
it cites, so those counts can exceed `total`. A reference's own `state` and the
obligation's `source_state` do not affect its authority bucket.
The tool returns every obligation inline. Its `ledger_id` and `closure_digest`
identify the complete result, including summaries, reports, ownership and
dependency graphs, Evidence links, and limitations.
+11 -6
View File
@@ -27,11 +27,13 @@ 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.
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.
`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.
## 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. 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 16 MiB and explicitly marked when truncated.
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. 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 16 MiB and explicitly marked when truncated. GDB startup waits 10000 ms and each MI command waits 120000 ms. This release has no runtime override for those deadlines, the 16 MiB command output budget, session capacity, or the pending-command queue. `command_timeout_ms` does not change them.
Before a normal close, REA checks for live GDB thread groups. If one remains and a durable `inferior_tty` is verified, REA asks GDB to detach it and confirms the detach before stopping the debugger. Without that sink, select and apply a disposition to each inferior through `gdb_console` (for example, detach or kill as appropriate), then retry close. REA never infers target ownership from a PID or path. When the MCP server shuts down, REA terminates only its directly owned GDB process after confirming no inferior or a sink-backed detach; otherwise it retains the process and its output streams for retry.
@@ -49,7 +51,7 @@ close_gdb_session {"session_id":"…"}
Persistent command Evidence retains the caller-selected and resolved startup paths in `parameters.launch_provenance`. These paths describe launch provenance, not the current target after unrestricted commands. `rizin_debug_session_status.output_invalid_utf8` reports whether lossy frame decoding has been observed during the session. Command Evidence sets `output_invalid_utf8` only when loss was observed in frames collected for that command, including retained partial output; earlier startup or command frames do not mark later valid output as lossy.
Session status retains up to 64 recent protocol frames. Each history entry keeps a complete-character UTF-8 prefix of at most 16 KiB, plus a truncation marker when needed; `recent_output_truncated` reports omitted history. This history budget also accounts for replacement characters from invalid UTF-8. It does not shorten command Evidence, which has its own 16 MiB output budget.
Session status retains up to 64 recent protocol frames. Each history entry keeps a complete-character UTF-8 prefix of at most 16 KiB, plus a truncation marker when needed; `recent_output_truncated` reports omitted history. This history budget also accounts for replacement characters from invalid UTF-8. It does not shorten command Evidence, which has its own 16 MiB output budget. Rizin debug startup waits 30000 ms and each command frame waits 120000 ms. This release has no runtime override for those deadlines, the 16777216 byte frame cap, the 16777216 byte command-output cap, the 4096 frame command cap, session capacity, or the pending-command queue. Evidence names the cap that stopped the command. The one-shot `command_timeout_ms` setting does not change a debug session.
```sh
rea debug-with-rizin --inferior-tty /dev/pts/7 ./program dr
@@ -61,7 +63,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.
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.
## Cutter bridge
@@ -70,8 +72,11 @@ The client and plugin must support the current v2 handshake; older peers are
rejected before dispatch because they cannot preserve cross-caller recovery proof.
The client checks the final encoded request against the bridge's 16 MiB frame
limit before dispatch; an oversized command does not retain a cleanup owner.
Discovery admits at most 256 descriptor candidates and reads and probes at most
eight concurrently. A larger directory reports `bridge_directory_capacity_exceeded`.
Each bridge request waits 25000 ms. Discovery admits at most 256 descriptor
candidates and reads and probes at most eight concurrently. A larger directory
reports `bridge_directory_capacity_exceeded`. This release has no runtime
override for the 25000 ms request deadline, the 16777216 byte frame, or the
256-candidate discovery budget.
Terminal receipts are recovered before another command, including from a new CLI
invocation. A signed status response also confirms completion of older bridge
+3
View File
@@ -126,6 +126,9 @@ matching help retains original one-based line numbers. Empty query selects all.
returns all remaining matches; CLI `--all` and `--limit` conflict.
The window reports examined, matching and returned records and `has_more`.
A filtered empty page is not evidence that the installation has no decoders.
When sigrok reports a selected decoder ID as not installed, help and decoding
return `invalid_request` naming `decoder` or `decoders`; omit `decoder` to list
installed IDs. An installed decoder that fails to load remains an execution failure.
Discovery refuses more than 65,535 line separators before constructing record
objects (65,536-line representation budget), in addition to the 4 MiB output cap.
@@ -0,0 +1,18 @@
# Plan
1. Move tool naming (`parameterNames`, `pathTemplate`, `toolName`,
`compareTools`) from `environmentManifestRules.ts` to a new
`environmentManifestNames.ts`, so the rules module has room for the rule.
2. Add `prefix-variance` to `TEMPLATE_RULES`. A `prefix-variance` parameter
records the rule it shares (`sharedRule`), which names it and keys it.
3. Add the pass after `sibling-variance`: walk positions in order, index the
opt-in parameters at each position by their preceding parts, template
qualifying literals, then rebucket by naming key, keeping a tool's own rule
for each merged position.
4. Share the 32-segment split between the three comparing passes and list the
segment-limited rules once for the limitation.
5. Document the rule in the view schema description and in
`docs/web-network-captures.md`.
6. Tests: shared positions across methods and depths, the spelling condition,
merges that keep a tool's own rule, pairs, format-rule positions and the
segment limit.
@@ -0,0 +1,59 @@
# Share templated positions across tools
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
A session usually edits one scratch entity: it creates it, then updates and
deletes it at a fixed path such as `/api/v1/collections/0RZ576QZQA5KG`. The
manifest templates a segment only when sibling routes of the same method vary
there, so those writes stay literal tools (`patch_collections_0rz576qzqa5kg`)
even though the reads of the same collection were templated as
`/api/v1/collections/{collection_id}`. A literal write tool addresses one
entity that may not exist in a later state, and nothing can model its effect.
A new opt-in template rule, `prefix-variance`, templates such a literal with
the parameter another tool already uses at that position, so the write becomes
`patch_collections_by_collection_id` on the same template as the read.
## Requirements
- `prefix-variance` applies only when listed, after every other opt-in rule, so
it can share the parameters they produce.
- A literal at position `p` is templated when another tool on the same origin,
of any method and any segment count, has a parameter at `p` from one of the
other opt-in rules, after the same preceding parts. Parameters in the
preceding parts compare by the rule that names them, so a position templated
by `prefix-variance` matches the parameter it shares.
- The literal qualifies when it is one of the values such a parameter
observed at that position, when it carries a digit and has the length and
character classes (digits, lower case, upper case, each other character) of
one of them, or when every value the parameter observed, one or more, has one
length of eight or more characters, as generated identifiers often do, and
the literal has that length and uses only their character classes, so an
identifier that happens to hold no digit still qualifies. An empty literal, a
stop word or a `v1`-style version never qualifies. Each parameter's spelling
is summarised once, so a literal is checked without rescanning its values,
and a value longer than 128 characters is compared only exactly, so a huge
segment costs no classification.
- Positions templated by the four format rules are never shared, so
`/api/teams/{numeric}` still absorbs no literal.
- Values stay with the parameter that observed them: when parameters of
different rules share a position, the literal shares the one that observed
it, else the earliest one it is spelled like.
- The parameter takes the name of the parameter it shares (`collection_id`,
`uid`, a pair's `_namespace` and `_name`) and is labelled `prefix-variance`.
- Tools whose routes now match, by method and by the rule that names each
parameter, merge. A merged parameter keeps the rule of a tool that templated
that position itself, when one did.
- Like the sibling rules, it compares routes of at most 32 segments and says so
in a limitation when a longer route exists.
## Non-goals
- No change to the default rules or to any other rule: manifests that do not
list `prefix-variance` are byte-for-byte unchanged.
- No templating from response bodies, such as an id a create returned; the
manifest reads request lines only.
- No change to which groups are excluded: a write whose response has no body
or content type outside `/api/` is still excluded as `non-json`.
@@ -0,0 +1,10 @@
# Tasks
- [x] Record the outcome, requirements and non-goals.
- [x] Move tool naming into its own module.
- [x] Add the `prefix-variance` rule, its names, its merge and its limitation.
- [x] Document the rule in the schema and the capture guide.
- [x] Add rule tests.
- [x] Run static, formatting, duplication, documentation and test gates.
- [x] Project the 51 corpus captures with and without the rule and record the results.
- [x] Rebuild five randomly drawn environments end to end and compare them with their published versions and the live applications.
@@ -0,0 +1,149 @@
# Verification
Verified base: 33b0643e on main 1629266e (the rule, the alias removal `knip`
asked for, and the fixed-length clause the end-to-end runs below led to). Executed on macOS with Node v22.23.1 (the repository pins
24.18.0 in `.nvmrc`, which was not installed).
## Gates
- `npm run build:cached`, `npm run check:fast` (typecheck and lint),
`npm run format:check`, `npx knip`, `npm run scan:todos`,
`npm run docs:check`, `npm run verify:test-discovery` and
`npm run mcp-catalog:check`: passed.
- `npm run jscpd`: 3.49% duplicated lines over 3561 files with 3067 clones;
main measures 3.49% with 3068, since the shared segment-limit split removes
one clone.
- oxlint reports no new finding on the changed files. The manifest accumulator
keeps main's three warnings (file length, and the complexity and parameter
count of its record append), one line longer.
- `npx vitest run --project domain --project services`: 7119 passed, 5
pre-existing skips.
- `npm run test:prepare`, then `npm run test:focused --` the analysis-view MCP
and CLI boundaries, the compact input schema and tool schema validity
boundaries, and the environment serve and export boundaries:
7 files, 76 passed.
New tests in `environmentManifestView.test.ts` cover:
- a literal shared across methods and depths, and the same capture without the rule;
- the spelling condition: same classes and length, case, missing digit, a colon word, a version;
- a digit-free member of a fixed-length identifier family, against a shorter word, a hyphenated
value and a family shorter than eight characters;
- merges that keep a tool's own rule, including a sibling-variance parameter under a shared one;
- both halves of a templated pair;
- format-rule positions and the segment limit.
## The showcase captures
The 50 showcase captures (45 HAR inspections and 5 `capture_browser_scenario`
captures) were projected with main's build and this branch's.
- Without the rule, both builds give identical results for every capture: the
manifest with no rules, the manifest with `after-uid-literal`,
`opaque-token`, `sibling-variance` and `sibling-pair-variance`, and the
environment-state view. 150 of 150 outputs hash equal.
- With `prefix-variance` added to `after-uid-literal`, `opaque-token` and
`sibling-variance`, 69 literal tools in 10 captures are templated, 25 of them
writes:
- Komga: collection and readlist `PATCH` and `DELETE`, book pages.
- n8n: workflow `PATCH`, `DELETE` and archive, and the reads of a workflow
whose id holds no digit.
- Memos: memo `PATCH` and `DELETE`, user settings reads, and the reads of
two memos whose ids hold no digit.
- Navidrome: playlist `PUT` and `DELETE`.
- Mealie: recipe `PATCH` and `DELETE`.
- Grist: document `PATCH` and `DELETE`, and table record writes.
- Redpanda Console: subject and topic writes.
- Sentry: release `PUT` and `DELETE`.
- Shlink: short URL `PATCH`.
- Mattermost: `me` reads, which `sibling-variance` already folds into the
user parameter.
Tools go from 1591 to 1578, because Redpanda Console's per-subject reads and
the per-memo and per-workflow reads merge.
- Literals the rule leaves alone, as intended, include Espo CRM `layout`,
Federal Register `facets`, Ghost `slug`, Shlink `stats`, Lychee
`Metrics::photo` and Vault's path names.
- The environment-state view succeeds on every capture with the rule. Its types
move to the templated collections (531 become 526 as literal ones merge); the
one fewer item view is the Redpanda Console merge.
With the write plans of #5600 (its branch with this commit and main merged in,
locally):
- Served write tools rise from 33 to 39: Komga collection create, update and
delete; n8n workflow delete; Sentry release update and delete.
- Write tools with no state target fall from 102 to 87.
- The newly modelled tools that stay unserved are limited by the capture:
- no read after the write: Komga readlists, Mealie, Navidrome, Redpanda Console;
- n8n's item read shows an expanded `shared` relation its create response
never returned;
- Shlink returns `tags` sorted while the update echoes the request's order.
## Five environments end to end
Five of the ten environments the rule changes were drawn at random, redrawing
until the draw held one whose new writes are served: Komga, Mealie, n8n,
Sentry and Shlink. Each was rebuilt on its own Linux VM with `npm pack` of
#5600, main and this branch merged locally, from the same scoped HAR as its
published version: manifest with `prefix-variance`, exported bundle, smoke
task, the generated retrieval tasks for `gpt-oss-120b` and `GLM-5.3-Flash`, and
a dry-run dataset. Where writes are served, hand-written write tasks ask for a
change in plain language and are graded from the environment's state through
`data/get`. The first n8n run found that a digit-free workflow id stayed
literal; the fixed-length clause fixed it, and n8n was rebuilt.
| Environment | Tools (published → rebuilt) | Served from state (reads / writes) | Retrieval gpt-oss | Retrieval GLM | Writes gpt-oss | Writes GLM |
| ----------- | --------------------------- | ---------------------------------- | ----------------- | ------------- | -------------- | ---------- |
| Komga | 42 → 46 | 26 / 3 | 12/12 (was 11/12) | 12/12 (11/12) | 3/4 | 4/4 |
| Mealie | 39 → 39 | 36 / 0 | 12/12 (12/12) | 12/12 (12/12) | – | – |
| n8n | 30 → 33 | 17 / 1 | 9/9 (9/9) | 9/9 (9/9) | 2/2 | 2/2 |
| Sentry | 34 → 34 | 26 / 3 | 12/12 (12/12) | 11/12 (12/12) | 2/4, rerun 0/4 | 4/4 |
| Shlink | 17 → 17 | 5 / 0 | 5/7, rerun 7/7 | 7/7 (7/7) | – | – |
Every smoke task passed. The misses:
- Sentry writes with gpt-oss: every failed episode passed `id: "sentry"` for
the `/api/0/` segment the tool schema describes as `observed: 0`, so its calls
addressed `/api/sentry/…`; every passing episode, and every GLM episode, passed
`0`. The served tools work; the format-rule parameter for a segment that only
ever held `0` invites the guess.
- Komga update with gpt-oss and Shlink's first gpt-oss run: the model provider
rejected tool calls the model generated (`null` and `undefined` for optional
query arguments) before the environment saw them.
- Sentry `qa-03` with GLM: the model never called the alert-rules tool.
The same calls were also replayed against the live applications, seeded by
the same scripts (ids differ, so each side maps its ids to names):
- Komga 1.29.1, 9 of 12 steps equal: create, update and delete of a
collection and the reads after them agree except for the generated id. The
collection list after a create omits the new collection, because that list
keeps its recorded membership, and the 404 for a deleted collection replays
the recorded body with its path and timestamp.
- n8n 1.123.85, 3 of 8 steps equal: n8n refuses to delete a workflow that is
not archived (400) and the environment deletes it; the workflow list replays
its recorded response, so a delete does not show in it; archiving is not
served. The read of the digit-free workflow failed in the first build (the
state held only its list fields) and agrees in the second, apart from ids the
seed generates.
## After the review round
The branch merged main at 52c84f6f, then kept each shared position's values
with the parameter that observed them (one donor per naming rule, the literal
taking the donor that observed it, else the earliest it is spelled like),
summarised each donor's spelling once, and let one observation form a
fixed-length family, as the requirement states. Two new tests, a mixed-rule
position and a single observed workflow id, fail on the previous head and pass
here. The gates above passed again (domain and services 7255 passed with 5
pre-existing skips; the focused boundaries 76; `jscpd` 3.49%). Over the 50
showcase captures the tools with and without `prefix-variance` are unchanged,
name for name: 69 literal tools templated, 25 of them writes.
A second review round bounded the spelling summary: characters are classified
in one pass, and a value longer than 128 characters is compared only exactly.
Its test fails on the previous head, the gates passed again (domain and
services 7257, focused boundaries 76, `jscpd` 3.49%), and the census over the 50
captures is again unchanged.
@@ -0,0 +1,32 @@
# Implementation plan
1. In `environmentStateRules.ts`, parse pointers into tokens and let `at` and
`replaceAt` read and replace element 0 of a one-element array at the root
or in one member; extend `locateItem` with those forms after the object
forms, requiring an identity name there; add `identityCandidates`, the
identity names then suffix-named fields, and let `locateList` prefer arrays
identified by name, then arrays of two or more items identified by suffix.
2. In `EnvironmentStateExtraction.ts`, plan lists with the shared candidates,
treat an empty recording as unconstraining, accept a suffix-named field only
once a recording shows it distinct across two or more items, leave reads
without a list or identity to documents, drop the written-path requirement
and the `not-entity` fallback, record `path-varies` apart from
`query-varies`, and keep each entity's earliest sighting;
`environmentStateMerge.ts` cites records in timeline order. Remove
`not-entity` from the reason type and result schema, add `path-varies`, and
describe the new pointer forms and `field-name` basis.
3. Move the classification of retained bodies (none, base64, cut, JSON, text)
into `replayedBody` in `environmentSource.ts`, which owns recorded
exchanges; replay renders from it and tool descriptions read its JSON case.
4. Make `environmentToolDefinitions` take the `EnvironmentSource`; describe the
first `2xx` JSON record, else the first JSON record labelled with its status,
one level deep with the state's list rule and the existing sample budget;
list the manifest tool's statuses; and charge the whole description and the
tool's MCP annotations to the catalog reservation. Update the serve and
bundle callers.
5. Let the tool definitions own the MCP annotations derived from the method and
the query-`POST` rule; `serveEnvironment.ts` registers them and the card
leaves them out.
6. Cover domain, services, adapters and the compiled MCP boundary; document the
rules in the capture guide, descriptions and hints in the agent environments
guide, and the contract text.
@@ -0,0 +1,99 @@
# Wider environment state coverage and tool descriptions
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
The `environment-state` view backs more read tools with state, and a served
environment tells agents more about each tool. Read-only paths whose responses
are not entities become documents, listed items may be identified by an
`…Id` or `…_id` field, an item wrapped in a one-element array is located
inside it, and reads that vary by path get their own recorded reason. Each tool description says what the tool returns and which statuses
were recorded, and every MCP tool carries method hints. This increment adds no
tool, view kind, provider, network activity or write effect.
## Requirements
- A `GET` list tool (its template does not end in a parameter) that yields no
item or list plan holds its latest initial value as a `document` when its
initial reads have one request line, whether or not the capture writes its
path, unless its value changed between two of those reads across any
recorded write (even under another root), which leaves it `fields-differ`. A
value whose rendered JSON exceeds 256 KiB stays `rendered-oversize` and is
replayed from the recorded body, which itself fit. More request lines give
`path-varies` when they differ by path (a template with a parameter before
its end) and `query-varies` when they share one path. With every such read
now a document, `path-varies`, `query-varies`, `body-unusable` or
`rendered-oversize`, the unreachable `not-entity` reason leaves the result
schema, and `no-identity` names item tools only. A document holds the value
of its one `path`.
- When no path parameter proves an identity and no identity name identifies
the listed items, the `field-name` identity is the first key of the first
listed item, in its order, whose name ends in `Id` or `_id`, that every item
carries as a distinct string or number, and that at least one initial
recording shows distinct across two or more items. The last condition goes
beyond "every item carries it distinctly": one item satisfies that
vacuously, and a lone item's suffix-named fields are often foreign keys (a
reaction keyed by its user, a comment by its post), so a list that only ever
held one item stays a document. Empty recordings constrain no field.
Identity names keep precedence, also when choosing among an envelope's
arrays, where a suffix-named field identifies only an array of two or more
items.
- An item response that is a root array of exactly one object, or an object
with one member holding such an array, locates its entity at `/0` or
`/<member>/0` when one of the identity names in that object repeats the path
value. Objects at the root or in one member keep precedence. `at` and
`replaceAt` read and replace exactly these forms besides the root and one
member, so the gate and renderers run unchanged.
- An entity's state order is its earliest sighting on the timeline and it cites
its records in timeline order, whichever plan observed it first.
- Each tool description reads `METHOD template; <returns>; recorded statuses
…; replays retained records […]; evidence ev_…` after the untrusted-catalog
warning. `<returns>` describes one level deep the first cited `2xx` record
replay answers as JSON: an object's fields, an array of objects' fields, or
an envelope whose member lists objects, where a list is an array of objects
found by the state's list rule; an empty field set reads `with no fields`.
Only when no `2xx` record replays as JSON does the first cited JSON record of
another status give it, labelled `status <code> returns …`; with no JSON
record it is omitted. The statuses are the manifest tool's `statuses`, the
one owner of that set (`no recorded status` when it is empty). Field names
and statuses are sampled within the description sample budget and charged
with the whole description to the catalog reservation before it is retained.
One owner in the environment source classifies each retained body as none,
base64, cut, JSON or text; replay answers from that classification and
descriptions read only its JSON case.
- MCP tools carry `readOnlyHint` (true for the safe methods `GET`, `HEAD`,
`OPTIONS` and `TRACE`, and for query `POST`s),
`destructiveHint: true` for `DELETE` (unset for other writes, so the MCP
default applies, and for reads, where clients ignore it), `idempotentHint`
(true for the safe methods, `PUT` and `DELETE`) and `openWorldHint: false`. The
tool definitions own the hints and charge them to the catalog reservation;
the environment card keeps its `name`, `description`, `inputSchema` tool
shape.
## Acceptance and limits
Synthetic captures cover read-only documents, `path-varies` and
`query-varies`, suffix-named identities with their key order, their
two-item proof, empty first recordings and their precedence (including the
choice between an envelope's arrays), a Ghost-like `{"posts":[...]}` item under
a trailing-slash path merging with its list view, a root one-element array, a
one-element filtered list that stays unidentified, and the pointer forms. The
Grafana-shaped fixture covers documents for its read-only paths (its one-member
team list among them) and object, array and enveloped-list descriptions;
synthetic sources cover the `2xx` preference over an earlier error body, the
first of several JSON records, the labelled fallback, the manifest's statuses,
empty, scalar and fieldless bodies, a truncated member name, a name whose
JSON quoting exceeds the sample, a recorded body under 256 KiB whose rendered
JSON exceeds it, non-JSON bodies and the catalog budget; the in-process and
compiled MCP servers advertise the
hints. A filtered list may still answer with one element whose identity name
repeats the requested value; list filters, pagination, lossless big integers
and cross-endpoint entity linking remain out of scope. Documents inline whole
recorded values and enlarge the state result; agent tokens, CPU time and peak
memory of the state and description build were not measured.
## Deferred issue criteria
Serving from the state, the AgentEnv data plane and write effects remain later
increments of #4933. This PR must reference rather than close the issue.
@@ -0,0 +1,16 @@
# Tasks
- [x] Record the coverage and description contract and its limits.
- [x] Extend pointers, item location and identity candidates.
- [x] Require two distinct items before a suffix-named field identifies a list.
- [x] Hold read-only non-entity reads as documents, retire `not-entity` and
separate `path-varies` from `query-varies`.
- [x] Order entities by earliest sighting and cite records in timeline order.
- [x] Classify retained bodies in one owner and describe tool responses,
preferring `2xx` records, with the manifest's statuses.
- [x] Advertise MCP tool annotations, charged to the catalog, without changing
the card.
- [x] Add domain, services, adapters and compiled-boundary coverage.
- [x] Document the rules, descriptions and hints; update the contract text.
- [x] Run static, formatting, duplication, documentation and test gates and the
real-capture measurement.
@@ -0,0 +1,123 @@
# Verification
Verified base: 7a668b73f4783b42eb0e8ce1eeff079f86d53e22 (the environment-state
view branch).
Executed on the implementation worktree with its existing locked installation
under Node.js v22.23.1 (the repository pins 24.18.0, which was not available);
every check below ran as written in this pass, after the last source change.
- `npm run format` then `npm run format:check`: passed.
- `npm run check:fast`: typecheck and lint passed (3959 files, no errors). The
touched files carry two lint warnings, both present on the base: `max-params`
in `environmentReplay.ts` and an unused `_` in `environmentCapacity.test.ts`.
- `npx knip`: passed (exit 0).
- `npm run jscpd`: 3.40% duplicated lines over 3348 files, under the 3.5%
gate. The three clones that touch changed test files pair the same blocks as
on the base; none involves code this change adds.
- `npm run scan:todos`: no markers.
- `npm run docs:check`: the built documentation checks and the README
consistency conformance test (5 tests) passed.
- `npx vitest run --project domain --project services`: 341 files, 5859 tests
passed, 5 pre-existing skips.
- `npm run build:cached`: passed.
- `npm run test:focused --` the environment serve adapters and capacity tests,
the environment serve MCP boundary, the environment serve and export CLI
boundaries, the environment serve shutdown process boundary, the
analysis-view MCP and CLI boundaries, the compact input-schema contract and
MCP boundary, the tool-schema validity boundary, the analysis-detail and
advertised-input-example contracts and the qlog acceptance test: 14 files,
113 tests passed, 1 pre-existing skip.
Real captures (outside the test suite): the built view ran through the
application service on the same 44 recorded captures (39 HAR, 5 browser
scenario) with all three template rules, in 7 to 50 ms each. 42 produced a
state, and the same two browser-scenario captures are refused with
`resource_constraint` as on the base. The base column is the brief's baseline,
which the same script reproduced on the base build in an earlier pass; this
pass did not rebuild the base.
| Measure | Base | This change |
| ---------------------------------- | ----- | ----------- |
| Types | 568 | 576 |
| Entities | 2,213 | 2,246 |
| Documents | 2 | 299 |
| Item views | 211 | 213 |
| List views | 497 | 504 |
| Document views | 2 | 299 |
| State-backed read tools (of 1,258) | 710 | 1,016 |
| Recorded: `not-entity` | 306 | (retired) |
| Recorded: `no-identity` | 129 | 32 |
| Recorded: `path-varies` | (new) | 32 |
| Recorded: `query-varies` | 5 | 69 |
| Recorded: `body-unusable` | 32 | 32 |
| Recorded: `list-differs` | 31 | 31 |
| Recorded: `no-init-recording` | 26 | 26 |
| Recorded: `query-body` | 12 | 12 |
| Recorded: `fields-differ` | 3 | 4 |
| Recorded: `envelope-varies` | 4 | 4 |
Seven list types gained a suffix-named identity (`permissionId`, `product_id`,
`ItemId`, `user_id`, `channel_id`, `spl_id` and `eventId`). Seven more had one
before the two-item proof and lost it, because every initial response listed a
single item: an Airbyte organization, a Mattermost reaction and team
membership, and a Redpanda broker and schema reference became documents; a
NOAA station read is `query-varies` and a Redpanda consumer read
`path-varies`. Two Ghost item tools render from `/members/0` and `/tags/0`. A
third Ghost item tool is located at `/posts/0` but stays `fields-differ`: its
recordings differ by an `include` query.
State result size, as the JSON length of each Evidence summed over the 42
states: 2,677 KiB on the base and 3,196 KiB now (+19%), measured by the same
script. The largest growth per capture is Metabase (133 to 236 KiB), Directus
(21 to 93), Redpanda Console (26 to 68) and Vault (9 to 43). Documents take
492,516 of the 3,274,243 UTF-8 bytes of the results now; the base held two.
Agent tokens, CPU time and peak memory of the state view were not measured.
Tool descriptions on the 44 captures' manifests: all 44 catalogs were admitted
within their reservation. 1,336 of 1,400 tools state what they return: 1,333
from a `2xx` record and three labelled with another status (Miniflux and
NocoDB 404, Shlink 501), because none of their records succeeded as JSON. The
summaries add 261,502 bytes to a 1,185,893-byte catalog of names, descriptions
and input schemas (median 136, 95th percentile 554, maximum 1,133 bytes per
tool); five reached the field sample bound (one Keycloak, one Lychee and three
Sentry tools with 46 to 105 fields). The MCP annotations add 113,182 bytes to
`tools/list` and none to the card. Building a catalog took a median 1.6 ms and
at most 7.6 ms (Metabase), after the source was loaded. Agent tokens, CPU time
and peak memory of the description build were not measured.
After merging the environment state view's review fixes (its branch at
6ad63bcc, which also brings main's evidence-file sources), every gate ran
again on the merged tree: `npm run check:fast`, `npm run format:check`,
`npx knip`, `npm run scan:todos` and `npm run docs:check` passed; `npm run
jscpd`: 3.39% duplicated lines over 3362 files; `npx vitest run --project
domain --project services`: 344 files, 5914 tests passed, 5 pre-existing
skips; `npm run build:cached`, then `npm run test:focused --` the serve and
tool-definition adapters, the serve MCP and CLI boundaries, the export CLI
boundary, the analysis-view MCP and CLI boundaries, the compact input-schema
budget and the analysis-detail and advertised-input-example contracts: 10
files, 98 tests passed.
The real-capture comparison was repeated on the merged code, with the base
measured on the merged state view without this change: all 44 captures now
produce a state, and 1,011 of 1,277 read tools are state-backed against 625
on the base (216 item, 416 list and 379 document views against 214, 409 and
2). Recorded reasons: `no-identity` 216 to 32 and `not-entity` 310 to none;
`query-varies` 5 to 76 and `path-varies` 36 are document reads under more
than one query or path; `list-differs` 35, `body-unusable` 33,
`no-init-recording` 28, `query-body` 12, `envelope-varies` 4 are unchanged,
and `fields-differ` is 9 to 10.
Not covered here: serving from the state, the AgentEnv data plane and write
effects, which are later increments.
## After the squash onto main
The branch was squashed onto main once the state-serve branch merged (166fb082). Against a merge of the previous head
into that main, the squash keeps both sides of every conflict and drops nothing of this branch. Two tests then failed
on it and were refitted with the oversized-document fix in 2a926a0a: the bundle test of a state catalog over the
discovery budget uses 300 read tools, inside the window where the state's catalog is refused and replay's, with its
response summaries, still fits (280–320 by a probe), and the rea-state validation test names the pushed document by
the count of documents the state holds. The replay-only card test pinned main's 11,131-byte card; it now pins this
branch's 12,818-byte card, whose descriptions name each tool's recorded responses. The branch then merged main at
d7ca93cc without conflicts.
@@ -0,0 +1,44 @@
# Implementation plan
1. Export the Evidence file authenticator from `environmentSource.ts` with a
`state` role, and validate a HAR capture there with the owned borrowing
parse so later views reuse it. Add `environmentState.ts` to check state
Evidence against a loaded source, build the served catalog (the tools a
state serves and the data plane its card declares), derive the state from
the capture through the analysis view service with the capture as retained
Evidence, and prepare it: unavailable when its catalog exceeds the
discovery budget under the served name or it cannot be derived.
2. Add `environmentStateDocument.ts` to validate `rea-state/v1` documents
against the served types and views, and `environmentStateStore.ts` for the
live store with reset, atomic replace and the `data/get` snapshot.
3. Export `selectsRecordedLine`, the body-taking `replayAnswer` and the path
rule `pathValueMatches` from `environmentReplay.ts`, export one recorded-body
predicate `stateBodyJson` from the state accumulator and `changesState` from
the state rules, and widen `collectionPathOf` to any value with a path, so
state answers reuse selection, matching, body and write rules.
4. Add `environmentStateAnswer.ts` for item, list and document answers, the
recorded fallback and the answer function serve and export share.
5. Declare the state data plane on the card in `environmentTools.ts`, taking
the data plane in an options object so the discovery budget reserves the
card and descriptions actually served.
6. Add `stateIntake.ts` for parts, files on this host and downloads
(injectable fetcher and timeout, cancellation, coarse failure categories)
and `environmentDataPlane.ts` for the three methods, run one at a time in
arrival order and cancelled by the server's stop or the caller's
departure; let `serveEnvironment.ts` serve the shared catalog and answer
function, queue every message of a batch at arrival, bound the replies of
one response and stop the data plane before closing the listener.
7. Make `environment-serve` prepare the state under its served name after
loading the source, add `--replay-only`, log an unavailable state as a
warning before binding, and report `state` in the stopped document.
8. Let `environment-export` prepare the state the same way under the bundle
name, only after the name checks, and plan the served
tool count, the verifier check from the shared answer function, the `serve`
verifier and the README's serving section from it; report `state` in the
export output and expect the served count in the hosted-lane script.
9. Cover services, adapters, cli-boundary, mcp-boundary and process-boundary
behaviour; document default serving, `--replay-only`, the fallback, the
answers, the data plane, the card and the export.
No dependency, provider or REA tool contract changes. The only new network
activity is an `https://` download that a `data/add` call names.
@@ -0,0 +1,196 @@
# Serve the environment from its state by default
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
`rea environment-serve <manifest> --evidence <capture>` serves the environment
from the initial state the capture observed, with nothing else to supply: at
startup it derives the `environment-state` view of the capture in-process with
the manifest's `template_rules`. Read tools whose views are state-backed answer
from one live state, the `/agentenv` data plane implements `data/reset`,
`data/add` and `data/get` against it, and the card advertises those operations
and an intake declaration. `--replay-only` serves exactly the stateless replay
main serves: every manifest tool replays its recordings, `/agentenv` answers every method as not found and the card is
byte-identical. A state that cannot be derived, or whose catalog exceeds the
discovery budget, starts the server in replay mode, logs why and reports it. Exported bundles serve the state by default,
and their verifier and README match what they serve. Serving is read-only in
this increment: write tools are not served from the state.
## Requirements
- Derive the state in the application layer, shared by serve and export:
`deriveEnvironmentState(source, capture, operation)` authenticates the
capture value the command already read (an owned snapshot authenticated by
the source load is reused, not parsed again), projects the
`environment-state` view with the manifest's `template_rules` through
`inspectAnalysisViewValidated` with the capture as retained Evidence (the
Evidence id equals that of a projection by hand, and the capture's strings
are not charged again as inline input, which refuses captures of a few MiB),
then applies `loadEnvironmentState`: `inspect_analysis_view` Evidence of kind
`environment-state`, its `parent_evidence_id` equal to the capture's, its
`template_rules` equal to the manifest's in the same order, and every view
naming a manifest tool, refusing otherwise with `invalid_request` whose issue
path starts with `state`. The source validates a HAR capture with the owned
borrowing parse, so the view reuses that validation.
- `prepareServedState(source, capture, {name, operation})` first builds the
state catalog under the served name, then derives the state, and reports
`{served: true, evidence_id}` or
`{served: false, reason: "unavailable", code, message}` with the refusal's
projected code and message: a state catalog over the discovery budget is
unavailable with `resource_constraint`, and no state is derived for it. A
derived state whose catalog fits always serves from the state. The CLI
reports `{served: false, reason: "replay-only"}` itself for `--replay-only`
and derives nothing.
- Keep one state store per server process, shared by every MCP session and the
data plane: entities keyed by type, collection and id with per-collection
order taken from the entities array, documents keyed by tool. `reset`
restores the Evidence state, `replace` installs a validated document whole,
and mutations are synchronous.
- Answer item views by rendering the entity the path arguments name into the
recorded envelope of `envelope_ordinal`. Match arguments as replay does: a
held collection segment equal to the argument as written or after
percent-decoding, preferring verbatim spellings, and the identity as the last
argument percent-decoded, then as written, never re-split on `/`. Any held
entity answers, and query arguments only set `replay.exact_match`. Refuse an
entity the state does not hold, or one lacking a required field, as a tool
error and never invent a body.
- Answer list views from the state only when the arguments name one of the
view's `checked_record_ordinals` exactly (every path and query value equal and
no recorded query parameter unsupplied), choosing among them as replay
chooses: every collection entity in state order for `membership: "all"`,
otherwise the recorded members the state still holds. A member lacking one
of the view's `required_fields` makes the call a tool error stating that the
state holds no complete member, never a partial body, as for items. Lines
recorded after a write or with other filters were never checked and answer
from recordings.
- Answer document views with the state's value when the arguments name the
document's checked line exactly.
- Answer recorded views, tools without a view and unrecorded requests from the
recording, labelled `state: {source: "recording", reason}`; label state
answers `state: {source: "state", view, …}` with the base record's replay
provenance and `truncated: false`; refuse a state answer over 256 KiB as a
tool error rather than cut it.
- Serve `data/reset` (`{}`), `data/add` with exactly one `data` or `file` part
(`bytes`, a `file://` regular file or an `https://` download with a 30 s
timeout covering the body, no redirects and a streamed 32 MiB cap;
`mimeType` when given is `application/json`) and `data/get` (`{parts:
[{kind: "data", data: rea_state}]}`). A `file://` uri whose host is neither
empty nor `localhost`, or whose path is a UNC path, is `-32602` before
anything is opened. A document that `data/get` would write back past 32 MiB,
once it adds the members a document may omit, is `-32602`, so a reported
state always loads again. Anything else is `-32602`, including empty `parts`; a
failed download is `-32000`; errors never echo a uri or content and name a
coarse failure category in `error.data.reason`. Calls run one at a time in
arrival order across requests; every message of a batch is queued when the
batch arrives, so no other request's call lands between them, and
notifications run without a reply.
- Stopping the server (`close` or `prepareShutdown`) aborts the data plane's
stop signal, and a request's own signal aborts when its client leaves: the
download in progress ends with reason `cancelled`, queued calls are answered
`-32000` with reason `cancelled` without running, and a `data/add` whose
file read finished after the cancellation does not replace the state.
- A `file://` read whose descriptor close fails answers `-32000` and keeps the
descriptor in a server-lifetime owner. The next file read retries the close
first and, while it still fails, answers `-32000` without opening the file.
`close` retries it again and rejects while it is still open.
- Bound one `/agentenv` response to 64 MiB of replies (twice the intake
limit). Each reply is measured in bounded JSON parts as it is produced, in
message order, and assembled only when it fits, so a reply past the budget is
counted but never serialized whole. Room is kept for every later reply's
budget error and for a batch's brackets and commas, so the body never
passes the budget; a reply that does not fit is replaced by `-32000` with
`data: {max_response_bytes, reply_bytes}`, unless that error is no smaller
than the reply, so the body is never built past the budget and small write
replies are kept.
- Validate `rea-state/v1` documents against the served types and views: known
types, collections that instantiate their template, ids equal to the identity
field, unique keys, `view_fields` naming item or list views of the type
without setting the type's identity field to a value whose scalar text
differs from the entity's id (refused at that view's `fields`),
documents naming document views once at the initial path, and the optional
`state_evidence_id` and `types` that `data/get` reports equal to the served
ones. Refuse numbers without an exact native value and `__proto__` members.
Name the first problem by JSON pointer, never deeper than a caller-data
member (`fields`, `value`, `types`), and leave the state unchanged.
- Keep record citations Evidence provenance: refuse `record_ordinals` or a
`record_ordinal` other than the Evidence's for that entity or document, and
hold the Evidence's citations only for loaded values equal to the Evidence's;
edited or added values cite none.
- In state mode, declare `operations: ["data/reset", "data/add", "data/get"]`
and the `urn:agentenv:intake/v1` extension, describe tools as answering from
the state where it backs them, and leave out `POST`, `PUT`, `PATCH` and
`DELETE` tools other than query `POST`s. The discovery budget reserves the
card and descriptions actually served (`servedCatalog` returns the tools and
the data plane the card declares). A state catalog can exceed the budget a
replay catalog fits, so such a state is unavailable and the environment and
its export serve replay: everything main served and exported is still
served and exported.
- The CLI has no `--state`; `--replay-only` (default false) serves replay
without deriving. Otherwise it prepares the state under the name it serves
(`ENVIRONMENT_NAME` or the manifest's). When the state is unavailable the
command logs a warning with the code and message through its listener logger
before binding, and serves replay. The stopped document's `state` member is
the report. `serveEnvironment` keeps taking an optional prepared state and
serves replay without one; the served catalog (`servedCatalog`) and the
answer function (`environmentToolAnswer`) are shared with export.
- `environment-export` keeps the bundle command
`rea environment-serve /env/manifest.json --evidence /env/evidence.json` and,
after its name checks, prepares the state under the bundle name as that
server will, to predict what it serves, so a refused `--name` derives
nothing; its report is served or unavailable, never replay-only. The
verifier's `TOOL_COUNT` and the command's `tool_count` are the served tool
count, and its check tool and expected status come from calling the server's
answer function with `{}` on the first served `GET` tool without path
parameters whose answer is not an error. The verifier and smoke task use the
id `serve`. The README's "What the environment serves" section describes
serving from the state, the replay fallback and `--replay-only`, says which
applies to the bundle (with the code and message when the state is
unavailable), and keeps the not-emulation and untrusted-content caveats. The
export output adds `state`. The hosted-lane verifier script expects the
served count.
## Acceptance and limits
Services tests cover derivation (the by-hand Evidence id and state, a capture
the manifest was not projected from, a capture inline admission refuses),
the prepared reports (served, unavailable for a `capture_browser_scenario`
capture with header values but no response bodies, unavailable with
`resource_constraint` for a 350-tool read-only manifest whose replay catalog
fits), bundle plans served from the state and in replay (tool count, check tool
and status, a check whose state answer differs from replay's in record and
status, a check that skips a tool the server cannot answer, the replay fallback
of that 350-tool manifest, name refusals before any preparation, a manifest
neither mode admits, verifier, smoke task and README wording), the loader
refusals, store reset, order, round trip and
atomic replace, the first-problem pointers, citation provenance after a load and
every answer path: item (including percent-encoded collection segments and
ids), list `all`, list `recorded`, lines recorded after a write or with other
filters, a line replay's tie-break would not pick, document, recorded view, tool
without a view, unrecorded request, missing entity, missing required field and
the body limit, a view value that changes an entity's identity, and a list
member missing a required field. Adapters tests cover the data-plane shapes,
`-32602` for empty and malformed parts and invalid documents, inexact numbers,
bytes, `file://` and its failure categories, `file://` uris naming another
host or a UNC path, injected https downloads (status, declared and streamed
size, the timeout whether or not the fetcher honours its signal), the default
fetch's refused redirects, arrival-order application across requests, a batch
another request's reset cannot split, cancellation by the server's stop and by
a departed caller, a server that closes while a download is stalled, a
retained state-file close that the next file read and `close` retry, the
response budget, batches and notifications, the card declaration, absent write
tools, a served query `POST`, the per-mode discovery reservation and a
`data/add` that changes the next tool answer over MCP. The mcp-boundary tests serve the state the
built CLI derives from only a manifest and a capture, and `--replay-only` with
every manifest tool, `-32601` data-plane answers and a card whose SHA-256
equals that of the card main serves for the fixture; the process-boundary test checks the stopped document's
`state` for a derived state, `--replay-only` and an unavailable state, and the
warning logged before binding; the cli-boundary export test checks the served
tool count, the verifier and the README of an exported bundle. No test reaches
an external network. Write effects, list filters beyond recorded lines and
`tables` in the intake declaration are out of scope.
## Deferred issue criteria
Write effects are a later increment of #4933. This PR must reference rather
than close the issue.
@@ -0,0 +1,32 @@
# Tasks
- [x] Record the serving contract, requirements and deferred increments.
- [x] Load and cross-check state Evidence for a served source.
- [x] Add the store, the document validator and the snapshot.
- [x] Answer item, list and document views from the state with recorded
fallbacks.
- [x] Serve the data plane and declare it on the card; leave write tools out.
- [x] Address review: answer lists and documents only from checked lines,
match item arguments as replay does, reserve the served card only, keep
record citations Evidence provenance, and run data-plane calls in arrival
order with a tested download timeout.
- [x] Derive the state from the capture at startup, as retained Evidence, with
the loader's checks; reuse the capture's validation.
- [x] Replace `--state` with `--replay-only`, fall back to replay with a logged
warning when no state can be derived, and report `state` when stopped.
- [x] Derive the state in `environment-export` to plan the served tool count,
the verifier check and the README; report `state` in its output.
- [x] Add services, adapters and boundary coverage; remove the `--state` flag
tests.
- [x] Document default serving, `--replay-only`, the fallback, answers, data
plane, document format, card and export.
- [x] Address review: serve and export replay when the state's catalog
exceeds the discovery budget, check the bundle name before preparing the
state, keep replay-only out of export plans, test a check the state
answers differently from replay, and pin main's replay card digest.
- [x] Address review: bound one `/agentenv` response's replies, queue a
batch's messages together at arrival, cancel the running download and
queued calls when the server stops or the caller leaves, refuse
`file://` uris naming another host or a UNC path, refuse view values
that change an entity's identity, and apply list views' required fields.
- [x] Run static, formatting, duplication, documentation and test gates.
@@ -0,0 +1,79 @@
# Verification
Verified base: 6676582675e08574146a812ae58cff67e2e53282 (this branch's head),
with the review fixes of this pass uncommitted.
Executed on the implementation worktree on macOS with Node v22.23.1 (the
repository pins 24.18.0 in `.nvmrc`, which was not installed). Every check
below ran as written after the last source change, in this order.
- `npm run format` then `npm run format:check`: passed (4330 files).
- `npm run check:fast`: typecheck and lint passed. The lint report holds 0
errors and no diagnostic in any file this pass touches.
- `npx knip`: passed (exit 0, no findings).
- `npm run jscpd`: 3.37% duplicated lines over 3373 files (2800 clones), under
the 3.5% gate; no clone involves a file this pass changed.
- `npm run scan:todos`: passed, no markers.
- `npm run docs:check`: the built documentation checks (6 tasks) and the README
consistency conformance test (5 tests) passed.
- `npx vitest run --project domain --project services`: 347 files, 5948 tests
passed, 5 pre-existing skips.
- `npm run build:cached` (the built output already included this pass's
sources), then `npm run test:focused --` the serve CLI, MCP and process
boundaries, the export CLI boundary, the CLI startup imports, the agentenv
verifier identity boundary, the bundle package pin conformance test, the CI
change scope boundary, the analysis-view CLI and MCP boundaries, and the
`serveEnvironment`, `serveEnvironmentState`, `environmentCapacity` and
`stateIntake` adapters tests: 14 files, 335 tests passed.
- After this file was written: `npm run format`, `npm run format:check`,
`npm run scan:todos` and `npm run docs:check` passed again.
The review fixes are covered by these tests, all passing in the runs above:
- Response budget: a batch of four `data/get`s over a loaded state of about a
third of the budget keeps two results, answers the other two with `-32000`
and `max_response_bytes`, keeps the trailing reset's `{}`, and its body stays
within the budget.
- Batch order: a batch `[data/add (gated download), data/get]` with a
`data/reset` from another request sent while the download is held reports
the loaded state from its `data/get`, and the reset applies last.
- Cancellation: aborting the data plane's stop signal ends a stalled download
(its fetcher's signal is aborted) and answers the queued reset as
`cancelled` without running it; a call made after the stop does not run; a
caller whose request signal aborts gets `cancelled` and the state is
unchanged. Over loopback HTTP, `close()` of a served state returned while a
download that never answers was in progress, and that download's signal was
aborted.
- File closes: with an injected close failure, `data/add` of a `file://` uri
answered `-32000` without echoing the uri. The descriptor stayed open, and
the next file load closed it before loading. While the close kept failing,
over loopback HTTP the next file load answered `-32000` ("still open") and
an inline `data` part still loaded. `close()` rejected with the unfinished
close, then resolved and closed the descriptor once the close succeeded.
Both tests fail on the previous head, which dropped the descriptor.
- File hosts: `file://remote.example.test/...` and
`file:////remote.example.test/...` are refused with the network-share
message and no failure category; a `file://localhost/...` uri loads.
- Loaded identity: `view_fields` setting `id` to `3`, `"3"` or `null` on user 2
is refused at `/rea_state/entities/7/view_fields/0/fields`; `"2"` loads.
- List required fields: a folder without `title` makes `get_folders` a tool
error naming the incomplete entity.
That each test fails without its fix is source-derived; the fixes were not
reverted to show it.
Not covered here: a Windows host (the UNC refusal ran on macOS, where
`file:////host/share` converts to `//host/share`; the host check runs before
any path conversion on every platform), a real `https://` download (tests
inject the fetcher), the memory and time of a budgeted batch (not measured),
agent token usage, a real AgentEnv framework run, a real-world capture, and
write effects, which are a later increment.
After refusing bodies too deep for the state's own nesting (a recorded body
must nest at most the JSON depth limit minus 16 levels to enter the state):
`npm run check:fast`, `npm run format:check`, `npx knip`, `npm run scan:todos`
passed; `npm run jscpd`: 3.37% over 3373 files; `npx vitest run --project
domain --project services`: 347 files, 5950 tests passed, 5 pre-existing
skips; `npm run build:cached`, then `npm run test:focused --` the serve CLI,
MCP and process boundaries, the export CLI boundary and the two serve adapter
tests: 6 files, 48 tests passed.
+6 -1
View File
@@ -57,7 +57,12 @@ triggers or arbitrary SQL.
Rows are optional. `table` selects one exact ordinary-table name. `columns`
(CLI repeated `--column`) selects exact visible column names in the requested
order; omission selects all visible columns, including generated columns.
Unselected cells are not evaluated or projected. Malformed column-name bytes
Unselected cells are not evaluated or projected. A selected virtual generated
column whose expression needs a function REA's connection lacks (extensions and
application functions are not loaded), or raises an SQL error for a sampled
row, is refused as `capability_unavailable`. The error names the table and
each failing selected column with SQLite's message, and recommends only columns
whose sampled values were read; stored and other generated cells stay readable. Malformed column-name bytes
cause a format refusal when all columns are requested. In inventory scope, an explicit valid subset can
still be sampled without decoding unrelated malformed column names.
Column selection requires a
+57 -5
View File
@@ -46,6 +46,24 @@ without executing tests or building. A missing Git base reports how to fetch
it or select another revision.
Source typechecking needs no compiled runtime or generated test metadata.
Typecheck, runtime compilation, and Frida binding verification share
`scripts/run-typescript.mjs`. The pinned tsrs package supplies Linux x64/arm64
(GNU and musl) and macOS arm64 compilers. Windows and other unsupported hosts
retain the existing stable official TypeScript compiler. Its upstream version
can differ from tsrs, so both compiler tasks hash the selected package and version
through `scripts/run-turbo.mjs` before cache lookup. Outputs from different
compilers occupy different cache entries. Repository npm scripts and focused-test
preparation use that entrypoint; a raw Turbo compiler task without matching
identity fails instead of publishing an ambiguous successful cache entry.
A missing binary on a supported host also fails without silently falling back.
The Rust compiler defaults to at most two checker threads. For `--noEmit` checks,
`--maxMemory 3G` also sets a soft memory target; emit builds do not use that target.
Explicit compiler arguments can override the defaults. These controls do not
impose a process-family memory limit. Termux's explicit build, Stryker's compiler
API dependency, and independent fixture compiler oracles keep their existing
TypeScript versions.
`npm run build:cached` compiles the CLI/MCP runtime and its packaged skill.
Turbo's strict task environment preserves caller-selected `GOMAXPROCS`,
`GOMEMLIMIT`, and `UV_THREADPOOL_SIZE` for resource-constrained builds and
@@ -233,6 +251,27 @@ and retain representative compiled CLI parity checks. Every distinct source
scenario still runs through the public workflow; session-lifecycle scenarios
need their own sessions and cleanup assertions.
When a matrix already holds a named tool definition from live `tools/list`,
pass that definition through the SDK's `toolDefinition` call option. This keeps
the selected output-schema validation without eagerly compiling unrelated
catalog schemas. Re-read definitions if the server advertises a changed list.
Artifact verification uses `artifactMcpCall` for both lifecycle and analysis
calls. A fresh SDK client has no held tool listing, so a bare `callTool` can
skip output-schema validation. The helper discovers actual advertised
definitions, rejects missing or ambiguous schemas, and validates the requested
tool without compiling unrelated catalog schemas. REA registers process-static
tool contracts; bindings share one discovery per negotiated connection and are
discarded on reconnect or failed discovery. Keep raw artifact calls on this
shared path and retain explicit request options such as timeouts.
Caller-supplied tool definitions are rejected: the helper owns selection from
the advertised catalog so a stale or permissive schema cannot bypass it.
The regression fixture changes only `canonical_path` in a real stdio response
to a number. The old helper accepts that response; the named schema must reject
it. This client-boundary fault injection does not establish native Apple
behavior, which remains covered by the macOS verification lane.
Report unavailable authority as a named skip. Await owned cleanup before a
verifier reports success. Native process inspectors are released at Vitest
worker teardown; fork termination does not run Node exit hooks, and per-file
@@ -648,11 +687,22 @@ missing entries are omitted as whole entries, and consumers rebuild cache misses
including when every entry is unavailable. Malformed plans, non-file entries,
and I/O errors other than disappearance still fail publication. After a trusted
static run, superseded cross-run Turbo snapshots for that OS and architecture
are deleted down to the two newest generations. The Windows lane relies on the Linux static job for typechecking. They retain the normal build and prepack
commands, which restore matching outputs or rebuild on a cache miss. Native
are deleted down to the two newest generations. The Windows lane also runs the
official compiler's complete source typecheck with two checker threads,
independently of the accelerated Linux checker. Both lanes retain the normal
build and prepack commands, which restore matching outputs or rebuild on a cache
miss. Native
artifacts, dependency installs, package archives, and runtime verification remain
host-local. Keep platform-dependent outputs out of this shared cache.
The Windows native verifier's private-runtime ACL observer reads SID rules once
for both trustee validation and counting. It deliberately rejects the convenience
`Access` property, which resolves account names. Its `private-runtime-acl` stderr
markers report completed phases and elapsed time; inspect these on a timeout
before attributing it to startup, ACL reads, or identity lookup. The original
15-second deadline and all ownership, DACL, and readonly assertions remain in
force. A passing retry alone does not explain a previous timeout.
Record cold and warm preparation separately from fresh test execution when
benchmarking the optional `npm run check:pr` gate. Compare multiple runs on the
same host, including subprocess and queue costs; a cached pass is not a test
@@ -1754,9 +1804,11 @@ projects the `environment-manifest` view with all three template rules through
the built CLI, packs the checkout with `npm pack`, exports the bundle with
`--package-tarball` so the image installs this checkout rather than the
registry package, and runs `agent-env run` on the bundle. It checks that the
export reports the planned file set with the inputs copied byte-exact, that
the runner exits `0` with `tasks/smoke.json` reported as passed and the
verifier's description naming the tool count, and that no container exists
export reports the planned file set with the inputs copied byte-exact and a
bundle served from the derived state, which serves fewer tools than the
manifest because write tools are not served, that the runner exits `0` with
`tasks/smoke.json` reported as passed and the verifier's description naming
that served tool count, and that no container exists
after the run that did not exist before; the container inventory runs even when
the run fails or times out, so a failed receipt still lists what it left. The one exception is the framework's
own image-store helper, `agentenv-registry` (or `agentenv-registry-<port>`),
+2 -2
View File
@@ -59,7 +59,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
46,src/application/Uninstall.ts:removeManagedPath symlink,Managed cache/state/skill path is a symbolic link,Refused to follow symlink {path}.,Exposes local path and does not explain manual safety check,"REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.",implemented,focused assertion passed
47,src/application/Uninstall.ts:removeManagedPath failure,Managed item removal fails,{path} could not be removed.,Exposes local path and gives no recovery,"This item could not be removed. Check file permissions, then rerun uninstall.",implemented,verified by injected managed-path removal failure test
48,src/application/DoctorDiagnostics.ts:node,Runtime version is outside supported ranges,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",Low risk; exact dependency recovery,Keep current copy,unchanged,verified by doctor unit test
49,src/application/Doctor.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, and CachyOS.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests
49,src/application/DoctorDiagnostics.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Linux distributions with a recognized apt, dnf, or pacman package family, and the experimental Windows x64 Ghidra P0 boundary; automated Hopper setup additionally requires Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests
50,src/application/Doctor.ts:analysis engine,Optional deep-analysis executable cannot be found,"Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.",Low risk in setup-specific diagnostic; exact recovery paths,Keep current copy,unchanged,verified by doctor unit tests
51,src/application/Doctor.ts:target,Optional target path fails shared validation,Supply a readable local app or program path.,Low risk; tells caller valid target class,Keep current copy,unchanged,verified by doctor unit test
52,src/application/Setup.ts:approval,Setup plan lacks approval,"Review the setup plan, then rerun interactively or with --yes.",Low risk; exact authorization recovery,Keep current copy,unchanged,verified by setup unit tests
@@ -70,7 +70,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
57,src/application/Setup.ts:unsupported platform,Platform is neither macOS nor Linux,REA supports Hopper on macOS and selected 64-bit Linux distributions.,Low risk; states support boundary,Keep current copy,unchanged,verified by setup unit test
58,src/application/SetupHost.ts:unsupported Node,Runtime version cannot run setup,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ and rerun setup.",Low risk; exact dependency and retry,Keep current copy,unchanged,verified by setup host tests
59,src/application/Setup.ts:unsupported macOS,macOS version is missing or older than 12,Upgrade to macOS 12 or newer.,Low risk; exact support floor,Keep current copy,unchanged,verified by setup host tests
60,src/application/Setup.ts:unsupported Linux,Linux distribution is outside supported set,"REA supports Hopper on Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, and CachyOS.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
60,src/application/SetupHost.ts:unsupported Linux,Linux distribution is outside supported set,"Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, and Omarchy; configure an existing supported provider instead.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
61,src/application/process/ProcessCli.ts:readJson read,Process scenario or evidence file cannot be read,Raw filesystem exception through CLI framework,Can expose local paths/codes and no recovery,"Process input file could not be read. Check that the path exists and is readable.",implemented,verified by focused CLI test
62,src/application/JsonInputFile.ts:memory admission,Projected process JSON value exceeds available heap headroom,Insufficient heap headroom to materialize the complete JSON value,Heap admission depends on live runtime capacity rather than a fixed 64 MiB file limit,"Provide a smaller valid JSON value. For Evidence workflows, re-analyze a smaller selection of the original target and use its complete Evidence; splitting JSON text or trimming Evidence fields does not produce valid workflow input.",implemented,Source verified against shared JSON reader and recovery constant; former fixed-size refusal is superseded
63,src/application/process/ProcessCli.ts:readJson parse,Process scenario or evidence file is malformed JSON,Raw JSON parser exception through CLI framework,Can expose parser implementation and lacks recovery,"Process input file is not valid JSON. Repair the file, then try again.",implemented,verified by focused CLI test
1 harm_rank location trigger current_copy user_risk proposed_replacement implementation_status verification_result
59 46 src/application/Uninstall.ts:removeManagedPath symlink Managed cache/state/skill path is a symbolic link Refused to follow symlink {path}. Exposes local path and does not explain manual safety check REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually. implemented focused assertion passed
60 47 src/application/Uninstall.ts:removeManagedPath failure Managed item removal fails {path} could not be removed. Exposes local path and gives no recovery This item could not be removed. Check file permissions, then rerun uninstall. implemented verified by injected managed-path removal failure test
61 48 src/application/DoctorDiagnostics.ts:node Runtime version is outside supported ranges Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Low risk; exact dependency recovery Keep current copy unchanged verified by doctor unit test
62 49 src/application/Doctor.ts:host src/application/DoctorDiagnostics.ts:host Host OS/distribution is unsupported REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, and CachyOS. REA supports macOS 12+, Linux distributions with a recognized apt, dnf, or pacman package family, and the experimental Windows x64 Ghidra P0 boundary; automated Hopper setup additionally requires Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy. Low risk; clearly states supported choices Keep current copy unchanged verified by doctor unit tests
63 50 src/application/Doctor.ts:analysis engine Optional deep-analysis executable cannot be found Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH. Low risk in setup-specific diagnostic; exact recovery paths Keep current copy unchanged verified by doctor unit tests
64 51 src/application/Doctor.ts:target Optional target path fails shared validation Supply a readable local app or program path. Low risk; tells caller valid target class Keep current copy unchanged verified by doctor unit test
65 52 src/application/Setup.ts:approval Setup plan lacks approval Review the setup plan, then rerun interactively or with --yes. Low risk; exact authorization recovery Keep current copy unchanged verified by setup unit tests
70 57 src/application/Setup.ts:unsupported platform Platform is neither macOS nor Linux REA supports Hopper on macOS and selected 64-bit Linux distributions. Low risk; states support boundary Keep current copy unchanged verified by setup unit test
71 58 src/application/SetupHost.ts:unsupported Node Runtime version cannot run setup Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ and rerun setup. Low risk; exact dependency and retry Keep current copy unchanged verified by setup host tests
72 59 src/application/Setup.ts:unsupported macOS macOS version is missing or older than 12 Upgrade to macOS 12 or newer. Low risk; exact support floor Keep current copy unchanged verified by setup host tests
73 60 src/application/Setup.ts:unsupported Linux src/application/SetupHost.ts:unsupported Linux Linux distribution is outside supported set REA supports Hopper on Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, and CachyOS. Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, and Omarchy; configure an existing supported provider instead. Low risk; exact supported alternatives Keep current copy unchanged verified by setup unit test
74 61 src/application/process/ProcessCli.ts:readJson read Process scenario or evidence file cannot be read Raw filesystem exception through CLI framework Can expose local paths/codes and no recovery Process input file could not be read. Check that the path exists and is readable. implemented verified by focused CLI test
75 62 src/application/JsonInputFile.ts:memory admission Projected process JSON value exceeds available heap headroom Insufficient heap headroom to materialize the complete JSON value Heap admission depends on live runtime capacity rather than a fixed 64 MiB file limit Provide a smaller valid JSON value. For Evidence workflows, re-analyze a smaller selection of the original target and use its complete Evidence; splitting JSON text or trimming Evidence fields does not produce valid workflow input. implemented Source verified against shared JSON reader and recovery constant; former fixed-size refusal is superseded
76 63 src/application/process/ProcessCli.ts:readJson parse Process scenario or evidence file is malformed JSON Raw JSON parser exception through CLI framework Can expose parser implementation and lacks recovery Process input file is not valid JSON. Repair the file, then try again. implemented verified by focused CLI test
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,286 @@
# Static GPU code-object verification
The [receipts](gpu-code-objects-2026-10-21.json) pin the measured runtime tree,
publisher URLs/digests, actual commands, completed answers and resource costs.
Downloaded binaries, toolchains and complete Evidence bundles remain outside Git.
## Correctness
NVIDIA CUDA 12.9.1 redistribution archives were verified against their publisher
manifest. nvcc 12.9.86 compiled a small owned kernel for sm_80 and sm_90a with
uncompressed, size-compressed and speed-compressed fatbins. cuobjdump 12.9.82
independently confirmed two parameters at offsets 0/8 with sizes 8/4, 12 parameter
bytes, 10 physical registers and architecture-dependent shared memory. Separate
PTX/CUBIN and a host ELF object exercised the same parser boundaries.
Clang 14 produced a gfx900 HIP bundle and AMDGPU ELF object; llvm-readelf supplied
the MessagePack metadata oracle. An LLVM 19 offload bundler produced a real CCOB
v2 Zstd bundle. All nine producer artifacts were reread on the final runtime
source with complete payload coverage. CCOB v1/v3 and zlib have portable boundary
fixtures, rather than real producer verification.
Released Linux and Windows nvJPEG 12.4.0.76 artifacts each contain 165 CUBIN and
10 PTX payloads, with 3,968 kernel declarations across 15 target declarations.
Independent extraction matched all 165 decoded CUBIN hashes and all 10 PTX byte
sequences; cuobjdump resource output matched all 3,720 native kernel
name/register/shared-memory tuples. Pre-review MCP workflows rechecked the Linux
sm_100 subset's 248 complete kernel facts against those prior oracle facts.
The pre-review public CLI analyzed the entire Windows DLL, exported complete
Evidence, and reproduced all previously verified facts exactly.
Windows validation ran on Linux: LLVM independently identified the PE section
coordinates, and cuobjdump consumed its extracted fatbin. cuobjdump did not
recognize the DLL directly. This does not establish Windows runtime behavior,
GPU execution, host launch correlation or legacy AMDGPU YAML support.
## Initial completed MCP workflows
Both fresh stdio MCP sessions analyzed only `sm_100` in the same Linux library,
retained 176 inventory objects and 248 kernel facts, queried the same named
kernel, exported Evidence, and repeated the historical query three times.
The complete variant returned the selected-target report; the selected variant
returned a same-call kernel page. Both preserved the exact answer: one parameter
of 168 bytes, 31 physical registers and 9,216 shared bytes.
| Cost | Complete report | Selected page |
| --------------------------------------------------------------- | ----------------: | -----------------: |
| Used input declarations + three primary calls, estimated tokens | 406,862 | 26,106 |
| All input declarations + primary calls, estimated tokens | 635,381 | 254,625 |
| Full advertised catalog + all six calls, estimated tokens | 1,475,258 | 1,094,502 |
| Process-family elapsed seconds | 15.492 | 15.443 |
| Process-family CPU seconds | 18.220 | 18.074 |
| Sampled peak family RSS bytes | 1,139,601,408 | 1,321,566,208 |
| First inspection milliseconds | 8,572 | 8,810 |
| Three warm retained-page calls, milliseconds | 10.7 / 10.6 / 9.8 | 12.0 / 11.4 / 10.6 |
Tokenx estimates include advertised schemas, arguments and full SDK replies;
they are not actual model usage. Text and structured reply representations are
both counted. Initialization and JSON-RPC envelopes are unmeasured. Complete
catalog discovery substantially reduces the overall percentage saving.
These are workflow variants on the same implementation, not a before/after
parser speed benchmark. Node compile caching was disabled, but OS caches and
unrelated host load were uncontrolled. No general latency, CPU or memory
improvement is established. The selected workflow used more peak memory in this
pair. An earlier all-target complete reply exceeded the existing transport guard;
that incomplete attempt is excluded from the completed comparison.
The full Windows DLL CLI task took 4.064 seconds / 4.909 CPU seconds /
503,459,840 peak family RSS bytes. All profiled completed tasks stayed within
the 8 GiB and 420 second guards, using two CPU affinities. Peak RSS sums payload
descendants every 20 ms and is approximate; the observer is excluded.
## Regression checks
- The initial focused run passed 265 tests in 19 files: GPU domain/codec/parser
cases, public CLI/MCP, retained views, output profiles, cancellation and cleanup,
plus shared ELF/PE Go, Rust and IL2CPP consumers.
- Full static checks passed before the catalog/docs-only follow-up; its receipt
records 6,866,296,832 peak family RSS bytes. The final CI run qualifies the PR.
- Documentation/catalog/license checks passed after fixing missing catalog
ownership, translated README links and the new MessagePack dependency notices.
- An earlier combined schema run hit two 30 second timeouts on two CPUs. The
isolated canonical schema suite passed all 15 cases without increasing limits.
This is recorded as a successful isolation, not a proven baseline failure.
Retention caches are scoped to immutable Evidence through a WeakMap; historical
pages do not reopen files. Live inspections always stream a fresh full-file hash
and revalidate the descriptor/path. No live-state freshness cache is claimed.
## Review follow-up
At `36b87ed`, the public CLI reproduced both review findings: selecting
`amdgcn-amd-amdhsa--gfx900` in the real Clang-produced standalone object returned
zero kernels with an unknown target, and malformed `--targets` JSON was reported
at the `view` issue path.
At `09c6062`, AMD ELF note parsing owns target discovery and prepared kernel
declarations. Inventory reads those bounded notes before selection; selected
analysis reuses them without decoding MessagePack twice. Aggregate encoded note
bytes are limited to 8 MiB before reads, with existing per-value/depth limits;
the parsed JavaScript representation expands beyond that source-byte count.
The prepared declarations live only for the current payload inspection. Named
CLI option parsing also supplies the actual parsed input to schema diagnostics.
The real public CLI now inventories the gfx900 target, returns the producer's
one kernel/two parameters/12-byte parameter area/6 SGPR/2 VGPR when selected,
and preserves an unselected object without kernel facts for a different target.
Those three complete CLI tasks together took 5.974 seconds / 6.951 CPU seconds /
383,623,168 sampled peak family RSS bytes, including the Python task driver.
All nine producer artifacts were reread with complete payload coverage.
The review regression run passed 35 cases in four files, including public CLI,
MCP, metadata capacity and cleanup. A further 10 product-catalog cases passed
after adding the missing CLI `analysis` option description identified by CI.
The earlier paired nvJPEG measurements remain pinned to their original runtime;
these follow-ups do not establish a new CUDA performance result.
## Bounded metadata and partial-fact review
The latest qualification at `ad1e17a` rebases onto main `5241597`, preserving
both Rust and GPU retained views. All 70 focused cases in nine files passed,
including both public CLI/MCP workflows; the separate SDK-converted canonical
schema suite passed all 15 cases.
CUDA parsing shares an aggregate 262,144 record/symbol visit budget, 128 MiB
section-read budget and 16 MiB serialized-size accounting budget for index facts.
Equal scoped byte ranges and duplicate register values are coalesced before
index growth; conflicting values survive. Parsed parameter metadata is reused.
The public CLI completed an 861,896-byte CUBIN containing 1,024 headers aliasing
65,536 records and retained the correct physical count. The old 67-million-entry
expansion is source-derived; it was not run to exhaustion.
Decoder coverage now reaches object, report and target pages: absent CUBIN
symbol tables and unknown section names remain partial. AMD note parsing uses
a tagged Result that retains earlier parsed notes on later malformed, capacity,
unsupported or interrupted reads; retained kernel facts are emitted before the
original failure is propagated. Standalone PTX recognition continues beyond the
64 KiB fast path up to its actual 16 MiB text budget and reuses complete probe
bytes. Unsupported/overflowing register counts stay null instead of low totals.
The public CLI verified an AMD valid-note/truncated-note input and a PTX file
with a 70 KiB leading comment, in addition to the alias case. Those three tasks
together took 5.132 seconds / 6.222 CPU seconds / 405,835,776 peak family RSS
bytes, including the Python driver. Regression cases additionally cover invalid
MessagePack and unsupported extensions in standalone and bundled AMD objects.
The revised Windows DLL workflow preserved all 165 decoded CUBIN hashes, all
10 PTX byte identities and all 3,720 native kernel facts. Independently counting
register declarations in cuobjdump-extracted PTX verified all 248 PTX kernels;
20 kernel totals now correctly include previously missed declarations. This
is a virtual-declaration oracle, not physical allocation or GPU execution.
The Linux sm_100 workflows still match all 248 independently checked native
kernel facts. All nine real producer artifacts retain complete payload coverage.
The latest complete/selected MCP pair performed the same six calls and answered
the same question as the initial pair. Receipts retain both measurement stages.
| Cost | Complete report | Selected page |
| --------------------------------------------------------- | --------------: | ------------: |
| Used input declarations + primary calls, estimated tokens | 407,368 | 26,612 |
| All input declarations + primary calls, estimated tokens | 636,495 | 255,739 |
| Full catalog + all six calls, estimated tokens | 1,480,698 | 1,099,942 |
| Process-family elapsed seconds | 15.487 | 15.923 |
| Process-family CPU seconds | 18.404 | 18.760 |
| Peak family RSS bytes | 1,251,254,272 | 1,201,774,592 |
These remain tokenizer estimates and same-code workflow variants. The single
latest pair establishes no general latency/CPU/memory improvement; OS caches
and host contention remain uncontrolled. The Windows CLI rerun completed in
4.078 seconds / 4.880 CPU seconds / 498,380,800 peak family RSS bytes.
Main CI independently reproduced the browser termination and WASM optional-input
assertion failures without GPU changes. Cancelled fail-fast shards are not
counted as passing. Those baseline failures are outside this feature's fixes.
## Target declaration and CUDA ABI qualification
The latest code `f5be46f` is based on main `f801b2f`. Inventory and payload
target declarations remain separate, with typed consistency and inspection
coverage preserved in object pages even with selected fields. A mismatched
outer/inner base target remains partial and annotates affected kernels; CUDA
base-only agreement does not establish agreement on a feature profile. Target
filters select inventory declarations, and unexamined inner targets stay unknown.
Standalone and unknown-ID bundled AMDGPU files use the same bounded target
discovery owner before filtering. Portable controls wrap the same real Clang
ELF with two-separator, one-separator and opaque HIP IDs. All three inventory
then exact-target CLI workflows retain the verified kernel. They are alternate
wrapper controls, not verification of an actual legacy compiler producer.
The shared fixed ELF header owner now retains OS ABI. NVIDIA's released CUBINs
include distinct `0x33` and `0x41` flag layouts. The earlier low-byte-only
interpretation falsely produced `sm_2` for 55 modern payloads; that attempted
target-check implementation and its timings are superseded diagnostic evidence,
not a correctness-qualified benchmark. The ABI-qualified decoder retains raw
flags and exact fixed-header coordinates, and leaves unrecognized patterns unknown.
Header ownership is checked when importing portable Evidence.
All 165 released Windows CUBIN targets and header values were checked against
cuobjdump-extracted files: 110 use OS ABI `0x33`, 55 use `0x41`; no false conflicts
remain. All 3,968 corrected kernel facts and 10 PTX byte identities match the
prior independent oracle. The nine real producer artifacts retain complete
payload coverage. Unsupported layout controls retain their headers and make
unknown target discovery partial in the default MCP summary.
After the shared-header change, 251 cases in nine files passed, including
Go/Rust/IL2CPP consumers, GPU CLI/MCP and provenance forgery. The last uncertainty
propagation check passed 46 public/boundary cases. The separate SDK-converted
canonical schema suite passed all 15 cases on the current base.
The latest qualified complete/selected pair retains the same sm_100 question,
answer and six-call workflow. It runs at `21eaa0d`, before the final unresolved-
discovery-only guard.
| Cost | Complete report | Selected page |
| --------------------------------------------------------- | --------------: | ------------: |
| Used input declarations + primary calls, estimated tokens | 418,967 | 26,653 |
| All input declarations + primary calls, estimated tokens | 648,242 | 255,928 |
| Full catalog + all six calls, estimated tokens | 1,493,041 | 1,100,727 |
| Process-family elapsed seconds | 21.307 | 24.604 |
| Process-family CPU seconds | 23.538 | 26.454 |
| Peak family RSS bytes | 1,146,519,552 | 1,341,333,504 |
Selected replies save context but do not establish faster analysis. The latest
pair used more elapsed/CPU time and peak memory for selected delivery; variability
is visible across the preserved trials. Fresh process lifetime is controlled;
OS caches, frequency and unrelated load are not. A same-code diagnostic timing
repeat varied substantially, and no cause or persistent speedup is inferred.
The tokenizer/client scope and omitted initialization costs remain as above.
The final Windows CLI task took 5.446 seconds / 5.916 CPU seconds / 498,192,384
peak family RSS bytes. The six HIP wrapper CLI tasks took 15.696 seconds /
16.427 CPU seconds / 382,410,752 peak family RSS bytes. All completed tasks stayed
within the 8 GiB family guard.
## Payload admission and PTX uncertainty follow-up
At `893e04d`, PTX parsing owns typed per-kernel coverage and limitations for
unknown parameter layouts, shared declarations and virtual register counts.
Inventory forwards that coverage to objects, reports and historical pages.
The old register-only interpretation in inventory was removed.
A 67-case focused run passed across five files, including parser, public CLI/MCP,
provider boundary and lifecycle tests. Three source admission cases also passed
(one overlaps the focused run). NVIDIA `ptxas 12.9.86` accepted a pointer-parameter
PTX control; the public CLI retained its unknown layout and reported partial
coverage. A released nvJPEG example independently extracted by cuobjdump uses
`.param .u64 .ptr .align 1`; this remains outside supported layout interpretation.
The revised Windows DLL report retains all 176 object factual fields, all 3,968
kernel factual fields and all 10 exact PTX byte identities. Eight PTX objects and
59 kernels now expose their pre-existing unknown parameter layouts with partial
coverage and added limitations. The earlier complete payload-coverage claim for
those PTX objects is superseded; decoded numerical facts are unchanged. Nine real
producer artifacts still retain complete payload coverage.
The shared work owner now reserves uncompressed payload input once, and compressed
input plus decoded output atomically, before reading/allocating. CCOB uses the
same admission owner as payload analysis and AMD note reads. The allowance is
512 MiB of that work, with 128 MiB per input/output buffer. Admitted failed work
remains charged. A real sparse HIP file contains twelve aliases of one 128 MiB
ELF payload: file-read instrumentation observed four full payload reads and eight
capacity refusals with no payload read or decoded digest; the earlier four kernel
facts survive. CUDA and CCOB oversized-output controls likewise prove refusal
before the compressed input read. Old rejected-alias rereads are source-derived;
they were not run to exhaustion.
The nine-producer pass took 1.148 seconds / 1.100 CPU seconds / 122,732,544 peak
family RSS bytes. The pointer-PTX and released-DLL public CLI tasks together took
10.102 seconds / 11.302 CPU seconds / 512,335,872 peak family RSS bytes. The focused
run took 142.441 seconds / 211.528 CPU seconds / 3,478,200,320 peak family RSS bytes,
including the required artifact build. No resource guard stopped these tasks.
The earlier MCP cost pairs remain pinned to their measured revisions; no new
speedup is inferred from this follow-up.
Three verifier attempts are retained as unsuccessful in the JSON record: an
incorrect bare-record assumption for the Evidence bundle, refusal to overwrite
an existing export, and a stale expectation that the now-corrected PTX coverage
would stay complete. The successful comparison requires all factual values and
PTX bytes to match while checking the intended uncertainty changes explicitly.
`npm run check` passed at this source revision, including type/lint/format, module
boundaries and architecture guards: 135.345 seconds / 266.563 CPU seconds /
5,371,879,424 sampled peak family RSS bytes; no resource guard stop.
`npm run docs:check` also passed, including all five README conformance cases.
+65 -27
View File
@@ -120,6 +120,8 @@ HTTP fields and ordered duplicate headers are retained, with stored body bytes
in binary sidecars. Bodies are not dechunked or decompressed. Metadata is an
XML node tree; optional `_w.txt` and `_g.txt` files retain opaque WebSocket and
gRPC message bytes. Missing members remain null, distinct from empty bodies.
A zero-byte `_c.txt` or `_s.txt` member, which Fiddler writes for a session
without that message, is also null and adds a record limitation.
See Telerik's [SAZ format reference](https://www.telerik.com/fiddler/fiddler-everywhere/documentation/knowledge-base/fiddler-archives).
## Protobuf and gRPC bodies
@@ -178,6 +180,9 @@ sanitized source even with explicit sensitivity options. If that source pass
is interrupted, unchecked bodies are not published as partial Evidence.
Framing overrides apply only to format-owned body coordinates and preserve
exclusions for the transport declared in MIME metadata.
Explicitly excluded or conflicting MIME metadata is not treated as absent:
automatic framing leaves such a non-empty body uninterpreted, adds a record
limitation and keeps `protobuf_coverage` partial unless `framing` is selected.
Unknown or explicitly excluded HTTP coding metadata is not treated as absent:
SAZ/native bodies remain opaque until identity coding is established. Raw
header values and retained body bytes remain available in source Evidence.
@@ -449,12 +454,13 @@ template and `params` carry the four format rules with names `id`, `uuid`,
only when listed, each at most once (a repeated rule is an `invalid_request`),
in this order:
| Rule | Applies to | Parameter |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `after-uid-literal` | The literal segment right after a literal `uid`, unless it is a stop word | `uid` |
| `opaque-token` | A lowercase alphanumeric segment of twelve or more characters mixing letters and digits that is not a `v1`-style version | `uid` |
| `sibling-pair-variance` | Groups equal in method, origin and segment count that differ in two adjacent literals right after a plural literal at index 2 or later and followed by a literal, with at least three distinct pairs and both positions varying (`/repos/{owner}/{repo}/issues`); stop words and `v1`-style versions never qualify | singular plural literal plus `_namespace`, then `_name` |
| `sibling-variance` | Groups equal in method, origin and segment count that differ in one literal at index 2 or later; the siblings whose literal is identifier-like (a digit or `:`, or three or more hyphenated names under a plural literal) merge, never a stop word, and the other literals stay their own tools | singular previous literal plus `_id`, else `id` |
| Rule | Applies to | Parameter |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `after-uid-literal` | The literal segment right after a literal `uid`, unless it is a stop word | `uid` |
| `opaque-token` | A lowercase alphanumeric segment of twelve or more characters mixing letters and digits that is not a `v1`-style version | `uid` |
| `sibling-pair-variance` | Groups equal in method, origin and segment count that differ in two adjacent literals right after a plural literal at index 2 or later and followed by a literal, with at least three distinct pairs and both positions varying (`/repos/{owner}/{repo}/issues`); stop words and `v1`-style versions never qualify | singular plural literal plus `_namespace`, then `_name` |
| `sibling-variance` | Groups equal in method, origin and segment count that differ in one literal at index 2 or later; the siblings whose literal is identifier-like (a digit or `:`, or three or more hyphenated names under a plural literal) merge, never a stop word, and the other literals stay their own tools | singular previous literal plus `_id`, else `id` |
| `prefix-variance` | A literal at a position that another tool, of any method or segment count, templates by one of the rules above after the same preceding segments, when the literal is one of that parameter's observed values, carries a digit and has the length and character classes (digits, lower case, upper case, each other character) of one, or has the single length of eight or more characters every observed value has and only their character classes; stop words and `v1`-style versions never qualify, and tools that now match merge | the name of the parameter it shares |
With `sibling-variance` selected, literal siblings within its comparison scope
fold into existing opt-in parameters as observed values unless they are stop words.
@@ -471,7 +477,17 @@ cut to their first label, `_by_<params>` appended when parameters exist,
sanitized to `[a-z0-9_]`, at most 60 characters and made unique with numeric
suffixes: `get_dashboards_by_uid`, `get_plugins_settings_by_plugin_id`.
`sibling-variance` and `sibling-pair-variance` compare routes of at most 32 segments; longer routes keep
`prefix-variance` runs last so it can share the parameters the other rules
produce: with `sibling-variance`, a `PATCH` or `DELETE` of the one entity a
session edited, `/api/v1/collections/0RZ576QZQA5KG`, becomes
`/api/v1/collections/{collection_id}` beside the reads that varied there, and
so do deeper routes such as `/api/v1/collections/{collection_id}/books`. Its
parameter is labelled `prefix-variance`, unless a merged tool templated the
position by its own rule. When parameters of different rules share a position,
a literal shares the one that observed it, else the earliest one it is spelled
like.
`sibling-variance`, `sibling-pair-variance` and `prefix-variance` compare routes of at most 32 segments; longer routes keep
their grouped templates and the manifest says so in a limitation. `body_shape`
is derived for `POST`, `PUT` and `PATCH` from the tool's earliest record when it
carries an `application/json` or `application/*+json` request body: top-level
@@ -506,9 +522,9 @@ Pass the same parent to `inspect_analysis_view` with
`view: {"kind":"environment-state"}` to extract the initial state the
capture's reads observed, and a render plan per read tool that reproduces those
reads from it. Where the environment manifest names an application's tools, the
state holds its data: entities by type and identity, plus documents for written
paths whose reads are not entities. Use the `template_rules` of the manifest the
state accompanies so tool names match; the view groups records the same way.
state holds its data: entities by type and identity, plus documents for reads
that are not entities. Use the `template_rules` of the manifest the state
accompanies so tool names match; the view groups records the same way.
```json
{
@@ -544,13 +560,27 @@ refused for them, as the manifest is not.
Each `GET` tool whose template ends in a parameter is an item tool, and its
type's `collection_template` is the template without that parameter; any other
`GET` tool is a list tool of its own template. An item tool proves its type's
`GET` tool is a list tool of its own template. Empty segments are ignored, so a
trailing slash names the same collection. An item tool proves its type's
`id_field` when every initial response carries the requested path value in the
same field, at the root or in one wrapper member (`id_basis: "path-parameter"`).
Otherwise the field is the first of `id`, `uid`, `uuid`, `_id`, `ID`, `UID`,
`Id`, `guid`, `key`, `slug` and `name` that every listed item carries as a
distinct string or number (`id_basis: "field-name"`). A list is the response
array, or the one array of objects in an envelope that is not itself an entity.
same field of one entity object (`id_basis: "path-parameter"`). The object is
the response itself or one wrapper member; failing both, it is the only element
of a one-element array at the root (pointer `/0`) or in one member
(`/<member>/0`), as in `{"posts":[{...}]}` or `{"data":[{...}]}`, provided one
of the identity names below repeats the value, since a filtered list may also
answer with one element that repeats its parent's value. Otherwise the field is
the first of `id`, `uid`, `uuid`, `_id`, `ID`, `UID`, `Id`, `guid`, `key`,
`slug` and `name` that every listed item carries as a distinct string or
number; when none does, it is the first key of the first listed item, in its
order, whose name ends in `Id` or `_id` (such as `orgId` or `user_id`), that
every item carries as a distinct string or number, and that at least one
initial response shows distinct across two or more items
(`id_basis: "field-name"`). A single listed item is distinct from nothing, and
its suffix-named fields are often foreign keys, so a list that only ever held
one item is left to a document. Empty initial lists constrain no field. A list
is the response array, or the one array of objects in an envelope that is not
itself an entity, preferring an array whose items an identity name identifies,
then an array of two or more items a suffix-named field identifies.
`entities` merge every initial observation of one identity within one
collection path: an item view's latest values win over a list view's, a view
@@ -567,9 +597,9 @@ Every entity cites its `record_ordinals`.
| Shape | Plan |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `item` | The entity object at `pointer` (the root or one member), the ordered `fields` the view shows and the `required_fields` every recording had, and `envelope_ordinal`: the recording whose response, with the entity replaced, every item response shares |
| `item` | The entity object at `pointer` (the root, one member, or `/0` or `/<member>/0`), the ordered `fields` the view shows and the `required_fields` every recording had, and `envelope_ordinal`: the recording whose response, with the entity replaced, every item response shares |
| `list` | The array at `pointer`, its `fields`, `membership` (`all`: every entity of the collection in state order, when each recording listed exactly that, had no query, no item-total member (`total`, `count`, `totalCount`, `item_count`, `totalcount`, `itemcount`, but not `total_pages` or `per_page`; a number or a string of digits) differing from the number listed, and no `has_more`, `next` or next-page token member set; `recorded`: the entities each recording listed) and `counters` that equal the number listed |
| `document` | The path's latest initial value, held in `documents` |
| `document` | The latest initial value of a list tool whose responses hold no identified list, when its initial reads share one request line; held in `documents` with that `path`, whether or not the capture writes it, unless its value changed across any recorded write between those reads (`fields-differ`) |
| `recorded` | A `reason` the tool replays its recordings instead |
An item or list plan is kept only when it reproduces the latest initial
@@ -578,11 +608,13 @@ regard to member order; a volatile field compares its recorded value. Each
plan lists the `checked_record_ordinals`. Otherwise the tool is a recorded view
with reason `envelope-varies`, `fields-differ` or `list-differs`; the other
reasons are `query-body` (a query `POST`), `no-init-recording`,
`body-unusable`, `not-entity`, `no-identity` and `query-varies` (a document
read under several queries). Documents are held only for list-shaped paths
that a `POST`, `PUT` or `PATCH` tool writes: a non-entity read of an item path
such as `/api/files/{id}`, or of a path that is only deleted, stays recorded
with `no-identity` or `not-entity`. A body is usable when its content type is
`body-unusable`, `no-identity` (an item read without a proved identity),
`path-varies` (another read under more than one path, as a template with a
parameter before its end), `query-varies` (another read of one path under
more than one query) and `rendered-oversize` (a document value whose JSON
renders to more than 256 KiB). A document holds the value of its one `path`, so for such
a template it says nothing about the template's other paths. A body is usable
when its content type is
JSON-like, it is UTF-8 text of at most 256 KiB, every number survives native
conversion exactly, and no member is named `__proto__`.
@@ -594,19 +626,25 @@ that the state establishes no authorization model, no way the application
computes responses or derived values, and no effects of writes. The state
accumulator charges the manifest's admission and grouping, every record line,
each body as temporary bytes before it is decoded and parsed, and every value
it keeps, within the same 96 MiB budget. The view performs no network activity
and serves nothing.
it keeps, within the same 96 MiB budget. Documents hold whole recorded response
values whose recorded text and rendered JSON are each at most 256 KiB, so a
capture with many read-only paths yields a correspondingly larger state result. The view performs no network activity
and serves nothing; `rea environment-serve` derives it from the capture at
startup, serves read tools from it and loads replacement states through the
AgentEnv data plane, as
[serve from the state](agent-environments.md#serve-from-the-state) describes.
## Resource and lifecycle boundaries
Complete evidence has a 32 MiB input, 96 MiB decoder reply and 64-level nesting
budget. Exceeding a budget returns no partial success.
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.
HAR, SAZ and qlog run in an owned
192 MiB Node old-generation heap with one V8 worker. Mitmproxy decoding applies
Linux limits of 768 MiB address space, 30 seconds CPU and 96 MiB file output;
both commands have a 30-second wall deadline and independently supervised
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.
+1 -1
View File
@@ -52,7 +52,7 @@ The result contains directly registered listener types/flags, callback coordinat
## Bounds and verification
Each protocol message has a 64 MiB budget; script/context metadata and request metadata each have an 8 MiB retained budget. Complete retained source text has a 32 MiB budget. Oversized or malformed evidence returns no partial successful result. Each command and the complete inspection/source-join phase have a 20-second deadline; cleanup has a separate 5-second command deadline plus transport closure. The selected observation duration must fit the host timer's positive 32-bit millisecond range. REA does not control the externally owned browser's CPU or memory.
Each protocol message has a 64 MiB budget; script/context metadata and request metadata each have an 8 MiB retained budget. Complete retained source text has a 32 MiB budget. Oversized or malformed evidence returns no partial successful result. Each command and the complete inspection/source-join phase use `command_timeout_ms` (default 20000). Cleanup uses a separate 5000 ms grace; `command_timeout_ms` does not raise or shorten it, and this release has no runtime override for that grace. Transport closure follows the grace. The selected observation duration must fit the host timer's positive 32-bit millisecond range. REA does not control the externally owned browser's CPU or memory.
`npm run verify:browser:runtime` uses caller-supplied `REA_BROWSER_EXECUTABLE` and the native `ps` command on Linux/macOS against an owned synthetic fixture, through real public CLI and stdio MCP. It checks exact Unicode/CRLF text/digests, same-URL identities, same-document navigation, inert declared names, executed and zero-count branches, function-only unknowns on repeated observation, listener selection, initiator association, actual arming and preservation of the external page. A separate fixture page closes during an armed MCP window to verify prompt termination and inline cleanup diagnostics. The optional positional entrypoint to `scripts/verify-browser-runtime.mjs` permits the same checks against an isolated installed tarball.
+3 -2
View File
@@ -71,7 +71,7 @@ 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 a 20-second deadline. Reported actual V8 heap limits
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
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
@@ -86,7 +86,8 @@ cleanup overrides success with `cleanup_incomplete` and the owning
operation/resources.
Complete evidence is bounded by a 4 MiB map, 32 MiB codec reply, 262144 decoded
rows/segments and 64 nested section levels. Exceeding a budget returns an explicit
rows/segments and 64 nested section levels. Those structural budgets and the
192 MiB codec heap have no runtime override. 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.
@@ -0,0 +1,50 @@
"""Read archived AppKit connector UIDs independently with Python's plistlib."""
import argparse
import hashlib
import json
from pathlib import Path
import plistlib
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--bundle', type=Path, required=True)
parser.add_argument('--document', required=True)
parser.add_argument('--baseline', type=Path, required=True,
help='Complete pre-change 64-document Evidence; equality control, not the oracle')
parser.add_argument('--small-baseline', type=Path, required=True,
help='Complete pre-change 1-document Evidence')
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
data = (args.bundle / args.document).read_bytes()
objects = plistlib.loads(data)['$objects']
connections = []
for index, value in enumerate(objects):
if not isinstance(value, dict) or not {'NSSource', 'NSLabel'} <= value.keys():
continue
classname = objects[value['$class'].data]['$classname']
if classname not in ('NSNibOutletConnector', 'NSNibControlConnector'):
continue
connections.append({
'archive_index': index,
'kind': 'outlet' if classname == 'NSNibOutletConnector' else 'action',
'name': objects[value['NSLabel'].data],
'source': str(value['NSSource'].data),
'destination': str(value['NSDestination'].data),
})
if not connections:
raise ValueError('No supported archived connectors; choose a keyed AppKit document')
args.output.write_text(json.dumps({
'document': args.document,
'archive_sha256': hashlib.sha256(data).hexdigest(),
'connections': connections,
'complete_result_paths': {
'1': str(args.small_baseline.resolve()),
'64': str(args.baseline.resolve()),
},
}, indent=2) + '\n')
if __name__ == '__main__':
main()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,225 @@
# Retained Interface Builder graph validation
Issue #5855. Baseline `f801b2fbfd28f4e3713adefddc7bd73db44c284c`, Linux
x86_64 / AMD EPYC 7B12 / Node 24.18.0 / npm 11.16.0. This is static archive
analysis on Linux, without executing an app or using ibtool or a native
analysis provider.
## Question and oracle
Identify the AAC settings outlets and VBR action in the official
[XLD 20250302 app](https://tmkk.undo.jp/xld/index_e.html), including archive
source/destination object identities, connection evidence and limitations.
- [Official DMG](https://tmkk.undo.jp/xld/xld-20250302.dmg) SHA-256:
`0032a5470ea4e32a11a35b8077ebf4986102891f8eb82743094f2c6621ad8aeb`.
- Selected document:
`Contents/PlugIns/XLDAacOutput.bundle/Contents/Resources/English.lproj/XLDAacOutput.nib`.
- Archive SHA-256:
`4fdbfba451ee64b1d333c96a4f3d1a0c4b543a36b9b3686e1151b51e4dd14c72`.
- Python's independent `plistlib` reads the actual `$objects` table and
`NSNibOutletConnector` / `NSNibControlConnector` records. Observed connections:
| Kind | Label | Source UID | Destination UID |
| ------ | ------------- | ---------- | --------------- |
| outlet | o_bitrate | 2 | 10 |
| outlet | o_gaplessFlag | 2 | 41 |
| outlet | o_prefPane | 2 | 53 |
| outlet | o_quality | 2 | 62 |
| outlet | o_useVBR | 2 | 58 |
| action | vbrChecked: | 58 | 2 |
The benchmark checks each connector, both endpoint records, relationship kinds,
archive path/digest and page coverage against these independent facts. It also
compares all returned document rows with the exported complete parent; all
64-document normalized results match the pre-change complete CLI Evidence.
The complete exported parent Evidence is equal across versions for both
admission sizes. The reproduction oracle requires a separate pre-change
complete Evidence file for each size, and the harness compares the entire
parent, including its identity and parameters, for the selected limit. Equality is a preservation control, not an independent proof
of every decoded fact.
The default 64-document report contains 6,528 nodes and 1,378 edges. The small
control admits one document from the same unmodified real app, with 80 nodes
and 13 edges. Both reports retain truncation and hierarchy limitations. Resource
sessions have no executable subject or executable digest; archive identities
must not be promoted to executable identity.
## Completed workflow and accounting
`interface-builder-views.mjs` uses the real SDK and compiled stdio MCP server:
connect, discover the full catalog, open bundle resources, decode with summary
delivery, exhaust three 32-row node pages and one edge page for the selected
document, repeat those same four pages as a warm query diagnostic, export the
complete Evidence, verify the answer and complete parent, and await cleanup.
The repeat is included in all whole-task costs. Each run starts a fresh server
and isolated account. Decoding is the first decode in that server; retained
queries follow the summary's first validation. OS caches were not flushed, so
these are not disk-cold measurements.
Artifact acquisition/extraction, build time and independent oracle preparation
are outside this analysis task. No actual model session or billed model token
usage was measured. The SDK benchmark answers a fixed factual question; it does
not establish agent usability or general provider performance.
Experiments ran serially using `resource-run.py --observe-payload-exit`: one CPU
(affinity 15), 100% quota, 4 GiB hard memory / 3 GiB early stop, no swap and a
192-task ceiling. CPU and peak memory cover the SDK, server and observer family.
Peak is cgroup memory.peak, including charged page cache, rather than summed RSS.
Payload-exit cgroup counters are authenticated and cleanup is verified; final
whole-scope counters are unavailable after the transient scope disappears.
Every accepted and failed task has exit/cleanup verification and zero observed
memory high/max/OOM/OOM-kill and PID-limit events. Missing final counters are
not interpreted as zero.
## Observations
Medians across all accepted baseline runs and the measured optimized
production-source cohort. The later harness-only review fix does not change
those production files; its revalidation is recorded separately. Five
earlier candidate runs remain labeled pre-review diagnostics and are excluded
from the final-source medians. One failed initial harness run is also retained.
All individual runs, phase timings, token estimates and raw receipt hashes are
in `interface-builder-views.json`.
| Admitted documents | Revision | Runs | Whole wall s | Family CPU s | Family peak MiB | First four pages ms | Repeated four pages ms |
| ------------------ | -------- | ---- | ------------ | ------------ | --------------- | ------------------- | ---------------------- |
| 64 | baseline | 5 | 30.798 | 30.532 | 1614.7 | 2606.3 | 1817.8 |
| 64 | final | 5 | 27.683 | 27.424 | 1551.1 | 338.8 | 94.3 |
| 1 | baseline | 4 | 15.702 | 15.456 | 952.6 | 389.5 | 101.0 |
| 1 | final | 4 | 18.101 | 17.822 | 944.8 | 367.5 | 101.7 |
The larger sampled task has about 10% lower whole wall/CPU medians and 94.8%
lower repeated-page latency. Whole-task ranges overlap substantially:
27.420–51.481 s baseline and 25.247–45.451 s final. Startup/initial decoding
vary independently of the retained-query change; these observations do not
establish a general whole-task speedup or stable memory reduction. Both
versions' slow first runs remain included.
The small sampled task is slower overall (15.3% wall), with no repeat-page
benefit. In the two later interleaved small pairs, baseline took 16.883/18.317 s
and final took 16.988/20.276 s. Do not interpret the earlier unpaired small
measurements as evidence of a cold-start improvement. Startup variance remains
unexplained; this optimization is justified by repeated large-parent queries,
not a claim that every completed task becomes faster.
An additional production-boundary diagnostic isolates the changed projection
from startup, decoding and transport. It imports both compiled owners, seals
separate clones of the actual exported parent with each owner's snapshot
module, and alternates version order for three epochs. Each epoch projects a
summary and the same eight pages; every resulting payload is equal. Median
projection CPU falls from 157.4 to 30.3 ms for the one-document parent and
6003.5 to 914.7 ms for the 64-document parent. Snapshot preparation, Evidence
authentication and all adapters are excluded from this diagnostic; the public
tasks above account for their whole costs. First large-summary CPU rises from
620.2 to 879.8 ms because representation comparison is paid up front. This is a
reuse tradeoff, not a faster first decode. The diagnostic source/receipt hashes,
per-call wall/CPU times and bounded process-family counters are retained in the
JSON; it does not replace real public workflow evidence.
Selection still scans retained rows to count every match; no additional
per-document index is retained. The whole original graph and serialization
costs still exist.
Public input/output contracts and complete parent Evidence are unchanged.
Tokenizer estimates count the entire 212-tool input catalog once, every RPC's
arguments and text response, including warm diagnostics and export. The raw SDK
catalog, including output schemas, is reported separately. Structured content
and model-specific discovery/exposure may incur different costs. The 64-document task estimates are 282,885 baseline and 282,887 for the
initial candidate (198,492 input-catalog tokens in each); the two-token change
comes solely from the export destination. The first ten RPC requests and
replies are exactly equal. Raw SDK catalog exposure estimates 891,652 tokens
including output schemas. Final-source values and every run are retained in
the JSON. There is no claimed token reduction.
## Ownership and reuse
The Interface Builder view owner validates an unknown normalized result with
its existing schema. Reuse requires the application's seal plus exact owned
immutable snapshot identity and representation-preserving comparison against
the parsed result. A weak set records only successful snapshots. It retains no
second graph, file-path cache, index, transient errors or failed subject
bindings. Every view still checks its current parent's target binding and
constructs current parent provenance and limitations. Different results,
mutable imports, forged seals and schema normalization revalidate normally.
Document selection uses the shared matching pager to count all matching rows
while retaining only the requested page. Unfiltered requests keep direct
slicing. Row IDs, ordering, provenance, decoder truncation and unknowns remain
unchanged. CLI and MCP share the same application/domain implementation.
The snapshot comparison helper's type accepts owning schemas with optional
undefined members; its runtime comparison is unchanged and refuses a parsed
representation that actually adds an undefined field.
## Reproduction and limits
Extract the official app without running it. On the baseline, save complete
CLI JSON with `rea decode-interface-builder <XLD.app> --format json` and a
separate control with `rea decode-interface-builder <XLD.app> --max-documents 1
--format json` (without profiler logging mixed into either JSON). Then:
```sh
python3 experiments/analysis-efficiency/interface-builder-oracle.py \
--bundle '<XLD.app>' \
--document 'Contents/PlugIns/XLDAacOutput.bundle/Contents/Resources/English.lproj/XLDAacOutput.nib' \
--baseline '<complete-64-document-evidence.json>' \
--small-baseline '<complete-1-document-evidence.json>' --output '<oracle.json>'
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/interface-builder-views.mjs \
--runtime '<absolute baseline or candidate checkout>' --input '<XLD.app>' \
--oracle '<oracle.json>' --output '<new output directory>' --documents 64
```
Repeat with `--documents 1`. Paths must be absolute. The oracle uses standard
Python only; token estimates use offline tiktoken 0.12.0 / cl100k_base. Count
compact JSON tool name/description/inputSchema entries once, compact
`{name,arguments}` for every transcript event, and every returned text block;
these are estimates, not actual model usage.
Failed attempts remain visible. `base64-a` completed all eight page RPCs but the
first harness incorrectly expected an action-source `contains` edge. Production
correctly returned `target_action`; the harness was corrected before accepted
runs. That failure's costs/receipt remain in the JSON. An initial typecheck
refused the snapshot helper's unnecessarily narrow generic constraint for the
optional resource root; the constraint was corrected without changing runtime
comparison. Build resource observations with a still-exiting Turbo child are
not qualified task measurements.
KeepingYouAwake 1.6.8 (official ZIP SHA-256
`8001a149b4490c008fdac19898bce9902d516c4aa6412a7eb0f9a37443b15c6b`)
was also downloaded and smoke-decoded through the real CLI
(17 documents, 1,369 nodes, 542 edges), but its NIBArchive records cannot be read
by this plistlib oracle. It is not a comparative benchmark or independent NIB
correctness claim. macOS ibtool, runtime UI, other archive variants, globally
complete hierarchy/class interpretation, #5720/#5721 and the remaining source
decoder issues are unverified or unchanged here.
## Integration verification
The measured comparison above was pinned to `f801b2fbf`. The branch was then
rebased onto `f5410bdc4` (the separate #5806 scalar-record fix). All six recorded owned-source
fingerprints remained identical across that rebase. On the rebased runtime both real SDK
tasks passed again, with the complete retained parent equal to the original
baseline for both admission sizes. The complete real CLI Evidence and portable
CLI page also equal the baseline/MCP results. These integration runs are recorded
separately in the JSON and excluded from the pinned comparison medians.
The relevant 12-file suite passed 204 tests after the paging refactor. After
rebasing, another six-file run passed 78 tests covering scalar source facts,
keyed archive number metadata, Interface Builder views, production MCP summary
paging and compiled CLI numeric/hierarchy boundaries. Both cohorts verified
owned cleanup with no observed memory/PID limit events; tests used the bounded
two-CPU, 4 GiB guard. Native ibtool/macOS runtime behavior is not established by
these tests.
Greptile identified that the original script only enforced the pre-change
comparison for 64 documents; the one-document equality above had been checked
manually. The oracle now requires both baseline files and the harness compares
the selected complete parent, including its Evidence ID and parameters, for
either limit. Both real workflows passed again. An intentional wrong baseline
(the 64-document parent supplied for the 1-document task) is rejected after all
other real-task checks succeed, with cleanup verified. These review checks and
updated harness fingerprints are recorded separately. Production source and
original comparison medians are unchanged; original measured fingerprints are
preserved rather than relabeled as the new harness.
@@ -0,0 +1,238 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { finished } from "node:stream/promises";
import { pathToFileURL } from "node:url";
import { parseArgs } from "node:util";
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
const { values } = parseArgs({
options: {
runtime: { type: "string" },
input: { type: "string" },
oracle: { type: "string" },
output: { type: "string" },
documents: { type: "string", default: "64" },
},
});
for (const key of ["runtime", "input", "oracle", "output"])
assert.equal(values[key], resolve(values[key]));
const maxDocuments = Number(values.documents);
assert.ok([1, 64].includes(maxDocuments));
const oracle = JSON.parse(await readFile(values.oracle, "utf8"));
const baselinePath = oracle.complete_result_paths?.[String(maxDocuments)];
assert.equal(
typeof baselinePath,
"string",
"Provide a pre-change baseline for the selected document limit.",
);
assert.equal(baselinePath, resolve(baselinePath));
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
assert.equal(
sha256(await readFile(join(values.input, oracle.document))),
oracle.archive_sha256,
);
const require = createRequire(join(values.runtime, "package.json"));
const { Client } = await import(
pathToFileURL(require.resolve("@modelcontextprotocol/client"))
);
const { StdioClientTransport } = await import(
pathToFileURL(require.resolve("@modelcontextprotocol/client/stdio"))
);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-interface-builder-views-"));
const report = {
question:
"Identify archived AAC settings outlets and the VBR action with source/destination object identities, coverage and complete Evidence access.",
max_documents: maxDocuments,
calls: [],
};
const client = new Client({
name: "rea-interface-builder-views",
version: "1",
});
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
env: {
HOME: account,
PATH: process.env.PATH,
NODE_OPTIONS: "--max-old-space-size=2048",
UV_THREADPOOL_SIZE: "1",
REA_LOG_LEVEL: "silent",
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log"));
const serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const transcript = [];
const call = createRetainedQueryCaller(client, (receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
});
async function result(name, input, phase = name) {
const reply = await call(name, input, phase);
assert.ok(!reply.isError, JSON.stringify(reply));
assert.ok(reply.structuredContent);
assert.equal(reply.content.length, 1);
assert.equal(reply.content[0].type, "text");
assert.deepEqual(JSON.parse(reply.content[0].text), reply.structuredContent);
return reply.structuredContent;
}
try {
await client.connect(transport);
const catalog = await client.listTools();
assert.equal(catalog.nextCursor, undefined);
report.catalog_tools = catalog.tools.length;
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
await result("open_bundle_resources", { path: values.input });
const initial = await result("decode_interface_builder", {
max_documents: maxDocuments,
detail: "summary",
});
const summary = initial.normalized_result;
report.parent_evidence_id = summary.parent_evidence_id;
assert.equal(summary.resource_root.executable_sha256, null);
assert.equal(summary.artifact.sha256, null);
assert.equal(summary.summary.truncated, true);
assert.ok(
summary.summary.documents.some(
(d) =>
d.relative_path === oracle.document &&
d.archive_sha256 === oracle.archive_sha256,
),
);
const collections = {};
for (const pass of ["first", "warm"]) {
for (const collection of ["nodes", "edges"]) {
const rows = [];
let offset = 0;
for (;;) {
const selected = await result(
"inspect_analysis_view",
{
source: {
kind: "retained-evidence",
evidence_id: report.parent_evidence_id,
},
view: {
kind: "interface-builder",
collection,
document: oracle.document,
offset,
limit: 32,
},
},
`retained:${pass}:${collection}:${offset}`,
);
const page = selected.normalized_result;
assert.equal(page.parent_evidence_id, report.parent_evidence_id);
assert.equal(page.source_truncated, true);
assert.ok(page.unknowns.length > 0);
rows.push(...page.items);
if (page.coverage.exhausted) {
assert.equal(rows.length, page.coverage.total);
break;
}
assert.ok(page.items.length > 0);
offset += page.items.length;
}
if (pass === "warm") assert.deepEqual(rows, collections[collection]);
else collections[collection] = rows;
}
}
const byId = new Map(collections.nodes.map((n) => [n.id, n]));
for (const expected of oracle.connections) {
const connector = collections.nodes.find(
(n) => n.kind === expected.kind && n.name === expected.name,
);
assert.ok(connector);
const sourceRelation =
expected.kind === "outlet" ? "contains" : "target_action";
const source = collections.edges.find(
(e) => e.to === connector.id && e.relation === sourceRelation,
);
assert.equal(
byId.get(source.from).attributes.interface_builder_object_id,
expected.source,
);
const relation = expected.kind === "outlet" ? "outlet_to" : "target_action";
const destination = collections.edges.find(
(e) =>
e.from === connector.id &&
e.relation === relation &&
byId.get(e.to)?.attributes.interface_builder_object_id ===
expected.destination,
);
assert.equal(
byId.get(destination.to).attributes.interface_builder_object_id,
expected.destination,
);
for (const row of [connector, source, destination])
assert.ok(
row.evidence.some(
(e) =>
e.artifact_path === oracle.document &&
e.artifact_sha256 === oracle.archive_sha256,
),
);
}
report.answer = oracle.connections;
await result("export_evidence_bundle", {
path: join(values.output, "evidence.json"),
});
const bundle = JSON.parse(
await readFile(join(values.output, "evidence.json"), "utf8"),
);
const parent = bundle.records.find(
(e) => e.evidence_id === report.parent_evidence_id,
);
assert.equal(parent.subject, null);
assert.equal(parent.operation, "decode_interface_builder");
const full = parent.normalized_result;
for (const collection of ["nodes", "edges"])
assert.deepEqual(
collections[collection],
full.graph[collection].filter((r) =>
r.evidence.some((e) => e.artifact_path === oracle.document),
),
);
const baselineBytes = await readFile(baselinePath, "utf8");
const expectedParent = JSON.parse(baselineBytes);
report.baseline_sha256 = sha256(baselineBytes);
report.baseline_evidence_id = expectedParent.evidence_id;
const mismatch = `Complete retained Evidence differs from the pre-change ${maxDocuments}-document baseline.`;
assert.equal(parent.evidence_id, expectedParent.evidence_id, mismatch);
assert.deepEqual(parent, expectedParent, mismatch);
report.available_counts = {
nodes: full.graph.nodes.length,
edges: full.graph.edges.length,
};
report.oracle_sha256 = sha256(await readFile(values.oracle));
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.failure = String(cause?.stack ?? cause);
process.exitCode = 1;
} finally {
if (
!(await finalizeRetainedQueryReceipt({
report,
client,
transport,
account,
serverLog,
serverLogFinished,
output: values.output,
transcript,
}))
)
process.exitCode = 1;
}
@@ -0,0 +1,84 @@
"""Summarize completed CLI trials; token counts are estimates, not model usage."""
import argparse
import hashlib
import json
import statistics
from pathlib import Path
import tiktoken
parser = argparse.ArgumentParser()
parser.add_argument('root', type=Path)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
root = args.root
inputs = json.loads((root / 'measurement-inputs.json').read_text())
expected = inputs['expected_runtime_bindings']
assert expected['complete']['git_revision'] == inputs['baseline_revision']
assert expected['selected']['git_revision'] == inputs['feature_parent_revision']
assert expected['complete']['root'] != expected['selected']['root']
assert expected['complete']['compiled_cli_sha256'] != expected['selected']['compiled_cli_sha256']
assert expected['complete']['source_diff_sha256'] == hashlib.sha256(b'').hexdigest()
assert expected['complete']['untracked_source_sha256'] == hashlib.sha256(b'[]').hexdigest()
enc = tiktoken.get_encoding('cl100k_base')
count = lambda text: len(enc.encode(text, disallowed_special=()))
compact = lambda obj: json.dumps(obj, ensure_ascii=False, separators=(',', ':'))
runs = []
for case in sorted(root.glob('pair-*')):
if not case.is_dir():
continue
report = json.loads((case / 'report.json').read_text())
assert report['status'] == 'passed' and report['cleanup_verified']
assert report['runtime_binding_verified']
assert report['runtime_binding'] == expected[report['mode']], case.name
assert str(Path(report['runtime']).resolve()) == report['runtime_binding']['root']
receipt = next(json.loads(line) for line in reversed(case.with_name(case.name + '.log').read_text().splitlines()) if line.startswith('{"resource_scope":'))
obs = receipt['payload_resource_observation']
assert receipt['abort'] is None and receipt['payload_resource_observation_verified']
assert obs['payload_exit_code'] == 0 and obs['payload_cleanup_verified']
assert obs['resource_events']['memory.events']['oom_kill'] == 0
tokens = {'question_and_recipe': count(report['question'] + '\n' + report['recipe']),
'arguments': sum(count(compact(call['arguments'])) for call in report['calls']),
'help_and_replies': sum(count((case / call['stdout_file']).read_text()) for call in report['calls'])}
tokens['whole_task'] = sum(tokens.values())
runs.append({'case': case.name, 'mode': report['mode'], 'cache': report['cache'], 'runtime': report['runtime'], 'runtime_binding': report['runtime_binding'], 'runtime_binding_audit_ms': report['runtime_binding_audit_ms'],
'wall_seconds': receipt['elapsed_seconds'], 'cpu_seconds': obs['cpu_stat']['usage_usec'] / 1e6,
'peak_process_family_bytes_including_page_cache': obs['memory_peak_bytes'],
'tokens_estimated': tokens, 'calls': report['calls'],
'parent_evidence_id': report['parent_evidence_id'], 'parent_semantic_sha256': report['parent_semantic_sha256'],
'answer_sha256': report['answer_sha256'], 'oracle_verified': report['oracle_verified'],
'cleanup_verified': True, 'resource_events': obs['resource_events']})
assert len(runs) == 12
for key in ['answer_sha256', 'parent_semantic_sha256', 'parent_evidence_id']:
assert len({row[key] for row in runs}) == 1, key
summary = {}
for cache in ['cold', 'warm']:
summary[cache] = {}
for mode in ['complete', 'selected']:
rows = [r for r in runs if r['mode'] == mode and r['cache'] == cache]
assert len(rows) == 3
summary[cache][mode] = {key: {'median': statistics.median(r[key] for r in rows), 'min': min(r[key] for r in rows), 'max': max(r[key] for r in rows)} for key in ['wall_seconds', 'cpu_seconds', 'peak_process_family_bytes_including_page_cache']}
summary[cache][mode]['whole_task_tokens_estimated'] = [r['tokens_estimated']['whole_task'] for r in rows]
result = {'status': 'passed', 'tokenizer': 'tiktoken==0.12.0/cl100k_base; estimates, not model usage',
'context_scope': 'Question, supplied command recipe, actual selected-command help, all logical CLI argv and all stdout. No model invoked; reasoning, framing, irrelevant tool discovery and actual native-agent usage unmeasured.',
'cache_scope': 'cold disables Node compile cache; warm primes the identical workflow once per runtime/mode before timing. Fresh processes and isolated HOME each trial; OS page cache uncontrolled.',
'resource_scope': 'Whole task driver plus all CLI children; includes pre/post source-and-compiled-runtime binding verification, bundle publication/authentication, help, extra calls and local answer/oracle checking. Kernel cgroup peak includes file cache; not sampled RSS. Download/build/oracle preparation and priming excluded.',
'acquisition': json.loads((root / 'acquisition.json').read_text()), 'measurement_inputs': inputs,
'summary': summary, 'runs': runs}
native = root / 'native-control'
if native.exists():
binding = json.loads((native / 'runtime-binding.json').read_text())
assert binding['runtime_binding_verified'] and binding['runtime_binding'] == expected['complete']
assert str(Path(binding['runtime']).resolve()) == binding['runtime_binding']['root']
receipt = next(json.loads(line) for line in reversed((root / 'native-control.log').read_text().splitlines()) if line.startswith('{"resource_scope":'))
obs = receipt['payload_resource_observation']
assert receipt['abort'] is None and receipt['payload_resource_observation_verified'] and obs['payload_exit_code'] == 0 and obs['payload_cleanup_verified']
baseline_case = root / next(run['case'] for run in runs if run['mode'] == 'complete')
assert json.loads((native / 'answer.json').read_text()) == json.loads((baseline_case / 'answer.json').read_text())
control_source = Path(__file__).with_name('managed-cli-local-control.py').read_text()
result['existing_native_control'] = {'runtime': binding['runtime'], 'runtime_binding': binding['runtime_binding'], 'runtime_binding_audit_ms': binding['runtime_binding_audit_ms'], 'repetitions': 1,
'tokens_estimated_script_plus_help_plus_answer': count(control_source) + count((native / 'help.txt').read_text()) + count((native / 'answer.json').read_text()),
'token_scope': 'Projection source, original CLI help and returned answer; excludes shared question and launcher argv. No actual model usage measured.',
'wall_seconds': receipt['elapsed_seconds'], 'cpu_seconds': obs['cpu_stat']['usage_usec'] / 1e6,
'peak_process_family_bytes_including_page_cache': obs['memory_peak_bytes'], 'answer_matches': True, 'cleanup_verified': True}
args.output.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(summary, indent=2))
@@ -0,0 +1,44 @@
"""Existing native code-composition control: save complete stdout, return selected facts."""
import argparse
import json
import subprocess
import time
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument('runtime', type=Path)
parser.add_argument('target', type=Path)
parser.add_argument('output', type=Path)
parser.add_argument('--measurement-inputs', type=Path, required=True)
args = parser.parse_args()
args.output.mkdir()
node = 'node'
inputs = json.loads(args.measurement_inputs.read_text())
expected = inputs['expected_runtime_bindings']['complete']
assert expected['git_revision'] == inputs['baseline_revision']
helper = Path(__file__).with_name('managed-cli-runtime.mjs')
def capture_binding():
return json.loads(subprocess.check_output([node, str(helper), str(args.runtime)], text=True))
binding_started = time.monotonic()
binding = capture_binding()
binding_audit_ms = {'before': (time.monotonic() - binding_started) * 1000}
assert binding == expected
entry = str(args.runtime / 'scripts/rea.mjs')
help_result = subprocess.run([node, entry, 'inspect-managed-members', '--help'], check=True, capture_output=True, text=True)
(args.output / 'help.txt').write_text(help_result.stdout)
with (args.output / 'parent.json').open('w') as file:
subprocess.run([node, entry, 'inspect-managed-members', str(args.target), '--type', 'Newtonsoft.Json.JsonConvert', '--json'], check=True, stdout=file)
parent = json.loads((args.output / 'parent.json').read_text())
data = parent['normalized_result']
answer = {'parent_evidence_id': parent['evidence_id'], 'artifact_sha256': data['artifact']['sha256'],
'selection': data['selection'], 'source_coverage': data['coverage'],
'total_methods': len(data['methods']), 'total_member_refs': len(data['member_refs']),
'methods': data['methods'][:8], 'member_refs': data['member_refs'][:16],
'limitation': 'MemberRef rows are assembly-wide; methods are selected-type rows. Page exhaustion is not an absence claim.'}
(args.output / 'answer.json').write_text(json.dumps(answer))
binding_finished = time.monotonic()
assert capture_binding() == binding
binding_audit_ms['after'] = (time.monotonic() - binding_finished) * 1000
(args.output / 'runtime-binding.json').write_text(json.dumps({'runtime': str(args.runtime), 'runtime_binding': binding, 'runtime_binding_verified': True, 'runtime_binding_audit_ms': binding_audit_ms}))
print(json.dumps(answer))
@@ -0,0 +1,18 @@
import json,hashlib,argparse
from pathlib import Path
import dnfile
parser=argparse.ArgumentParser();parser.add_argument('root',type=Path);root=parser.parse_args().root;path=root/'Newtonsoft.Json.dll'
pe=dnfile.dnPE(str(path));assert pe.net is not None
counts={name:len(getattr(pe.net.mdtables,name).rows) if getattr(pe.net.mdtables,name,None) else 0 for name in ['TypeDef','MethodDef','MemberRef','Field','Property','Event','Param','StandAloneSig','TypeSpec','ExportedType']}
selected=[]
for index,row in enumerate(pe.net.mdtables.TypeDef.rows,1):
name=(str(row.TypeNamespace)+'.' if str(row.TypeNamespace) else '')+str(row.TypeName)
if name!='Newtonsoft.Json.JsonConvert':continue
selected.append({'token':f'0x{0x02000000|index:08x}','name':name,'methods':[{'token':f'0x{0x06000000|m.row_index:08x}','name':str(m.row.Name),'rva':m.row.Rva,'signature_hex':m.row.Signature.value.hex()} for m in row.MethodList]})
assert len(selected)==1
refs=[{'token':f'0x{0x0a000000|i:08x}','name':str(row.Name),'signature_hex':row.Signature.value.hex()} for i,row in enumerate(pe.net.mdtables.MemberRef.rows[:16],1)]
assembly=pe.net.mdtables.Assembly.rows[0]
oracle={'decoder':'dnfile==0.18.0 (independent Python PE/CLI parser)','assembly_sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'assembly_name':str(assembly.Name),'assembly_version':'.'.join(str(getattr(assembly,k)) for k in ['MajorVersion','MinorVersion','BuildNumber','RevisionNumber']),'row_counts':counts,'selected_types':selected,'member_refs_first_16':refs,'target_execution':False}
(root/'oracle.json').write_text(json.dumps(oracle,indent=2)+'\n')
print(json.dumps({'assembly':oracle['assembly_name'],'version':oracle['assembly_version'],'row_counts':counts,'selected_type':selected[0]['name'],'selected_methods':len(selected[0]['methods']),'target_execution':False}))
pe.close()
@@ -0,0 +1,87 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { readFile, readdir, realpath } from "node:fs/promises";
import { join, relative } from "node:path";
import { fileURLToPath } from "node:url";
const sha = (value) => createHash("sha256").update(value).digest("hex");
/** Bind a trial to its source checkout and the compiled CLI actually launched. */
export const captureRuntimeBinding = async (runtime) => {
const root = await realpath(runtime);
const git = (args) =>
execFileSync("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024 });
const gitRevision = git(["rev-parse", "HEAD"]).toString().trim();
const sourcePaths = ["src", "scripts", "package.json", "package-lock.json"];
const sourceDiff = git([
"diff",
"--no-ext-diff",
"--no-textconv",
"--binary",
"HEAD",
"--",
...sourcePaths,
]);
const untracked = git([
"ls-files",
"--others",
"--exclude-standard",
"-z",
"--",
...sourcePaths,
])
.toString()
.split("\0")
.filter(Boolean)
.sort();
const untrackedEntries = [];
for (const name of untracked)
untrackedEntries.push([name, sha(await readFile(join(root, name)))]);
const files = ["scripts/rea.mjs", "package.json", "package-lock.json"];
const visit = async (directory) => {
const entries = await readdir(directory, { withFileTypes: true });
for (const entry of entries.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
)) {
const path = join(directory, entry.name);
assert.ok(!entry.isSymbolicLink(), `Unbound compiled CLI link: ${path}`);
if (entry.isDirectory()) await visit(path);
else if (entry.isFile() && /\.(?:[cm]?js|json)$/.test(entry.name))
files.push(relative(root, path));
}
};
await visit(join(root, "dist/cli-runtime"));
assert.ok(
files.includes(join("dist", "cli-runtime", "cli.js")),
"Missing compiled CLI entry",
);
const hash = createHash("sha256");
let bytes = 0;
for (const name of files.sort()) {
const value = await readFile(join(root, name));
bytes += value.length;
hash.update(JSON.stringify([name, value.length, sha(value)]) + "\n");
}
return {
binding_version: 1,
root,
node: process.version,
git_revision: gitRevision,
source_diff_sha256: sha(sourceDiff),
untracked_source_sha256: sha(JSON.stringify(untrackedEntries)),
compiled_cli_sha256: hash.digest("hex"),
compiled_cli_files: files.length,
compiled_cli_bytes: bytes,
};
};
export const assertRuntimeBinding = (actual, expected, mode) => {
assert.ok(expected, `Missing expected runtime binding for ${mode}`);
assert.deepEqual(actual, expected, `Runtime binding differs for ${mode}`);
};
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
console.log(
JSON.stringify(await captureRuntimeBinding(process.argv[2]), null, 2),
);
}
@@ -0,0 +1,72 @@
{
"status": "passed",
"scope": "Single completed SDK regression after shared JSON reader/admission changes; not a new paired source-to-bundle performance comparison. Existing semantic-node cap one answers static correspondence only; no equal semantic-graph coverage claim. Corpus/reference preparation performed earlier and excluded.",
"artifact": {
"name": "Redux Toolkit 2.2.7",
"tarball_sha256": "f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9"
},
"runtime_parent_revision": "4fab2dfda16236dc407aa179920780d6a970dda3",
"production_manifest": "managed-cli.json#measurement_inputs.production_sources_sha256",
"input_schema_profile": "default compact",
"output_schema_profile": "full",
"tokens_estimated": {
"input_catalog_once": 49054,
"arguments": 307,
"text_replies": 7017,
"whole_task": 56378
},
"token_scope": "tiktoken==0.12.0/cl100k_base; one name/description/input-schema catalog plus actual arguments and MCP text replies; excludes output schemas, framing, model prompts/reasoning and actual model usage. Not actual native-agent token usage.",
"actual_model_usage": null,
"wall_seconds": 21.38,
"cpu_seconds": 21.022683,
"peak_process_family_bytes_including_page_cache": 974041088,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 9837.265845000002,
"is_error": false,
"reply_bytes": 25941
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 256.3669919999993,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 3012.057904000001,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 148.9011160000009,
"is_error": false,
"reply_bytes": 364
}
],
"independent_proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
}
@@ -0,0 +1,266 @@
import {
captureRuntimeBinding,
assertRuntimeBinding,
} from "./managed-cli-runtime.mjs";
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { createHash } from "node:crypto";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join, resolve } from "node:path";
const execute = promisify(execFile);
const [runtime, root, output, mode, cache = "cold"] = process.argv.slice(2);
for (const value of [runtime, root, output])
assert.equal(resolve(value), value);
assert.ok(["complete", "selected"].includes(mode));
assert.ok(["cold", "warm"].includes(cache));
await mkdir(output);
await mkdir(join(output, "tmp"));
const oracle = JSON.parse(await readFile(join(root, "oracle.json"), "utf8"));
const target = join(root, "Newtonsoft.Json.dll");
const hash = (value) => createHash("sha256").update(value).digest("hex");
assert.equal(hash(await readFile(target)), oracle.assembly_sha256);
const stable = (value) =>
Array.isArray(value)
? value.map(stable)
: value !== null && typeof value === "object"
? Object.fromEntries(
Object.keys(value)
.sort()
.map((key) => [key, stable(value[key])]),
)
: value;
const report = {
status: "incomplete",
runtime,
mode,
cache,
node: process.version,
question:
"Report identity/selection/source coverage for JsonConvert, its total method count and the first eight complete method records, plus the first sixteen assembly-wide MemberRef records and continuation coverage.",
recipe:
mode === "selected"
? "Use --detail summary --evidence-bundle FILE; pass the exact parent_evidence_id to inspect-analysis-view --evidence-bundle FILE with source kind retained-evidence and view {kind:managed-members,collection:methods|member_refs,offset:0,limit:8|16}."
: "Use inspect-managed-members --type Newtonsoft.Json.JsonConvert; the completed full Evidence supplies the requested rows and remains accessible.",
calls: [],
cleanup_verified: false,
};
const began = performance.now();
const environment = {
PATH: process.env.PATH,
HOME: output,
TMPDIR: join(output, "tmp"),
LANG: "C.UTF-8",
REA_LOG_LEVEL: "silent",
UV_THREADPOOL_SIZE: "1",
NODE_OPTIONS: "--max-old-space-size=2048",
...(cache === "cold"
? { NODE_DISABLE_COMPILE_CACHE: "1" }
: { NODE_COMPILE_CACHE: join(root, `compile-cache-${mode}`) }),
};
const call = async (label, args, json = true) => {
const started = performance.now();
const result = await execute(
process.execPath,
[join(runtime, "scripts/rea.mjs"), ...args],
{
cwd: output,
env: environment,
maxBuffer: 16 * 1024 * 1024,
timeout: 120000,
},
);
await writeFile(join(output, label + ".stdout"), result.stdout);
await writeFile(join(output, label + ".stderr"), result.stderr);
report.calls.push({
label,
arguments: ["rea", ...args],
elapsed_ms: performance.now() - started,
stdout_bytes: Buffer.byteLength(result.stdout),
stdout_file: label + ".stdout",
});
return json ? JSON.parse(result.stdout) : result.stdout;
};
let parent, summary, methods, refs;
try {
const inputs = JSON.parse(
await readFile(join(root, "measurement-inputs.json"), "utf8"),
);
const expected = inputs.expected_runtime_bindings?.[mode];
assert.ok(expected, `Missing expected runtime for ${mode}`);
assert.equal(
expected.git_revision,
mode === "complete"
? inputs.baseline_revision
: inputs.feature_parent_revision,
);
assert.notEqual(
inputs.expected_runtime_bindings.complete.root,
inputs.expected_runtime_bindings.selected.root,
);
assert.notEqual(
inputs.expected_runtime_bindings.complete.compiled_cli_sha256,
inputs.expected_runtime_bindings.selected.compiled_cli_sha256,
);
const bindingStarted = performance.now();
report.runtime_binding = await captureRuntimeBinding(runtime);
report.runtime_binding_audit_ms = {
before: performance.now() - bindingStarted,
};
assertRuntimeBinding(report.runtime_binding, expected, mode);
await call("help-members", ["inspect-managed-members", "--help"], false);
if (mode === "complete") {
parent = await call("complete", [
"inspect-managed-members",
target,
"--type",
"Newtonsoft.Json.JsonConvert",
"--json",
]);
methods = parent.normalized_result.methods.slice(0, 8);
refs = parent.normalized_result.member_refs.slice(0, 16);
} else {
await call("help-view", ["inspect-analysis-view", "--help"], false);
const bundlePath = join(output, "parent.json");
summary = await call("summary", [
"inspect-managed-members",
target,
"--type",
"Newtonsoft.Json.JsonConvert",
"--detail",
"summary",
"--evidence-bundle",
bundlePath,
"--json",
]);
const id = summary.normalized_result.parent_evidence_id;
assert.ok(id);
const bundle = JSON.parse(await readFile(bundlePath, "utf8"));
assert.equal(bundle.records.length, 1);
parent = bundle.records[0];
assert.equal(parent.evidence_id, id);
for (const [collection, limit] of [
["methods", 8],
["member_refs", 16],
]) {
const value = await call(collection, [
"inspect-analysis-view",
JSON.stringify({
source: { kind: "retained-evidence", evidence_id: id },
view: { kind: "managed-members", collection, offset: 0, limit },
}),
"--evidence-bundle",
bundlePath,
"--json",
]);
const page = value.normalized_result;
assert.equal(page.parent_evidence_id, id);
assert.deepEqual(page.source_coverage, parent.normalized_result.coverage);
assert.deepEqual(
page.page.rows,
parent.normalized_result[collection].slice(0, limit),
);
assert.equal(
page.coverage.total,
parent.normalized_result[collection].length,
);
assert.equal(page.coverage.next_offset, limit);
assert.equal(page.coverage.exhausted, false);
if (collection === "methods") methods = page.page.rows;
else refs = page.page.rows;
}
}
const data = parent.normalized_result;
assert.equal(data.artifact.sha256, oracle.assembly_sha256);
assert.equal(data.methods.length, 72);
assert.equal(data.member_refs.length, 2149);
assert.equal(data.types.length, 1);
assert.equal(data.types[0].token, oracle.selected_types[0].token);
for (let i = 0; i < data.methods.length; i++) {
const row = data.methods[i],
wanted = oracle.selected_types[0].methods[i];
assert.equal(row.token, wanted.token);
assert.equal(row.name, wanted.name);
assert.equal(row.rva, wanted.rva);
assert.equal(
row.signature.raw_sha256,
hash(Buffer.from(wanted.signature_hex, "hex")),
);
}
for (let i = 0; i < refs.length; i++) {
const row = refs[i],
wanted = oracle.member_refs_first_16[i];
assert.equal(row.token, wanted.token);
assert.equal(row.name, wanted.name);
assert.equal(
row.signature.raw_sha256,
hash(Buffer.from(wanted.signature_hex, "hex")),
);
}
assert.equal(methods.length, 8);
assert.equal(refs.length, 16);
if (summary) {
assert.equal(summary.normalized_result.summary.reported_counts.methods, 72);
assert.equal(
summary.normalized_result.summary.reported_counts.member_refs,
2149,
);
assert.deepEqual(summary.normalized_result.summary.coverage, data.coverage);
assert.deepEqual(
summary.normalized_result.summary.selection,
data.selection,
);
}
const answer = {
parent_evidence_id: parent.evidence_id,
artifact_sha256: oracle.assembly_sha256,
selection: data.selection,
source_coverage: data.coverage,
total_methods: data.methods.length,
total_member_refs: data.member_refs.length,
methods,
member_refs: refs,
limitation:
"MemberRef rows are assembly-wide; methods are selected-type rows. Page exhaustion is not an absence claim.",
};
report.parent_evidence_id = parent.evidence_id;
report.parent_semantic_sha256 = hash(JSON.stringify(stable(parent)));
report.answer_sha256 = hash(JSON.stringify(stable(answer)));
report.oracle_verified = {
all_selected_method_metadata: 72,
returned_methods: 8,
returned_member_refs: 16,
};
const bindingFinished = performance.now();
assertRuntimeBinding(
await captureRuntimeBinding(runtime),
report.runtime_binding,
mode,
);
report.runtime_binding_audit_ms.after = performance.now() - bindingFinished;
report.runtime_binding_verified = true;
report.status = "passed";
await writeFile(join(output, "parent-evidence.json"), JSON.stringify(parent));
await writeFile(join(output, "answer.json"), JSON.stringify(answer));
} finally {
report.elapsed_ms = performance.now() - began;
report.cleanup_verified = true;
await writeFile(
join(output, "report.json"),
JSON.stringify(report, null, 2) + "\n",
);
}
console.log(
JSON.stringify({
status: report.status,
mode,
cache,
parent: report.parent_evidence_id,
answer: report.answer_sha256,
calls: report.calls.map((c) => ({
label: c.label,
elapsed_ms: c.elapsed_ms,
bytes: c.stdout_bytes,
})),
}),
);
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,219 @@
# Portable managed CLI member views
Issue #5148 / PR #5167. Baseline `main@3dbe314ada9f6dcc994ca4655dc0faf082503847`;
feature integrates the original `7b3ed4d025edd24d062cde0327a53bd311e174fc`.
The measured production-file SHA-256 manifest is in [managed-cli.json](managed-cli.json).
These are working-tree measurements. Each trial verifies both source and the
complete compiled CLI graph before and after execution against its explicitly
selected build. The two roles have distinct checkout roots and compiled digests.
Identity-audit time is included in whole-task costs and recorded separately.
The audit reads compiled files before timing individual CLI calls, warming those
filesystem pages; cold means disabled Node compile cache, not disk-cold startup.
## Question and independent checks
Report identity, selected type, source coverage, the total method count, the first
eight complete method records, and the first sixteen assembly-wide MemberRef
records, including continuation coverage. Keep complete original Evidence available.
The question deliberately does not ask for every method or reference.
Public artifact: [Newtonsoft.Json 13.0.3](https://www.nuget.org/packages/Newtonsoft.Json/13.0.3),
from the NuGet flat-container archive. The 2,441,966-byte archive SHA-256 is
`872fc189e638ab1056555b03aaa38f68bcb54286e221aa646eb1129babf63c77`.
Selected member `lib/net6.0/Newtonsoft.Json.dll`, 712,464 bytes, SHA-256
`22c649f75fce5be7c7ccda8880473b634ef69ecf33f5d1ab8ad892caf47d5a07`.
Package version 13.0.3 is distinct from metadata assembly version 13.0.0.0.
No library code is executed; REA parses PE/CLI statically.
An independent Python oracle uses `dnfile==0.18.0` and `pefile==2024.8.26`.
It finds 494 TypeDefs, 4,208 MethodDefs and 2,149 MemberRefs. The selected
`Newtonsoft.Json.JsonConvert` has 72 methods. Every trial checks all 72 method
tokens, names, RVAs and original signature hashes, plus the sixteen returned
MemberRef tokens, names and signature hashes, against this oracle. This does
not independently establish complete CIL semantics or opcode correctness.
The full parent Evidence and requested answers are identical across all twelve
trials; returned rows equal the exact parent slices. Selection and source
coverage remain distinct from page exhaustion. MemberRefs remain assembly-wide.
## Completed-task cost
Linux x64, Node 24.18.0/npm 11.16.0. Three alternating pairs per cache condition,
serial, one CPU; 4 GiB hard / 3 GiB early-stop family guard, no swap, 192-task
ceiling. Every task includes actual selected-command help, all CLI calls,
provider parsing, bundle publication/authentication, local answer extraction
and oracle comparisons, plus both runtime-identity audits. Build, download, oracle preparation and warm priming
are excluded. OS page cache is uncontrolled. No build/test overlapped timing.
Complete mode starts two CLI processes (help plus analysis). Selected mode starts
five (two help commands, summary/publication, methods page, MemberRef page).
Cold disables the Node compile cache; warm primes the exact workflow once per
runtime/mode, then still starts fresh processes with isolated HOME each trial.
All trials complete with verified process cleanup and no OOM/resource-stop event.
| Whole task, median | Complete | Summary + two pages |
| ----------------------------------------------- | -------: | ------------------: |
| Estimated context tokens, both cache conditions | 451,373 | 13,078 |
| Cold wall seconds | 9.969 | 14.801 |
| Cold family CPU seconds | 9.743 | 14.555 |
| Cold kernel family peak MiB | 393.281 | 400.098 |
| Warm wall seconds | 10.339 | 24.141 |
| Warm family CPU seconds | 10.097 | 23.842 |
| Warm kernel family peak MiB | 401.809 | 407.164 |
Context estimates include the question, supplied command recipe, all logical
argv, actual help and stdout. Tokenizer `tiktoken==0.12.0`, `cl100k_base`.
The reduction is 97.1% for this fixed task. There is no model invocation;
actual billed/native-agent usage, reasoning and transport framing are unmeasured.
The memory counter is the kernel cgroup process-family peak including file
cache, not sampled RSS. Ranges and individual receipts are in the JSON.
This is an opt-in context/portability improvement with extra latency and CPU,
not a speed or memory optimization. Complete provider parsing and full authentication of file-supplied bundles
remain. Fresh managed Evidence is sealed at its producing factory and reused
without another payload clone, including low-headroom error projection. Complete output remains the default and is preferable
for small outputs or questions requiring every row. No cache or parser cap
was introduced.
## Existing native code composition
The unchanged baseline can already redirect full stdout to a file and select
rows in code. [The measured control](managed-cli-local-control.py) returns the
identical answer and retains the complete original Evidence. One cold run:
11.249 s wall, 11.000 s family CPU,
375.3 MiB kernel peak. Projection source,
original help and returned answer together estimate 10,555 tokens; the shared
question and launcher argv are excluded. This single-run control does not
establish stable performance or actual native-agent usage. It does demonstrate
that raw CLI reply savings are not universal savings over code composition.
The additive interface supplies authenticated reusable projections through the
same application/domain owner as MCP, without caller-maintained JSON slicing.
## Correctness and resource boundaries
The public CLI regression pages all eight managed collections after process
exit, target deletion and a working-directory change. Complete parent identity,
original row order, signatures, source/page coverage and unknowns survive.
Existing outputs are not replaced; failed publication retains completed facts.
The Evidence factory parses mutable caller data into independent trees, computes
identity, then seals its fresh owned snapshot with active ancestry/iterators.
It does not retain a set/array of every completed object. Existing sealed roots
can be shared; mutable input aliases are cloned independently. General graph
sealing keeps its separate cycle, sharing, hidden-field and atomic-failure rules.
Managed and derived-view factories seal their own completed Evidence, so CLI
error projection can borrow it without reauthentication or another payload copy.
The view owner retains strict JSON normalization for optional-field contracts;
the CLI no longer copies its already validated completed projection again.
Shared heap admission traverses by depth rather than queueing every report row
or allocating an entries array. It reserves parsed working copies and actual
escaping expansion before cloning/hashing, reporting UTF-16 storage explicitly.
Canonical bundle equality streams existing ordinary JSON encoder parts, preserving
native key order, omission, negative zero and Unicode semantics without two
whole-document strings. All production bundle readers explicitly pass their
operation: CLI import/export/compare/inspection, retained views, MCP import/
comparison and both native-matching read passes. Errors retain the original
cause, cleanup primary and lease through retry.
One process primitive dispatches queued callbacks and then their OS signal
notifications. The managed producer checks it before accepting complete Evidence.
Publication/read stages use it before their final checks. Inline, file and bundle
CLI views share the existing command finalizer and actual resource scope; the
finalizer checks interruption after close while preserving any primary/cleanup
failure. Existing inline error labels remain intact. Synchronous analysis is
cooperatively cancellable between stages, not preemptible.
Four compiled publication cases (SIGINT/SIGTERM × event/actual POSIX signal)
fail without their checkpoint and pass restored. Eight additional compiled
completion cases cover default complete delivery and inline projection; all fail
with only their new completion checkpoints removed and pass restored. Fixture
hooks match the selected target path or exact parent ID. Two real Newtonsoft
publication runs and four real default/inline completion runs retain the identical
parent or independently checked selected summary, exit 130/143 and preserve
committed receipts where applicable. No target library code is executed.
All 556 focused tests across 36 files pass, including direct
finalizer/cleanup ownership, WASM/Android cancellation and native matching.
All six `npm run check` tasks plus architecture guards, and all six
`npm run docs:check` tasks plus five README tests pass. The expanded domain
project passes 5,617 tests in 287 files with
3 skipped cases; this overlaps the focused gate. Its first
batched shard hit two existing 30-second timeouts in unchanged large synthetic
tests. Both passed isolated and in the fresh shard rerun under the same timeout;
the cause of the batch variation is unverified. No production/timeout change
was made for those cases. An earlier unsplit expansion was manually stopped
before its heap ceiling; that incomplete attempt is not counted as a pass.
Eight producing/operation negative controls also catch graph-sized sealing/
admission scratch and wrong operation attribution, including native reload.
Native built-in instrumentation is confined to the isolated process-global lane.
Initial test-only spy/probe/schema-import issues were corrected before these
passing gates. No actual heap exhaustion is induced. Simulated providers do not
qualify foreign engines; Windows OS signals remain unverified locally.
## Shared-reader real-artifact regression
The existing Redux Toolkit 2.2.7 source/bundle task also completes through the
updated reader with default full output schemas and compact input schemas.
An independent Python SHA-256/path oracle verifies 180 historical files,
99 embedded source nodes and 210 candidate relations (33 strong-location rows,
147 unknown). Original signals and partial source coverage remain visible.
This uses the existing explicit semantic-node cap of one for static correspondence;
it does not establish equivalent semantic-graph coverage or behavioral equivalence.
One completed task: 21.380 s wall,
21.023 s family CPU,
928.9 MiB kernel family peak,
56,378 estimated context tokens (one input catalog,
all arguments and MCP text replies; excludes output schemas, framing and model
reasoning). Corpus/reference preparation was performed earlier and excluded.
This is a regression verification with resource observation, not a new paired
performance comparison. Actual model usage remains unmeasured. Details:
[managed-cli-shared-read.json](managed-cli-shared-read.json).
## Reproduction
Extract the selected DLL to a private lab as `Newtonsoft.Json.dll`. Create an
`acquisition.json` containing the URL, hashes, sizes and selected ZIP member
shown above; never commit the DLL. Prepare the independent oracle:
```bash
uv run --no-project --with dnfile==0.18.0 --with pefile==2024.8.26 python \
experiments/analysis-efficiency/managed-cli-oracle.py /absolute/lab
```
Build baseline and feature separately with the pinned toolchain. Run each under
the shared resource guard, alternating complete/selected, selected/complete,
complete/selected for cold and warm. Use a new output directory for every run:
```bash
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/managed-cli-task.mjs \
/absolute/runtime /absolute/lab /absolute/lab/pair-cold-1-selected selected cold
```
Save each guard output to the adjacent `pair-*.log`. Warm mode uses the lab's
mode-specific compile-cache directory; prime each identical workflow once first.
Supply a `measurement-inputs.json` with exact revisions and production hashes,
plus `expected_runtime_bindings.complete` and `.selected`. Capture each with
`node experiments/analysis-efficiency/managed-cli-runtime.mjs /absolute/runtime`
after building. Bind complete to the clean baseline revision, selected to the
feature parent revision plus tracked/untracked source digests, with different
roots and compiled-graph digests. The collector rejects a wrong build before any
CLI call, rechecks it afterward, and aggregation independently enforces identity.
A real wrong-runtime attempt is rejected before analysis. A copied trial with a
tampered revision fails aggregation; removing only that consumer guard accepts it.
The twelve-trial aggregation reproduces without private pilots. The native
control also requires `--measurement-inputs /absolute/lab/measurement-inputs.json`
and performs the same before/after identity checks.
Then aggregate with:
```bash
uv run --no-project --with tiktoken==0.12.0 python \
experiments/analysis-efficiency/managed-cli-costs.py /absolute/lab \
--output /absolute/results.json
```
The original issue reporter's executable, foreign operating systems and actual
model sessions remain unverified. Setup pilots and earlier unbound cohorts, including one failed setup run
while compiled artifacts were absent after a typecheck failure, are excluded.
@@ -0,0 +1,90 @@
"""Account for catalog, complete SDK tasks and separately recorded model trials."""
import argparse
import hashlib
import json
from pathlib import Path
import tiktoken
def compact(value):
return json.dumps(value, ensure_ascii=False, separators=(",", ":"), allow_nan=False)
def resource(case):
path = case.with_name(case.name + ".log")
scope = next(json.loads(line) for line in reversed(path.read_text().splitlines())
if line.startswith('{"resource_scope":'))
observation = scope["payload_resource_observation"]
assert scope["abort"] is None and scope["payload_resource_observation_verified"]
assert observation["payload_cleanup_verified"] and observation["payload_exit_code"] == 0
assert all(observation["resource_events"]["memory.events"][key] == 0
for key in ["high", "max", "oom", "oom_kill"])
return {"wall_seconds": scope["elapsed_seconds"],
"cpu_seconds": observation["cpu_stat"]["usage_usec"] / 1e6,
"kernel_family_peak_bytes_including_page_cache": observation["memory_peak_bytes"],
"resource_stop": scope["abort"], "cleanup_verified": True,
"final_whole_scope_counters": scope["resource_events_final"],
"guard_sources_sha256": scope["payload_observer_sources_sha256"],
"log_sha256": hashlib.sha256(path.read_bytes()).hexdigest()}
def sdk(case, count):
report = json.loads((case / "report.json").read_text())
assert report["status"] == "passed" and report["cleanup_verified"]
catalog = json.loads((case / "catalog.json").read_text())
calls = json.loads((case / "transcript.json").read_text())
inputs = [{key: tool[key] for key in ["name", "description", "inputSchema"]}
for tool in catalog["tools"]]
arguments = sum(count(compact({"name": call["name"], "arguments": call["arguments"]}))
for call in calls)
text = sum(count(block["text"]) for call in calls for block in call["reply"]["content"]
if block["type"] == "text")
return {"case": case.name, "kind": "SDK", "profile": report["profile"],
"artifact_sha256": report["artifact_sha256"],
"tool_count": len(catalog["tools"]), "calls": report["calls"],
"parent_evidence_id": report["parent_evidence_id"],
"parent_semantic_sha256": report["parent_semantic_sha256"],
"token_estimates": {"input_catalog_once": count(compact(inputs)),
"arguments": arguments, "text_replies": text,
"whole_task": count(compact(inputs)) + arguments + text,
"raw_catalog_including_output_schemas": count(compact(catalog))},
"actual_model_usage": None, **resource(case),
"receipt_sha256": {name: hashlib.sha256((case / name).read_bytes()).hexdigest()
for name in ["report.json", "catalog.json", "transcript.json"]}}
def model(case):
summary = json.loads((case / "summary.json").read_text())
assert summary["exit_code"] == 0 and not summary["timeout"]
assert summary["selected_artifact_bytes_unchanged"]
assert json.loads((case / "cleanup.json").read_text())["profile_removed"]
return {"case": case.name, "kind": "model", "client": summary["codex_version"],
"requested_model": summary["requested_model"], "reasoning": summary["reasoning"],
"model_identity_scope": summary["model_identity_scope"],
"actual_model_usage": summary["usage"],
"artifact_sha256": summary["selected_artifact_sha256_before"],
"calls": summary["completed_mcp_calls"],
"factual_correctness": "requires independent oracle and answer review",
**resource(case),
"receipt_sha256": {name: hashlib.sha256((case / name).read_bytes()).hexdigest()
for name in ["summary.json", "answer.txt", "events.jsonl",
"trial-settings.json", "context-receipt.json"]}}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("cases", type=Path, nargs="+")
args = parser.parse_args()
encoding = tiktoken.get_encoding("cl100k_base")
count = lambda value: len(encoding.encode(value, disallowed_special=()))
result = {"tokenizer": f"tiktoken {tiktoken.__version__}", "encoding": encoding.name,
"estimate_scope": "One complete name/description/input-schema catalog, every RPC argument and text reply; raw output-schema transport counted separately. Model usage is separate and is not inferred from estimates.",
"records": [sdk(case, count) if (case / "report.json").exists() else model(case)
for case in args.cases]}
args.output.write_text(json.dumps(result, indent=2) + "\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,54 @@
"""Independent ELF64 little-endian / zlib oracle for the pinned Alpine corpus."""
import argparse
import hashlib
import json
from pathlib import Path
import struct
import zlib
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("artifact", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
assert args.artifact.stat().st_size <= 256 * 1024 ** 2
binary = args.artifact.read_bytes()
assert binary[:6] == b"\x7fELF\x02\x01"
table = struct.unpack_from("<Q", binary, 40)[0]
stride, count, strings_index = struct.unpack_from("<HHH", binary, 58)
assert stride == 64 and 0 < count < 65535 and strings_index < count
sections = [struct.unpack_from("<IIQQQQIIQQ", binary, table + index * stride)
for index in range(count)]
string_row = sections[strings_index]
names = binary[string_row[4]:string_row[4] + string_row[5]]
matches = [row for row in sections
if names[row[0]:names.index(b"\0", row[0])] == b".dep-v0"]
assert len(matches) == 1
row = matches[0]
assert row[5] <= 1024 ** 2
compressed = binary[row[4]:row[4] + row[5]]
decoder = zlib.decompressobj()
decoded = decoder.decompress(compressed, 8 * 1024 ** 2 + 1)
assert len(decoded) <= 8 * 1024 ** 2 and decoder.eof
assert not decoder.unconsumed_tail and not decoder.unused_data
raw = json.loads(decoded)
packages = [{"name": item["name"], "version": item["version"],
"source": item.get("source"), "kind": item.get("kind"),
"dependencies": item.get("dependencies", []), "root": item.get("root", False)}
for item in raw["packages"]]
result = {
"artifact_sha256": hashlib.sha256(binary).hexdigest(),
"section_sha256": hashlib.sha256(compressed).hexdigest(),
"json_sha256": hashlib.sha256(decoded).hexdigest(),
"packages": packages,
"edges": [{"from_index": index, "to_index": destination}
for index, item in enumerate(packages) for destination in item["dependencies"]],
"serde_rows": [{"index": index, **{key: item[key] for key in ["name", "version", "kind"]}}
for index, item in enumerate(packages) if item["name"] == "serde"],
}
args.output.write_text(json.dumps(result, indent=2) + "\n")
if __name__ == "__main__":
main()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,228 @@
# Default MCP input catalog cost
Continues [#5861](https://github.com/morluto/rea/issues/5861). Baseline is
`705058f3e2df82503f1c601fc464aea7cbb626b8`. The candidate selects the existing
compact input presentation when the setting is omitted. Explicit `full` and
`compact` choices, all 213 tools, descriptions, output contracts, canonical
validation, provider behavior and complete Evidence remain available.
This is a change to the default advertisement. Callers requiring full field
guidance and nested validation declarations can set
`REA_MCP_INPUT_SCHEMA_PROFILE=full`. Compact drops nested annotation prose and
reduces structurally oversized/deep schemas; it is not an equivalent complete
input declaration. The canonical server validator still admits or rejects the
same inputs. No new profile, tool-selection API, cache or discovery protocol is
introduced. Output schemas retain their existing default.
## Completed SDK tasks
The [receipt](mcp-input-catalog.json) pins sources, publisher URLs/digests,
individual calls and resource observations. The unchanged Alpine v3.21 x86_64
releases are ripgrep 14.1.1-r0 (3,416,168 bytes) and Zellij 0.40.0-r0
(17,151,864 bytes). Neither binary was executed. The independent
[oracle](mcp-input-catalog-oracle.py) reads ELF section headers, decompresses
`.dep-v0` with Python zlib, and derives original package indices and dependency
edges from the embedded JSON, independently of REA.
Each [production stdio SDK task](mcp-input-catalog.mjs) discovers and validates
the complete advertised input catalog, requests exact serde package fields,
reads the same retained page twice, probes a negative offset, exports complete
Evidence, compares every package and edge with the oracle, and closes the
transport. RPCs bind the exact advertised named tool definition. Tool metadata
outside input schemas and complete parent Evidence are equal before/after.
The real negative input is refused in every task.
Three fresh-process pairs per artifact alternate order (before/after,
after/before, before/after). Both runtimes use Node 24.18.0, npm 11.16.0 and
MCP SDK 2.3.1 on Linux x86_64. Node compile caching is disabled; OS caches are
not flushed and unrelated host load is uncontrolled. Artifact acquisition,
builds and oracle preparation are outside the measured analysis task.
| Artifact | Default before/after | Whole-task estimated tokens | Wall median seconds | CPU median seconds | Family peak median MiB |
| -------- | -------------------- | --------------------------: | ------------------: | -----------------: | ---------------------: |
| ripgrep | full | 204,358 | 7.379 | 7.159 | 804.3 |
| ripgrep | compact | 51,801 | 7.274 | 7.036 | 815.5 |
| Zellij | full | 204,278 | 7.373 | 7.162 | 839.0 |
| Zellij | compact | 51,721 | 7.374 | 7.131 | 827.0 |
The input-facing catalog alone changes from 200,425 to 47,868 estimated tokens
(76.1% lower). Each whole task includes the catalog once, all five calls'
arguments and text replies: about 74.7% lower. These are offline
`tiktoken==0.12.0` / `cl100k_base` estimates, not model usage. The raw SDK catalog
including output schemas changes from 903,377 to 750,623 estimated tokens;
clients exposing that entire representation have a smaller proportional gain.
JSON-RPC envelopes and client-specific conversion/replay are unmeasured here.
Wall ranges overlap (ripgrep 7.27–7.68 vs 7.07–7.27 seconds; Zellij 7.17–7.48 vs
7.07–7.38). No general elapsed, CPU or memory improvement is established.
Fresh processes include first-use cost and two same-session historical reads;
the latter are not fresh live-state observations.
## Actual model tasks and the limit of the claim
Six real Codex CLI 0.161.0 tasks used fresh isolated profiles, the complete
catalog, no enabled-tool filter, and requested `gpt-6.1-sol` / medium reasoning.
The resolved server-side model identity is unknown. Each profile completed a
direct ripgrep package question, a broad Zellij metadata question, and an
intentional invalid-offset question. Artifact bytes were unchanged and owned
profiles were removed. Answers match independent package/index/count/digest
facts, cite actual returned Evidence or its parent, preserve embedded-metadata
limitations, and accurately report the negative cases without retrying.
| Question | Actual input tokens full → compact | Calls full → compact | Guarded wall seconds full → compact |
| -------------- | ---------------------------------: | -------------------: | ----------------------------------: |
| Direct package | 169,419 → 173,274 | 2 → 2 | 33.854 → 31.448 |
| Broad metadata | 126,522 → 166,082 | 1 → 2 | 28.434 → 34.989 |
| Invalid offset | 167,283 → 140,530 | 1 → 1 | 32.983 → 29.641 |
These are one pair per question on the baseline runtime's explicit full/compact
variants, not repeated model-performance cohorts. Compact used an extra call
for the broad question. **Actual Codex token, latency or task-quality gains are
not established.** Recorded client usage includes cached input; it does not
identify the exact rendered catalog contribution. The change addresses the
default declaration cost for clients carrying the complete catalog; it does not
promise identical savings for clients performing tool search or other schema
conversion. Explicit full remains useful when the additional guidance avoids
extra calls.
All analysis/model runs use one CPU, 4 GiB family hard / 3 GiB early memory
limits, no swap and 192 tasks. CPU and memory.peak are kernel cgroup counters
observed at payload exit, including the controller and charged page cache.
All accepted tasks have authenticated payload-exit observations, successful
exit and cleanup, and zero observed high/max/OOM/PID-limit events. Final
whole-scope counters are unavailable after the transient scope disappears.
The first SDK harness attempt used an incorrect retained-source argument and
was correctly refused. It is retained as a failed attempt and excluded from
accepted comparisons; the harness was corrected before the repeated cohorts.
## Reproduction
Generate an oracle with Python 3 and the pinned extracted binary:
```bash
python3 experiments/analysis-efficiency/mcp-input-catalog-oracle.py /absolute/ripgrep /absolute/oracle.json
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/mcp-input-catalog.mjs \
--runtime /absolute/runtime --artifact /absolute/ripgrep \
--oracle /absolute/oracle.json --output /absolute/new-output \
> /absolute/new-output.log 2>&1
# In an analysis environment with tiktoken==0.12.0 installed:
python3 experiments/analysis-efficiency/mcp-input-catalog-costs.py \
--output /absolute/costs.json /absolute/new-output
```
Repeat against baseline/candidate runtimes with the setting omitted. Explicit
`--profile full` and `--profile compact` verify caller overrides. The existing
`agent-trial.py` runs the recorded model question files and explicit provider
settings; authentication/profile files and raw binaries are never committed.
Use [the cost script](mcp-input-catalog-costs.py) with the pinned tokenizer to
account for complete SDK tasks and separately recorded model usage.
Keep the sibling log: accounting requires its authenticated resource receipt.
Its name appends `.log` to the entire case-directory basename, including dots.
A real `reproduce.sdk.1` Zellij task and its accounting completed successfully;
reprocessing every original record with this path fix leaves all costs equal.
This additional check is not part of the original performance cohorts.
## Latest-main integration and validation
Rebased onto `25e1a24a3ed3671a0148757555f18d16c29d936a`. A separate real Zellij
pair on that candidate, explicit full versus omitted/default, passed all facts,
full parent Evidence and negative checks. Catalog input estimates remain
200,425 versus 47,868 tokens; these integration runs do not replace or extend
the earlier repeated cohorts. Explicit full on the candidate also reproduced
the pre-change full catalog exactly.
The initial ten-file local run passed 88 tests, skipped one and failed two: a
live-identity assertion still expected the old default digest, and the complete
canonical-schema suite timed out at 30 seconds. The assertion now checks the
default compact identity. Both affected suites passed all 26 tests with serial
files and unchanged deadlines; the timeout's cause is not established. After
rebasing, 32 profile/identity/production-doctor tests passed. Static checks
passed on both bases. Documentation checks caught a new README link absent from
translated link inventories (including a duplicate-link attempt); the
introduction now names the existing guide without changing the link inventory.
Final documentation and CI results qualify the PR.
## Review follow-up: ownership of catalog identity
The former `CATALOG_IDENTITY` meant the full advertisement, while two consumers
treated it as the default live identity. The owner now exports explicit
`FULL_CATALOG_IDENTITY` and `DEFAULT_CATALOG_IDENTITY`; the ambiguous export is
removed. Canonical build/packaging consumers use full identity, default
diagnostics use default identity, and custom/explicit profiles keep their own
identity. Readiness captures and validates `binary_session` from the actual
server rather than assuming a profile. Claude's verifier derives its expected
identity using the shared input/output profile parsers.
Nine related test files passed 106 tests with two skips. Static and
documentation gates passed again. Actual readiness tasks passed with default,
explicit full, and compact input/output-off settings. The final real Zellij SDK
task reproduced the earlier complete parent and advertised catalog exactly.
Real Claude Code 2.1.296 with the loopback fixture discovered all 213 schemas
and returned `binary_session` for both profiles. It persisted the 55,471-byte
result, preventing the verifier's existing inline-only recognizer from
continuing. The untouched baseline reproduces the same failure (55,473-byte
result) after preparing its matching packaged skill. This is not a passing
end-to-end Claude analysis claim; the unrelated recognizer is left unchanged.
The actual persisted native responses were independently parsed with the named
production output contract: both selected catalog identities match, and the
completed readiness reports record the same digests. Receipts retain the
baseline/candidate failures, cleanup and this narrower successful check.
First-head CI found three remaining tests that assumed a full default
advertisement. The ZIP retained-inventory, browser-endpoint and literal-seed
cases now exercise full and compact: full retains its strict declaration and
guidance assertions; both keep canonical refusal and valid workflow checks.
Four affected files passed all 27 tests serially on main
`6d356b84344659085f2b912857b5e27fe0160c9b`; static/documentation gates passed.
An earlier parallel-file attempt was stopped at the 3 GiB early boundary before
OOM and is incomplete. Serial execution stayed below the same boundary without
raising the 4 GiB family limit. No performance cohort includes these test runs.
Additional CI cases now select full presentation when testing nested schemas
or literal advertised examples. Default source-comparison and JavaScript-fact
workflows still exercise canonical invalid-input refusal. After regenerating
the ignored catalog metadata, four files passed 31 tests; the full-catalog
ordering case hit its 20-second child deadline. That case passed alone in a
fresh process with the same deadline (14.85-second test duration). All five
files have passing checks; the combined run did not pass and its timeout cause
is not established. Family peaks were 2.43 GiB for the combined run and 1.0 GiB
for the standalone run, within unchanged limits. These validation attempts are
separate from the performance cohorts.
The next CI portable aggregate passed 15,703 tests and skipped 29, with one
remaining Godot inventory advertisement assertion failing. That workflow now
runs under both full and compact, preserving canonical invalid-field refusal
and retained facts after source removal. Its three local tests passed within
the same resource limits, including encrypted-directory unknown coverage.
A further review found that CLI status relied on an implicit default catalog,
so explicit full input advertised a different identity through CLI and MCP.
CLI status now derives input/output presentation from parsed configuration;
configuration retains the output profile too. Runtime identity requires an
explicit catalog from each caller. Two suites passed 25 tests, including real
production stdio, `capabilities` and `providers` comparisons for default, full,
compact and both output-off combinations. This target-free verification is
separate from real artifact measurements and does not qualify native engines.
Rebased again onto `8e3bf56e1` after upstream browser Evidence schema changes.
A separate completed real Zellij SDK task preserved its complete parent and
every catalog field except output schemas; whole-task estimate remains 51,721
tokens. Fourteen production CLI/MCP profile/lifecycle tests passed. Two
absolute-path tests also passed: full advertisement conformance explicitly
selects full, while actual relative-path refusals keep the default compact
presentation. This fixes the remaining nested-path assertion found by CI.
These integration checks do not replace the original performance cohorts.
A later unchanged-head CI retry passed the earlier Ghidra Java-cleanup failure
but exposed a full-schema assumption in call-direction coverage. That workflow
now tests full and compact with actual canonical invalid-input refusals. A
fresh-process audit also found a browser scenario cross-field assertion; it
keeps default valid calls and typed refusals while checking full producer
constraints separately. Five isolated suites passed 14 tests across call paths,
ADB start, browser scenarios, source selection and managed analysis. The first
browser attempt failed and remains recorded; no performance cohort changed.
@@ -0,0 +1,176 @@
// Compare complete public MCP tasks with caller-selected input advertisements.
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { Ajv2020 } from "ajv/dist/2020.js";
const { values } = parseArgs({
options: {
runtime: { type: "string" },
artifact: { type: "string" },
oracle: { type: "string" },
output: { type: "string" },
profile: { type: "string", default: "default" },
},
});
for (const key of ["runtime", "artifact", "oracle", "output"])
assert.ok(
values[key] && resolve(values[key]) === values[key],
`${key} must be absolute`,
);
assert.ok(["default", "full", "compact"].includes(values.profile));
await mkdir(values.output, { recursive: false });
const hash = (value) => createHash("sha256").update(value).digest("hex");
const oracle = JSON.parse(await readFile(values.oracle, "utf8"));
const artifactSha = hash(await readFile(values.artifact));
assert.equal(artifactSha, oracle.artifact_sha256);
const environment = {
PATH: process.env.PATH,
HOME: values.output,
REA_LOG_LEVEL: "silent",
UV_THREADPOOL_SIZE: "1",
NODE_OPTIONS: "--max-old-space-size=2048",
NODE_DISABLE_COMPILE_CACHE: "1",
...(values.profile === "default"
? {}
: { REA_MCP_INPUT_SCHEMA_PROFILE: values.profile }),
};
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
cwd: values.output,
env: environment,
stderr: "pipe",
});
const client = new Client({ name: "rea-catalog-task", version: "1" });
const report = {
profile: values.profile,
artifact_sha256: artifactSha,
calls: [],
checks: [],
status: "incomplete",
cleanup_verified: false,
};
const transcript = [];
let stderr = "";
transport.stderr?.on("data", (value) => {
stderr += value;
});
const began = performance.now();
try {
await client.connect(transport);
const catalog = await client.listTools();
assert.equal(catalog.nextCursor, undefined);
const definitions = new Map(catalog.tools.map((tool) => [tool.name, tool]));
assert.equal(definitions.size, catalog.tools.length);
const ajv = new Ajv2020({ strict: false, validateFormats: false });
for (const tool of catalog.tools)
assert.ok(
ajv.validateSchema(tool.inputSchema),
`Invalid schema: ${tool.name}`,
);
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
report.tool_names = [...definitions.keys()];
const call = async (name, args) => {
const definition = definitions.get(name);
assert.ok(definition, `Missing named tool ${name}`);
const start = performance.now();
const reply = await client.callTool(
{ name, arguments: args },
{ toolDefinition: definition },
);
report.calls.push({
name,
elapsed_ms: performance.now() - start,
is_error: reply.isError === true,
});
transcript.push({ name, arguments: args, reply });
return reply;
};
const input = {
path: values.artifact,
view: {
collection: "packages",
names: ["serde"],
fields: ["name", "version", "kind"],
},
};
const first = await call("inspect_rust_binary", input);
assert.notEqual(first.isError, true);
const selected = first.structuredContent;
assert.ok(selected?.normalized_result?.page);
assert.deepEqual(selected.normalized_result.page.rows, oracle.serde_rows);
assert.equal(selected.subject.digest.sha256, artifactSha);
const parentId = selected.normalized_result.parent_evidence_id;
for (let index = 0; index < 2; index++) {
const reply = await call("inspect_analysis_view", {
source: { kind: "retained-evidence", evidence_id: parentId },
view: {
kind: "rust",
collection: "packages",
names: ["serde"],
fields: ["name", "version", "kind"],
},
});
assert.notEqual(reply.isError, true);
assert.deepEqual(
reply.structuredContent.normalized_result.page.rows,
oracle.serde_rows,
);
assert.equal(
reply.structuredContent.normalized_result.parent_evidence_id,
parentId,
);
}
const malformed = await call("inspect_rust_binary", {
path: values.artifact,
view: { collection: "packages", offset: -1 },
});
assert.equal(malformed.isError, true);
report.checks.push(
"Independent package rows and digest; repeated retained identity; canonical negative-offset refusal",
);
const exported = await call("export_evidence_bundle", {
path: join(values.output, "evidence.json"),
});
assert.notEqual(exported.isError, true);
const bundle = JSON.parse(
await readFile(join(values.output, "evidence.json"), "utf8"),
);
const parent = bundle.records.find(
(record) => record.evidence_id === parentId,
);
assert.ok(parent, "Complete original Evidence is retained");
assert.equal(parent.subject.digest.sha256, artifactSha);
assert.deepEqual(
parent.normalized_result.auditable_metadata.packages,
oracle.packages,
);
assert.deepEqual(
parent.normalized_result.auditable_metadata.dependency_edges,
oracle.edges,
);
report.parent_evidence_id = parentId;
report.parent_semantic_sha256 = hash(JSON.stringify(parent));
report.answer = oracle.serde_rows;
report.status = "passed";
} finally {
await client.close();
await transport.close();
report.cleanup_verified = transport.pid === null;
assert.ok(report.cleanup_verified);
report.elapsed_ms = performance.now() - began;
await writeFile(
join(values.output, "report.json"),
JSON.stringify(report, null, 2) + "\n",
);
await writeFile(
join(values.output, "transcript.json"),
JSON.stringify(transcript),
);
await writeFile(join(values.output, "server.log"), stderr);
}
@@ -0,0 +1,34 @@
import argparse, json, statistics
from pathlib import Path
import tiktoken
parser=argparse.ArgumentParser()
parser.add_argument('root', type=Path)
parser.add_argument('--pattern', default='paired-*')
parser.add_argument('--output', type=Path)
args=parser.parse_args()
root=args.root
enc=tiktoken.get_encoding('cl100k_base')
compact=lambda v: json.dumps(v,ensure_ascii=False,separators=(',',':'))
count=lambda v: len(enc.encode(v,disallowed_special=()))
results=[]
for case in sorted(root.glob(args.pattern)):
if not case.is_dir(): continue
report=json.loads((case/'report.json').read_text())
assert report['status']=='observed' and report['cleanup_verified']
scope=next(json.loads(line) for line in reversed(case.with_name(case.name+'.log').read_text().splitlines()) if line.startswith('{"resource_scope":'))
observed=scope['payload_resource_observation']
assert scope['abort'] is None and scope['payload_resource_observation_verified'] and observed['payload_cleanup_verified'] and observed['payload_exit_code']==0
catalog=json.loads((case/'catalog.json').read_text())
transcript=json.loads((case/'transcript.json').read_text())
inputs=[{k: t[k] for k in ['name','description','inputSchema']} for t in catalog['tools']]
arg=sum(count(compact({'name':r['name'],'arguments':r['arguments']})) for r in transcript)
text=sum(count(b['text']) for r in transcript for b in r['reply']['content'] if b['type']=='text')
results.append({'case':case.name,'mode':report['mode'],'output_schema_profile':report.get('output_schema_profile','full'),'runtime_revision':report['runtime_revision'],'runtime_worktree_status':report['runtime_worktree_status'],'token_estimates':{'input_catalog_once':count(compact(inputs)),'arguments':arg,'text_replies':text,'whole_task':count(compact(inputs))+arg+text},'actual_model_usage':None,'wall_seconds':scope['elapsed_seconds'],'cpu_seconds':observed['cpu_stat']['usage_usec']/1e6,'peak_process_family_bytes_including_page_cache':observed['memory_peak_bytes'],'calls':report['calls'],'retained_records':report['retained_records'],'proof':json.loads((case/'independent-proof.json').read_text()),'resource_events':observed['resource_events'],'cleanup_verified':True})
summary={}
for mode in ['complete','selected']:
rows=[r for r in results if r['mode']==mode]
summary[mode]={key:statistics.median(r[key] for r in rows) for key in ['wall_seconds','cpu_seconds','peak_process_family_bytes_including_page_cache']}
summary[mode]['whole_task_tokens_estimated']=[r['token_estimates']['whole_task'] for r in rows]
out={'status':'passed','tokenizer':'cl100k_base, tiktoken 0.12.0; estimates only; one input catalog plus arguments plus MCP text replies; not actual model usage','cold_warm':'Fresh server process and isolated HOME each run; alternating complete/selected, selected/complete, complete/selected. OS page caches are warm/uncontrolled; not an OS-cold claim.','tarball_sha256':'f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9','preparation_scope':'Reference-source import is performed once outside these repeated SDK trials and reported separately. Both workflows use the explicit existing semantic cap of one for the static correspondence question; full semantic coverage is not claimed.','summary':summary,'runs':results}
(args.output or root/'costs.json').write_text(json.dumps(out,indent=2)+'\n')
print(json.dumps(summary,indent=2))
@@ -0,0 +1,63 @@
"""Verify published source/map facts independently of REA's parsers."""
import argparse
import collections
import hashlib
import json
from pathlib import Path, PurePosixPath
parser = argparse.ArgumentParser()
parser.add_argument("case", type=Path)
parser.add_argument("reference", type=Path)
args = parser.parse_args()
root = args.case.parent
bundle = json.loads((args.case / "evidence.json").read_text())
reference = json.loads(args.reference.read_text())
oracle = json.loads((root / "oracle.json").read_text())
application = next(r for r in bundle["records"] if r["operation"] == "analyze_javascript_application")
comparison = next(r for r in bundle["records"] if r["operation"] == "compare_source_to_bundle")["normalized_result"]
expected = {(r["map"], r["source_index"]): r for r in oracle["rows"]}
nodes = [n for n in application["normalized_result"]["graph"]["nodes"] if n["kind"] == "source-module"]
assert len(nodes) == len(expected) == 99
by_path = collections.defaultdict(set)
by_basename = collections.defaultdict(set)
seen = set()
for node in nodes:
observation = node["observations"][0]
location = observation["source_map_reference"]
key = (PurePosixPath(location["map_path"]).name, observation["properties"]["source_index"])
row = expected[key]
assert key not in seen
seen.add(key)
assert node["identity"]["original_source"] == row["map_source"] == location["source_name"]
assert node["identity"]["source_sha256"] == row["content_sha256"] == observation["properties"]["content_sha256"]
assert node["identity"]["source_map_sha256"] == hashlib.sha256((root / "artifact/package/dist" / row["map"]).read_bytes()).hexdigest()
path = "src/" + row["source_path"]
by_path[path].add(node["node_id"])
by_basename[PurePosixPath(path).name].add(node["node_id"])
actual_files = {p.relative_to(root / "reference").as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
for p in (root / "reference/src").rglob("*") if p.is_file()}
entries = {e["path"]: e for e in reference["entries"] if e["kind"] == "file"}
assert set(entries) == set(actual_files)
assert all(entries[p]["sha256"] == sha for p, sha in actual_files.items())
items = {item["source_path"]: item for item in comparison["items"]}
assert set(items) == set(actual_files)
candidate_count = 0
for path, item in items.items():
assert item["source_sha256"] == actual_files[path]
candidates = {candidate["current_node_id"] for candidate in item["candidates"]}
assert candidates == by_basename[PurePosixPath(path).name], path
candidate_count += len(candidates)
assert set(item["current_node_ids"]) == by_path[path]
assert item["status"] == ("split" if by_path[path] else "unknown")
assert all(signal["kind"] != "exact-source-digest"
for candidate in item["candidates"] for signal in candidate["signals"])
assert candidate_count == comparison["coverage"]["candidate_evaluations"] == 210
assert comparison["coverage"]["status"] == "partial"
proof = {"status": "passed", "method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": len(actual_files), "embedded_nodes": len(nodes), "candidate_relations": candidate_count,
"strong_location_rows": sum(bool(by_path[p]) for p in actual_files),
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."}
with (args.case / "independent-proof.json").open("x") as output:
json.dump(proof, output, indent=2)
output.write("\n")
print(json.dumps(proof))
@@ -0,0 +1,548 @@
{
"status": "passed",
"tokenizer": "cl100k_base, tiktoken 0.12.0; estimates only; one input catalog plus arguments plus MCP text replies; not actual model usage",
"cold_warm": "Fresh server process and isolated HOME each run; alternating complete/selected, selected/complete, complete/selected. OS page caches are warm/uncontrolled; not an OS-cold claim.",
"tarball_sha256": "f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9",
"preparation_scope": "Reference-source import is performed once outside these repeated SDK trials and reported separately. Both workflows use the explicit existing semantic cap of one for the static correspondence question; full semantic coverage is not claimed.",
"summary": {
"complete": {
"wall_seconds": 11.597,
"cpu_seconds": 11.243998,
"peak_process_family_bytes_including_page_cache": 659202048,
"whole_task_tokens_estimated": [152668, 152668, 152668]
},
"selected": {
"wall_seconds": 10.89,
"cpu_seconds": 10.569455,
"peak_process_family_bytes_including_page_cache": 656396288,
"whole_task_tokens_estimated": [54841, 54841, 54841]
}
},
"runs": [
{
"case": "off-paired-1-complete",
"mode": "complete",
"output_schema_profile": "off",
"runtime_revision": "01788bac863ac786bfbb648cad69b11e23b40a89",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47987,
"arguments": 37273,
"text_replies": 67408,
"whole_task": 152668
},
"actual_model_usage": null,
"wall_seconds": 12.215,
"cpu_seconds": 11.872135,
"peak_process_family_bytes_including_page_cache": 650854400,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 7275.801749,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 81.34299699999974,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 74.90714800000023,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "off-paired-2-selected",
"mode": "selected",
"output_schema_profile": "off",
"runtime_revision": "543b273d7adf581f6fd52c2938020207e455d3df",
"runtime_worktree_status": " M experiments/analysis-efficiency/source-to-bundle-costs.py\n M experiments/analysis-efficiency/source-to-bundle.mjs\n M src/cli/sourceToBundleCommand.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n M tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48100,
"arguments": 298,
"text_replies": 6443,
"whole_task": 54841
},
"actual_model_usage": null,
"wall_seconds": 11.196,
"cpu_seconds": 10.7977,
"peak_process_family_bytes_including_page_cache": 650358784,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6273.303819000001,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 81.66274700000031,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 20.52900899999986,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 83.38822599999912,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "off-paired-3-selected",
"mode": "selected",
"output_schema_profile": "off",
"runtime_revision": "543b273d7adf581f6fd52c2938020207e455d3df",
"runtime_worktree_status": " M experiments/analysis-efficiency/source-to-bundle-costs.py\n M experiments/analysis-efficiency/source-to-bundle.mjs\n M src/cli/sourceToBundleCommand.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n M tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48100,
"arguments": 298,
"text_replies": 6443,
"whole_task": 54841
},
"actual_model_usage": null,
"wall_seconds": 10.89,
"cpu_seconds": 10.569455,
"peak_process_family_bytes_including_page_cache": 656396288,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6153.662525000002,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 78.89196699999957,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 20.370968999999604,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 81.88148700000056,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "off-paired-4-complete",
"mode": "complete",
"output_schema_profile": "off",
"runtime_revision": "01788bac863ac786bfbb648cad69b11e23b40a89",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47987,
"arguments": 37273,
"text_replies": 67408,
"whole_task": 152668
},
"actual_model_usage": null,
"wall_seconds": 11.597,
"cpu_seconds": 11.243998,
"peak_process_family_bytes_including_page_cache": 659202048,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6342.5286989999995,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 90.04261700000097,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 97.75641600000017,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "off-paired-5-complete",
"mode": "complete",
"output_schema_profile": "off",
"runtime_revision": "01788bac863ac786bfbb648cad69b11e23b40a89",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47987,
"arguments": 37273,
"text_replies": 67408,
"whole_task": 152668
},
"actual_model_usage": null,
"wall_seconds": 10.899,
"cpu_seconds": 10.597762,
"peak_process_family_bytes_including_page_cache": 663457792,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6138.704318,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 87.58297700000003,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 76.88620699999956,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "off-paired-6-selected",
"mode": "selected",
"output_schema_profile": "off",
"runtime_revision": "543b273d7adf581f6fd52c2938020207e455d3df",
"runtime_worktree_status": " M experiments/analysis-efficiency/source-to-bundle-costs.py\n M experiments/analysis-efficiency/source-to-bundle.mjs\n M src/cli/sourceToBundleCommand.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n M tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48100,
"arguments": 298,
"text_replies": 6443,
"whole_task": 54841
},
"actual_model_usage": null,
"wall_seconds": 10.802,
"cpu_seconds": 10.466533,
"peak_process_family_bytes_including_page_cache": 660979712,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6126.120589999999,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 79.81148600000051,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 18.2459889999991,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 82.41327699999965,
"is_error": false,
"reply_bytes": 342
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
}
],
"baseline_revision": "01788bac8",
"configuration_scope": "Both cohorts explicitly use existing REA_MCP_OUTPUT_SCHEMA_PROFILE=off. REA still canonical-validates successful results; clients do not receive output schemas. Defaults remain unchanged. Pilot off-1/off-2 and warm calibration excluded.",
"implementation_files_sha256": {
"src/application/javascript/SourceToBundleReference.ts": "00f590540b1d8a1755badd7318a4f65b472671e505989c98338f2c63dbe5f7fe",
"src/cli/applicationCommands.ts": "7a436590d7d8f38c059b1ef9a008a20b6021eb04a6caafb72d473149f8762a00",
"src/cli/sourceToBundleCommand.ts": "943ee98cd4abb6674a8f3d90d84d28744ff5a262200a57a45fc1a5b7fba9d105",
"src/contracts/applicationToolContracts.ts": "3b910f1ad1baf13b428d2d108a0020b6235a06dc0bd2bfa2f4b3024ccf57100c",
"src/contracts/javascript/applicationWorkflowInputContracts.ts": "4256f7de82fabf3cc946ca607b8429defa4e8b9f6d3f4be935c20f70741c2d42",
"src/domain/analysisView/analysisView.ts": "56107291635491f3606d57fb85a2b2b175a4689d291d3855a899cb201203148a",
"src/domain/analysisView/sourceToBundleView.ts": "a8cf9a894a4d0383ae109cfc6671c3ba9aa2b75b2b95e229b1297e55f8393de5",
"src/server/createServer.ts": "400764b1ef9bad9d7250cc179736f20daf4cadcde622bbea34e30861b9b23e04",
"src/server/registerApplicationTools/compareSourceToBundle.ts": "8796d139d011ccc89fecfc01dc0fea48b57d6da3823f5148120e34f868a9e0a0",
"src/server/registerApplicationTools/helpers.ts": "c49ec1a9a28d531696012af5d731818deab3fe59f71f4bdcceb3fbb79736824a",
"src/server/registerApplicationTools/types.ts": "959dbfc7162048da2bf477aaa545cb10c766f4e64a25ec5ff370d37244fc0eb7"
}
}
@@ -0,0 +1,51 @@
"""Prepare a pinned public source/bundle corpus without extracting or executing package scripts."""
import argparse
import hashlib
import json
from pathlib import Path, PurePosixPath
import tarfile
parser = argparse.ArgumentParser()
parser.add_argument('tarball', type=Path)
parser.add_argument('output', type=Path)
args = parser.parse_args()
expected = 'f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9'
assert hashlib.sha256(args.tarball.read_bytes()).hexdigest() == expected
args.output.mkdir()
bundles = ['redux-toolkit.browser.mjs', 'redux-toolkit.legacy-esm.js', 'redux-toolkit.modern.mjs']
selected = {f'package/dist/{name}{suffix}' for name in bundles for suffix in ['', '.map']}
with tarfile.open(args.tarball) as archive:
for member in archive:
if not member.isfile():
continue
path = PurePosixPath(member.name)
if path.is_absolute() or '..' in path.parts:
raise ValueError('Unsafe archive member')
if not (member.name.startswith('package/src/') or member.name in selected):
continue
data = archive.extractfile(member).read()
destination = args.output / 'artifact' / str(path)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(data)
if member.name.startswith('package/src/'):
layout = args.output / 'reference' / str(path.relative_to('package'))
else:
layout = args.output / 'artifact/shipped' / str(path.relative_to('package'))
layout.parent.mkdir(parents=True, exist_ok=True)
layout.write_bytes(data)
rows = []
for name in sorted(bundles):
document = json.loads((args.output / 'artifact/package/dist' / (name + '.map')).read_text())
assert len(document['sources']) == len(document['sourcesContent']) == 33
for index, (source, content) in enumerate(zip(document['sources'], document['sourcesContent'])):
assert source.startswith('../src/')
relative = source.removeprefix('../src/')
embedded_sha = hashlib.sha256(content.encode()).hexdigest()
source_sha = hashlib.sha256((args.output / 'reference/src' / relative).read_bytes()).hexdigest()
rows.append({'map': name + '.map', 'source_index': index, 'source_path': relative,
'map_source': source, 'content_sha256': embedded_sha,
'source_sha256': source_sha, 'exact': embedded_sha == source_sha})
assert len(list((args.output / 'reference/src').rglob('*.*'))) == 180
assert len(rows) == 99 and not any(row['exact'] for row in rows)
(args.output / 'oracle.json').write_text(json.dumps({'independent_method': 'Python JSON and SHA-256 over published map sourcesContent versus published source files; no REA parser.', 'rows': rows}, indent=2) + '\n')
print(json.dumps({'tarball_sha256': expected, 'source_files': 180, 'embedded_nodes': 99, 'executed_target_code': False}))
@@ -0,0 +1,916 @@
{
"status": "passed",
"tokenizer": "cl100k_base, tiktoken 0.12.0; estimates only; one input catalog plus arguments plus MCP text replies; not actual model usage",
"cold_warm": "Fresh server process and isolated HOME each run; alternating complete/selected, selected/complete, complete/selected. OS page caches are warm/uncontrolled; not an OS-cold claim.",
"tarball_sha256": "f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9",
"preparation_scope": "Reference-source import is performed once outside these repeated SDK trials and reported separately. Both workflows use the explicit existing semantic cap of one for the static correspondence question; full semantic coverage is not claimed.",
"summary": {
"complete": {
"wall_seconds": 11.899,
"cpu_seconds": 11.551076,
"peak_process_family_bytes_including_page_cache": 698986496,
"whole_task_tokens_estimated": [152592, 152592, 152592]
},
"selected": {
"wall_seconds": 14.733,
"cpu_seconds": 14.376874,
"peak_process_family_bytes_including_page_cache": 996917248,
"whole_task_tokens_estimated": [54765, 54765, 54765]
}
},
"runs": [
{
"case": "paired-1-complete",
"mode": "complete",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47913,
"arguments": 37272,
"text_replies": 67407,
"whole_task": 152592
},
"actual_model_usage": null,
"wall_seconds": 12.618,
"cpu_seconds": 12.259151,
"peak_process_family_bytes_including_page_cache": 699125760,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6728.166864999999,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 205.6020669999998,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 77.03590900000017,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "paired-2-selected",
"mode": "selected",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": " M src/cli/applicationCommands.ts\n M src/contracts/applicationToolContracts.ts\n M src/contracts/javascript/applicationWorkflowInputContracts.ts\n M src/domain/analysisView/analysisView.ts\n M src/server/createServer.ts\n M src/server/registerApplicationTools/compareSourceToBundle.ts\n M src/server/registerApplicationTools/helpers.ts\n M src/server/registerApplicationTools/types.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n?? experiments/analysis-efficiency/source-to-bundle.mjs\n?? src/application/javascript/SourceToBundleReference.ts\n?? src/cli/sourceToBundleCommand.ts\n?? src/domain/analysisView/sourceToBundleView.ts\n?? tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48026,
"arguments": 297,
"text_replies": 6442,
"whole_task": 54765
},
"actual_model_usage": null,
"wall_seconds": 15.327,
"cpu_seconds": 14.954805,
"peak_process_family_bytes_including_page_cache": 997220352,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 7057.07386,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 261.767695999999,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2391.5776320000004,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 179.67298699999992,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "paired-3-selected",
"mode": "selected",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": " M src/cli/applicationCommands.ts\n M src/contracts/applicationToolContracts.ts\n M src/contracts/javascript/applicationWorkflowInputContracts.ts\n M src/domain/analysisView/analysisView.ts\n M src/server/createServer.ts\n M src/server/registerApplicationTools/compareSourceToBundle.ts\n M src/server/registerApplicationTools/helpers.ts\n M src/server/registerApplicationTools/types.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n?? experiments/analysis-efficiency/source-to-bundle-oracle.py\n?? experiments/analysis-efficiency/source-to-bundle.mjs\n?? src/application/javascript/SourceToBundleReference.ts\n?? src/cli/sourceToBundleCommand.ts\n?? src/domain/analysisView/sourceToBundleView.ts\n?? tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48026,
"arguments": 297,
"text_replies": 6442,
"whole_task": 54765
},
"actual_model_usage": null,
"wall_seconds": 14.117,
"cpu_seconds": 13.83662,
"peak_process_family_bytes_including_page_cache": 996917248,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6560.107647999999,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 248.57295700000032,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2105.5653280000006,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 163.21972800000003,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "paired-4-complete",
"mode": "complete",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47913,
"arguments": 37272,
"text_replies": 67407,
"whole_task": 152592
},
"actual_model_usage": null,
"wall_seconds": 11.899,
"cpu_seconds": 11.551076,
"peak_process_family_bytes_including_page_cache": 687702016,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6484.389229,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 216.41979700000047,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 83.66305800000009,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "paired-5-complete",
"mode": "complete",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": "",
"token_estimates": {
"input_catalog_once": 47913,
"arguments": 37272,
"text_replies": 67407,
"whole_task": 152592
},
"actual_model_usage": null,
"wall_seconds": 11.608,
"cpu_seconds": 11.240981,
"peak_process_family_bytes_including_page_cache": 698986496,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6322.663412,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 206.36909699999887,
"is_error": false,
"reply_bytes": 427799
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 84.77780899999925,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
},
{
"case": "paired-6-selected",
"mode": "selected",
"runtime_revision": "aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38",
"runtime_worktree_status": " M src/cli/applicationCommands.ts\n M src/contracts/applicationToolContracts.ts\n M src/contracts/javascript/applicationWorkflowInputContracts.ts\n M src/domain/analysisView/analysisView.ts\n M src/server/createServer.ts\n M src/server/registerApplicationTools/compareSourceToBundle.ts\n M src/server/registerApplicationTools/helpers.ts\n M src/server/registerApplicationTools/types.ts\n M tests/acceptance/applications/applicationWorkflowCli.test.ts\n?? experiments/analysis-efficiency/source-to-bundle-oracle.py\n?? experiments/analysis-efficiency/source-to-bundle-prepare.py\n?? experiments/analysis-efficiency/source-to-bundle.mjs\n?? src/application/javascript/SourceToBundleReference.ts\n?? src/cli/sourceToBundleCommand.ts\n?? src/domain/analysisView/sourceToBundleView.ts\n?? tests/boundary/mcp/sourceToBundleViewsMcp.test.ts\n",
"token_estimates": {
"input_catalog_once": 48026,
"arguments": 297,
"text_replies": 6442,
"whole_task": 54765
},
"actual_model_usage": null,
"wall_seconds": 14.733,
"cpu_seconds": 14.376874,
"peak_process_family_bytes_including_page_cache": 992952320,
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6604.5753270000005,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 257.93621600000006,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2186.2845859999998,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 170.3188169999994,
"is_error": false,
"reply_bytes": 334
}
],
"retained_records": [
{
"evidence_id": "ev_02b198af5997beaf606bf300768cc1ac8cd608fcd0860acbe9b791dbd5bae5dc",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_094eb596bdb817bd982fd99d2324665598ae2e393727b655e338480593f3b8fe",
"operation": "compare_source_to_bundle"
},
{
"evidence_id": "ev_4e4bc466268275396594c26f6a657f8d2fe77b85275be57983c41ea10366db7f",
"operation": "analyze_javascript_application"
},
{
"evidence_id": "ev_95d8fe5e75c019d86fc17bafeea32445e82eec06e0ec07d0ecdafd1e8bf8ef34",
"operation": "inspect_analysis_view"
},
{
"evidence_id": "ev_a903f44a3fcc9a0bdf13e7cd4e0befb6d5b7efb4c594837a125e04e0d98f9c78",
"operation": "record_unknown"
},
{
"evidence_id": "ev_e060ca3f86616ab4c6cf8fe50ec812a5f732e0d2156d31343fb13d1e90357089",
"operation": "inspect_analysis_view"
}
],
"proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
},
"cleanup_verified": true
}
],
"implementation_files_sha256": {
"src/application/javascript/SourceToBundleReference.ts": "00f590540b1d8a1755badd7318a4f65b472671e505989c98338f2c63dbe5f7fe",
"src/cli/applicationCommands.ts": "7a436590d7d8f38c059b1ef9a008a20b6021eb04a6caafb72d473149f8762a00",
"src/cli/sourceToBundleCommand.ts": "70887d0a060c2b315df2eeaabb5889f5e47e61da79dbc9ca076e49b8858c3571",
"src/contracts/applicationToolContracts.ts": "3b910f1ad1baf13b428d2d108a0020b6235a06dc0bd2bfa2f4b3024ccf57100c",
"src/contracts/javascript/applicationWorkflowInputContracts.ts": "4256f7de82fabf3cc946ca607b8429defa4e8b9f6d3f4be935c20f70741c2d42",
"src/domain/analysisView/analysisView.ts": "56107291635491f3606d57fb85a2b2b175a4689d291d3855a899cb201203148a",
"src/domain/analysisView/sourceToBundleView.ts": "a8cf9a894a4d0383ae109cfc6671c3ba9aa2b75b2b95e229b1297e55f8393de5",
"src/server/createServer.ts": "400764b1ef9bad9d7250cc179736f20daf4cadcde622bbea34e30861b9b23e04",
"src/server/registerApplicationTools/compareSourceToBundle.ts": "8796d139d011ccc89fecfc01dc0fea48b57d6da3823f5148120e34f868a9e0a0",
"src/server/registerApplicationTools/helpers.ts": "c49ec1a9a28d531696012af5d731818deab3fe59f71f4bdcceb3fbb79736824a",
"src/server/registerApplicationTools/types.ts": "959dbfc7162048da2bf477aaa545cb10c766f4e64a25ec5ff370d37244fc0eb7"
},
"separate_preparation": {
"reference_import": {
"wall_seconds": 4.757,
"cpu_seconds": 4.602731,
"peak_process_family_bytes_including_page_cache": 383381504,
"cleanup_verified": true
},
"application_default": {
"wall_seconds": 25.824,
"cpu_seconds": 25.433681,
"peak_process_family_bytes_including_page_cache": 1100005376,
"cleanup_verified": true
},
"application_explicit_semantic_one": {
"wall_seconds": 12.933,
"cpu_seconds": 12.600848,
"peak_process_family_bytes_including_page_cache": 731721728,
"cleanup_verified": true
},
"semantic_scope": "Comparison facts exactly agree except lineage across default and semantic-one; full semantic coverage is not equivalent. These two one-run SDK observations are not repeatable speedup evidence."
},
"native_cli_control": {
"wall_seconds": 2.842,
"cpu_seconds": 2.669896,
"peak_process_family_bytes_including_page_cache": 374730752,
"cleanup_verified": true,
"comparison_bytes": 205231,
"external_three_row_projection_bytes": 22108,
"scope": "Public CLI full Evidence exactly equals MCP. An external Python projection is already possible; raw SDK context reduction does not establish actual model savings over code composition. Byte sizes are not token counts."
},
"warm_continuation_calibration": {
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6546.054383000001,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 250.82817400000022,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2071.093723,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 71.82136900000114,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 97.2455460000001,
"is_error": false,
"reply_bytes": 332
}
],
"scope": "One additional fresh-process trial, excluded from paired whole-task medians. Second inspect_analysis_view returns identical Evidence with an already reused SDK validator; full parent remains retained.",
"wall_seconds": 14.218,
"cpu_seconds": 13.858723,
"peak_process_family_bytes_including_page_cache": 993660928,
"cleanup_verified": true
},
"subsequent_cli_error_envelope_fix": {
"file": "src/cli/sourceToBundleCommand.ts",
"sha256": "943ee98cd4abb6674a8f3d90d84d28744ff5a262200a57a45fc1a5b7fba9d105",
"scope": "Preserves the previous error: Application workflow failed field around finishAnalysisCommand failures. Original paired MCP measurements predate this CLI-only negative-result fix. Actual CLI wrong-application output now exactly equals the baseline, including all projected diagnostic fields. MCP producer/view implementation is unchanged."
},
"subsequent_review_guards": {
"scope": "Additional post-review checks, excluded from the original paired medians. Shared JSON authentication headroom now precedes graph validation; cancellation dispatch follows validation and all pure comparison/view computation before any session writes. Candidate pages now have cross-source boundary regression coverage. Matching/scoring and complete comparison Evidence are unchanged.",
"implementation_files_sha256": {
"src/application/JsonAuthenticationHeadroom.ts": "9f3cd40e0dc204d30a1c979c9e18535344963edbd2e75c47f633fa72a6447f96",
"src/application/analysisView/evidenceFileSource.ts": "5b7adedb474de0ac9a1576f46a801db8232d473189689129ed3aece13322a42a",
"src/application/javascript/SourceToBundleReference.ts": "a65aa4b5853001d133a4af385dc70c051144dfc7e4e22cd0b919bce5fa65ccc4",
"src/cli/applicationCommands.ts": "7a436590d7d8f38c059b1ef9a008a20b6021eb04a6caafb72d473149f8762a00",
"src/cli/sourceToBundleCommand.ts": "943ee98cd4abb6674a8f3d90d84d28744ff5a262200a57a45fc1a5b7fba9d105",
"src/contracts/applicationToolContracts.ts": "3b910f1ad1baf13b428d2d108a0020b6235a06dc0bd2bfa2f4b3024ccf57100c",
"src/contracts/javascript/applicationWorkflowInputContracts.ts": "4256f7de82fabf3cc946ca607b8429defa4e8b9f6d3f4be935c20f70741c2d42",
"src/domain/analysisView/analysisView.ts": "56107291635491f3606d57fb85a2b2b175a4689d291d3855a899cb201203148a",
"src/domain/analysisView/sourceToBundleView.ts": "a8cf9a894a4d0383ae109cfc6671c3ba9aa2b75b2b95e229b1297e55f8393de5",
"src/server/createServer.ts": "400764b1ef9bad9d7250cc179736f20daf4cadcde622bbea34e30861b9b23e04",
"src/server/registerApplicationTools/compareSourceToBundle.ts": "a5a3f125c9fb31b39fe50ae9135b9f3d76a2ce08e245fcb0a352ec550bef0083",
"src/server/registerApplicationTools/helpers.ts": "c49ec1a9a28d531696012af5d731818deab3fe59f71f4bdcceb3fbb79736824a",
"src/server/registerApplicationTools/types.ts": "959dbfc7162048da2bf477aaa545cb10c766f4e64a25ec5ff370d37244fc0eb7"
},
"regressions": {
"shared_headroom_and_evidence_reader_tests": 7,
"first_post_review_guard_suites": 12,
"final_cancellation_and_cross_source_page_suites": 65,
"negative_control_disabled_guards": {
"expected_failed_tests": 3,
"scope": "All three new guard regressions fail when the admission and cancellation gates are temporarily disabled; production source restored before successful checks."
}
},
"verification": [
{
"profile": "full",
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6483.442064,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 252.73620699999992,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2205.9964070000005,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 85.7624880000003,
"is_error": false,
"reply_bytes": 332
}
],
"wall_seconds": 14.217,
"cpu_seconds": 13.843447,
"peak_process_family_bytes_including_page_cache": 976711680,
"complete_parent_byte_equal_to_original": true,
"whole_task_cl100k_estimate": 54835,
"independent_proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"cleanup_verified": true,
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"profile": "off",
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6463.359534,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 94.13822900000014,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 17.32437199999913,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 79.37489800000003,
"is_error": false,
"reply_bytes": 330
}
],
"wall_seconds": 11.297,
"cpu_seconds": 10.913648,
"peak_process_family_bytes_including_page_cache": 656244736,
"complete_parent_byte_equal_to_original": true,
"whole_task_cl100k_estimate": 54835,
"independent_proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"cleanup_verified": true,
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
}
]
},
"subsequent_reference_provenance": {
"scope": "Final post-review verification, excluded from historical paired medians. File calls now preserve reference_source kind/path/requested_path from the same reader. Full Evidence identity intentionally distinguishes chosen file origins; inline output and normalized comparison facts are unchanged. Read-I/O cancellations report the comparison operation and preserve their cause.",
"implementation_files_sha256": {
"src/application/JsonAuthenticationHeadroom.ts": "9f3cd40e0dc204d30a1c979c9e18535344963edbd2e75c47f633fa72a6447f96",
"src/application/JsonFiles.ts": "dfc6eaf9b6d0b7f8d4e12d2c39f9e0c5288a29a16fae0451f4c7be1ee2e31f65",
"src/application/analysisView/evidenceFileSource.ts": "5b7adedb474de0ac9a1576f46a801db8232d473189689129ed3aece13322a42a",
"src/application/javascript/JavaScriptApplicationWorkflowService.ts": "657e36d2a20aee2722c76d6494d092db2d06ce5cfa6e9ba0506bcf13aa4e9d1d",
"src/application/javascript/SourceToBundleReference.ts": "8206198666a249a6d545ec79684b9788b7937603a7d9968ffba007ce9abe30f7",
"src/cli/applicationCommands.ts": "7a436590d7d8f38c059b1ef9a008a20b6021eb04a6caafb72d473149f8762a00",
"src/cli/sourceToBundleCommand.ts": "b905a11c377c159658c1b77e9ecfde2a2961604f1366cfc67509341f4a924fa9",
"src/contracts/applicationToolContracts.ts": "3b910f1ad1baf13b428d2d108a0020b6235a06dc0bd2bfa2f4b3024ccf57100c",
"src/contracts/javascript/applicationWorkflowInputContracts.ts": "4256f7de82fabf3cc946ca607b8429defa4e8b9f6d3f4be935c20f70741c2d42",
"src/domain/analysisView/analysisView.ts": "56107291635491f3606d57fb85a2b2b175a4689d291d3855a899cb201203148a",
"src/domain/analysisView/sourceToBundleView.ts": "a8cf9a894a4d0383ae109cfc6671c3ba9aa2b75b2b95e229b1297e55f8393de5",
"src/server/createServer.ts": "400764b1ef9bad9d7250cc179736f20daf4cadcde622bbea34e30861b9b23e04",
"src/server/registerApplicationTools/compareSourceToBundle.ts": "e14f07f6114f7ce4ec7d97034713074125d3133c876f12a8656a9322017882ee",
"src/server/registerApplicationTools/helpers.ts": "c49ec1a9a28d531696012af5d731818deab3fe59f71f4bdcceb3fbb79736824a",
"src/server/registerApplicationTools/types.ts": "959dbfc7162048da2bf477aaa545cb10c766f4e64a25ec5ff370d37244fc0eb7"
},
"regressions": {
"mcp_profiles_and_source_guards_passed": 6,
"cli_and_shared_json_ownership_selected_tests_passed": 29,
"negative_control_without_provenance_and_cancellation_attribution": 3
},
"verification": [
{
"profile": "full",
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6576.780192,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 264.797908999999,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 2090.7180249999983,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 164.20850099999916,
"is_error": false,
"reply_bytes": 330
}
],
"wall_seconds": 14.417,
"cpu_seconds": 14.027529,
"peak_process_family_bytes_including_page_cache": 995631104,
"normalized_comparison_byte_equal_to_original": true,
"file_provenance_recorded": {
"kind": "file",
"path": "/tmp/rea-perf-source-bundle-20261021/reference-main-aa51.json",
"requested_path": "/tmp/rea-perf-source-bundle-20261021/reference-main-aa51.json"
},
"evidence_id_differs_due_to_file_provenance": true,
"whole_task_cl100k_estimate": 54896,
"independent_proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"cleanup_verified": true,
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
},
{
"profile": "off",
"calls": [
{
"name": "analyze_javascript_application",
"elapsed_ms": 6315.5019010000005,
"is_error": false,
"reply_bytes": 21651
},
{
"name": "compare_source_to_bundle",
"elapsed_ms": 91.56914900000083,
"is_error": false,
"reply_bytes": 13199
},
{
"name": "inspect_analysis_view",
"elapsed_ms": 24.95524800000021,
"is_error": false,
"reply_bytes": 19393
},
{
"name": "export_evidence_bundle",
"elapsed_ms": 81.6187800000007,
"is_error": false,
"reply_bytes": 328
}
],
"wall_seconds": 11.104,
"cpu_seconds": 10.819556,
"peak_process_family_bytes_including_page_cache": 658169856,
"normalized_comparison_byte_equal_to_original": true,
"file_provenance_recorded": {
"kind": "file",
"path": "/tmp/rea-perf-source-bundle-20261021/reference-main-aa51.json",
"requested_path": "/tmp/rea-perf-source-bundle-20261021/reference-main-aa51.json"
},
"evidence_id_differs_due_to_file_provenance": true,
"whole_task_cl100k_estimate": 54896,
"independent_proof": {
"status": "passed",
"method": "Independent Python JSON/SHA-256/POSIX path and basename joins; no REA parser or matcher.",
"source_files": 180,
"embedded_nodes": 99,
"candidate_relations": 210,
"strong_location_rows": 33,
"limitation": "Static multiple-location projections; no semantic-equivalence or removal claim."
},
"cleanup_verified": true,
"resource_events": {
"memory.events": {
"low": 0,
"high": 0,
"max": 0,
"oom": 0,
"oom_kill": 0,
"oom_group_kill": 0
},
"pids.events": {
"max": 0
}
}
}
],
"native_cli": {
"legacy_inline_evidence_equal_to_baseline": true,
"file_cli_mcp_evidence_identical_for_same_origin": true,
"normalized_comparison_equal_to_baseline": true,
"selected_file_parent_resolves": true
}
}
}
@@ -0,0 +1,210 @@
# Selected source-to-bundle facts
Issue #6112. Baseline: `main@aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38`.
Measured implementation: the production-file SHA-256 manifest in
[source-to-bundle.json](source-to-bundle.json). These are working-tree
measurements, not a claim that the baseline commit already contains the feature.
The original cohort predates the CLI error-envelope and review guard fixes,
pinned separately in the JSON. The CLI invalid-application output was restored
exactly. Post-guard SDK tasks verified both output profiles before the later
reference-file provenance fix. Final tasks preserve normalized comparison facts
while full Evidence IDs now commit the selected file origin. Historical paired
medians are not presented as new measurements of the later guard changes.
## Analyst question and correctness
For `src/createSlice.ts`, `src/createReducer.ts` and
`src/query/core/buildThunks.ts`, report source correspondence and candidate counts,
then inspect the exact supporting candidate signals for the two split files.
Keep the complete comparison available. This is static location correspondence;
it does not establish behavioral equivalence or removal.
Public corpus: [Redux Toolkit 2.2.7 npm tarball](https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz),
1,184,767 bytes; SHA-256
`f2517c466b50424b661a27e48039f15db7783e5822f46b5c7d39f20104ac67a9`.
The published `src/` and three root `dist/` bundles/maps are copied byte-for-byte
into separate roots, retaining the `src/` and `dist/` prefixes. No target code or
package scripts execute. REA runs its own static analysis helpers.
An independent Python JSON/SHA-256/path oracle checks all 180 source-file hashes,
99 embedded source nodes and 210 candidate relations in every completed task.
All 99 embedded source strings differ from the published source bytes; path
correspondence must not become an exact-source or semantic-equivalence claim.
The complete report has 33 split and 147 unknown rows. The selected files have
3, 3 and 0 candidates respectively. Selected mapping/candidate identities,
original indices, signals, mapped flags and source coverage agree with the full
retained comparison. Global classification runs before selection.
## Completed-task costs
Linux, Node 24.18.0, one CPU, 4 GiB hard / 3 GiB early-stop process-family guard,
192-task ceiling and no swap. Six SDK tasks alternate complete/selected,
selected/complete, complete/selected. Each starts a fresh server with an isolated
HOME. OS page caches are warm/uncontrolled: these are process-cold observations,
not OS-cold measurements. All six exit successfully, release their processes and
record no OOM, resource stop or task-limit event.
Each task includes catalog discovery, application preparation, comparison and
full Evidence export. Selected tasks additionally query candidate signals via
`inspect_analysis_view`. Both cohorts use the existing explicit semantic-node
cap of one because the question only needs source/map correspondence. This is
not equivalent semantic-graph coverage; the omission metadata remains visible.
| Whole task | Existing full inline graph/report | File graph + selected mappings/candidates |
| ----------------------------------------- | --------------------------------: | ----------------------------------------: |
| Input catalog, cl100k estimate | 47,913 | 48,026 |
| All call arguments, estimate | 37,272 | 297 |
| All MCP text replies, estimate | 67,407 | 6,442 |
| Total estimated context | 152,592 | 54,765 |
| Wall seconds, median | 11.899 | 14.733 |
| Process-family CPU seconds, median | 11.551 | 14.377 |
| Kernel process-family peak MiB, median | 666.6 | 950.7 |
| Comparison reply bytes, text + structured | 427,799 | 13,199 |
| Additional candidate reply bytes | 0 | 19,393 |
Context estimates fall 64.1%, including the slightly larger catalog and extra
call. Tokenizer: `tiktoken==0.12.0`, `cl100k_base`; one name/description/input-schema
catalog plus every argument and MCP text reply. Output schemas, transport
framing, model prompts/reasoning and actual billed usage are not included.
There was no model invocation; these are not actual model token savings.
This workflow trades extra cold-client time/memory for substantially smaller
context. Comparison itself takes approximately 206–216 ms before and 249–262 ms
with file validation and selected delivery. The first candidate-view call takes
2.1–2.4 seconds. A separate calibration repeats the identical candidate query:
2,071 ms first, 72 ms second, with identical Evidence. This is consistent with
one-time SDK validator work; it is not a general speedup or a server-only profile.
No comparison cache was added: the measured matching computation is already
small, and a file cache would need additional freshness/identity rules.
Reference import is a separate one-time public CLI step: 4.757 s wall,
4.603 s CPU, 365.6 MiB peak. Add that same preparation cost to either first task;
download/extraction and compilation are excluded. An additional baseline with
default semantic coverage retains 22,090 semantic nodes and exports 42,870,148
bytes; explicit cap one retains one and exports 2,896,937 bytes. All comparison
facts agree after excluding expected Evidence lineage. Those single-run
observations demonstrate question-specific existing task selection, not a new
semantic-analysis optimization or equal-coverage performance claim.
## Existing output-schema selection
A second six-task cohort explicitly selects the existing
`REA_MCP_OUTPUT_SCHEMA_PROFILE=off` for **both** workflows, with baseline
`main@01788bac8`. REA still validates successful results against the canonical
contracts and returns complete structured content; clients do not receive output
schemas to compile. This is an explicit caller configuration, not a new default.
The independent oracle and selected/full fact checks pass in every run.
| Whole task, output profile off | Full graph/report | Selected facts |
| -------------------------------------- | ----------------: | -------------: |
| Total estimated context | 152,668 | 54,841 |
| Wall seconds, median | 11.597 | 10.890 |
| Process-family CPU seconds, median | 11.244 | 10.569 |
| Kernel process-family peak MiB, median | 628.7 | 626.0 |
| Candidate query milliseconds | not needed | 18–21 |
The context reduction remains approximately 64%. Whole-task ranges overlap
(complete 10.899–12.215 s; selected 10.802–11.196 s), so the apparent median
improvement does not establish a stable speedup. Unlike the default full-output
cohort, this configured workflow avoids the extra cold SDK validator cost and
has similar task memory/time. Two initial pilot cases are excluded. All six
observed runs, explicit configuration and implementation hashes are retained in
[source-to-bundle-output-off.json](source-to-bundle-output-off.json).
## Existing CLI/code-composition control
The public CLI on the same saved application/reference produces complete Evidence
identical to MCP (205,231 output bytes). The measured CLI comparison step is
2.842 s wall, 2.670 s CPU, 357.4 MiB process-family peak. A separate Python
projection of the three rows already yields 22,108 bytes. A native agent can
redirect full JSON to a file and select facts in code, so raw SDK savings do not
establish savings over every native client. These byte sizes are not token counts;
actual native-agent context and external projection CPU are unmeasured.
The new CLI reference-file form preserves the normalized facts and records its
file origin. Its saved Evidence
can be queried by `inspect-analysis-view` using the same source-to-bundle
projector as MCP. The interface supplies reusable, authenticated facts and
relationships instead of requiring each caller to maintain an ad-hoc projection.
## Reproduce
Use pinned Node/npm, `npm ci` and `npm run build:cached` for baseline and feature
worktrees. Install dependencies once per lockfile; do not run npm through a shared
node_modules symlink. Run heavy steps serially with the checked-in resource guard.
Set absolute paths for `BASE_RUNTIME`, `FEATURE_RUNTIME` and `LAB`.
```sh
curl -fL 'https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz' -o /tmp/toolkit.tgz
python3 experiments/analysis-efficiency/source-to-bundle-prepare.py /tmp/toolkit.tgz "$LAB"
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node "$BASE_RUNTIME/scripts/rea.mjs" import-reference-source "$LAB/reference" --json \
> "$LAB/reference.json" 2> "$LAB/reference-import.log"
```
For each of the six ordered trials, use a new output directory; set `RUNTIME` to
the baseline for `MODE=complete`, feature for `MODE=selected`. Capture both stdout
and stderr in the adjacent `.log` so the resource receipt remains available.
```sh
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/source-to-bundle.mjs \
--runtime "$RUNTIME" --corpus "$LAB" --reference "$LAB/reference.json" \
--output "$LAB/paired-1-complete" --mode "$MODE" \
> "$LAB/paired-1-complete.log" 2>&1
python3 experiments/analysis-efficiency/source-to-bundle-oracle.py \
"$LAB/paired-1-complete" "$LAB/reference.json"
uv run --no-project --with tiktoken==0.12.0 python \
experiments/analysis-efficiency/source-to-bundle-costs.py "$LAB"
```
Use a separate output name and `--mode selected --warm-continuation` for the
validator calibration; exclude it from paired medians. For the configured
cohort, add `--output-profile off`, use new `off-paired-*` directories and the
01788bac8 control, then run the cost reader with
`--pattern 'off-paired-*' --output "$LAB/costs-off.json"`. Retain the tarball,
reference, transcripts, bundles, independent proofs and guard receipts locally.
Never commit target binaries, dependency trees, credentials or generated builds.
## Review guards and regression controls
The file reader admits JSON assembly; a shared application guard additionally
reserves live heap headroom before schema validation clones data and computes the
root commitment. Evidence-file and historical-graph readers use the same owner,
with their original operation-specific diagnostics. Validation yields before
checking a pending cancellation, including validation failures. MCP computes its
selected view before yielding and committing any application source, comparison,
residual unknown or view to the session.
Seven shared headroom/Evidence-reader tests pass. A separate 65-test focused run
covers file cancellation, graph paths, actual SDK cancellation during validation
and comparison, and cross-source pages with candidate counts 2/1/2. The latter
checks original indices, totals and next offsets across boundaries and filters.
All three new guard regressions fail in a negative control with those gates
temporarily removed, then pass after restoration. Memory admission is an estimate;
the reported hard process-family ceilings come from the experiment guard.
Post-guard public-package SDK tasks, before the later provenance fix, retained all
180 source-file / 99 embedded-node / 210 relation oracle checks and byte-identical
complete comparison Evidence. Their receipts, source hashes and costs are under
`subsequent_review_guards` in the main JSON; they are excluded from paired medians.
## Reference-file provenance and cancellation attribution
The final file form records `parameters.reference_source` with `kind: file`, the
exact caller `requested_path` and the reader's absolute selected `path`. The
shared file owner supplies that path without a second interpretation; POSIX
`..` and symlink semantics remain intact. Identical graphs in different chosen
files have the same normalized facts/mapping IDs and distinct Evidence IDs.
Inline legacy Evidence remains identical. Actual public-package CLI file
Evidence equals MCP file Evidence for the same origin; relative CLI paths also
have maintained coverage.
Reader cancellation is reattributed to `compare_source_to_bundle`, preserving the
original cause. Six MCP/profile/guard tests and 29 selected CLI/shared-JSON
ownership tests pass. Removing provenance recording and cancellation attribution
makes three tests fail as expected. Additional Evidence-reader/file cancellation
checks pass. Final real-package tasks estimate 54,896 cl100k tokens in both output
profiles, preserve all independent oracle facts, and retain the new provenance;
these are additional verification runs, not new paired medians. The main JSON's
`subsequent_reference_provenance` records final source hashes and receipts.
@@ -0,0 +1,251 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { resolve } from "node:path";
import { parseArgs } from "node:util";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
const { values } = parseArgs({
options: {
runtime: { type: "string" },
corpus: { type: "string" },
reference: { type: "string" },
output: { type: "string" },
mode: { type: "string" },
"output-profile": { type: "string", default: "full" },
"warm-continuation": { type: "boolean", default: false },
},
});
for (const key of ["runtime", "corpus", "reference", "output"])
assert.ok(
values[key] && resolve(values[key]) === values[key],
key + " must be absolute",
);
assert.ok(["complete", "selected"].includes(values.mode));
assert.ok(["full", "off"].includes(values["output-profile"]));
const root = values.corpus,
runtime = values.runtime,
output = values.output;
await mkdir(output);
const reference = JSON.parse(await readFile(values.reference, "utf8"));
const sha = (value) => createHash("sha256").update(value).digest("hex");
const revision = execFileSync("git", ["rev-parse", "HEAD"], {
cwd: runtime,
encoding: "utf8",
}).trim();
const sourceState = execFileSync("git", ["status", "--porcelain"], {
cwd: runtime,
encoding: "utf8",
});
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(runtime, "scripts/rea.mjs"), "mcp"],
cwd: output,
env: {
PATH: process.env.PATH,
HOME: output,
REA_LOG_LEVEL: "silent",
REA_MCP_OUTPUT_SCHEMA_PROFILE: values["output-profile"],
UV_THREADPOOL_SIZE: "1",
NODE_OPTIONS: "--max-old-space-size=2048",
NODE_DISABLE_COMPILE_CACHE: "1",
},
stderr: "pipe",
});
const client = new Client({ name: "source-bundle-baseline", version: "1" });
let stderr = "";
transport.stderr?.on("data", (chunk) => {
stderr += chunk;
});
const report = {
status: "incomplete",
runtime_revision: revision,
runtime_worktree_status: sourceState,
mode: values.mode,
output_schema_profile: values["output-profile"],
reference_sha256: sha(await readFile(values.reference)),
node_version: process.version,
calls: [],
cleanup_verified: false,
};
const transcript = [];
const began = performance.now();
try {
await client.connect(transport);
const catalog = await client.listTools();
await writeFile(join(output, "catalog.json"), JSON.stringify(catalog));
const definitions = new Map(catalog.tools.map((t) => [t.name, t]));
const call = async (name, args) => {
const start = performance.now();
const reply = await client.callTool(
{ name, arguments: args },
{ toolDefinition: definitions.get(name) },
);
transcript.push({ name, arguments: args, reply });
report.calls.push({
name,
elapsed_ms: performance.now() - start,
is_error: reply.isError === true,
reply_bytes: Buffer.byteLength(JSON.stringify(reply)),
});
return reply;
};
const analyzed = await call("analyze_javascript_application", {
input_path: join(root, "artifact/shipped"),
detail: "summary",
max_semantic_nodes: 1,
max_semantic_nodes_per_file: 1,
});
assert.notEqual(analyzed.isError, true, JSON.stringify(analyzed));
report.preparation_summary = analyzed.structuredContent.normalized_result;
const parentId = report.preparation_summary.parent_evidence_id;
assert.ok(parentId);
const compared = await call("compare_source_to_bundle", {
reference:
values.mode === "complete"
? reference
: { kind: "file", path: values.reference },
application: { kind: "retained-evidence", evidence_id: parentId },
...(values.mode === "complete"
? {}
: {
view: {
kind: "source-to-bundle",
collection: "mappings",
source_paths: [
"src/createSlice.ts",
"src/createReducer.ts",
"src/query/core/buildThunks.ts",
],
},
}),
});
assert.notEqual(compared.isError, true, JSON.stringify(compared));
if (values.mode === "selected") {
const selected = compared.structuredContent.normalized_result;
assert.equal(selected.items.length, 3);
assert.deepEqual(
selected.items.map((r) => [r.source_path, r.status]),
[
["src/createReducer.ts", "split"],
["src/createSlice.ts", "split"],
["src/query/core/buildThunks.ts", "unknown"],
],
);
const candidates = await call("inspect_analysis_view", {
source: {
kind: "retained-evidence",
evidence_id: selected.parent_evidence_id,
},
view: {
kind: "source-to-bundle",
collection: "candidates",
source_paths: ["src/createSlice.ts", "src/createReducer.ts"],
fields: ["score", "confidence", "signals"],
},
});
assert.notEqual(candidates.isError, true, JSON.stringify(candidates));
assert.equal(
candidates.structuredContent.normalized_result.items.length,
6,
);
if (values["warm-continuation"]) {
const repeated = await call(
"inspect_analysis_view",
transcript.at(-1).arguments,
);
assert.deepEqual(repeated, candidates);
}
}
report.comparison_reply = compared.structuredContent;
const exported = await call("export_evidence_bundle", {
path: join(output, "evidence.json"),
});
assert.notEqual(exported.isError, true);
const bundle = JSON.parse(
await readFile(join(output, "evidence.json"), "utf8"),
);
const comparison = bundle.records.find(
(r) => r.operation === "compare_source_to_bundle",
);
report.retained_records = bundle.records.map((r) => ({
evidence_id: r.evidence_id,
operation: r.operation,
}));
if (comparison) {
report.comparison_id = comparison.normalized_result.comparison_id;
report.comparison_summary = comparison.normalized_result.summary;
report.coverage = comparison.normalized_result.coverage;
report.selected_items = comparison.normalized_result.items.filter((r) =>
[
"src/createSlice.ts",
"src/createReducer.ts",
"src/query/core/buildThunks.ts",
].includes(r.source_path),
);
report.complete_comparison_bytes = Buffer.byteLength(
JSON.stringify(comparison),
);
}
report.status = "observed";
assert.equal(report.comparison_summary.split, 33);
assert.equal(report.comparison_summary.unknown, 147);
const views = transcript.filter(
(r) =>
["compare_source_to_bundle", "inspect_analysis_view"].includes(r.name) &&
r.reply.structuredContent.normalized_result.parent_evidence_id,
);
for (const view of views) {
const result = view.reply.structuredContent.normalized_result;
assert.equal(result.parent_evidence_id, comparison.evidence_id);
assert.deepEqual(
result.source_coverage,
comparison.normalized_result.coverage,
);
for (const row of result.items) {
const original = comparison.normalized_result.items[row.source_index];
assert.equal(row.mapping_id, original.mapping_id);
assert.equal(row.source_path, original.source_path);
if (result.view.collection === "candidates") {
const candidate = original.candidates[row.candidate_index];
assert.equal(row.current_node_id, candidate.current_node_id);
assert.deepEqual(row.signals, candidate.signals);
assert.equal(
row.mapped,
original.current_node_ids.includes(candidate.current_node_id),
);
} else {
assert.equal(row.status, original.status);
assert.equal(row.candidate_count, original.candidates.length);
}
}
}
} finally {
await client.close();
await transport.close();
report.cleanup_verified = transport.pid === null;
report.elapsed_ms = performance.now() - began;
await writeFile(
join(output, "report.json"),
JSON.stringify(report, null, 2) + "\n",
);
await writeFile(join(output, "transcript.json"), JSON.stringify(transcript));
await writeFile(join(output, "server.log"), stderr);
}
console.log(
JSON.stringify({
status: report.status,
calls: report.calls,
comparison_summary: report.comparison_summary,
coverage: report.coverage,
selected: report.selected_items?.map((r) => ({
path: r.source_path,
status: r.status,
candidates: r.candidates.length,
})),
complete_comparison_bytes: report.complete_comparison_bytes,
cleanup_verified: report.cleanup_verified,
}),
);
+36 -1
View File
@@ -2,7 +2,7 @@ REA npm dependency inventory
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
License identifiers are upstream lockfile declarations, not a legal assessment.
Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
Locked package entries: 751; runtime: 179.
Locked package entries: 758; runtime: 180.
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
@algolia/abtesting@1.25.0 | development | MIT
@@ -690,6 +690,11 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@modelcontextprotocol/server/v/2.3.1
Archive: https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz
Integrity: sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==
@msgpack/msgpack@3.1.3 | runtime | ISC
Lockfile path: node_modules/@msgpack/msgpack
Package: https://www.npmjs.com/package/@msgpack/msgpack/v/3.1.3
Archive: https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz
Integrity: sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==
@napi-rs/lzma-linux-x64-gnu@1.5.1 | development, optional | MIT
Lockfile path: node_modules/@napi-rs/lzma-linux-x64-gnu
Package: https://www.npmjs.com/package/@napi-rs/lzma-linux-x64-gnu/v/1.5.1
@@ -1385,6 +1390,31 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@toon-format/toon/v/2.3.1
Archive: https://registry.npmjs.org/@toon-format/toon/-/toon-2.3.1.tgz
Integrity: sha512-sWqEMeAJGMda9qRrfPKrX3C4c33CO9SJuvJzzrcBEn5sbDB+k6pSLkDsfN0rVnLQwR97MV3sDgVbcxQNsq8xVw==
@tsrs/darwin-arm64@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0
Lockfile path: node_modules/@tsrs/darwin-arm64
Package: https://www.npmjs.com/package/@tsrs/darwin-arm64/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/@tsrs/darwin-arm64/-/darwin-arm64-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-sAirMpaGA9uRQm2W+t4zC7PjnijNuFa4PCSIfLKKo0JyAUCRfnMeev0UGBQ9F3g+9Nz187tvikUs5Irh5+eajA==
@tsrs/linux-arm64-gnu@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0
Lockfile path: node_modules/@tsrs/linux-arm64-gnu
Package: https://www.npmjs.com/package/@tsrs/linux-arm64-gnu/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/@tsrs/linux-arm64-gnu/-/linux-arm64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-zm9/z9yylig68JiZF4Aq0Cm26n09KEU8YoQRlG7tdDWTBNAIViYw8knxnWYsjMWVZ2XiU4mYq1xdrOKuOgABHg==
@tsrs/linux-arm64-musl@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0
Lockfile path: node_modules/@tsrs/linux-arm64-musl
Package: https://www.npmjs.com/package/@tsrs/linux-arm64-musl/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/@tsrs/linux-arm64-musl/-/linux-arm64-musl-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-Lw7VVU/YQ9JcarODICEiwzMLdFqZprqh5RGTgbS5Uc6I95P635/g5xA1pOC8RVs4efBvAR30J54iDG3l5JEYiw==
@tsrs/linux-x64-gnu@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0
Lockfile path: node_modules/@tsrs/linux-x64-gnu
Package: https://www.npmjs.com/package/@tsrs/linux-x64-gnu/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/@tsrs/linux-x64-gnu/-/linux-x64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-EdBwBgNICGu1r1h23ixdcKwsMo6E9ll7ZsY3mNERNYqE1Pwdtcat99d5E7zB1J+oy/Xk6YqiizaB6nolIjpDTw==
@tsrs/linux-x64-musl@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0
Lockfile path: node_modules/@tsrs/linux-x64-musl
Package: https://www.npmjs.com/package/@tsrs/linux-x64-musl/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/@tsrs/linux-x64-musl/-/linux-x64-musl-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-LHk+AOAtXuTlhJhOJ2+elriEIX17ycXljojeNxrrzckOipTG1LksoVWuCSas2nu00EnWAzrdKpt9oqPylUKZfQ==
@turbo/darwin-64@2.11.7 | development, optional | MIT
Lockfile path: node_modules/@turbo/darwin-64
Package: https://www.npmjs.com/package/@turbo/darwin-64/v/2.11.7
@@ -3395,6 +3425,11 @@ tslib@2.8.1 | development | 0BSD
Package: https://www.npmjs.com/package/tslib/v/2.8.1
Archive: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz
Integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tsrs@0.18.0-ts7.1.0-dev.20260929 | development | Apache-2.0
Lockfile path: node_modules/tsrs
Package: https://www.npmjs.com/package/tsrs/v/0.18.0-ts7.1.0-dev.20260929
Archive: https://registry.npmjs.org/tsrs/-/tsrs-0.18.0-ts7.1.0-dev.20260929.tgz
Integrity: sha512-9T0b4Uam/yMFIK16GUx0fUrJqYZnJ/gvSmV9JB8NTyDePvwCI45WFWxyEV/UMV3KfdNRJG/PBsm3fSIB9WmWsA==
tsx@4.23.15 | development | MIT
Lockfile path: node_modules/tsx
Package: https://www.npmjs.com/package/tsx/v/4.23.15
Loaded 100 of 999 files, more files were not shown because too many files have changed in this diff. Show more