* test: assert absolute-path facts without frozen sentences
Sibling path tests now require the field path, invalid_format, and regex
expectation, and they record that each negative case ran. NUL rejection
follows the argument that contains the byte instead of searching for the
word NUL.
* test: retain actionable NUL path diagnostics
---------
Co-authored-by: Cursor Agent <[email protected]>
The help renderer appends "(default: X)" whenever the option field declares a
schema default, so descriptions that also spell the default out print it twice:
--block-size <value> Power-of-two block size from 256 to 65536 bytes
(default 4096) (default: 4096)
Remove the hand-written default from the eleven options that own a schema
default across inspect-byte-entropy, inspect-devicetree,
inspect-packet-capture, inspect-packet-fields, inspect-signal-decoders and
decode-signal-recording, matching the rule in cliDecimalField's contract that
the domain field owns bounds, defaults and help.
Options without a schema default keep their prose: --match_limit, --line_limit,
--limit and --packet_limit are nullable or optional, so the renderer emits no
annotation and the description is the only place the effective value appears.
Add a boundary test asserting each such option states its default exactly once,
so a restated default cannot come back unnoticed.
Expose the existing opt-in Ghidra checkpoint lifecycle in tool descriptions and summary capabilities. Distinguish confirmed initial analysis from restored Programs, preserving the checkpoint path and digest through shared CLI/MCP results.
Remove prose-driven checks and their scaffolding; retain canonical effects and lifecycle/source-fact coverage. Validated with 445 focused tests, 13 real Ghidra CLI/MCP scenarios and three isolated local Codex CLI tasks covering disabled setup, saving edits and fresh-agent restoration.
* fix(cli): advertise domain constraints for selector and path options
Android class, method, and mapping fields, ADB setting keys, and browser
endpoints used bare strings in --schema. They now reuse the domain fields,
so empty selectors, relative mapping paths, and malformed setting keys
fail in the advertised schema.
* fix(cli): keep selector limits when argument parsing rejects them
Empty browser and Electron selectors now fail in the CLI parser, so their
diagnostics are VALIDATION_ERROR with the one-character minimum. ADB
setting keys stay on the selector repair path, which restores maximum 256
and the identifier pattern.
* test(cli): assert empty-selector codes without frozen prose
Empty browser and Electron selectors still fail as VALIDATION_ERROR
too_small on the named field. The tests no longer copy Zod's sentence.
* test(cli): compare endpoint diagnostics with the domain schema
Browser and Inspector tests take custom issue text from the schema that
owns it, and empty capture comparison must name both missing inspection
fields.
* fix(cli): remove duplicate merged schema import
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(mcp): advertise absolute paths for tools that reject relative inputs
The domain schemas accepted relative paths, then handlers rejected them
with isAbsolute. Those inputs now use absoluteLocalPathStringSchema, and
the duplicate handler checks are gone.
* fix(javascript): own recovery path admission in the schema
Relative recovery paths now fail in absoluteLocalPathStringSchema, including
the additional_paths index. Drop the duplicate handler check, and make the
CLI, MCP, and tools/list tests assert that schema fact.
* test: assert absolute-path facts instead of error prose
Recovery and web-script tests keep the field, format code, and regex
expectation. They no longer freeze the schema's explanatory sentence.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(javascript): keep timer cancellation within Node's handle family
Node keeps timeouts and immediates in disjoint handle families. clearTimeout
and clearInterval are aliases that act only on a Timeout from setTimeout or
setInterval, while clearImmediate acts only on an Immediate from setImmediate.
Crossing the families returns without doing anything, so the scheduled
callback still runs.
REA linked a cancellation whenever the cleared binding traced back to any timer
schedule, so clearImmediate(setTimeout(work, 10)) reported a complete
cancellation and the graph emitted a resolved cancels-timer relation. It also
flagged no uncertainty for it.
Require the cancel and the schedule to share a handle family before linking
them. A cross-family clear now keeps linkedTimerId null and resolution
unresolved, and the graph reports the cancellation as a candidate with an
ambiguous-target unknown instead of a resolved fact.
Verified against Node v24.22.1 over all nine schedule/cancel pairs: setTimeout
and setInterval with clearTimeout or clearInterval cancel, and setImmediate
with clearImmediate cancels; the other five combinations leave the callback
running. Node's own lib/timers.js shows why: clearTimeout requires
timer?._onTimeout, clearImmediate requires immediate?._onImmediate and returns
early otherwise, and clearInterval delegates to clearTimeout.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): say cross-family timer clearing is runtime-dependent
Review feedback from @morluto: the previous wording claimed clearing across the
timer handle families "returns without doing anything, so the timer keeps
running". That does not hold on every supported Node version. Measured on Node
24.22.1, all three cross-family pairs fire:
| scheduled with | cancelled with | Node 24.22.1 |
| --- | --- | --- |
| `setTimeout` | `clearImmediate` | fires |
| `setImmediate` | `clearTimeout` | fires |
| `setImmediate` | `clearInterval` | fires |
`clearImmediate` cancelling a `Timeout` on Node 22 is the case the old claim
missed. Within a family the clear always cancels, which is why the analysis
links only same-family pairs; across families the outcome depends on the
runtime, which is exactly why the analysis reports `unresolved` rather than
`complete` in either direction.
The behavior and the tests are unchanged. The `timerHandleFamily` doc comment,
the linked-origin comment, and the test comment now state the runtime
dependence and why it justifies an unresolved result.
Add opt-in REA_GHIDRA_CHECKPOINT_DIR for owned Ghidra Program persistence through the shared CLI/MCP provider. Save completed initial analysis and metadata changes on normal close; verify and restore the last successful packed Program without auto-analysis.
Preserve generation identity, exclusive ownership, typed cancellation and cleanup retry. Refresh checkpoint-dependent profiles after retirement, including failed-switch restoration, while retaining caller selection provenance. Keep source executables and caller projects unchanged.
Validated with 720 focused tests (3 platform skips), 13 real Ghidra CLI/MCP scenarios, static/docs checks and CI. Review findings addressed. No version bump or release publication.
Refs #8165.
* feat(desktop): read XML with a plain DOCTYPE, and check an element's whole text
- Both readers accept a DOCTYPE with only a name and an optional PUBLIC
or SYSTEM identifier, record it as the view's doctype, and never fetch
the DTD or expand entities beyond XML's five; an internal subset, an
external entity and a parameter entity stay refused alike.
- text_equals and text_contains compare the concatenated text of the
element (or text node or attribute) a pointer reaches, as XPath
string() does, with optional normalize_whitespace; both evaluators
agree, and a text check on a truncated element is undecided.
- Whitespace-only text between elements is dropped only when the element
holds no other text, so text split into runs keeps its spaces.
- The draft suggests text_equals for an element whose text changed.
So an AbiWord .abw reads as XML, and its paragraph's text is checked
whole however AbiWord splits it into <c> runs, as Writer's spans are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): draft whole-text checks on the paragraph, read only text as text, and skip asctime stamps
- A changed value is read at both ends, so text that became a run still
marks its paragraph, and the drafted text_equals moves up out of mixed
content to the nearest ancestor named by an attribute, such as
AbiWord's p by its xid.
- Text checks accept only a text node or an attribute value; element
names and doctype fields fail, in both evaluators.
- isTimestamp also recognises asctime dates (Www Mmm dd hh:mm:ss yyyy).
- XML files are drafted with the keyed diff the archive entries use, so
no suggestion reaches an element by position; the shared logic moves to
appTaskDraftNamed.ts.
- Text-check tests assert facts, not whole sentences.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep checks on a changed attribute inside a paragraph whose text check replaces its runs'
A drafted whole-text check on a paragraph hid every change inside it, so a
link whose label and href both changed drafted only the paragraph's text and
the new destination went unchecked. The text check now covers text and runs
added or removed whole; a changed name or attribute of an element both
versions hold keeps its own check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read an attribute called name or attributes as an attribute when the draft keeps its check
The owner of `.../attributes/name` was taken as `.../attributes`, so a
changed `name` attribute inside a paragraph with a text check was dropped
again. The owner is now read by segment, attributes first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): draft text checks from text in runs added whole, and name a text element by its own id before its container
Text typed into a run the diff lists whole was never read, so the draft kept
only a presence check on the run, which wrong text also passed. Such text now
marks the nearest element both ends hold, or, when none can take the check,
the added element's own text. The climb also names an element by an
identifier of its own (id, xml:id, xid) before a larger container, so a
headline under a long layer keeps a whole-text check that survives re-split
runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): expect the whole-text check where #8988's pairing test meets text checks
On this branch a changed element's text is drafted as `text_equals` on the
element, so the identity pairing test now expects that check, under the
same `[@role=body]` key.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep checks for elements without text beside a text edit, and normalize DOCTYPE system id line endings
A review found two gaps.
- A paragraph's whole-text check covered every element added or removed
inside it, including ones holding no text: an image frame, a line break.
A solver who typed the text but never inserted the image passed. Such an
element now keeps its own presence or absence check.
- The Python reader kept a DOCTYPE system literal's CRLF where TypeScript
normalizes it to LF. Both now report LF.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): import textSyntaxFormat once in the state format tests
main's mixed-case JSON suffix fix and this branch both added the import,
and the merge kept both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Fixes#8838.
Admit all symbolic query entry points through the existing resolver budget, preserve cache-hit depth and restore entered frames in finally. Retain independent facts with partial source coverage instead of losing entire scripts to recursive spreads.
Validated 273 focused tests, check:fast/docs:check, real CLI/stdio-MCP projections and complete parent reads, independent public-source oracle, and unchanged full results for the official Electron control and Bitwarden selected preload.
Full technical CI 42308068679 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; no completed Codex review of this head is available. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Fixes#7540.
Retain every raw dryRun literal at the shared inert token binder before Incur boolean coercion; preserve parser-owned values, original execution, defaults and variadics. Return an actionable typed PARSE_ERROR before setup or environment publication.
Validated 40 focused tests, independent nine-call compiled-CLI before/after configuration controls, check:fast and docs:check.
Full technical CI 42302162266 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; no completed Codex review of this head is available. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Fixes#7375. Fixes#7608.
Share argument consumption and agent facts between admission and scope checks; reject invalid registrations before settings, backups or runner startup. Preserve missing-value priority, explicit redaction, discovery and valid registration controls.
Validated 33 focused tests (one platform skip), current-main RED and independent compiled-CLI before/after workflows; check:fast passes.
Full technical CI 42297438912 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; no completed Codex review is available and the bot reports exhausted quota. The original author readiness declarations remain verbatim; no new human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Compare the preceding valid TypeDef coordinate independently for FieldList and MethodList, retaining readable members while leaving affected ownership unknown. Keep the scalar producer and shared owner lookup authoritative across member inspection, metadata inventory and native boundary workflows.
Validated with 577 managed/workflow tests, seven malformed/working ordering vectors, and independent dnfile/pefile raw metadata from a pinned public SystemEvents assembly. Four deliberately changed metadata bytes reproduce both decreases across an invalid row. Six CLI calls and ten stdio MCP tool calls before/after preserve all 458 fields and 195 methods and raw signatures/CIL hashes, keep ordinary results identical across three operations, and retain unresolved filter candidates rather than false absence. Alternative #8868 passes the expanded 206 inspector/ownership controls; converge on this generic implementation.
The original three unchecked human declarations are preserved. The explicitly authorized readiness-only exception applies after technical CI and review; no human-review attestation is added.
Fixes#8861
Full technical CI 42293217445 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; Codex cannot complete because the repository review quota is exhausted. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Classify a native method-93 decoder window refusal once at the ZIP owner as limit/memory, retaining the fixed 128 MiB cap, native cause and existing corruption/CRC/size/cancellation handling. Derive recovery labels from the cap and share one independent frame fixture.
Validated with 43 reader/translationtests,three actual CLI/stdio MCP cases, a pinned public GLFW payload rewrapped in independently verified ZIP controls, six equivalent CLI calls before/after, static checks and documentation checks. All input bytes remain unchanged, normal extraction matches exactly, and failed outputs are absent. ELF/GPU component refusal-policy gaps remain separate unverified full-format workflows.
The original three unchecked human declarations are preserved. The explicitly authorized readiness-only exception applies after technical CI and review; no human-review attestation is added.
Fixes#8623
Full technical CI 42279406899 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; Codex cannot complete because the repository review quota is exhausted. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Keep independent Objective-C class, metaclass, category and protocol records when an adopted-protocol list is unreadable. The shared list reader retains readable prefixes through tagged Result failures; consumers preserve partial/unknown semantics with unchanged limits. Advance the source-owned reader commitment to v3 and reject stale v2 snapshot bindings.
Validated with 232 focused tests, current-main and original-proposal RED checks, named provider-result/schema/identity controls, and a verified public GLFW Mach-O paired with independent LLVM/Python byte evidence. Ordinary binary results remain identical; a deliberately mutated count preserves ten rows instead of eight. The final head passes the real macOS CI lane, with twelve CLI tests including both new protocol-count cases over CLI and stdio MCP. Snapshot binding and same-version replay checks pass.
The original unchecked human declarations are preserved. The explicit maintainer readiness exception applies after technical CI and automated review, without adding a human-review attestation.
Fixes#8945
Full technical CI 42263548929 passes, including all eight shards and required aggregates. The Author readiness-only exception is explicitly authorized; no human-review attestation is added.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
* fix(desktop): read the PPA key add-apt-repository writes into Signed-By
On Ubuntu 24.04, add-apt-repository puts the PPA's armored key inside the
deb822 Signed-By field, and REA refused such repositories, so PPAs added the
standard way could not be exported. REA now reads that key by deb822 rules,
verifies the release with it through the same private gpgv keyring as key
files, as the entry's only candidate (as apt does), retains it as armored and
exports it as apt-keys/<name>.asc. A held key that does not verify is refused
with a reason that names no URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): check the held-key refusal's facts, not its sentence
The PPA refusal tests now assert that the reason names the sources file
and the release file and no URL, and that export carries that reason,
instead of comparing whole sentences.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* test(process): align symlink fixture with filesystem checkpoints
* test(frida): assert the CLI spawn Evidence contract
* test(mcp): use an unsupported analysis-view parent
* test(browser): separate source-location line bounds from parity
* fix(dotnet): reject invalid UTF-8 metadata version strings
* test(dotnet): preserve valid multibyte metadata version text
* fix(desktop): recognise JSON scalar files with mixed-case suffixes
* test(setup): use the current registration command in CodeBuddy fixture
* test(cli): split doctor help and timing boundary groups
* style(test): format split MCP doctor boundary groups
Resolve exported SQLite metadata through built-in PRAGMAs on the private connection. Preserve application tables/views, source encoding, nullable rows and the existing resource limits.
Validated with 93 focused tests, current-main RED controls, and actual CLI exports of the public Chinook dataset with native/shipped-reader parity and unchanged source hashes. No live GUI, container or installed agentenv qualification is claimed.
Full technical CI 42247722923 (all eight shards and final aggregate) passed; Greptile review scored 5/5 on the merged head. The original human readiness declarations remain unfilled; the explicitly authorized maintainer readiness exception applies without adding a human-review attestation.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Keep full schemas and canonical analysis unchanged while loading the cross-domain view through existing named discovery. Qualify real recent-tool workflows with token ledgers, latency, CPU, family RSS and retained-follow-up controls.
BREAKING CHANGE: The opt-in hot-cold catalog no longer preloads inspect_analysis_view. Discover it before calling it, batch with its producer when needed, or use the unchanged default full profile.
Preserve per-side capacity diagnostics in shared managed member comparisons and apply repeatable CLI type selection to both source inspections. Keep partial and historical unknown semantics intact, with one typed diagnostic projection and side schema.
Validated with 159 focused tests, final-main CLI/MCP boundary checks, schema validation and official public WPF 8.0.32/8.0.33 assemblies. Full source Evidence remains available through the retained-record MCP workflow. No target execution or budget increase.
Fixes#9021
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
* fix(javascript): apply package type scope to application sources
Application analysis parsed .js sources without their package scope, so an unbound require in a type-module package became a resolved CommonJS dependency. The parent now observes each source's nearest same-container package.json, stopping at node_modules, and the worker parses with that package type as reference imports already do.
Fixes#7323
* fix(javascript): keep package type unknown behind unavailable manifests
The package-type lookup indexed only readable files, so a nearer
package.json whose bytes were unavailable was skipped and an ancestor's
"type" applied to sources it does not own. Index unavailable manifests
by container and directory too, and stop the walk with "unknown" when
one is the nearest owner, as the Next app-context lookup already does.
* fix(javascript): keep Electron preloads outside module package scope
Electron's preload loader ignores a package's "type": "module" and gives
a .js preload `require`; ESM preloads need the .mjs suffix. Applying the
package type to every .js source marked such a preload as an ES module,
so its `require("electron")` edges and IPC pairing disappeared.
A source that requires an Electron module now keeps the context REA
inferred before package scope was applied. Node-loaded sources in a
module scope still drop unbound `require`.
* fix(javascript): admit Electron script loaders only on renderer evidence
The previous commit dropped a module package type whenever a source
required an Electron module. A Node-loaded ES module that requires
Electron, or one whose Electron call goes through a local `require`,
then regained the false require edges the package scope removes.
The parser applies the package type unconditionally again. The worker
re-reads a package-module script that requires an Electron module once
outside the package scope. The source keeps that context only when the
static pass there directly observes a contextBridge exposure or a
renderer IPC operation, which only Electron's renderer-side loaders
provide.
* fix(javascript): check Electron renderer evidence without a second static pass
b54845a6b ran the full static pass on an unscoped parse of every
package-module script that requires Electron, then discarded it when no
renderer operation turned up, so such main-process sources paid for two
full passes.
A candidate must now also name contextBridge or ipcRenderer, which any
renderer evidence needs. The evidence check runs only the static pass's
binding and Electron identity preamble with its exposure and IPC
descriptors. The full static pass runs once, on the chosen parse.
* fix(javascript): confirm Electron renderer evidence with the static pass
The focused evidence check from 4a86bca9b also counted renderer calls in
bundler factory defaults that the bundler convention never runs, which
the static pass skips. Such a default switched a type-module file to
script context, so its unrelated unbound requires became imports again.
The focused check now only screens. It follows the static pass's own
traversal, so a negative result still rules evidence out. A positive
result is confirmed by the static pass's direct findings on the unscoped
parse, and those findings become the file's facts when they hold.
* fix(javascript): confirm renderer evidence before wrapper substitution
f71236400 confirmed a screened source from the static pass's final
contextBridge and IPC findings, without wrapper substitutions. The
wrapper pass replaces a directly observed exposure inside a helper with
a substituted finding, so such a preload lost its imports again.
Before its wrapper pass, the static pass now records whether its source
traversal directly observed a contextBridge exposure or a renderer IPC
operation, and observedElectronRendererCall exposes that. The traversal
still skips bundler factory defaults that never run, and the reported
findings are unchanged.
* fix(javascript): resolve declared preload loaders before source analysis
* fix(javascript): leave preload discovery failures to source analysis
A source that failed during preload discovery threw out of the whole
application analysis, so one oversized source or a per-source timeout in a
module package returned no Evidence. Skip a source that exceeds its worker
limits and end discovery on any other failure except cancellation; source
analysis then meets the same source, records its failure and keeps partial
Evidence as it does without discovery.
* fix(javascript): report preload discovery gaps in Evidence limitations
When preload discovery skipped a source or stopped early, source analysis
could still read that source, so the result looked complete while a .js
preload declared there kept its package scope. Return each discovery gap
with the discovered paths and add it to the analysis limitations, which
both complete and partial Evidence carry.
* feat(process): add a non-executing dry run to capture_process_scenario
Preparation prompts asked an agent to prepare a scenario without running
it but could only suggest the tool that launches the target. Add
dry_run: true so the CLI and MCP resolve the executable, working
directory, observation paths and admission checks, then return planned
Evidence under the rea.process-scenario-plan predicate. Planned Evidence
is not capture Evidence and comparison tools refuse it as a capture.
The preparation prompt now suggests the dry-run call, and the scenario
identity commitments exclude the operation-only field.
Fixes#8398
* fix(process): redact planned sensitive input and share admission
Address review findings on the dry-run contract:
- Redact sensitive input events in planned Evidence and preserve only
their byte length.
- Recheck cancellation after the last awaited admission step.
- Run the sensitive-matcher and filesystem-root admission checks that a
capture performs before launch.
- Assert the prompt advertises the dry_run field without freezing
explanatory wording.
* test(process): hold the final admission step for cancellation
Add a preparation host seam so the regression can cancel while the last
filesystem admission call is pending, then assert the returned
AnalysisCancelledError retains the caller's reason.
* refactor(process): remove the unused plan alias
Keep the plan schema as the single exported contract and satisfy the
unused-export check.
* fix(process): report admission-time aborts as planning cancellation
`prepareProcessScenarioPlan` returned the admission error verbatim, so an
abort that landed while `admitFilesystemObservationRoots` awaited its
`lstat` surfaced as a cancelled `ProcessCaptureError` with
`operation: process_capture` even though no process ran. Convert that
result through the same `cancelled()` projection the neighbouring checks
use, and pin it with a test.
* fix(contracts): keep the process result invariants on the dry-run union
The output schema for `capture_process_scenario` became a union of the
capture and plan shapes, and the union no longer carried the description
that advertised the cross-field invariants to MCP clients — the boundary
test reads `normalized_result.description`. Attach a description to the
union that names both variants and keeps the capture invariants verbatim.
* test(process): exercise the post-admission cancellation check
The previous regression aborted immediately after starting preparation and
released the held admission afterwards. Preparation awaits runtime path
resolution first, so that abort was always observed by the pre-admission
check; deleting the post-admission "if (aborted()) return cancelled();"
still left the test green.
Signal from the admitRoots seam when admission is actually entered, abort
only after that point, then let admission resolve ok. Preparation now has
to reach the check between a successful admission and the ok result, and
the assertion pins both the AnalysisCancelledError tag and the caller's
reason as its cause.
Verified red-green: removing only the post-admission check fails this test
(expected true to be false at prepared.ok) while the other two pass, and
restoring it passes all three.
* test(process): gate the admission seam on acknowledgement
Race the admission acknowledgement against planning's own settlement and a
bounded deadline, and release and join planning in `finally`, so a planning
result that arrives before admission fails the test instead of stalling it
until the suite timeout.
* fix(contracts): hold plans to the scenario refinements and redact inputs
`processScenarioPlanSchema` described the resolved dry-run response but
dropped every cross-field check the input contract applies, so a plan
could advertise out-of-order events, an event past `timeout_ms`, or a
terminal beyond the renderer cell budget while claiming it had been
validated. Extract those checks into `scenarioRefinements` and refine the
plan's scenario with them.
The plan's input event also carried `sensitive` and an optional
`input_bytes`, which accepted a sensitive event that still shipped its
data. Model the public and sensitive forms as separate exact variants:
public input forbids `input_bytes`, sensitive input requires an empty
`data` plus its byte length.
---------
Co-authored-by: 2SinceJuly <[email protected]>
* feat(rizin): select the decoder and map address for one-shot commands
execute_rizin_command and rea execute-rizin-command accept optional
asm_arch, asm_bits, asm_cpu and base_address (CLI --asm-arch,
--asm-bits, --asm-cpu and --base-address). REA passes them to Rizin
before the artifact path as -a, -b, -e asm.cpu= and -m. Rizin 0.9.1
refuses -e asm.arch= and -e asm.bits= at startup, while -a and -b work
in 0.7.4 and 0.9.1. Rizin applies -e settings after -a and -b.
base_address is the address of file offset 0, as in REA's raw-binary
inputs.
REA checks only the spelling, because Rizin owns the arch and CPU
vocabulary and its builds differ. The Homebrew build of Rizin 0.9.1
has no xtensa or riscv plugin; the official static Linux build of
0.7.4 has both. Evidence records each input and the complete argument
list. When any input is set, Evidence also states that REA does not
confirm the decoder or map that Rizin used: Rizin uses another value
for one it cannot apply and exits 0. Rizin 0.9.1 keeps its default and
reports the value on stderr, and Rizin 0.7.4 turned -b 8 for arm into
16 without a message. Calls without these inputs keep their argument
list and Evidence.
The real-provider gate tests/boundary/providers/RizinDecoderInputs.test.ts
checks the effect of each flag through MCP with Rizin's own e asm.*,
omlj and byte reads. It skips when no Rizin is on PATH. It passes with
Rizin 0.9.1 (Homebrew, macOS arm64) and 0.7.4 (official static build,
Linux x86_64).
* docs(rizin): map multi-segment ESP32 images with om
An ESP32-family app or bootloader image holds several segments, each
loaded at its own address, so one base_address cannot map it. The
recipe removes the default map with om-* and adds one om map per
segment, using the segment offsets from the image format or from
esptool image-info. It does not depend on an ESP image reader in REA.
The offsets were checked against esptool 4.12.0 image_info on a
synthetic two-segment ESP32 image. Rizin 0.9.1 and 0.7.4 mapped its
segments at their load addresses. Rizin 0.7.4, whose static Linux
build has the xtensa plugin, decoded the IRAM segment as nop.n, nop.n,
ret.n and nop.n.
* fix(rizin): read the file at base_address when a loader would claim it
On Rizin 0.9.1, base_address could read 0xff at the mapped address with
exit 0. -m maps the file, but Rizin still runs its format loaders, and
a loose one can claim a raw image. Rizin 0.9.1's AVR loader matches
when bytes 1 and 3 look like rjmp and turns io.va off, so reads use
file offsets. The BIOS loader, in 0.7.4 and 0.9.1, matches a 64 KiB
file with a jmp 16 bytes from its end and maps it at another address.
An acceptance run on a raw ESP32-S3 segment hit the first case.
With base_address, REA now passes -n together with -m. Rizin then skips
the loaders and maps the whole file at the address: rizin.c calls
rz_io_map_new at the -m address and leaves io.va at its default. The
input therefore means a raw image, and Rizin loads no sections, symbols
or entry point, even from an ELF file. Evidence states this in its own
limitation, and the decoder limitation no longer covers the map.
The real-provider gate adds an AVR-like and a BIOS-like image and reads
below the base, at it, at the end of the image and past it. Without -n
it fails on Rizin 0.9.1. It passes with Rizin 0.9.1 (Homebrew, macOS
arm64) and 0.7.4 (official static build, Linux x86_64). The ESP om
recipe now starts with e io.va=true, because reads use the maps only
while io.va is on.
* fix(rizin): advertise name patterns that compile in v mode
The asm_arch and asm_cpu pattern ended its character class with an
unescaped '-', which JavaScript's v (unicodeSets) mode rejects, so
clients compiling advertised patterns in that mode could not use
execute_rizin_command's schema. Escaping it keeps the same names valid
in every mode, as main's own patterns write it.
* fix(rizin): state what the decoder and map inputs leave unconfirmed
The base_address limitation reported a map REA never observes and left
out that -n keeps Rizin's default arch, bits and byte order. The asm_*
limitation implied Rizin replaces a bad CPU, but e asm.cpu echoes any
value. Compact schemas drop property descriptions, so the tool
description now carries the raw-loading warning. The real-provider
gate checks that asm_cpu changes a decode, and a base_address-only call.
* docs(rizin): record the gate's full run on Rizin 0.7.4
* test(rizin): follow the test guide in the decoder input tests
Focused tests resolve execute_rizin_command from its contract family,
the harness owns the temporary directory before setup can fail, and
guidance is compared with its owner, not with sentences from it.
* feat(domain): guide agents from neighboring-tool argument mistakes (#2146)
* feat(mcp): guide agents from argument-name mistakes in MCP tools (#2146)
* fix(domain): suppress argument replacement when expected field is present (#2146)
Suppress argument replacement guidance when the expected field is already present in the call input, preventing invalid suggestions such as advising procedure when procedure was already provided. Defrost explanatory prose assertions in tests and bind test cases to production toolContract schemas.
* test(domain): use named contract families and decouple guidance prose in tests (#2146)
Import named contract families (OFFICIAL_TOOL_CONTRACTS, APPLICATION_TOOL_CONTRACTS, ENHANCED_TOOL_CONTRACTS) instead of the full tool catalog per docs/testing.md. Replace hardcoded explanatory message and remediation strings with dynamic references to the owning guidance constants.
* test(domain): restore independent replacement field assertions (#2146)
Keep exact checks for expected replacement field names ('procedure', 'pattern', 'address', 'query') independent of the owning guidance helper per docs/testing.md, while retaining dynamic validation for message and remediation prose.
Apply declaring-type selection before member-row admission, recovering only referenced unselected names. Reuse known cores and skip recovery for unscoped inspection. Add valid/malformed selected and unselected generic call regressions.
251 focused tests and check:fast pass. Public CLI scale reproduction and independent metadata oracle confirm complete selected members; public CoreLib/Roslyn/WPF facts remain identical. Real stdio MCP summary and paging retain complete source coverage and provenance.
Original proposal by sx4im. The maintainer explicitly authorized integration and waived the human-declaration gate; unchecked author declarations are preserved without attesting human review.
Fixes#8980.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Attribute REA capture-limit failures at the shared Rizin/objdump execution boundary. Preserve raw signals, output prefixes, unknown completion and incomplete-cleanup facts; report a termination request without claiming confirmed exit.
134 focused tests, check:fast and actual Rizin CLI controls over an owned ELF and /usr/bin/ls pass. Original proposal by cpruijsen.
Fixes#9031.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Preserve original SQLite TEXT bytes and decode them using the database encoding in exported Python readers. Extend the real encoding matrix to leading FEFF and quoted column names.
45 focused tests, native public CLI and full export/helper parity checks, formatting and check:fast pass. Retains the original correction by ShuaixinHuang from #9074.
Fixes#9004.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Apply the two-file repair proposed in #9109 to unblock main CI. Preserve every original test assertion and use the current registration command owner.
Co-authored-by: gensokyo-dev <[email protected]>
Use whole-row multiset identity for incomplete/NULL declared keys and share it with task drafting. Count actual projected predicates across all rows so timestamp omission and duplicate multiplicities yield satisfiable checks.
Fixes#8997.
Validation:106 focused tests; nativeSQLite public CLI before/after cases and generatedPython evaluator agreement.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
* feat(cli): expose caller-selected reference-source path exclusions
Let import-reference-source accept a JSON array of Git-ignore rules and
pass them to the existing importer before the source tree is opened.
Fixes#2443
* docs(cli): use an effective reference exclusion negation
* test(cli): expect entry_kind on reference-source exclusions
Main records entry_kind on every historical source exclusion. The caller
exclusion assertions still compared objects without that field, so the
pruning test failed after merging main.
Co-authored-by: Yunzhou Li <[email protected]>
* test(cli): record caller *.tmp over project ignore
Caller exclusions are mandatory and override project rules, so project.tmp
matched by *.tmp is caller-excluded. Align the pruning assertion with that
result from the merged importer.
---------
Co-authored-by: morluto <[email protected]>
Co-authored-by: REA Maintenance <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Yunzhou Li <[email protected]>
* fix(cli): honor the selected format for unknown mcp subcommands
Incur 0.6.1 renders its unknown mcp subcommand rejection as TOON
regardless of --format or --json. Report the same COMMAND_NOT_FOUND
envelope in the explicitly selected format before dispatching to
Incur, share the pinned mcp subcommand table with completions, and
redact explicitly declared sensitive values from the diagnostic.
Fixes#7691
* fix(cli): correct only the subcommand in the mcp suggestion
The suggested command replaced every token equal to the unknown
subcommand, including option values such as --command. Record each
command token's argument position in the shared builtin split and
replace only the subcommand position.
* fix(cli): leave earlier Incur builtins and caller output flags intact
Incur serves --llms, --llms-full and its update check before the mcp
subcommand branch, so defer those invocations to Incur as well. Build
the suggested command from the caller's arguments rather than the
arguments adapted for Incur, so a delimited format and its columns
remain in the suggestion.
* fix(cli): ignore output values spelled like earlier Incur builtins
The earlier-builtin check matched raw tokens, so an output flag value
such as --columns --llms or --filter-output --llms deferred the
rejection to Incur's TOON path. Check the arguments Incur receives and
skip positions its builtin pass consumes as output values.
* fix(cli): omit mcp suggestions that would not replay caller input
Joining the corrected tokens with spaces re-tokenizes a value with
whitespace or shell metacharacters when the suggestion is copied, and
quoting differs between POSIX shells, cmd and PowerShell. Offer the
corrected command only when every token is a plain shell word and no
declared sensitive value was redacted; the message still names the
suggested subcommand.
* fix(cli): preserve safe MCP diagnostic output
* fix(cli): omit mcp suggestions with words zsh expands
zsh expands a word that starts with = to an executable path by
default, so a copied suggestion such as --command =node would replace
the caller's literal value. Treat =-leading words as non-replayable.
* fix(cli): render mcp diagnostics through the document renderer
Markdown output went through Incur's formatter, so caller text such as
*oops* rendered as emphasis instead of literal text. Use REA's document
renderer for every format, and redact declared sensitive literals from
the fixed call-to-action strings as well as the message.
* test(cli): keep the mcp correction on the subcommand after timing globals
---------
Co-authored-by: morluto <[email protected]>
Document existing compact output advertisements for full catalogs and explicit hot/cold refresh under the FastMCP1MiB SSE limit. Preserve canonical server validation and spell out the remaining Unsloth and large-result verification gaps.
Refs #9093; related #5861,#7155.
Validation: four real FastMCP HTTP profile trials; docs:check and formatting.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Exclude only proven separate values from Webpack loader alias walks; preserve unsupported, projected and unresolved origins as unknown and retain the real alias-state bound.
Refs #7253. The full Bitwarden precision regression remains open.
Validation: 336 focused tests and check:fast passed; real Bitwarden preload identity retained; full main/preload completed with disclosed partial coverage.
Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
* feat(ghidra): admit DOS MZ targets on Windows P0
Windows Ghidra P0 refused dos-mz although the MzLoader import, 16-bit
real-mode language attestation and load-image evidence are host-neutral.
Admit dos-mz in inspectWindowsP0TargetSupport and run verify:ghidra:dos on
Windows: inherit the system environment there, expect annotation and naming
edits to be refused (P0 has no mutation authority), and skip verifier
lineage like the 6502 lane. Explicit COM stays refused on Windows; its
verifier is built around annotation edits.
Refs #7121
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(ghidra): check naming refusal in the read-only DOS lane
With mutable: false the address-boundary helper skipped every naming
request, so the Windows report overstated its coverage. Assert that
set_address_name and set_addresses_names are refused with
capability_unavailable and leave the entry name unchanged, and declare
the optional mutable argument in the .d.mts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(ghidra): unwrap failed profile Result before asserting
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* fix(ghidra): expect an unchanged DOS entry on Windows P0 verification
Windows P0 refuses edits, so verifyGhidraAddressBoundaries skips the
rename-and-restore and the entry keeps its imported symbol source. The
post-check still expected user_defined, which only follows a rename.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Register rea in the global ~/.codebuddy/mcp.json through a new codebuddy
entry in SUPPORTED_CLIENT_DEFINITIONS (json dialect, ~/.codebuddy marker,
~/.codebuddy/skills skill directory). Document the client and cover
registration and uninstall with boundary tests. Loading, the bundled
skill, and real REA tool calls were verified in the CodeBuddy client.
* feat(python): qualify a recognized 3.14/3.15 pyc header
Python 3.15 is the default `python3` on current Homebrew, but its pyc was rejected with
"Pyc magic 3666 is outside the pinned CPython 3.7–3.13 table" — a message that says
nothing about the file and reads as if the version were unknown. Issue #8206.
Two facts were wrong.
**3.15 was not in the identity table at all,** so `pythonBytecodeIdentity` reported it as
unknown even though the table already pins 3.14 (3627). 3666 is now pinned as 3.15.
**A known identity without a pinned code layout shared one refusal with an unknown
magic.** The two are different outcomes: every 3.7+ pyc carries the same packed 16-byte
header, so a recognized newer magic can still be qualified even when no marshal layout is
pinned for it. The parser now separates them:
magic 3666 (3.15) -> CPython 3.15 header is recognized (magic 3666, 16-byte header,
flags 0x0, timestamp-based), but no marshal code layout is
pinned for that version, so its code objects cannot be decoded.
Standalone marshal decoding is pinned for CPython 3.7–3.13.
magic 1 -> Pyc magic 1 is not a pinned CPython identity, so its header
cannot be qualified; ...
The recognized case reports the observed version, the header shape, the PEP 552 flags and
whether the pyc is hash-based or timestamp-based, which is what an agent needs to decide
that a `.jsc`-style text-recovery attempt is pointless and that the file is a 3.15 pyc
rather than a corrupt one. It stays a typed `unsupported` failure: no pinned layout means
no decoded code object, so there is no partial success.
I did not guess a 3.14/3.15 marshal layout. The 3.11+ `TYPE_CODE` order is pinned through
3.13; asserting the same order holds for 3.14 or 3.15 without a real interpreter to
verify against would be an unsupported claim, which is why this change qualifies the
header instead of decoding it.
Docs and the tool description now say marshal decoding is pinned for 3.7–3.13 while the
identity table also recognizes 3.14 and 3.15.
Three regressions fail on the base commit.
* chore: retrigger title and readiness checks after the title was corrected
* test(python): pin the qualified header facts to the packed fields
Review finding. Every new case used `pythonPycHeader(magic)` with its default zero flags
and never asserted the reported flags or header mode, so a refusal that hardcoded
`flags 0x0, timestamp-based` would have passed all of them.
The cases now build the header from explicit fields and assert the facts that field
produces: `flags 0x0/timestamp-based` and `flags 0x1` or `0x3` with a PEP 552 source hash
for hash-based. A separate case parses the same magic in both modes and requires each
refusal to name its own mode and not the other.
Verified by hardcoding `flags 0x0` in the refusal: the two hash-based cases fail.
* fix(javascript): report empty Electron coverage instead of silent zeroes
`analyze_javascript_application` reported `browser_windows: 0` and
`ipc.operations: 0` with only generic partial/unknown flags, so a bundle that
imports `electron` but whose Electron facts were not recovered read exactly like
an application with no IPC at all. Issue #8201 is that false negative on a real
Rolldown bundle.
Two additions, both scoped to "Electron is referenced but nothing was recovered":
**A machine-readable indicator.** `electron_summary` gains
`electron_semantics: "recovered" | "empty"`. It is absent when no analyzed source
links an Electron module, so its presence distinguishes "not an Electron
application" from "Electron coverage is empty". It follows the existing
`protocol_registrations.exactOptional()` precedent, so previously captured
evidence stays valid.
**A graph limitation.** The limitation list already carried a note when at least
one Electron fact was recovered but said nothing when zero were. It now states
that zero IPC operations and zero BrowserWindows mean empty coverage rather than
absence.
`electronModuleReferenced` is the single owner of the module test. It reuses
`ELECTRON_MODULE`, which matches exactly the supported entry points (`electron`,
`electron/common`, `/main`, `/renderer`, `/utility`), so `electron-helper` does
not count, and it ignores type-only links because importing Electron types
promises no boundary fact.
Five tests cover recovered, empty, not-referenced, type-only, and supported
subpath versus unrelated package. The empty, recovered and type-only cases fail
on unmodified `main`.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): report Electron coverage from retained facts
Four review findings on the coverage indicator.
**A rejected application projection hid the reference.** A file whose expanded
relationships exceed the result budget is replaced by a placeholder with no
module links before the summary runs, while `graphLimitations` reads the raw
analysis. The same response could then warn in prose and omit the field. The
summary now takes the retained analysis for coverage only, and both outputs
read one `electronSemanticsState`, so they cannot disagree.
**A skipped semantic analysis hid the reference too.** `electronModuleReferenced`
read only `semantic.ir.moduleLinks`, which is absent when semantic analysis is
skipped for a budget and drops `import("electron")` or `module.require("electron")`
forms. It now falls back to the retained `javascript.references`, which the static
producer records for those inputs, while still excluding `type-import` links when
the richer owner is available.
`electronBoundaryFactCount` is now the single owner of the fact total, replacing a
duplicate inline reduce in the graph builder.
**Omission no longer claims absence.** The schema comment said a missing
`electron_semantics` means the application does not use Electron. The predicate
only establishes that no retained fact names one, and unread sources, failed
analysis, and dynamic module names can hide its use, so the contract now says
unknown.
Two regressions cover a rejected projection and a skipped semantic analysis; both
fail on the previous commit.
* fix(javascript): count a dynamic Electron import as coverage
Review finding: `electronModuleReferenced` returned as soon as module links
existed, so a literal dynamic import was ignored. Verified against the two fact
owners:
import("electron") -> references: [dynamic-import electron]
moduleLinks: []
require("electron") -> references: [require electron]
moduleLinks: [require electron]
An application that only loads Electron through `import("electron")` therefore
got neither `electron_semantics: "empty"` nor the empty-coverage warning, and the
zero counts stayed unexplained.
The two owners now answer their own half instead of one short-circuiting the
other. Module links answer a runtime static import or require and keep excluding
`type-import`; the retained reference is read for every kind except
`static-import`, which is exactly the type-only risk. A dynamic specifier is
still never counted by either owner, so this remains an observed-reference claim
rather than an absence claim.
The regression uses `import("electron")` and fails on the previous commit.
* fix(javascript): read only module-loading Electron references
Two review findings, both verified against the static reference producer.
**A retained runtime static import was skipped.** The excluded kind was meant to
protect the type-only distinction, but the static analyzer records a whole
`import type { IpcMain } from "electron"` declaration as no reference at all:
import type { IpcMain } from "electron" -> references: []
import { app } from "electron" -> references: [static-import electron]
require("electron") -> references: [require electron]
import("electron") -> references: [dynamic-import electron]
new Worker(new URL("electron", m)) -> references: [worker electron]
When semantic analysis is skipped for a budget the module links are absent, so
that exclusion dropped a real runtime ESM import and left the zero counts
unexplained.
**A worker URL path is not an Electron module.** `new Worker(new
URL("electron", import.meta.url))` records a `worker` reference whose specifier is
a path relative to the importing source, so counting it reported an ordinary
worker file as `electron_semantics: "empty"`.
`ELECTRON_REFERENCE_KINDS` is now an explicit allowlist of the kinds that load a
module by package specifier — `static-import`, `dynamic-import` and `require` —
so `worker` and `service-worker` URL paths are excluded. Module links are still
read first for the type-only distinction.
Three regressions cover a skipped-semantics ESM import, a skipped-semantics whole
type-only import, and a worker URL path; two of them fail on the previous commit.
* fix(javascript): own Electron applicability in the static producer
Review finding. A `worker` reference records a URL relative to the importing
source, so `new Worker(new URL("electron", import.meta.url))` names a local file
and is not an Electron module. The boundary owner already read only module-loading
reference kinds, but `finalizeStaticAnalysis` still treated every matching static
reference as `analysis_coverage.applicability.electron: "observed"`, which
`javascriptStaticSourceCoverageSummary` publishes. One response could therefore
say Electron was observed in its coverage view while the Electron summary omitted
`electron_semantics`.
`ELECTRON_REFERENCE_KINDS` now lives next to `ELECTRON_MODULE` in the domain owner
and is consumed by both the static producer and the Electron boundary owner, so the
two outputs cannot disagree. A worker URL path is `unknown` for both, while a
`require("electron")` is `observed` for both.
The regression asserts the domain applicability fact and the boundary summary agree
for a worker URL path; it fails when the producer stops filtering by kind.
* fix(javascript): require a loaded Electron module for applicability
Review finding. A CommonJS `require.resolve("electron")` records a `require`
reference with `operation: "resolve"`: it locates the module without loading it,
so no Electron boundary fact is expected. Both predicates dropped the operation,
so such a source reported `electron_semantics: "empty"` and the missing-coverage
warning even though Electron was never loaded.
Measured on the static producer:
require.resolve("electron") -> references: [require resolve electron]
applicability.electron: unknown
require("electron") -> references: [require load electron]
applicability.electron: observed
import("electron") -> references: [dynamic-import load electron]
applicability.electron: observed
`electronModuleReferenced` and `finalizeStaticAnalysis` now both require
`operation === "load"`, so the boundary summary and the coverage view stay aligned
and reachability-only references no longer claim missing coverage.
The regression asserts a resolve-only source produces neither the field nor the
warning, and it fails when the operation check is removed.
* fix(javascript): classify Electron loads in the static fact owner
Review finding, and the correct home for the previous two rounds of the same
complaint. A Webpack/Rspack factory may name a local module `"electron"`, and the
static producer then records a loaded `require` reference whose specifier is that
bundle module key. Treating the key as a package specifier reported
`electron_semantics: "empty"` plus the missing-coverage warning for a bundle that
never loads Electron.
Three changes, all in the domain owner, so no consumer re-derives the question:
- `JavaScriptModuleRangeIndex.hasKey` answers whether a specifier is a recovered
bundle module key. A `require` whose specifier is such a key loads a module
inside the artifact.
- `electronObserved` in `finalizeStaticAnalysis` now requires a loaded module-load
reference whose specifier is not a bundle key. The published
`analysis_coverage.applicability.electron` therefore owns every exclusion:
`type-import`, `require.resolve`, `worker`/`service-worker` URL paths, and
bundle-local keys.
- `electronModuleReferenced` consumes that owned fact instead of re-deriving it,
keeping the boundary summary and the coverage view from disagreeing. Historical
evidence without the field still falls back to the retained references.
Measured:
bundle-local module ID "electron" -> applicability unknown
require("electron") -> applicability observed
webpack external n(45840) -> applicability observed
The regression covers both the local key and the real external in the same
factory shape; it fails without the bundle-key exclusion.
* fix(javascript): identify a bundle module by its loader, not its key
Both review findings were right, and they show why the previous key-set approach
was wrong: bundle-key membership is a property of the file, but whether a
specifier names a module inside the artifact is a property of the reference.
`JavaScriptStaticReference` now records the loader that produced it, and
`electronObserved` excludes `factory-loader` references instead of every specifier
that happens to match a recovered module key. Measured on Node 24.22.1:
local module keyed "electron" -> unknown (factory-loader)
external 45840 = require("electron") -> observed (commonjs-loader)
factory keyed "electron" that loads the pkg -> observed (commonjs-loader)
The third case is the regression: a bundle whose factory is *named* `"electron"`
but whose body does `e.exports = require("electron")` is a real Electron load, and
the old key-set check suppressed it. The real load keeps its `commonjs-loader`
identity even when the same specifier also names a factory, so one response can
rely on the loader while the other relies on the key without either lying.
`JavaScriptModuleRangeIndex.hasKey` is removed; it answered the wrong question.
One regression covers all three shapes and fails when the `factory-loader`
exclusion is dropped.
* fix(javascript): carry the reference loader across the worker boundary
The worker schema is strict, so the new `loader` field broke the worker handshake:
`unrecognized_keys` on every `references` entry. That surfaced in CI as both a
worker-schema test failure and a package-baseline failure reporting
`worker_failure_reason: "io"` for a one-file Electron app.
`referenceShape` now accepts the same three loader identities as the domain type, so
the fact survives the worker boundary instead of collapsing every analysis to an
execution failure.
Verified: `JavaScriptAnalysisWorkerSchemas.test.ts` 10 passed, and the full
domain/application/javascript suite is 5484 passed | 10 skipped. The remaining
`SourceMapCommand` and `DarwinHelperCache` findings are present on unmodified
`main` and are not from this change.
* fix(javascript): remove the superseded key set and forward a deferred loader
Two review findings.
**The `keys` set is dead.** `JavaScriptModuleRangeIndex.keys` is left over from the
abandoned key-membership approach: the loader-identity refactor removed `hasKey` and
the write in `add`, so nothing reads it. Removed.
**The deferred require path dropped the loader identity.** `requireCallSpecifier`
returns a `loader` for `commonjs-loader`, `factory-loader` and `create-require`, and the
direct `addReference` call forwards it, but `finishModuleRequireCalls` did not. A
`createRequire` loader saved under a member literally named `require` therefore lost
its identity:
import { createRequire } from "node:module";
const holder = { require: createRequire(import.meta.url) };
holder.require("x");
before: require "x" with loader undefined
after: require "x" with loader "create-require"
Three regressions cover it; one fails without the change.
I did **not** change the inline-type-only import case. `isTypeOnlyImportDeclaration`
deliberately keeps a declaration whose bindings are all inline type-only, because
`hasUncertainTypeOnlyModuleSideEffect` reports that decision as
`type-only-module-side-effect` uncertainty: whether TypeScript emits the import depends
on the consumer's compiler options. Narrowing it to erased broke
`javascriptStaticCoverage.test.ts` and `javascriptSemanticAnalysis.require_links.test.ts`,
which assert exactly that contract. If Electron applicability should ignore that
reference, the decision belongs where applicability is derived, not in the shared
declaration predicate — say the word and I will do it there instead.
* fix(javascript): keep a commonjs-loader reference out of the bundle module index
Review finding. `finalizeLocatedFindings` attaches `module_runtime` to every finding
that sits inside a factory module range, whoever produced it. `resolveReference` then
sent every reference carrying `module_runtime` through the bundle-module lookup, so a
real CommonJS load inside a factory body resolved to the local module key that shared
its name:
"electron": (e, t, n) => { e.exports = require("electron") }
fact: specifier "electron", loader "commonjs-loader", module_key "electron"
before: resolved through the bundle module index to `#module:electron`
after: resolves as an external package specifier
Only a `factory-loader` specifier names a recovered bundle module key, which is what
`loader` already records after the loader-identity change. The module lookup is now
gated on it, and an absent `loader` keeps the previous behaviour for older findings.
* fix(javascript): emit no fs acquisition for a rejected literal mode
`fs.open`, `fs.openSync` and `fs/promises.open` validate the `mode` in argument 2 before
touching the filesystem, so a mode Node rejects throws synchronously and no descriptor
exists. The guard read only the flag, so a rejected mode kept its acquisition and any
linked release.
Measured on Node 24.22.1:
fs.openSync(p, "r", -1) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", 1.5) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", NaN) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", 1e21) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", true) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", 0n) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", {}) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", "8") -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, "r", "") -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, "r", "0o644") -> THREW ERR_INVALID_ARG_VALUE
--- accepted ---
fs.openSync(p, "r", 0o644) -> ok
fs.openSync(p, "r", 2147483647) -> ok
fs.openSync(p, "r", -0) -> ok
fs.openSync(p, "r", "0644") -> ok
fs.openSync(p, "r", null) -> ok
fs.openSync(p, "r", void 0) -> ok
The guard decides only exactly-readable literals. A numeric mode is validated as an
unsigned 32-bit integer; a string mode must be a canonical octal digit string, padded
leading zeros allowed, and its value must stay inside uint32; `null`, `undefined` and
`void <expr>` default the mode and are kept, as is a computed value.
Three boundaries are deliberate:
- Values in `[2^31, 2^32-1]` **abort the Node process** rather than throwing
(`Assertion failed: args[2]->IsInt32()`), so they stay undecided instead of being
claimed as a synchronous throw.
- A spread before the mode slot shifts the runtime position.
- A unary chain is resolved through its innermost readable operand, and a nested
boolean or string result is left undecided rather than applied as a number.
Regressions cover both directions. Full suite here: 5466 passed | 7 skipped.
* fix(javascript): remove the superseded string mode check
Review finding. `rejectsOpenModeLiteral` builds `octal` from a string literal or a
no-expression template and returns inside that branch for every value it matched. A
`StringLiteral` therefore never reaches the later `isStringLiteral` check, which cannot
run and is a second copy of the canonical-octal rule to maintain.
* test(javascript): cover the spread and boundary paths of the mode guard
Review feedback from @greptile-apps: the new mode tests skipped both paths the
guard deliberately leaves undecided. Two spread cases are added:
- `openSync(...args, "r", "0600")`, where a spread in the flag slot shifts the
runtime position of the mode;
- `openSync(p, "r", ...modes)`, where a spread occupies the mode slot itself.
Both must keep the acquisition, because neither establishes the mode value. The
boundary cases were already present: `2147483647` and `2147483648` are keep
cases (`2147483648` aborts the process rather than throwing) and `4294967296` is
the reject case, alongside the octal `37777777777` keep and `40000000000` reject
pair.
127 cases pass in this file.
pull-adb-file and push-adb-file declared device-path as any non-empty string. The CLI schema advertised a relative path as valid, and the service then refused it under the MCP field name. Reuse the shared device_path fields, and keep the regex constraint in CLI field errors as list-adb-directory does.
* fix(evidence): bound malformed file field diagnostics
`readEvidenceFileSource` projected every validator issue, and the generic error
delivery applied the MCP response budget afterwards. A file with many invalid
fields therefore produced a transport `resource_constraint` that replaced the
typed file error, so the caller lost the selected path entirely. Issue #8453
reports exactly that: 20,000 invalid values still returned a direct
`invalid_request` with the path and every field issue, while 125,000 returned
`resource_constraint` with neither.
`projectInputIssuesWithinBudget` already existed for this but had no production
caller. The reader now projects within a 64 KiB allowance and, when the whole list
does not fit, keeps the largest leading group that does and appends one explicit
omission issue naming how many of how many were dropped. The typed file error and
its `path` therefore survive the response boundary, and the diagnostics stay
actionable and explicitly incomplete instead of hiding the real failure.
Two regressions cover the bound and the fits-in-budget case. With the unbounded
projection restored, the bound test and its supporting fixtures fail.
* chore: retrigger title and readiness checks after the title was corrected
* fix(evidence): bound file diagnostics in one pass and cover the MCP boundary
Review findings on the first version.
**The prefix was not maximal.** `boundedContentIssues` halved the list and returned
the first attempt that fit, so a file just over the allowance lost roughly half its
diagnostics. Prefix selection now lives in the shared projector
(`projectInputIssuesBounded`), which walks the issues once and stops exactly at the
allowance, keeping the largest fitting prefix.
**A compound issue could still lose its location.** One `unrecognized_keys` issue
naming hundreds of keys expands into hundreds of field diagnostics and can exceed
the allowance on its own. The projector now retains a diagnostic at that issue's
path instead of returning only the omission notice.
**The regression never crossed the MCP boundary.** The issue is about the delivered
MCP error, so `evidenceFileSourceMcp.test.ts` sends the 125,000-value fixture
through the production `inspect_analysis_view` tool and asserts the delivered error
stays `invalid_request`, keeps `details.path`, keeps bounded `content_issues`, and
fits the response budget. Both regressions fail without the bound.
* test(evidence): move the MCP delivery regression to the adapters project
Review finding. `scripts/lib/test-projects.mjs` assigns `src/application/**` to the
`services` project, so the delivery regression sat in the wrong project even though
it exercises the production MCP tool registration. Moving it to `src/server/` puts
it in `adapters`, which runs the other MCP boundary tests and plans the same way.
* fix(web-capture): keep mitmproxy request targets and status codes in previews
Capture previews selected HAR/SAZ headline names (url, status) only, so a
native mitmproxy record lost its request target (scheme, host, port,
authority, path) and its status_code while keeping the reason phrase, and
the matching numeric sidecars were filtered out. Select those mitmproxy
headline fields too; headers and payloads stay omitted.
* 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 <[email protected]>
* fix(ci): isolate Linux Hopper process fixture inventory
* fix(android): make DEX descriptor pattern portable
* style: format comparison and IDA sources
* fix(web-capture): preview mitmproxy targets only as safe scalars
Select the mitmproxy target and status fields only for mitmproxy
records, only when they are scalars, and the path only in origin form.
An absolute, network-path or otherwise ambiguous path spelling can carry
userinfo that the native bridge's redaction does not recognise, and a
nested value could copy an arbitrary record subtree into the preview;
both stay in complete record pages. Other formats keep their existing
headline fields.
* fix(web-capture): keep the asterisk-form OPTIONS target in mitmproxy previews
* fix(web-capture): exclude ambiguous and oversized mitmproxy targets from previews
Origin-form checks alone accepted slash-backslash and slash-control
spellings such as /\user:password@host/a, which WHATWG resolves as an
authority with credentials and the recorded transport-URL interpretation
already treats as ambiguous credentials. Preview a mitmproxy path only
when parseRecordedTransportUrl finds no credentials, and omit mitmproxy
headline strings longer than 8 KiB so one record cannot exceed the reply
budget; both stay in complete record pages.
* fix(web-capture): keep userinfo-bearing mitmproxy target fields out of previews
Native redaction covers only request path and authority, so a malformed
state such as host "user:[email protected]" passed through the
preview. Omit every non-path mitmproxy headline string that contains a
userinfo marker (@); valid schemes, hosts and ports never do, and the
value stays in complete record pages.
* fix(web-capture): admit mitmproxy headline values only in their well-formed shapes
A per-string length cap still let a malformed capture fill every target
slot with escape-heavy text, so one default preview page could expand to
about 18 MiB of JSON. Preview each mitmproxy field only in its own shape:
a short scheme token, host/authority without controls, whitespace or
userinfo markers, integer port and status code, and a path of at most
2 KiB without control characters. That bounds every record's headline to
about 4 KiB serialized, even for an escape-heavy path.
* fix(web-capture): bind mitmproxy headlines to their message side and integer type
Select mitmproxy target fields only from the request and status_code
only from the response, so an unknown scalar extension such as
response.path or request.status_code is not promoted to a headline.
Admit port and status_code only when the record's numeric sidecar
reports the producer's integer type; a native float such as 443.0 is
otherwise indistinguishable from 443 after JSON parsing.
* fix(web-capture): require one canonical integer sidecar for mitmproxy numeric headlines
An inline parent could pair request.port 443 with an integer sidecar
whose literal is unbounded or does not represent the value, or with
duplicate sidecars, and the selected-pointer filter would copy them into
the preview. Admit port and status_code only with exactly one integer
sidecar whose literal is the value's canonical decimal, which also
bounds the copied literal to a few characters.
* fix(web-capture): add mitmproxy headlines only for HTTP flows
The mitmproxy container format does not make every retained flow HTTP; the bridge keeps non-HTTP and unknown flow states with their extensions. Add the mitmproxy target and status candidates only when the record's own type is "http".
* fix(web-capture): require consistent numeric sidecars for every mitmproxy headline
Selecting a field also selects its numeric sidecars, so a string target such as request.path with an unrelated multi-megabyte sidecar copied that literal into the preview. Admit integer slots only with exactly one canonical integer sidecar and string slots only with no sidecar.
* fix(web-capture): fail closed on any userinfo marker in previewed mitmproxy paths
URL parsing can fail before recognising userinfo: /\user:[email protected]:65536/a has an invalid port, so the parser reported no credentials and the path was previewed with them. Every userinfo needs "@", so preview a path only when it contains none and does not start with // or /\; such paths stay on complete pages. The parser check is no longer needed.
* fix(web-capture): never promote a redacted pointer to a mitmproxy headline
Inline or imported Evidence can carry a value at a pointer that its own redactions mark as sensitive. Skip any mitmproxy headline whose pointer has a redaction entry, so a preview never re-emits a value the record says was redacted.
* fix(web-capture): check integer headline types before comparing sidecar literals
The sidecar comparison called String(value) before the numeric shape check, so an inline object such as {"toString": null} at request.port threw a TypeError out of the view instead of being omitted. Require a number before canonicalizing it.
* fix(web-capture): honor ancestor value redactions before selecting mitmproxy headlines
A value redaction at /request or the record root excludes its whole subtree, but only exact-pointer matches were honored, so contradictory inline Evidence could still preview /request/host or /request/path. Treat a value redaction as covering every descendant pointer; a property-name redaction still only identifies the parent of an omitted key and hides no sibling values.
* fix(web-capture): treat redacted byte descriptors as exclusions for mitmproxy headlines
Producers mark excluded bytes with a redacted binary descriptor as well as a redaction entry, and the record schema accepts either facet alone. Skip a mitmproxy headline when a redacted descriptor covers its pointer, so contradictory Evidence that keeps the value cannot preview it.
* fix(web-capture): refuse a redacted flow type as the mitmproxy HTTP discriminator
Contradictory Evidence can keep type "http" while marking /type redacted. Apply the same redaction check to /type before treating the flow as HTTP, so no mitmproxy headline is added when the flow kind is unavailable.
* fix(web-capture): refuse mitmproxy headlines under contradictory numeric sidecars
A numeric sidecar records that the producer held a number at that pointer. Treat the flow as HTTP only when /type has no sidecar of its own, and promote a mitmproxy headline only when no ancestor (the record root, /request or /response) carries one.
* fix(web-capture): require a textual flow type before adding mitmproxy headlines
mitmproxy's flow state carries type as a string. When the native value was bytes, the bridge shows the UTF-8 display text "http" next to a retained binary descriptor, so the producer did not supply the discriminator mitmproxy defines. Treat the flow as HTTP only when /type has no numeric sidecar and no binary descriptor at or above it.
* fix(web-capture): refuse mitmproxy headlines under byte-valued ancestors
The mitmproxy bridge emits either a byte descriptor for a value or children for a dictionary, never both. Treat a byte descriptor on an ancestor like a numeric sidecar, and refuse a byte descriptor on an integer headline, while keeping the exact descriptor of byte-backed string fields such as the request path.
* fix(web-capture): check child facets and byte provenance of mitmproxy headlines
A facet beneath a scalar coordinate says the producer held a container there, so refuse the flow type or a headline field with a numeric sidecar, byte descriptor or redaction below it. A string headline may carry at most one byte descriptor, and only retained mitmproxy producer bytes whose content is the value's UTF-8; HAR content bytes or mismatched content keep it out.
* fix(web-capture): verify the byte count and digest of byte-backed headlines
The record schema checks a descriptor's length and digest shapes, not their relation to its content. Require a byte-backed string headline's descriptor to match the value's UTF-8 in content, byte count and SHA-256 before promoting it.
* fix(web-capture): reject byte-backed headlines without a UTF-8 form
Buffer.from replaces an unpaired surrogate with U+FFFD bytes, so a descriptor for those bytes matched a string no producer could have decoded. Require the value to round-trip through UTF-8 before comparing it with its descriptor, as CaptureRedaction already does.
* fix(web-capture): index mitmproxy facets once and refuse non-UTF-8 targets
Reject host, authority and path text containing an unpaired surrogate whether or not a byte descriptor backs it, since no producer could have reported such text. Build a per-record index of facet pointers (sidecars and descriptors by pointer, scalar pointers, container ancestors, withheld values and omitted names) once, only for mitmproxy HTTP records, so each headline check is a few set lookups instead of several passes over every facet array.
* fix(web-capture): bound the mitmproxy facet index to headline coordinates
Facet pointers have no length or depth bound, and recording every ancestor kept one set entry per prefix (a million for a 1 MB pointer). Record only the fixed headline coordinates (/type and the request/response target and status fields) that have a facet beneath them, examining prefixes no longer than those coordinates.
* fix(web-capture): index only facets at headline coordinates and their ancestors
Grouping every numeric sidecar and byte descriptor allocated an entry per facet even though only /type, the request/response headline fields and their ancestors are queried. Keep facets only at those pointers (at most two per pointer, which already decides every check), and let other facets merely mark the headline coordinates they lie beneath, using prefix comparisons that derive no strings.
* test(web-capture): pin headline omissions and controls beside many unrelated facets
The bounded facet index must not change what a mitmproxy preview withholds or keeps. Beside 20,000 unrelated sidecars, a redacted host and a path with a facet beneath it stay withheld while the scheme, port and status remain, with only their own sidecars retained. The test passes against both the current main projection and this change.
* test(web-capture): count long set additions in the million-character pointer tests
The two million-character pointer tests had no time or size bound, so on
Node 22 and 24 they passed against the old ancestor index too, and only
Node 26 caught a regression by running out of memory. Count the strings
longer than 1,000 characters the preview adds to sets instead: only the
pointer and strings cut from it are that long, the old index added
999,000 and 499,507 of them, and the new index adds none.
---------
Co-authored-by: Lucien <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
inspect-python-pyc and inspect-erlang-beam took their max-* limits through Number(), so 0x10 meant 16 and 1e2 meant 100. Admit decimal text only through cliDecimalField, which now keeps the shared field's description for help.
* fix(native): keep inspect_macho usable when dyld_info refuses the slice
dyld_info exits 1 with 'unknown platform' for Mach-O files without a
platform load command and with 'old arm64e bind opcodes not supported'
for pre-ABI arm64e binaries. inspect_macho then failed outright with the
generic execution_failure remediation although nm, otool and the load
table still describe the slice. Treat these two refusals like the
missing-UUID refusal: imports are reported unavailable, exports keep the
nm observations, and the dyld_info diagnostic is recorded as a limitation.
* test(native): cover dyld refusal fallback and limitations
* fix(decompile): keep the output failure streak across external functions
An external function writes no file, but the streak counter treated its outcome as a success and reset to zero. With external functions interleaved between unwritable local functions, decompile_all never reached the documented five-failure stop. The streak now ignores external outcomes.
Fixes#7374
* style(decompile): restore the repository formatter output
oxfmt --check rejected decompileAll.ts after a local prettier pass reflowed an unrelated type alias. Restore it so the diff is only the streak change.
* ci: retrigger after artifact-download ECONNRESET
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
---------
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
* test(process): align symlink fixture with filesystem checkpoints
* test(frida): assert the CLI spawn Evidence contract
* test(mcp): use an unsupported analysis-view parent
* test(browser): separate source-location line bounds from parity
* test(desktop): make Python output fixtures portable
* test(process): align symlink fixture with filesystem checkpoints
* test(frida): assert the CLI spawn Evidence contract
* test(mcp): use an unsupported analysis-view parent
* test(browser): separate source-location line bounds from parity
* fix(cli): stop completion after the option terminator
inspect-desktop-application took --max_entries and --max_probe_bytes through Number(), so 0x1 examined one entry and 1e1 ten. Admit decimal text only and keep the shared fields' bounds and defaults.
inspect-gpu-code-objects declared --expected_sha256 as any string. The CLI schema accepted an upper-case digest, as PowerShell's Get-FileHash prints it, and the command then refused it as invalid_request. Reuse the shared lowercase SHA-256 field so argument parsing refuses it with the CLI field name.
* test(cutter): synchronize peer descriptor close before cancellation
* test(process): align symlink fixture with filesystem checkpoints
* test(frida): assert the CLI spawn Evidence contract
* test(mcp): use an unsupported analysis-view parent
* test(browser): separate source-location line bounds from parity
* feat(harmony): disclose the unread pkgContextInfo.json declaration
A package that ships pkgContextInfo.json shows the entry in
unclassified_components, yet nothing said its isSO declarations were
skipped: bridge_candidates came only from the libs/<abi>/lib<name>.so
path rule. State that gap in limitations when the entry is inventoried,
so a path-convention hypothesis is not read as a package declaration.
The declaration-parsing half of #4489 needs a decision about who owns
reading inventoried file content; this projection receives identity and
digests only, and the shared inventory carrier guard rejects other
Evidence. No content is parsed here.
* test(harmony): assert the disclosure by its names, not its sentence
docs/testing.md requires asserting structured facts instead of freezing an
explanatory sentence, and says a replaced prose assertion must still pass on a
harmless wording change while failing when the fact is lost. The first version of
this test compared the whole limitation string verbatim, so any rewording of the
disclosure would have failed CI.
The assertions now require exactly one limitation naming pkgContextInfo.json,
carrying bridge_candidates, isSO, and the not-read/not-narrowed fact, and keep the
structural claim that the same package without the declaration file yields
identical bridge candidates and no such limitation.
* fix(desktop): launch spaced REA archives and Electron apps
Link the unpacked REA entrypoint to a fixed path so a top folder with
spaces is not split by the generated rea launcher. When a build request
marks the app as Electron, rewrite its installed desktop command with
the export path's launch policy before gtk-launch.
* fix(desktop): escape rewritten Exec values for desktop files
Command quoting alone left literal backslashes one escape short of the
desktop-entry value layer, so gio rejected the rewritten entry. Escape
the command with the same rules parseDesktopEntry undoes before writing
the Exec line.
---------
Co-authored-by: Cursor Agent <[email protected]>
State the existing discovery page and exact-name bounds using the validation constants. Isolate account apps and plugins in catalog trials by default, with an explicit opt-in and one shared feature policy for config and receipts.
* feat(javascript): compare static endpoints with HAR captures
* test(javascript): preserve original HAR fixture bytes during formatting
* test(javascript): load the byte-preserved HAR fixture
* feat(desktop): keep an agentenv computer up and drive it through REA's MCP tools
- A Python helper keeps one agentenv computer up per build with
run_bundle(keep=True) and reports its address, MCP URL, control token
and sandbox id; it tears the computer down when stdin closes.
- AgentEnvComputer owns the helper, waits for readiness, holds one MCP
session to the computer's computer_* tools, records through the
computer-recording routes, and removes the sandbox's containers by
label when agentenv cannot confirm teardown. The token never leaves it.
- start_build_computer starts a computer from a request (archive package,
apt repository or .deb, opened on demand with REA in /opt/rea) or from
a bundle folder; act_on_build_computer forwards screenshot, click,
drag, scroll, type, key and wait and returns the driver's images
unchanged; close_build_computer tears it down.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): remove a build computer's container by name when agentenv cannot confirm teardown
agentenv's local provider runs an ordinary computer as agent-<sandbox id>
without the agentenv.sandbox label, so the label-only fallback found
nothing and reported a running computer as removed. The fallback now
lists containers by exact name and by label. The real test holds its
recording open long enough for a first frame, checks the container by
name, and runs rea inside the computer; the guide and tool descriptions
say the app opens from the Applications menu and that a screenshot after
opening a menu should come from a wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): own failed build-computer starts, keep ids past oversized screenshots, bound recordings
- A start is owned from the helper's spawn. One that fails and whose
teardown is unconfirmed stays held under a computer_id close can retry;
closeAll refuses new starts, cancels and joins starts in progress, then
closes every held computer.
- When the first screenshot is too large, the error carries REA's result
with the computer_id, and start_build_computer takes screenshot region,
format and quality.
- Recordings are refused before reading when the manifest's total exceeds
the allowance, then fetched one segment at a time, each read bounded and
checked against its size and sha256.
- The helper keeps the tail of its output (a new tail retention mode in
ProviderProcessSupervisor), so a build's own error is not crowded out.
- A helper that reported no computer exits on its own before SIGTERM, so
no phantom owner is kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): bound a build computer's action log
Every action kept its full arguments for the computer's whole life, so a long
session grew REA's memory without limit. The log now keeps the latest 256
actions within 256 KiB, cuts each string to 1 KiB and each list to 64 items
(naming what it cut), and keeps the total and the last applied index of each
kind of action, so rules over the whole session hold after entries drop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
RFC 5280 4.1 defines an Extension as extnID, an optional critical
BOOLEAN and an extnValue OCTET STRING. Section 4.2 says a certificate
MUST NOT include more than one instance of an extension.
The decoder only checked that extensions were a SEQUENCE. Any entry,
and any repeat, was accepted.
adb prints the no-permissions state as "no permissions[ (reason)]; see [URL]"
and omits the reason when it finds no udev problem to name, for example on a
Linux host without a plugdev group. The state matcher required whitespace after
"no permissions", so that line counted as unrecognized and list_adb_devices
refused the whole listing, hiding every other attached device.
Accept ";" after this one state. Other states still need a whitespace or
end-of-line boundary.
* feat(desktop): capture a Linux desktop application in use
Add `capture-desktop-application`, a CLI-only, Linux and X11 capture that
launches an installed desktop application, watches it while it is used and
writes capture Evidence plus SHA-256 addressed content:
- inventory: desktop entry, launch command, executable, package and its
apt sources (or snap, flatpak, AppImage), Electron markers and candidate
state directories;
- session: the launched process tree, client windows from xprop with the
first application window time and a window match, and files held open;
- state: a stat walk of the home directory before launch, at checkpoints
read from stdin, at stop and after exit, with created, modified, deleted
and retyped paths and the content of changed files and state directories.
It refuses non-Linux hosts, missing DISPLAY or xprop, a non-empty output
folder and an application whose window is already open, and signals only
processes it started.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): own launched processes, confirm walk coverage and keep cancelled captures
- Launch through the owned provider path with a run token; release with
SIGTERM, then the token-verified group cleanup, after every session.
Processes without the token are never signalled; each records whether it
carries it, and the session records whether cleanup was verified.
- Report creations and deletions only where a walk listed the nearest
existing directory; count the rest as unconfirmed.
- Retain a file only when it is still the walked version, through owned
no-follow reads; charge the budget only for new content; create files
exclusively in a private output folder owned by the caller.
- Walk content roots outside the home directory, compare paths in their
physical spelling, and run executables from the caller's directory.
- Refuse when the window list cannot be read, re-evaluate window ownership
each sample, handle one queued request per sample with a bounded stdin
queue, and write the capture with end reason `cancelled` on interruption.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep owned cleanup failures and fresh digests
- Allow a capture cancelled before any requested checkpoint to hold only the
baseline.
- Retry an incomplete stop once; if cleanup is still unverified, write the
capture and fail with the run token and process group as cleanup
resources.
- Classify path changes after a walk (ENOTDIR and other selected-file
errors) as changed_since_walk instead of failing the capture.
- Treat a non-directory ancestor as proof that nothing existed beneath it.
- Report a before digest only for the file version it was read from.
- Derive stderr_truncated from stderr's own byte counts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* wip(desktop): state view over a capture folder
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep failed file-handle closes owned for retry
Content reads pass retainFailedClose, so a handle whose close fails stays
owned by the content store; the capture retries those closes after it is
written and fails with the output folder as the cleanup resource if one
still cannot be closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): read a captured application's state
Add inspect-desktop-application-state, a CLI command that reads a capture
folder into the application's state: every retained or changed file with
its role and reasons, a typed JSON view of each version (SQLite through the
shared worker, dconf through a private dconf dump, JSON, XML, key files,
text) and the kinds of change between versions (lines, values, keys, rows).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report process and handle cleanup failures together
When both the launched processes and a retained file's handle remain
unresolved after the capture is written, one cleanup error now lists both
reasons and both resource sets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): export a captured application as an agentenv computer app
Add export-desktop-application, which writes an agentenv bundle with one
computer_app environment from a capture folder and its state view: the
apt package, a boot launch matched on the captured window class with a
window ready check, owned paths from the state roles, a seed of the
baseline files, data hooks that call agentenv's add/get/reset helpers
with a typed JSON view on get, a skill, a README and a deploy smoke task.
The desktop CLI commands now load their services lazily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep state views faithful to the capture
- Read key files, XML attributes, dconf groups and SQLite tables into
dictionaries without inherited properties, so captured keys such as
__proto__ stay data.
- Version databases with their write-ahead log as of each checkpoint, and
stage only digest-verified bytes for the SQLite and dconf readers.
- Detect the format per version, report format changes, keep JSON null and
empty containers as values, keep XML text and preserved whitespace, and
mark truncated views (including XML depth) and comparisons of them.
- Number versions and changes by checkpoint index, decide baseline
presence from the captured type, and keep tagged SQLite integers, text
bytes and blobs distinct.
- Carry the capture's limitations and omitted content forward, match state
directories by their home path, index changes once per capture, keep
failed handle closes owned, and finish the CLI through the shared owner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep coverage, logs and handles honest in the state view
- Read coverage flags only from formats whose readers write them, so JSON
fields named truncated stay data.
- Fail a SQLite version whose write-ahead log existed but was not retained
or does not match its digest, instead of reading the database alone.
- Close retained content on every exit, including cancellation, and report
a close that still fails together with the primary failure.
- Finish the CLI with the canonical operation name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export only what the computer can install and seed
- Refuse packages not from the Ubuntu 24.04 archive, since the computer
installs by name from that archive.
- Start the launch command in the captured working directory.
- Respell the home path only in files the state view read as text.
- Report files that were not retained instead of dropping them silently.
- Load captured dconf keys for apps that own no files.
- Keep home-level files as owned paths; rename reserved Python class names.
- Mark truncated SQLite and text views in the class's get view, and read
file types the way the state view does.
- Release the output tree through a resource scope, and read seeds through
an owner closed on every exit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep staged folders owned, symlinked state roots and XML values
- Stage database bytes in a private runtime root released through a
resource scope, so a failed removal keeps its owner and is reported with
the read's outcome.
- Report cleanup facts and owners from desktop failures instead of
wrapping them as a plain adapter error.
- Record the state directories as walked, in their physical spelling, and
match roles against both spellings, so files in a symlinked state
directory are not mistaken for selected documents.
- Keep whitespace-only XML leaf values; drop only formatting between
elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read XML in the exported app the way the state view does
Keep whitespace-only leaf values and xml:space="preserve" text in the
generated get view; drop only formatting between elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): release retained handles with their owners, read older captures
- Release retained-content handles whose close failed through a resource
scope on every exit; a release that fails again reports the exact handle
owners. A read that finished keeps its bytes when only its close failed.
- Accept captures written before state directories were recorded, and say
in the state view's limitations that symlinked state directories may be
misclassed for them.
- Compute each XML element's whitespace context once instead of scanning
siblings for every text run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): boot without the working folder, bound XML, accept mirrors
- Try the captured working directory only when an argument is relative,
and start the app even when that folder is missing.
- Cut the generated XML view at REA's node and depth limits, and keep one
unreadable file from failing data/get.
- Accept Ubuntu 24.04 mirrors, not only the canonical hosts; PPAs and
other repositories are still refused.
- Read seeds through the retained-content owner's new signature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): state view review round
- Stage a retained -journal beside its database so the SQLite worker
accepts an inactive journal and refuses a hot one; a -wal or -journal
that existed but was not retained (including one omitted at the
baseline) makes the version unreadable.
- Keep tables that read when another fails, and let cancellation and
unfinished cleanup propagate instead of becoming view errors.
- Read text as a key file only when every line parses as one.
- Rank a file named in a window title or selected by the caller as a
document even when the application holds it open; match titles on
file-name boundaries; leave the display name out of dconf matching.
- Bound JSON and key file views (4 MiB, 500 levels, exact numbers) and
report too-large, missing and altered retained content separately.
- Add no version or change while a database stays absent.
- Read staged databases in place instead of copying them again.
- Give Evidence the same limitations as the result.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export review round
- Load captured dconf keys without D-Bus: install dconf-cli, compile the
existing and captured key files into the user database, and warn on
failure instead of stopping the server.
- Start relative-argument launches in the captured working directory,
the home included, since the framework launches from the app folder;
respell the home itself and option values under it.
- Read views in a thread, skip files over 64 MiB, align the generated
readers with the state view's, and close SQLite connections.
- Never own the home or a shared base folder; map a symlinked state
directory's files back to their home spelling.
- Report omitted files and unretained opened folders.
- Name the module so it cannot shadow another, and keep the class off
the module's own names; derive names the bundle check accepts.
- Write seeds as they are read, and remove an unpublished bundle on
failure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): capture review round
- Ask the launch process group and token carriers to exit before
killing them, on errors too; report incomplete cleanup with its facts.
- Count descendants of the launch as started by the capture even when
their environment cannot be read; treat a reused pid as a new process.
- Follow symlinked desktop entries; name a flatpak's state after its
application id; match flatpak windows by name only.
- Stop on end of input; refuse empty, duplicate and reserved checkpoint
names; require whitespace after the checkpoint keyword.
- Stay on each walk root's device, and never walk or retain `/` or a
directory containing the home; say so in the limitations.
- Refuse a window manager that does not publish _NET_CLIENT_LIST.
- Remove retained content when a capture fails before it is written.
- Skip reading a file larger than the remaining content budget.
- Record apt's archive origin for each package source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read an unset client list as no windows when supported
xfwm4 leaves _NET_CLIENT_LIST unset while it manages no windows, so the
capture refused an ordinary desktop with nothing open. Read
_NET_SUPPORTED with the list: an unset list means no windows when the
window manager supports it, and the capture is refused only when it
does not. `xprop`'s "no such atom" output now reads as unset too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): accept packages by apt's archive origin
Accept a package source when apt reports its origin as Ubuntu for a
noble suite, so mirrors count and PPAs or other repositories that merely
have "ubuntu" in their URL do not. Captures made before origins were
recorded fall back to Canonical's own archive hosts. Seed reads report
why retained content could not be used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps from vendor apt repositories
A closed-source app installed from its vendor's apt repository, such as
Typora, can now be captured and exported as a computer app.
- The capture records each non-Ubuntu repository listing the installed
version (uri, suite, components from apt's sources files, one-line or
deb822) and the key that signs it: the signed-by key, or the trusted
key that gpgv shows verifies the repository's signed index. The key
is retained with the capture.
- The capture records the packages owning the libraries the executable
loads (DT_NEEDED with RUNPATH and the linker cache, through REA's ELF
readers), since a vendor package may not depend on them.
- The export declares a verified vendor repository as an AptSource with
its key, installs a vendor package's library packages, and adds
--no-sandbox to an Electron app's launch, since a container denies
Chromium's sandbox.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report a locked database at once in data/get
A database the running app holds locked made each data/get wait sqlite's
five-second busy timeout before reporting it. Record the Typora run in an
agentenv computer in the verification.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): detect key files without splitting the whole text
Read lines one at a time and stop at the first that a key file view
would drop, instead of splitting a file of up to 64 MiB into arrays
before its format is known.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep symlinked homes and state folders under the home in exports
- Respell a path under its state folder's logical spelling only when that
spelling is under the captured home, so a symlinked HOME keeps its
files; pair walked and logical state folders only when the capture
lists the same number, and match the innermost folder first.
- When two files map to one home path, keep the one present before the
capture and name the other in the limitations.
- Read key files in the generated data/get with the state view's rule,
checked against it by running the helper under python3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): end captures on zombies and keep cleanup owners
- A zombie or dead process no longer keeps the session open.
- Remove only the partial capture file this run created.
- Keep the exact owners of handles and processes whose cleanup failed on
the returned error, so a retry can release them; test the published
capture's cleanup-failure branch with mixed outcomes and retries.
- Release the application once even when the exit checkpoint fails.
- A cancellation before the capture is written removes what it retained.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): vendor package review round
- Match apt sources through `apt-get indextargets`, which covers flat `./`
and `/` suites and gives each release file and sources entry.
- Accept a key whose signature verifies even when the release carries
another signature without a key here; keep only the verifying key from
a keyring holding several (OpenPGP packet splitter).
- Install the package from its repository's release (`name/<release>`),
declare one source per URI and suite, support only the capture host's
architecture, and refuse Ubuntu Pro repositories.
- List only library packages the computer can install; walk libraries only
for vendor installs, with RPATH inherited as ld.so does, normalized
paths, and DT_NEEDED bounded while reading.
- Put --no-sandbox after the program an env wrapper launches; keep the
gpgv folder's owner when its removal fails; respect --max-file-bytes for
keys; wait 0.25 s on a busy SQLite database in data/get.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): keep the vendor spec's gpgv command on one line
A code span broken across lines left `<binary keyring>` for VitePress to
read as an unclosed HTML tag, so the docs build failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): add a validated task to exported desktop applications
Each capture can now yield one agentenv task in its exported bundle. An
agent writes the task (a prompt plus declarative checks over the app's
data/get views); REA validates it and stays model-free.
- `describe-desktop-application-task` summarises a capture's net change
per file (role, format, views before and after) and drafts checks.
- Checks: file presence, lines present/absent/counted, JSON-pointer values,
SQLite rows and counts. REA keeps a task only if a check fails on the
seed and every check passes on the captured end state; a file that was
not retained at the baseline counts as absent, as the computer starts.
- `export-desktop-application --task` writes tasks/NAME.json (deploy,
agent, skills, recording, prompt, verifier), a compiled verify.py that
reads data/get, and an eval. A boundary test runs the verifier under
python3 and checks it agrees with REA's evaluator on every check kind.
- Docs, spec and skill guidance, with the solve-rate recipe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the seed test in its own block
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task verifier driven in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the Typora task run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep task checks bounded, complete and attributable
- line_count counts lines holding a substring (contains) instead of
matching a regular expression, which could stall the verifier on
agent-written text and read differently in Python.
- value_equals of a whole object or array is undecided on a truncated
view.
- The draft keeps a database whose rows changed under an unchanged hash,
and a file whose truncated views match while it changed.
- A task file that cannot be read keeps the reader's detail, and a failed
cleanup keeps the read failure as its primary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): leave a file with one version out of the task draft
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): update the task verification's test counts
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task solve rate with Claude Code and Sonnet 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps installed from a downloaded .deb
capture-desktop-application --package-file reads the file's control fields
with dpkg-deb and records its SHA-256, size and dependencies when it holds the
installed package; the file itself is never kept. The library walk now also
runs for local installs.
export-desktop-application --package-url declares agentenv's
Download(url, sha256) for that file, its dependencies and library packages as
apt packages, and a run script that installs it with dpkg.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): seed app-named dotfiles and respell UTF-16 seeds
The capture now treats dotfiles in the home named after the application
(.varicad-cfg.ini, .notesrc) as state roots, so a file the app only reads
during the capture is still seeded. The exported seeding respells the
captured home in UTF-16 text files with a byte-order mark, keeping the mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): hold readiness until the app's own window shows
An exported app is ready only once a window of its class that the taskbar
lists is shown, so a splash screen of the class, which skips the taskbar, no
longer counts as the app being up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record VariCAD captured, exported and run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep exported views strict JSON for infinite and inexact numbers
The exported data/get spells infinite and negative-zero SQLite reals as REA's
SQLite reader does ("Infinity", "-Infinity", "-0"), and refuses a JSON file
holding a number that reading would change (1e999, integers past 2^53, -0,
rounded fractions) with the state view's own error, instead of a view with
an infinity in it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep package-file facts tied to one file and URLs unechoed
- A --package-url refusal never repeats the URL, and credentials are checked
before the scheme.
- The package file's identity must hold between dpkg-deb's read and the hash.
- A failed close keeps its cleanup owner even when the capture is cancelled.
- Readiness also ignores windows typed as splash screens.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): record database logs and keep state changes distinct
- A selected path that is not a directory brings its -wal and -journal as
content roots, so the capture records whether they exist and retains them.
- The state view treats a file as absent only under recorded content roots
and refuses a database whose log presence is unknown.
- JSON changes list containers too, so an array that becomes an object with
the same members is a change.
- Key file entries escape the key as a JSON pointer segment, so [a/b] c and
[a] b/c stay apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep watching through unread samples and late workers
- An unreadable window list is unknown, not zero windows, and is named in
the limitations.
- Before ending the session, live run-token carriers no parent link reached
are tracked; an unreadable process table leaves the exit unknown.
- An env prefix's assignments find the program and name its XDG folders.
- flatpak run options that take a value are skipped with it, -- is honoured
and a /arch/branch suffix is dropped from the id.
- State directories include the entry's full id.
- Path options refuse empty values; roots count toward the entry limit.
- A capture file whose close failed stays owned and is retried on discard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): bound apt sources, keep older captures and cleanup owners
- Sources files parse into one stanza per line or deb822 paragraph with its
URIs and suites kept as lists, so no URI x suite product is built.
- Repository architectures and release are optional, so captures written
before they were recorded parse; the export treats them as unrestricted
and unknown.
- Library sources are matched against every index of a declared
repository's URI, suite and components.
- Two failures side by side keep both causes and both cleanup owners, and a
host file whose close fails reports a lease on its handle.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): match escaped key file entries and suggest containers through members
- The draft's key file lookup uses the state view's escaped `group/key`, so a
key holding `/` or `~` still gets its suggestion.
- JSON and XML changes now list containers too; the draft suggests an added
or removed container through its listed members, and a container only
when none of its members is listed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read relative PATH entries from the launch folder
A program, or an empty or relative PATH entry, is resolved from the folder the
program starts in, as the launch does, so `env PATH=. app` with the entry's
Path still finds the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): compare JSON views on one shared path
JSON and XML changes are found by walking both trees with one shared pointer
path, building a pointer only for a listed value, so memory follows depth
rather than every container's full path. A pointer over 4096 characters is
counted but not listed. A 256-level document with 4000-character keys went
from about 251 MiB of heap to under 1 MiB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep JSON Lines as text in the exported reader
The exported reader parses the whole text before an inexact number makes it
JSON with an error, so JSON Lines with an integer above 2^53 stay text as in
REA. Over-long integer spellings are unsafe without converting them. The
guide no longer claims large integers keep their value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep the state-path help free of dash-led words
The CLI alias check reads `-wal` and `-journal` in help text as short flags,
so the help names the write-ahead log and rollback journal instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never record or export apt source credentials
- Source URIs lose any user name and password as they are parsed, the way apt
splits them, and so do index-target URIs and descriptions and the sources
apt-cache policy lists, so matching works whether or not apt prints them.
- A repository whose entry held credentials is marked
`uri_credentials_removed`, and the export refuses it, or an older capture
whose URI still holds them, with a reason that names no URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up without normalizing their paths
Program paths and PATH entries are joined rather than resolved, so `..` after
a symbolic link means what it means to the launch. The lookup moves to its own
module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): note inexact JSON numbers with a flag, not a list
The exported reader only needs to know whether any number was inexact, so it
keeps a boolean instead of every rejected spelling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): refuse older captures that still hold apt credentials
A capture written before credentials were removed counted the credentialed
repository's libraries as installable, so the export refuses it whole and asks
for a new capture rather than declaring packages no source offers. Recorded
policy sources are redacted in export messages too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the credential fixture on an example host
A login on a real host reads as a secret to the commit scanner, so the older
capture's fixture uses apt.example.com like the other credential tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): tidy looked-up paths and search relative entries only for env
Empty and `.` segments are dropped from a looked-up program path, which keeps
`/snap/bin/` entries recognisable as snaps, while `..` is still left for the
kernel. Relative and empty PATH entries are searched only in the PATH an `env`
prefix sets, which travels with the exported launch; the host's own PATH does
not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep excluded and unwalked database logs unknown
The capture records the paths every walk left out, and the state view treats
a log as absent only under content roots the baseline walked completely and
outside those exclusions, so an excluded or unvisited log leaves the database
unread rather than read without it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): judge JSON numbers after duplicate keys settle
The exported reader marks an inexact number instead of flagging it during the
parse, then looks for marks in the parsed value, so a number a later duplicate
key overwrote no longer refuses the file, as in REA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): redact a repository's policy source when it names it
An older capture whose policy source matched no sources entry kept that text
whole, and the export named the repository by it; the export now names it
without any user name or password.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never signal the launch group on unknown membership
When the current read cannot observe a carrier's process group, the carrier
is signalled on its own instead of letting an older process-table row grant
a group-wide SIGTERM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up in the launch's order and refuse host-relative finds
The lookup searches PATH as the launch does, an empty entry meaning the
launch folder, so inspection and launch name the same program. A program found
only through a relative or empty entry of the host's own PATH is refused,
since the exported computer would not have it; an env prefix's PATH travels
with the command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): count unchecked paths and keep logs behind them unknown
Each walk counts the paths it could not stat and the directories on another
device it did not enter, and the checkpoint records it as `unchecked_paths`.
The state view treats a log as absent only when the baseline left nothing
unchecked, so a log whose stat failed stays unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): scan parsed JSON with one iterator per level
The check for marked numbers walks the parsed value with an iterator per
nesting level instead of copying each container's children; a million-entry
array went from about 15.7 MiB to 8.1 MiB of peak traced memory.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): start a relative program from its captured folder
An exported launch whose program path is relative, such as `./app` from an
entry's Path, now changes to the captured working directory first, as it
already did for relative arguments.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): read zipped documents and name XML elements in task checks
- A file that starts as a zip archive gets a `zip` view listing its entries in
archive order, each with its own format and view, within limits on entries,
bytes and expansion; the exported Python reader matches it.
- A pointer segment applied to an array may be a selector, `[KEY=VALUE]` or
`[@KEY=VALUE]` for XML attributes, matching exactly one member, so checks
name elements instead of positions that shift between saves and versions.
- `value_equals` takes an optional numeric `tolerance`, comparing numbers and
numeric strings.
- Changes inside zip views are listed by selector, and the task draft suggests
name-addressed checks.
FreeCAD's `.FCStd` is the first case: a hole cut into a plate is verified from
the zipped `Document.xml` on files from both FreeCAD 0.21 and 1.0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): record the desktop entry's package and app profiles in any case
- A launch from a desktop entry records the package that owns the entry, so
the export installs the app the entry starts (Writer, not only
libreoffice-common); the program's own package is kept as
executable_package when it differs.
- State folders match the app's names ignoring letter case, as direct
children of the XDG folders and the home, so GIMP's ~/.config/GIMP profile
is seeded and reset; roles follow the same rule.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): read namespaced XML alike in REA and the exported verifier
The exported Python reader now names XML elements and attributes as the
document spells them (svg, text:p, sodipodi:docname, xml:space), with
namespace declarations kept as attributes, which is what REA's view already
produced. Export therefore accepts checks into Inkscape SVGs, LibreOffice
content.xml and namespaced XML inside zips, and the draft suggests them.
expat replaces ElementTree: one pass with namespace processing refuses what
REA's parser refuses, and a streaming pass builds the view within the same
node and depth limits. Text splits at CDATA, comments and processing
instructions as REA's DOM splits it. Fixtures are real Inkscape 1.2.2 and
LibreOffice 24.2 saves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): install the program's own package and keep unknown owners unknown
- When the desktop entry's package does not own the program it runs, the
export installs the program's package too. The capture records why it
cannot (executable_package_reason), and the export then refuses.
- The export owns a state folder in the letter case the app wrote it, as
the state roles already did, so data/get and data/reset cover a profile
folder the app created during the capture.
- A dpkg-query that fails or exits 2 or higher leaves the package unknown
instead of reading as "no owner".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep selector checks undecided on partial views and pair members one to one
- A selector check is undecided while an array it searched may be missing
members: a cut zip listing, or a truncated XML or entry view. Both
evaluators agree.
- Diffs pair the members of two versions one to one, so two new elements
can no longer both match the same old one and hide an addition.
- The task draft descends into JSON values and readable entries added
whole, and never suggests an element by its position, last step included.
- The export's namespace refusal also checks XML entries inside the value a
pointer names, such as the whole listing.
- The exported reader keeps REA's 500-level JSON depth limit, so one deep
entry no longer hides the rest of the archive, and treats an entry whose
data runs past its declared size as damaged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): check approved packages against the repositories the export keeps
The capture approved library packages and the program's package against
every repository with a verified key, but the export can still leave a
repository out: its key dropped by the content budget, an architecture
restriction, or a conflicting key. Each repository now records the packages
only it supplies (`supplies`). The export drops stranded library packages
with a limitation, and refuses when the program's own package is stranded.
Captures without `supplies` behave as before.
The install limitations move to appExportInstallLimitations.ts to keep
appExportInstall.ts under the line limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): compare type changes in place, keep empty values and unread entries honest
- When an array becomes another type, its members are compared with what
their pointers name there, so values that are still present no longer
look removed.
- The draft checks an added empty array or object by equality.
- The exported reader's JSON depth check keeps one iterator per level and
copies no wide list, back to the memory peak before the check.
- An entry whose view could not be read is no `null` value: the draft
suggests no check on it, and a check into its view fails in both
evaluators with the read error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): decide entry view checks only on entries with a view, and walk JSON depth by level
- A check into an archive entry's view needs an entry read in a format
with a view. A binary or unread entry's `view` of `null` no longer
passes a `null` or absence check, in either evaluator, and the draft
suggests no absence inside an entry that ends without a view.
- REA's JSON depth check keeps one iterator per level instead of queuing a
pair per member, so 800,000 nulls need 18 MiB instead of more than 64.
The helper came with the state view; its Python twin was fixed here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): refuse an export whose desktop entry runs a program no package owns
When a package owns the desktop entry but no package owns the program it
runs, such as one in ~/.local/bin first on PATH, the capture keeps the
entry's package and records why the program cannot be installed. Export
refuses instead of installing a package that does not bring that program.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): bound zip entry reads, export metadata checks, and diff by the evaluator's pointer rules
- The exported reader inflates a zip entry at most 64 KiB at a time and
stops a byte past its declared size, so a forged small size no longer
holds the whole stream in memory (77.5 MiB to 0.1 MiB on a 32 KiB bomb).
- The namespace refusal applies only to checks that depend on an entry's
XML, so checks on an entry's name, size or digest, or on its absence,
export.
- Diffs find a member's counterpart by the evaluator's pointer rules, so a
selector step across a type change no longer reports a present value
removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read at most 1,000 zip directory records, and index arrays once per diff
- Both zip readers now read the central directory a record at a time,
keep the first 1,000 file records and count the rest, then hand the
library an archive whose directory holds only those. Directories are
told apart as zip.js does, so the Python reader stops listing
attribute-marked directories as files. 600,000 entries now peak at
1.3 MiB in Python (was 293.6 MiB) and need 24 MiB of heap in REA.
- The exported Python zip reader moves to appExportPythonZip.ts.
- Diffs look members up through an index built once per compared array,
so 20,000 selector keys against 20,000 members take 31 ms, not 5.4 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): pick seed and end by checkpoint, and keep the capture host out of bundles
- Export, task export and the task draft take --seed-checkpoint and
--end-checkpoint, so one capture can make the seed in the app and then
the edit; the defaults stay the baseline and the last version.
- Capture reads apt's lists folder and trusted keys from apt-config, so
a user-space apt-get update under APT_CONFIG is found, and starts the
app without AGENTENV_* variables.
- Capture records the host name and user. Export rewrites user@host, the
host name as a whole name and the captured home in text, UTF-16 and
dconf seeds, ships SEED_HOME=/home/user instead of the capture user's
home, lists scrubbed and unscrubbable seeds, and refuses checks that
name the host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): check tasks against the shipped seed and scrub JSON and XML by value
- Task validation reads the seed as the bundle ships it, after the scrub,
so a task whose end value the scrub writes into the seed is refused.
- JSON strings and XML text, attribute values, comments and CDATA are
scrubbed as their decoded values and rewritten only when they change,
so syntax stays valid and escaped identities are found.
- One leftover search covers every shipped file's path and contents,
generated module and dconf keys included; the strict whole-name rule
is kept for replacing only.
- A host named localhost is never replaced, but user@localhost is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): resolve a record's selector keys together, and follow Zip64 locators without sentinels
- The diff resolves all of a record's selector-shaped keys in one lookup:
conditions are normalized per field, selectors grouped by field set,
and only the values asked for are kept, so retained memory grows with
the keys, not members times field sets (68 MB to 5.5 MB, 76 MB to 4.9 MB
on the reported cases).
- Both zip readers follow a Zip64 locator just before the end record
whatever the classic record holds, as zipfile does, so valid Zip64
archives with ordinary classic values read again. The exported
module's synthetic tail carries a real Zip64 end record, so bytes that
only look like a locator no longer make zipfile refuse it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): scrub JSON values only, with a scanner whose stack does not grow with string length
JSON seeds are walked one string at a time, finding each closing quote
with indexOf and a backslash count instead of a backtracking regex, so a
16,000,000-character string no longer overflows the stack. Member names
are never rewritten, so two keys cannot collide; a name holding the host
leaves the file in unscrubbed_files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): scrub dconf and UTF-16 seeds by format, search check values for the host, and gate checks on the selected ends
- dconf: the host scrub ran over the whole key file, so on a host named
`true` `wrap=true` became an invalid `wrap=localhost`. Only decoded GVariant
string values are scrubbed now; keys, groups, bytestrings, dictionary keys
and object paths ship as captured and are reported.
- Task identity: the host search read the serialized check, whose field
names (`file`, `kind`) and default ids matched hosts of those names. It now
reads the check's string values only.
- Value checks: the text-file refusal read every recorded version; it now
reads only the shipped seed and the end.
- UTF-16 seeds: the plain scrubber renamed JSON keys and XML names; the
decoded text's format now picks the scrubber, keeping the byte order mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): pair members by identity first, build selectors lazily, and refuse oversized and deep text before building a view
- Pairing: an element with an identifier (name, id, xid, *:id) pairs only by
it, so an unchanged id still names an edited element instead of a removal
and an addition, and two objects of one type are never paired as a rename.
Selectors keep the tag apart from each attribute condition and spell only
the chosen one, so a long tag with many attributes no longer multiplies.
- Size: REA's detection and the exported reader decide JSON with a scanner
that builds no value, and refuse a view over the parsed-view limit before
building it, in files and zip entries alike.
- Deep text: the exported reader confirms the whole text is JSON before
reporting a nesting depth error, as REA reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): count a selector's matches over every sibling while identified elements choose only their identifiers
Identity-first pairing also dropped an identified sibling's other
attributes from the counts that prove a selector names one member, so a
shared attribute such as `color` or `fill` could be chosen though it matched
two siblings: the diff reported present elements removed and drafts lost
their checks. Counting now covers every attribute of every sibling; only the
choice is limited to an identified element's tag and identifiers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): tell a UTF-16 seed's format without building it, and scrub XML without one piece per token
Choosing the scrubber for a decoded UTF-16 seed ran the full state
detection, which built the whole XML document (or JSON value), so a large
seed within the capture limit could exhaust memory at export. A syntax pass
now decides the format (the JSON token scan, and the XML parser's events
keeping only the open elements), and the XML scrubber copies out only the
spans it changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(filesystem): settle a Windows ChangeTime before recording a baseline
Windows advances a file's ChangeTime shortly after it is written while
security components attach catalog and file-hash extended attributes. A
baseline captured during that advance disagrees with the next observation,
so an untouched file was reported as changed: "Root artifact changed before
inventory" and "Selected input changed while being read".
Dropping ctime on Windows is not an option because it is the only field
that catches a same-size rewrite whose modification time was restored.
Instead, keep ctime at full strength and stop capturing it mid-flight: on
win32 a ChangeTime still inside the drift window is observed again until two
consecutive observations agree, and the settled value becomes the baseline.
Applied where a baseline is captured and later compared: openRegularFile's
admitted metadata and the inventory root snapshot. The exhaustive process
capture walk is left alone, since settling there would delay every freshly
written file in the tree.
Refs #8975
Co-Authored-By: Claude Sonnet 5 <[email protected]>
* fix(filesystem): wait out the drift window instead of a repeated value
Settling on two matching observations was not enough. Two reads taken before
Windows performs its delayed ChangeTime update agree with each other and
disagree with every later one, so an unchanged file still failed a later
check: the reported drift lands 0.3-1.0 s after the write, well past a pair
of reads 25 ms apart.
Decide on the observation's age in the drift window instead, and keep
observing until the ChangeTime itself has aged out. Both the first read and
the empty-observation path now go through the same window check.
The window-exit test could not fail before: its clock was frozen, so the
window never closed and the exit branch never ran. Advance the clock while
sleeping and cover a match that precedes a delayed update.
Refs #8975
Co-Authored-By: Claude Sonnet 5 <[email protected]>
* fix(filesystem): configure Windows ChangeTime settle limits
REA_WINDOWS_CHANGE_TIME_DRIFT_MS, REA_WINDOWS_CHANGE_TIME_SETTLE_POLL_MS, and REA_WINDOWS_CHANGE_TIME_SETTLE_MAX_POLLS keep today's 2000 ms, 25 ms, and 40-poll defaults. Invalid values name the setting, current value, source, and range, and say to restart REA.
* fix(filesystem): settle on the selected environment and stop when cancelled
`settleChangeTimeBaseline` resolved its limits from `process.env`, but the
inventory already carries the environment its owner selected. A Windows
runtime with explicit settle settings therefore ran on ambient values or
the defaults, and an invalid ambient value could fail a runtime whose own
configuration was valid. `scanCanonical` now passes `options.environment`
through.
The wait loop also never observed the owning operation's signal, so a
cancelled inventory could keep polling for the whole drift window — up to
a minute at the configured maximum — and closing the artifact scope waited
on it. The loop now checks the signal before each poll and after the
sleep, and both call sites pass theirs.
Refs morluto/rea#8979
* fix(filesystem): keep the caller's typed error on cancellation
Checking the signal was not enough: `throwIfAborted` threw the raw
`signal.reason`, which `openRegularFile` passed through unchanged. Its
callers recognise cancellation through `RegularFileOperationCancelled`, so
a cancelled admission surfaced as an `ArtifactReaderFailure` with reason
`io` instead of a cancellation.
Let the seam supply the error a caller matches on, and have
`openRegularFile` pass its own cancellation type.
Refs morluto/rea#8979
* fix(filesystem): keep inventory cancellation typed during ChangeTime settle
scanCanonical passed the abort signal into settleChangeTimeBaseline without a cancellationError, so an aborted inventory threw the raw AbortError and analyze reported execution_failure. Inventory now throws ArtifactReaderFailure reason cancelled, which callers already translate as cancellation.
* fix(filesystem): skip ChangeTime settling for private PYZ images
External admission still waits out a fresh Windows ChangeTime. A private image REA just wrote is recorded as written, so a compressed PYZ spool does not add about a second per image.
---------
Co-authored-by: 2SinceJuly <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
Add per-connection discovery that loads canonical named tools on demand while preserving the default full catalog. Bound discovery replies before changing visibility, preserve family exclusions and Evidence workflows, and reset visibility on HTTP reconnect.
Verified with focused protocol tests and real Codex CLI artifact investigations; token costs remain task-dependent, so the profile is opt-in.
* feat(artifacts): select extraction entries and output destination
Refs morluto/rea#3490
* fix(artifacts): omit absent selection from execution parameters
* fix(artifacts): protect source directories and verify public selection
* fix(artifacts): keep optional selection inputs JSON-safe
* fix(artifacts): separate reader ownership from selected destinations
Validate complete reader inventory independently from collisions among selected output entries. Use occurrence-parent ownership instead of filename prefixes so independently owned PyInstaller entries remain selectable while nested-reader members remain refused. Validate advertised MCP schemas through the shared acceptance helper and add real PyInstaller/ASAR and excluded-entry regressions.
* fix(artifacts): advertise absolute extraction destinations
Reuse the shared host-absolute local path schema instead of an opaque refinement so SDK-converted JSON Schema preserves the runtime constraint. Extend the advertised-path and MCP-handler regression matrix for extract_artifact.output_root.
* fix(contracts): advertise unique extraction occurrence IDs
---------
Co-authored-by: gensokyo-dev <[email protected]>
* refactor(mcp): share official tool evidence registration
Route official tools through registerEvidenceTools and remove the duplicate
execution, progress, and Evidence delivery pipeline.
Preserve relationship coverage in the shared path and reuse the existing
source Evidence recording helper.
Validation: typecheck and 29 focused MCP tests passed.
Real native providers were not exercised.
* fix(mcp): support retained pseudocode summaries and paging
Allow procedure_pseudo_code to return an opt-in summary linked to its
retained complete Evidence. Extend inspect_analysis_view to page the
original pseudocode without repeating provider analysis.
Preserve default complete output and exclude delivery detail from
provider parameters and source Evidence identity.
Add regression coverage for large results, Unicode, null and empty
output, missing retention, transport-overflow recovery, and CLI paging.
Validation: 178 focused tests, build, check, docs:check, and test discovery
passed. Real stdio simulation reconstructed 98,000 code units exactly.
Real Ghidra, iTunes, and Claude Code verification remains unavailable.
Fixes#5032
* test(mcp): scope pseudocode contract lookup to official tools
* feat(desktop): capture and export apps installed from a downloaded .deb (#8954)
* feat(desktop): capture a Linux desktop application in use
Add `capture-desktop-application`, a CLI-only, Linux and X11 capture that
launches an installed desktop application, watches it while it is used and
writes capture Evidence plus SHA-256 addressed content:
- inventory: desktop entry, launch command, executable, package and its
apt sources (or snap, flatpak, AppImage), Electron markers and candidate
state directories;
- session: the launched process tree, client windows from xprop with the
first application window time and a window match, and files held open;
- state: a stat walk of the home directory before launch, at checkpoints
read from stdin, at stop and after exit, with created, modified, deleted
and retyped paths and the content of changed files and state directories.
It refuses non-Linux hosts, missing DISPLAY or xprop, a non-empty output
folder and an application whose window is already open, and signals only
processes it started.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): own launched processes, confirm walk coverage and keep cancelled captures
- Launch through the owned provider path with a run token; release with
SIGTERM, then the token-verified group cleanup, after every session.
Processes without the token are never signalled; each records whether it
carries it, and the session records whether cleanup was verified.
- Report creations and deletions only where a walk listed the nearest
existing directory; count the rest as unconfirmed.
- Retain a file only when it is still the walked version, through owned
no-follow reads; charge the budget only for new content; create files
exclusively in a private output folder owned by the caller.
- Walk content roots outside the home directory, compare paths in their
physical spelling, and run executables from the caller's directory.
- Refuse when the window list cannot be read, re-evaluate window ownership
each sample, handle one queued request per sample with a bounded stdin
queue, and write the capture with end reason `cancelled` on interruption.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep owned cleanup failures and fresh digests
- Allow a capture cancelled before any requested checkpoint to hold only the
baseline.
- Retry an incomplete stop once; if cleanup is still unverified, write the
capture and fail with the run token and process group as cleanup
resources.
- Classify path changes after a walk (ENOTDIR and other selected-file
errors) as changed_since_walk instead of failing the capture.
- Treat a non-directory ancestor as proof that nothing existed beneath it.
- Report a before digest only for the file version it was read from.
- Derive stderr_truncated from stderr's own byte counts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* wip(desktop): state view over a capture folder
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep failed file-handle closes owned for retry
Content reads pass retainFailedClose, so a handle whose close fails stays
owned by the content store; the capture retries those closes after it is
written and fails with the output folder as the cleanup resource if one
still cannot be closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): read a captured application's state
Add inspect-desktop-application-state, a CLI command that reads a capture
folder into the application's state: every retained or changed file with
its role and reasons, a typed JSON view of each version (SQLite through the
shared worker, dconf through a private dconf dump, JSON, XML, key files,
text) and the kinds of change between versions (lines, values, keys, rows).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report process and handle cleanup failures together
When both the launched processes and a retained file's handle remain
unresolved after the capture is written, one cleanup error now lists both
reasons and both resource sets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): export a captured application as an agentenv computer app
Add export-desktop-application, which writes an agentenv bundle with one
computer_app environment from a capture folder and its state view: the
apt package, a boot launch matched on the captured window class with a
window ready check, owned paths from the state roles, a seed of the
baseline files, data hooks that call agentenv's add/get/reset helpers
with a typed JSON view on get, a skill, a README and a deploy smoke task.
The desktop CLI commands now load their services lazily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep state views faithful to the capture
- Read key files, XML attributes, dconf groups and SQLite tables into
dictionaries without inherited properties, so captured keys such as
__proto__ stay data.
- Version databases with their write-ahead log as of each checkpoint, and
stage only digest-verified bytes for the SQLite and dconf readers.
- Detect the format per version, report format changes, keep JSON null and
empty containers as values, keep XML text and preserved whitespace, and
mark truncated views (including XML depth) and comparisons of them.
- Number versions and changes by checkpoint index, decide baseline
presence from the captured type, and keep tagged SQLite integers, text
bytes and blobs distinct.
- Carry the capture's limitations and omitted content forward, match state
directories by their home path, index changes once per capture, keep
failed handle closes owned, and finish the CLI through the shared owner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep coverage, logs and handles honest in the state view
- Read coverage flags only from formats whose readers write them, so JSON
fields named truncated stay data.
- Fail a SQLite version whose write-ahead log existed but was not retained
or does not match its digest, instead of reading the database alone.
- Close retained content on every exit, including cancellation, and report
a close that still fails together with the primary failure.
- Finish the CLI with the canonical operation name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export only what the computer can install and seed
- Refuse packages not from the Ubuntu 24.04 archive, since the computer
installs by name from that archive.
- Start the launch command in the captured working directory.
- Respell the home path only in files the state view read as text.
- Report files that were not retained instead of dropping them silently.
- Load captured dconf keys for apps that own no files.
- Keep home-level files as owned paths; rename reserved Python class names.
- Mark truncated SQLite and text views in the class's get view, and read
file types the way the state view does.
- Release the output tree through a resource scope, and read seeds through
an owner closed on every exit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep staged folders owned, symlinked state roots and XML values
- Stage database bytes in a private runtime root released through a
resource scope, so a failed removal keeps its owner and is reported with
the read's outcome.
- Report cleanup facts and owners from desktop failures instead of
wrapping them as a plain adapter error.
- Record the state directories as walked, in their physical spelling, and
match roles against both spellings, so files in a symlinked state
directory are not mistaken for selected documents.
- Keep whitespace-only XML leaf values; drop only formatting between
elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read XML in the exported app the way the state view does
Keep whitespace-only leaf values and xml:space="preserve" text in the
generated get view; drop only formatting between elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): release retained handles with their owners, read older captures
- Release retained-content handles whose close failed through a resource
scope on every exit; a release that fails again reports the exact handle
owners. A read that finished keeps its bytes when only its close failed.
- Accept captures written before state directories were recorded, and say
in the state view's limitations that symlinked state directories may be
misclassed for them.
- Compute each XML element's whitespace context once instead of scanning
siblings for every text run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): boot without the working folder, bound XML, accept mirrors
- Try the captured working directory only when an argument is relative,
and start the app even when that folder is missing.
- Cut the generated XML view at REA's node and depth limits, and keep one
unreadable file from failing data/get.
- Accept Ubuntu 24.04 mirrors, not only the canonical hosts; PPAs and
other repositories are still refused.
- Read seeds through the retained-content owner's new signature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): state view review round
- Stage a retained -journal beside its database so the SQLite worker
accepts an inactive journal and refuses a hot one; a -wal or -journal
that existed but was not retained (including one omitted at the
baseline) makes the version unreadable.
- Keep tables that read when another fails, and let cancellation and
unfinished cleanup propagate instead of becoming view errors.
- Read text as a key file only when every line parses as one.
- Rank a file named in a window title or selected by the caller as a
document even when the application holds it open; match titles on
file-name boundaries; leave the display name out of dconf matching.
- Bound JSON and key file views (4 MiB, 500 levels, exact numbers) and
report too-large, missing and altered retained content separately.
- Add no version or change while a database stays absent.
- Read staged databases in place instead of copying them again.
- Give Evidence the same limitations as the result.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export review round
- Load captured dconf keys without D-Bus: install dconf-cli, compile the
existing and captured key files into the user database, and warn on
failure instead of stopping the server.
- Start relative-argument launches in the captured working directory,
the home included, since the framework launches from the app folder;
respell the home itself and option values under it.
- Read views in a thread, skip files over 64 MiB, align the generated
readers with the state view's, and close SQLite connections.
- Never own the home or a shared base folder; map a symlinked state
directory's files back to their home spelling.
- Report omitted files and unretained opened folders.
- Name the module so it cannot shadow another, and keep the class off
the module's own names; derive names the bundle check accepts.
- Write seeds as they are read, and remove an unpublished bundle on
failure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): capture review round
- Ask the launch process group and token carriers to exit before
killing them, on errors too; report incomplete cleanup with its facts.
- Count descendants of the launch as started by the capture even when
their environment cannot be read; treat a reused pid as a new process.
- Follow symlinked desktop entries; name a flatpak's state after its
application id; match flatpak windows by name only.
- Stop on end of input; refuse empty, duplicate and reserved checkpoint
names; require whitespace after the checkpoint keyword.
- Stay on each walk root's device, and never walk or retain `/` or a
directory containing the home; say so in the limitations.
- Refuse a window manager that does not publish _NET_CLIENT_LIST.
- Remove retained content when a capture fails before it is written.
- Skip reading a file larger than the remaining content budget.
- Record apt's archive origin for each package source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read an unset client list as no windows when supported
xfwm4 leaves _NET_CLIENT_LIST unset while it manages no windows, so the
capture refused an ordinary desktop with nothing open. Read
_NET_SUPPORTED with the list: an unset list means no windows when the
window manager supports it, and the capture is refused only when it
does not. `xprop`'s "no such atom" output now reads as unset too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): accept packages by apt's archive origin
Accept a package source when apt reports its origin as Ubuntu for a
noble suite, so mirrors count and PPAs or other repositories that merely
have "ubuntu" in their URL do not. Captures made before origins were
recorded fall back to Canonical's own archive hosts. Seed reads report
why retained content could not be used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps from vendor apt repositories
A closed-source app installed from its vendor's apt repository, such as
Typora, can now be captured and exported as a computer app.
- The capture records each non-Ubuntu repository listing the installed
version (uri, suite, components from apt's sources files, one-line or
deb822) and the key that signs it: the signed-by key, or the trusted
key that gpgv shows verifies the repository's signed index. The key
is retained with the capture.
- The capture records the packages owning the libraries the executable
loads (DT_NEEDED with RUNPATH and the linker cache, through REA's ELF
readers), since a vendor package may not depend on them.
- The export declares a verified vendor repository as an AptSource with
its key, installs a vendor package's library packages, and adds
--no-sandbox to an Electron app's launch, since a container denies
Chromium's sandbox.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report a locked database at once in data/get
A database the running app holds locked made each data/get wait sqlite's
five-second busy timeout before reporting it. Record the Typora run in an
agentenv computer in the verification.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): detect key files without splitting the whole text
Read lines one at a time and stop at the first that a key file view
would drop, instead of splitting a file of up to 64 MiB into arrays
before its format is known.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep symlinked homes and state folders under the home in exports
- Respell a path under its state folder's logical spelling only when that
spelling is under the captured home, so a symlinked HOME keeps its
files; pair walked and logical state folders only when the capture
lists the same number, and match the innermost folder first.
- When two files map to one home path, keep the one present before the
capture and name the other in the limitations.
- Read key files in the generated data/get with the state view's rule,
checked against it by running the helper under python3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): end captures on zombies and keep cleanup owners
- A zombie or dead process no longer keeps the session open.
- Remove only the partial capture file this run created.
- Keep the exact owners of handles and processes whose cleanup failed on
the returned error, so a retry can release them; test the published
capture's cleanup-failure branch with mixed outcomes and retries.
- Release the application once even when the exit checkpoint fails.
- A cancellation before the capture is written removes what it retained.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): vendor package review round
- Match apt sources through `apt-get indextargets`, which covers flat `./`
and `/` suites and gives each release file and sources entry.
- Accept a key whose signature verifies even when the release carries
another signature without a key here; keep only the verifying key from
a keyring holding several (OpenPGP packet splitter).
- Install the package from its repository's release (`name/<release>`),
declare one source per URI and suite, support only the capture host's
architecture, and refuse Ubuntu Pro repositories.
- List only library packages the computer can install; walk libraries only
for vendor installs, with RPATH inherited as ld.so does, normalized
paths, and DT_NEEDED bounded while reading.
- Put --no-sandbox after the program an env wrapper launches; keep the
gpgv folder's owner when its removal fails; respect --max-file-bytes for
keys; wait 0.25 s on a busy SQLite database in data/get.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): keep the vendor spec's gpgv command on one line
A code span broken across lines left `<binary keyring>` for VitePress to
read as an unclosed HTML tag, so the docs build failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): add a validated task to exported desktop applications
Each capture can now yield one agentenv task in its exported bundle. An
agent writes the task (a prompt plus declarative checks over the app's
data/get views); REA validates it and stays model-free.
- `describe-desktop-application-task` summarises a capture's net change
per file (role, format, views before and after) and drafts checks.
- Checks: file presence, lines present/absent/counted, JSON-pointer values,
SQLite rows and counts. REA keeps a task only if a check fails on the
seed and every check passes on the captured end state; a file that was
not retained at the baseline counts as absent, as the computer starts.
- `export-desktop-application --task` writes tasks/NAME.json (deploy,
agent, skills, recording, prompt, verifier), a compiled verify.py that
reads data/get, and an eval. A boundary test runs the verifier under
python3 and checks it agrees with REA's evaluator on every check kind.
- Docs, spec and skill guidance, with the solve-rate recipe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the seed test in its own block
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task verifier driven in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the Typora task run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep task checks bounded, complete and attributable
- line_count counts lines holding a substring (contains) instead of
matching a regular expression, which could stall the verifier on
agent-written text and read differently in Python.
- value_equals of a whole object or array is undecided on a truncated
view.
- The draft keeps a database whose rows changed under an unchanged hash,
and a file whose truncated views match while it changed.
- A task file that cannot be read keeps the reader's detail, and a failed
cleanup keeps the read failure as its primary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): leave a file with one version out of the task draft
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): update the task verification's test counts
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task solve rate with Claude Code and Sonnet 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps installed from a downloaded .deb
capture-desktop-application --package-file reads the file's control fields
with dpkg-deb and records its SHA-256, size and dependencies when it holds the
installed package; the file itself is never kept. The library walk now also
runs for local installs.
export-desktop-application --package-url declares agentenv's
Download(url, sha256) for that file, its dependencies and library packages as
apt packages, and a run script that installs it with dpkg.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): seed app-named dotfiles and respell UTF-16 seeds
The capture now treats dotfiles in the home named after the application
(.varicad-cfg.ini, .notesrc) as state roots, so a file the app only reads
during the capture is still seeded. The exported seeding respells the
captured home in UTF-16 text files with a byte-order mark, keeping the mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): hold readiness until the app's own window shows
An exported app is ready only once a window of its class that the taskbar
lists is shown, so a splash screen of the class, which skips the taskbar, no
longer counts as the app being up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record VariCAD captured, exported and run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep exported views strict JSON for infinite and inexact numbers
The exported data/get spells infinite and negative-zero SQLite reals as REA's
SQLite reader does ("Infinity", "-Infinity", "-0"), and refuses a JSON file
holding a number that reading would change (1e999, integers past 2^53, -0,
rounded fractions) with the state view's own error, instead of a view with
an infinity in it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep package-file facts tied to one file and URLs unechoed
- A --package-url refusal never repeats the URL, and credentials are checked
before the scheme.
- The package file's identity must hold between dpkg-deb's read and the hash.
- A failed close keeps its cleanup owner even when the capture is cancelled.
- Readiness also ignores windows typed as splash screens.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): record database logs and keep state changes distinct
- A selected path that is not a directory brings its -wal and -journal as
content roots, so the capture records whether they exist and retains them.
- The state view treats a file as absent only under recorded content roots
and refuses a database whose log presence is unknown.
- JSON changes list containers too, so an array that becomes an object with
the same members is a change.
- Key file entries escape the key as a JSON pointer segment, so [a/b] c and
[a] b/c stay apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep watching through unread samples and late workers
- An unreadable window list is unknown, not zero windows, and is named in
the limitations.
- Before ending the session, live run-token carriers no parent link reached
are tracked; an unreadable process table leaves the exit unknown.
- An env prefix's assignments find the program and name its XDG folders.
- flatpak run options that take a value are skipped with it, -- is honoured
and a /arch/branch suffix is dropped from the id.
- State directories include the entry's full id.
- Path options refuse empty values; roots count toward the entry limit.
- A capture file whose close failed stays owned and is retried on discard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): bound apt sources, keep older captures and cleanup owners
- Sources files parse into one stanza per line or deb822 paragraph with its
URIs and suites kept as lists, so no URI x suite product is built.
- Repository architectures and release are optional, so captures written
before they were recorded parse; the export treats them as unrestricted
and unknown.
- Library sources are matched against every index of a declared
repository's URI, suite and components.
- Two failures side by side keep both causes and both cleanup owners, and a
host file whose close fails reports a lease on its handle.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): match escaped key file entries and suggest containers through members
- The draft's key file lookup uses the state view's escaped `group/key`, so a
key holding `/` or `~` still gets its suggestion.
- JSON and XML changes now list containers too; the draft suggests an added
or removed container through its listed members, and a container only
when none of its members is listed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read relative PATH entries from the launch folder
A program, or an empty or relative PATH entry, is resolved from the folder the
program starts in, as the launch does, so `env PATH=. app` with the entry's
Path still finds the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): compare JSON views on one shared path
JSON and XML changes are found by walking both trees with one shared pointer
path, building a pointer only for a listed value, so memory follows depth
rather than every container's full path. A pointer over 4096 characters is
counted but not listed. A 256-level document with 4000-character keys went
from about 251 MiB of heap to under 1 MiB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep JSON Lines as text in the exported reader
The exported reader parses the whole text before an inexact number makes it
JSON with an error, so JSON Lines with an integer above 2^53 stay text as in
REA. Over-long integer spellings are unsafe without converting them. The
guide no longer claims large integers keep their value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep the state-path help free of dash-led words
The CLI alias check reads `-wal` and `-journal` in help text as short flags,
so the help names the write-ahead log and rollback journal instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never record or export apt source credentials
- Source URIs lose any user name and password as they are parsed, the way apt
splits them, and so do index-target URIs and descriptions and the sources
apt-cache policy lists, so matching works whether or not apt prints them.
- A repository whose entry held credentials is marked
`uri_credentials_removed`, and the export refuses it, or an older capture
whose URI still holds them, with a reason that names no URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up without normalizing their paths
Program paths and PATH entries are joined rather than resolved, so `..` after
a symbolic link means what it means to the launch. The lookup moves to its own
module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): note inexact JSON numbers with a flag, not a list
The exported reader only needs to know whether any number was inexact, so it
keeps a boolean instead of every rejected spelling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): refuse older captures that still hold apt credentials
A capture written before credentials were removed counted the credentialed
repository's libraries as installable, so the export refuses it whole and asks
for a new capture rather than declaring packages no source offers. Recorded
policy sources are redacted in export messages too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the credential fixture on an example host
A login on a real host reads as a secret to the commit scanner, so the older
capture's fixture uses apt.example.com like the other credential tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): tidy looked-up paths and search relative entries only for env
Empty and `.` segments are dropped from a looked-up program path, which keeps
`/snap/bin/` entries recognisable as snaps, while `..` is still left for the
kernel. Relative and empty PATH entries are searched only in the PATH an `env`
prefix sets, which travels with the exported launch; the host's own PATH does
not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep excluded and unwalked database logs unknown
The capture records the paths every walk left out, and the state view treats
a log as absent only under content roots the baseline walked completely and
outside those exclusions, so an excluded or unvisited log leaves the database
unread rather than read without it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): judge JSON numbers after duplicate keys settle
The exported reader marks an inexact number instead of flagging it during the
parse, then looks for marks in the parsed value, so a number a later duplicate
key overwrote no longer refuses the file, as in REA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): redact a repository's policy source when it names it
An older capture whose policy source matched no sources entry kept that text
whole, and the export named the repository by it; the export now names it
without any user name or password.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never signal the launch group on unknown membership
When the current read cannot observe a carrier's process group, the carrier
is signalled on its own instead of letting an older process-table row grant
a group-wide SIGTERM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up in the launch's order and refuse host-relative finds
The lookup searches PATH as the launch does, an empty entry meaning the
launch folder, so inspection and launch name the same program. A program found
only through a relative or empty entry of the host's own PATH is refused,
since the exported computer would not have it; an env prefix's PATH travels
with the command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): count unchecked paths and keep logs behind them unknown
Each walk counts the paths it could not stat and the directories on another
device it did not enter, and the checkpoint records it as `unchecked_paths`.
The state view treats a log as absent only when the baseline left nothing
unchecked, so a log whose stat failed stays unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): scan parsed JSON with one iterator per level
The check for marked numbers walks the parsed value with an iterator per
nesting level instead of copying each container's children; a million-entry
array went from about 15.7 MiB to 8.1 MiB of peak traced memory.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): start a relative program from its captured folder
An exported launch whose program path is relative, such as `./app` from an
entry's Path, now changes to the captured working directory first, as it
already did for relative arguments.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(electron): reject non-directory scenario roots before launch (#8967)
* fix(contracts): align managed example MethodDef scope (#8966)
* docs(debugger): document configurable session limits (#8963)
* docs(debugger): document configurable session limits
* docs(debugger): clarify GDB control command deadlines
* docs(debugger): qualify configurable GDB output budget
* docs(debugger): clarify one-shot CLI settings scope
* docs(debugger): preserve repository line endings
* docs(testing): explain npm launcher selection in PowerShell (#8962)
* docs(mcp): explain PowerShell launch environment scope (#8961)
* docs(mcp): explain PowerShell launch environment scope
* docs(mcp): qualify saved setup selections and OpenCode environment
* fix(native): declare temporary filesystem writes for inspect_macho (#8959)
* fix(environment): retain ID ordering with missing clocks (#8957)
* fix(environment): retain ID ordering with missing clocks
* fix(environment): compare known creation clocks across gaps
---------
Co-authored-by: hhang <[email protected]>
* fix(gpu): distinguish ELF string scan limits from corruption (#8955)
Co-authored-by: hhang <[email protected]>
* fix(setup): replace the fixed Hopper download deadline with a stall timeout (#7150)
* fix(setup): replace the fixed Hopper download deadline with a stall timeout
Hopper package downloads aborted after a fixed 120 s, which was also the
largest deadline the download owner accepted. The current 32 MB Linux
package needs about 132 s at the reporter's sustained 245 KB/s, so setup
could never finish on that connection, and the timeout surfaced only as
Node's generic abort message.
Abort a download when no response bytes arrive for 30 s, or when it
outlives a 15-minute overall bound, with a tagged
HopperDownloadTimeoutError that names the URL, the limit reached and the
bytes received. Linux and macOS setup already relay a thrown download
error's message; the Linux publisher now keeps it for release metadata
and public key fetches instead of a generic verification failure.
Metadata and key fetches keep their 10 s bound.
Refs #7077
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(setup): keep the macOS release feed on the metadata limit
The stall-timeout change left the macOS release feed fetch on the
package download bounds. That fetch could then wait up to 15 minutes
instead of the 120 s it had, while the installation guide says release
metadata keeps a 10 s limit. Only the Linux publisher owned that limit.
Move the 10 s metadata limit into HopperDownload.ts beside the package
bounds. The Linux feed and key fetches use it, and the macOS installer
now passes it for its feed fetch through the installer host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(cli): parse environment-serve --port and --host like rea mcp --http (#7092)
* fix(cli): parse environment-serve --port and --host like rea mcp --http
environment-serve declared --port as a number, so incur coerced the flag text with Number(): an empty --port= bound an ephemeral port instead of failing, and 0x1F90 or 1e3 were reinterpreted. --host accepted whitespace. docs/cli.md says these flags resolve like rea mcp --http, which accepts only decimal port text and a non-empty trimmed host. The flags now use the same rules and are refused before binding.
* refactor(cli): reuse the shared MCP listener port and host schemas
* fix(adb): separate unresolvable paths and host capacity in adb spawn failures (#6188)
* fix(adb): name unexecutable adb paths in readiness refusals
When REA_ADB_PATH or the PATH-resolved adb is a directory or a file the
host refuses to execute, the version probe fails at spawn with EACCES or
EPERM. That fell through to the generic "did not report a usable version"
refusal, which named neither the path nor the cause.
Classify those spawn errors as binary_not_executable (provider rejection
executable_missing), name the selected path and errno, and give
remediation for the environment, PATH and host-policy cases. The generic
fallback now names the selected path too.
Fixes#5689
* fix(process): admit synchronous spawn errors as captured failures
Node throws spawn errors other than EACCES, EAGAIN, EMFILE, ENFILE and
ENOENT synchronously from execFile. execFileBytes called execFile inside
its Promise executor, so ENOTDIR or EPERM became a rejection without
capture fields and execFileOutputFailure returned undefined. Consumers
that classify by failure code, such as the adb version probe, could not
see the code, which left the adb EPERM branch unreachable.
Catch synchronous spawn errno errors at the capture boundary and attach
the same empty capture an asynchronous spawn failure carries. Argument
validation errors stay unadmitted.
The adb version probe now reports an ENOTDIR selected path, such as a
REA_ADB_PATH whose parent is a regular file, as a missing binary with
the filesystem error code instead of the generic version refusal.
* fix(process): expose spawn failures from the capture owner
execFileBytes knows when a command never started, but
ExecFileOutputFailure did not say so, so consumers reread raw syscall
and code fields and disagreed. Mark both the synchronous throw and an
asynchronous `spawn <path>` error with `spawnFailed: true`, and set
`path` on the synchronous error to match the asynchronous shape.
Argument validation errors stay unadmitted.
Consumers now use the owner field:
- inspectAdbClient never reports a spawn errno as `exit_code`. ENOENT,
ENOTDIR, ELOOP and ENAMETOOLONG are `binary_missing`; EACCES and EPERM
stay `binary_not_executable`; any other spawn errno, such as EINVAL,
is `binary_not_executable` with a path-naming "could not be started"
message. Each records `filesystem_error` and `path_source`. EACCES
remediation also names parent directory search permission, and EPERM
remediation no longer names host policy as the only cause.
- DarwinProcessRunTokenReader tags every not-started compiler as
`spawn_failed`, not only ENOENT. EACCES, EPERM and ENOTDIR were
previously `failed`.
- NativeDmgArtifactReader uses one not-started rule for both
`processNotStarted` (previously `spawn ` only, so synchronous errors
were missed) and the ENOENT unavailable/io split (previously any
`spawn` prefix). Its fake hosts now reject with real execFileOutput
spawn failures.
* fix(process): classify spawn failures consistently across consumers
Admit synchronous and asynchronous spawn errnos through one helper that
requires an errno and a `spawn` or `spawn <path>` syscall. The
synchronous shape now also gains `spawnargs`, so both shapes match.
Use the owner's `spawnFailed` fact in the remaining consumers:
- inspectAdbClient reports exhausted host capacity (EAGAIN, EMFILE,
ENFILE, ENOMEM) as a host failure to retry and no longer tells the
caller to change REA_ADB_PATH. Path-resolution errnos come from a
predicate the filesystem owner exports.
- runAdbCapture says adb could not be started instead of claiming an
exit code for a process that never ran.
- The Darwin compiler probe words every spawn_failed outcome as a start
failure, not a compilation failure.
- Asset catalog inspection treats any assetutil that never started as
unavailable, not only ENOENT.
* fix(process): report host spawn capacity as a retryable constraint
Spawn errnos that report exhausted host capacity (EAGAIN, EMFILE, ENFILE,
ENOMEM) were classified by adb readiness alone. assetutil reported them as
an unavailable tool, which marked inspect_asset_catalog unavailable in the
session, and adb commands reported them as protocol failures.
Classify them once in the process capture owner and report a retryable
capacity constraint with recovery advice from assetutil and adb commands.
* docs(adb): describe host capacity failures from device operations
* fix(adb): report exhausted host spawn capacity as runtime unavailability
A host that cannot start adb for lack of process, memory or descriptor
capacity is not a configuration problem. Give it its own failure code and
map it to the runtime_unavailable provider rejection instead of
not_configured.
* fix(adb): report probe-time host capacity as a retryable constraint
A device operation whose adb version probe could not start for lack of host
capacity was refused as capability_unavailable without retryable. It now
reports the same retryable capacity constraint as a later adb command.
* fix(adb): keep the selected adb path in probe-time capacity errors
The retryable capacity constraint raised at the version probe dropped the
selected_binary and path_source diagnostics the refusal used to carry.
* refactor(adb): keep the spawn-failure fact to adb consumers
Restore the DMG reader, Darwin compiler probe and assetutil to main. The
capture owner still marks a never-started command, including the spawn
errors Node throws synchronously, and only the adb readiness probe and
adb commands read that fact.
* test(adb): assert spawn-failure facts instead of refusal wording
The new adb tests compared complete refusal sentences and banned an old
phrase. They now check the errno, selected path, path source and the
owner's spawn-failure marker, so wording edits do not break them.
* fix(adb): own one host-capacity remediation and cover the spawn route
The host-capacity constraint and the configuration failure each carried their own remediation sentence. AdbConfiguration now owns the text and both use it, so the constraint's remediation.action equals diagnostics.remediation.
The synchronous spawn-throw test now runs on Node's execFile route and on the owned spawn route adb uses (stopSignal). The provider test covers inspect_adb_client under EMFILE next to list_adb_devices, and the docs say which operations report the retryable capacity constraint and which report runtime_unavailable.
* fix(process): report EMFILE and ENFILE spawn failures without stdio
After an EMFILE or ENFILE spawn failure Node creates no stdio, so the
owned stop-signal route threw a TypeError while reading the streams and
the later spawn error went unhandled. Attach the error listener first and
capture absent streams as empty, so adb reports retryable host capacity.
* fix(adb): keep a completed push when host capacity stops its digest check
A capacity failure starting sha256sum came back as a retryable error even
though adb push had already written the file. Repeating the request with
overwrite=false is then refused because the destination exists.
Return the receipt with an unavailable device digest and name the capacity
failure in its limitations, so the caller is told the transfer is unverified
rather than advised to retry.
* docs(adb): list host capacity as an unverified push case
---------
Co-authored-by: t <t@t>
* test(gdb): expect configurable stream truncation diagnostics (#8965)
* test(gdb): expect configurable stream truncation diagnostics
* build(gdb): include prerequisite duplicate-import repair from #8884
* fix(javascript): resolve module-level Electron options in functions (#8960)
* fix(javascript): resolve module-level Electron options in functions
* fix(javascript): limit module option ancestry walk
* fix(javascript): preserve uncertain captured argument ordering
* fix(javascript): retain hoisted activation uncertainty
* fix(javascript): retain function ownership through loops
* fix(javascript): account for classic global activations
* fix(javascript): bind first activations to lexical identities
* fix(javascript): retain uncertain global activation timing
* fix(javascript): isolate receiver facts and share computed reads
* fix(javascript): retain projected and opaque global activations
* fix(javascript): refresh activation bounds as opaque effects grow
* fix(ci): format website watch directories without changing configuration
* fix(javascript): share source reads across opaque-effect updates
* fix(javascript): retain constructed-code global activation bounds
* fix(javascript): share runtime activation and receiver facts
* fix(javascript): reuse pattern reads and function positions
* fix(javascript): isolate copied paths and preserve runtime reads
* style(javascript): format migrated reference imports
* fix(javascript): normalize constructor activation and receiver effects
* fix(javascript): retain aliased receiver timing and lexical global this
* fix(javascript): preserve effect-free receiver identity across activations
* fix(javascript): retain outer activation and prototype effects
* fix(javascript): index implicit reads without deep path rescans
* fix(javascript): retain aliased prototype and parameter read uncertainty
* fix(javascript): index reflected reads through intrinsic invocation facts
* fix(javascript): retain unresolved global activation reads
* chore: refresh PR branch after review fix publication
* fix(javascript): retain projected receiver reads and cache method facts
* fix(javascript): retain inherited and implicit activation reads
* fix(javascript): retain caller-owned this uncertainty
* fix(javascript): retain reflected reads with unresolved spread arguments
* fix(javascript): preserve enclosing activation for decorators
* fix(javascript): track intrinsic literal prototype effects
* fix(javascript): retain catch activation from implicit reads
* perf(javascript): reuse effective keys for projected reads
* fix(javascript): retain possible caller-owned ambient effects
* fix(uninstall): preserve caller-selected Kuna caches during purge (#7921)
* fix(uninstall): keep a managed cache namespace whole when it holds a caller-selected Kuna cache
purge --purge-data reported a REA_KUNA_CACHE_DIR selection below the default Kuna cache as retained, then removed it together with its parent namespace. A namespace that contains the selection is now retained and reported, so the retained report is true.
Fixes#7344
* fix(uninstall): also keep a namespace equal to, or reached by symlink traversal from, the selected Kuna cache
Compare the namespace and the selection with the same normalization, treat an equal path as contained, and resolve existing paths before planning so a symbolic link followed by .. is judged by where it leads.
* fix(uninstall): judge a selection with parent traversal where the filesystem resolves it
Collapsing .. lexically contradicts the existing rule that parent traversal after a symbolic link leads elsewhere, and broke the retained-through-symlink-traversal test. Compare spellings without .. directly and resolve existing paths for the rest, so the symlink-parent case still purges the default cache while a selection that resolves into a managed namespace is retained.
* fix(uninstall): keep a namespace whole when the selection's location is unknown
A realpath failure other than ENOENT/ENOTDIR (for example EACCES under an
inaccessible parent) no longer counts as "absent". Each namespace that may
contain the caller-selected Kuna cache is retained instead of purged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(changed-behavior): withhold equality for unknowns and contradictions (#7624)
find_changed_behavior derived behavior_status from each process
comparison's aggregate status alone. An unchanged comparison whose
first_divergence was unknown (residual unknowns prevent proving no
divergence) and a consistent contradiction comparison both fell
through to observed_unchanged, with no unresolved finding and no
changed-behavior registry unknown.
An unchanged comparison with unknown divergence now counts as unknown
and adds one comparison-level unresolved first_divergence finding
carrying its reason, as artifact comparisons already do for coverage.
A contradiction status makes the aggregate unknown, and contradiction
dimensions are classified and counted as contradictions instead of
unresolved branches. Complete unchanged comparisons stay
observed_unchanged and changed comparisons with unknown localization
stay observed_changed.
Fixes#7384Fixes#7383
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(process): reject contradictory filesystem capture facts (#7631)
* fix(process): reject captures whose top-level filesystem facts contradict their checkpoints
A process capture records its filesystem state twice: files_before,
files_after and filesystem_effects at the top level, and the initial
and final filesystem_checkpoints. Validation never reconciled the two,
and comparison reads only the checkpoints. A caller-authored capture
recording a created file at the top level while its final checkpoint
was empty was therefore accepted and compared as unchanged.
Semantic validation now requires the initial checkpoint to match
files_before with no effects, and the final checkpoint to match
files_after and filesystem_effects. The native producer builds both
representations from the same snapshots, so produced captures are
unaffected; consistent created-file captures still compare as changed.
Fixes#7371
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(process): cover file-list checkpoint mismatches independently of effects
Every rejected case in the checkpoint-consistency test also disagreed on
effects, so removing both file-list comparisons left it passing. Add
initial and final checkpoint cases whose file lists alone disagree while
effects and coverage accounting stay valid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(process): report deadlines expiring during final cleanup (#7635)
* fix(process): report a deadline that elapses during final cleanup as a timeout
runOwnedCommand checked its absolute command deadline before awaiting
process cleanup, but on the ordinary successful path it consulted only
the caller's cancellation signal afterwards. A command whose deadline
expired while successful cleanup was still awaited therefore resolved
as success.
After cleanup, an elapsed deadline now throws OwnedCommandFailure with
reason "timeout" and the completed snapshot, as docs/tool-design.md
requires a final signal check after the last awaited finalization step.
Existing primary failures, caller cancellation and incomplete-cleanup
classification keep their precedence.
Fixes#7369
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(process): drive the cleanup-deadline regression with controlled time
The regression test gave output collection 100 ms of real time before
the deadline. On a busy worker the deadline could expire during
collection instead, taking a different failure path and failing the
test even with the fix. Use fake timers, wait until cleanup has
started, then advance past the deadline and release cleanup, so the
ordering no longer depends on worker speed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(enhanced): bound batch decompile input size (#5937)
* fix(artifacts): handle early ASAR extraction cancellation (#5672)
* fix(artifacts): own source errors during output setup
* fix(artifacts): join silent source destruction before returning
* fix(artifacts): retain source cleanup failures with primary errors
* fix(artifacts): join source finalization and classify secondary failures
* fix(firmware): separate source cleanup from output rollback
* fix(artifacts): retain live source cleanup dependencies
* fix(artifacts): preserve source failure completion and semantics
* fix(artifacts): retain scope cleanup owners and typed failures
* fix(artifacts): keep cleanup classification and admission recovery
When the operation completed and only scope cleanup failed, keep the cleanup failure's own reason instead of reporting io, matching main (fixes artifactProvider.cleanupRetry). When a typed primary is rebuilt with pending scope cleanup, carry its userMessage and ASAR header admission so callers keep the configured ceiling and setting.
* fix(artifacts): translate plain file-close failures through the reader path
The outer file-cleanup branch rebuilt every ArtifactFileCleanupFailure as a
ProviderAdapterError, so dyld reads lost their io, unavailable, format and
cancelled reasons and their exact file-cleanup cause. Compose separately only
when the failed primary owns its own translation (a domain error, source
failure or scope cleanup failure); raw and reader primaries are already
projected by the file failure with its exact lease.
* fix(artifacts): bound source destruction by cancellation and keep wrapper admission
After cancellation, SafeOutputTree.write could wait indefinitely for a source
whose destruction did not settle, both in the destroy join and in the
iterator's wait for close. Both waits now end at cancellation plus a fixed 1 s
grace, modeled on the process-output closure grace. A destruction that
outlives it is reported as incomplete source cleanup under an exact lease that
releases only once closure is observed without a late error.
ArtifactResourceCleanupFailure and SafeOutputSourceFailure now keep the
primary's admission, matching withCleanup.
* fix(artifacts): keep one settled wait while observing a stuck source
observeClosure raced the same pending close promise against a new timer every
10 ms, so a source whose destruction never settled accumulated one reaction per
poll for as long as it stayed open. Each poll now waits on its own promise that
the close listener or the unref'd timer settles, leaving one pending wait and
one close listener. The lifecycle test also asserts that a late close releases
the retained owner without an explicit retry.
* fix(artifacts): settle pending reads before cancellation
* test(artifacts): observe the consumed ZIP iterator on cancellation
* test(artifacts): validate projected cleanup resources before sorting
* test(mcp): reserve parent capacity for timeout recovery
* fix(artifacts): contain ASAR cancellation before output consumption
* test(artifacts): hash ASAR cancellation fixture bytes
* fix(decompile): retain inventory limitations and selection (#8466)
* fix(decompile): retain inventory limitations with the latest selection
The manifest and decompile index dropped procedure-inventory
limitations, so a directory separated from its Evidence response could
not explain why functions were selected or omitted. Keep those
limitations on the manifest and in a latest_run index record that also
names the filter and counts that produced them. Older runs remain
represented only by their retained function records.
Fixes#8355
* test(decompile): surface export errors and check function caveats
Address review findings on the inventory-limitation regression:
- Throw the typed export error instead of returning from a failed Result.
- Assert historical function records keep their own pseudocode caveats
and do not receive the latest inventory warnings.
* fix(pyinstaller): leave PE carrier binary members unmarked (#6730)
The Windows bootloader sets no execute bit on extracted ARCHIVE_ITEM_BINARY
members (the S_IXUSR fchmod in pyi_archive.c is #ifndef WIN32). Mark type b
members executable only when the carrier dialect is posix.
* fix(jeb): reconnect after stale MCP sessions (#3754)
* fix(jeb): reconnect after stale MCP sessions
* fix(jeb): preserve failures for overlapping MCP requests
* fix(jeb): make cleanup operations explicit and stabilize timeout fixtures
* fix(setup): flag unresolved migrated Claude Desktop storage (#8971)
* fix(setup): flag unresolved migrated Claude Desktop storage
* fix(setup): preserve Claude MSIX process view when config is unresolved
* fix(enhanced): restore batch input record limit
* docs(tool-design): audit agent workflow guidance (#8439)
* docs(tool-design): audit agent workflow guidance
* docs(tool-design): check deployed guidance versions
* fix(cli): align ADB directory and package query schemas (#8974)
* fix(cli): align ADB directory and package query schemas
* fix(cli): preserve ADB selector correction details
* fix(devicetree): reject duplicate node paths in Evidence (#8970)
* fix(pe): classify valid DOS targets as unsupported (#7530)
fix(pe): classify confirmed DOS targets as unsupported
Signed-off-by: Rudy Celekli <[email protected]>
* fix(native): reject nonzero reserved ELF dynamic symbols (#7528)
fix(native): validate the reserved ELF dynamic symbol
Signed-off-by: Rudy Celekli <[email protected]>
* fix(evidence): traverse large source link sets safely (#8457)
* fix(verification): use inspected Ghidra JDK (#8440)
* fix(mcp): clarify retained dossier handoff in crash prompt (#8437)
* fix(evidence): clarify unsupported view recovery (#8412)
* fix(evidence): clarify unsupported view recovery
* fix(evidence): explain sensitive capture view refusal
* test(ghidra): check regex recovery at its owning boundary (#8578)
* test(ghidra): check structured snapshot profile mismatch (#8536)
* test(ghidra): check retained historical snapshot evidence (#8381)
* test(ghidra): check retained historical snapshot evidence
* test(ghidra): select historical observations by address
* test(cli): cover Frida argument admission and spawn ordering (#3356)
* fix(cli): reject spawn arguments in Frida attach mode
* test(cli): remove redundant Frida recorder assignments
---------
Co-authored-by: Kaoru0822 <[email protected]>
* fix(ghidra): report restricted-host temp write denial on Windows (#8889)
* fix(ghidra): report restricted-host temp write denial on Windows
Add a Windows doctor check that creates and deletes a file under os.tmpdir(), and classify a denied java_home.save in Ghidra startup output with its path, Win32 error 5, and TMP/TEMP remediation. The probe covers REA's own process only; a host that restricts only child processes is reported from the child's output at startup.
Fixes#7075
* fix(ghidra): probe the session root and retain failed probe cleanup
The Windows doctor temp_directory probe now writes under ghidraSessionRoot(), the same root Ghidra session runtimes use, instead of os.tmpdir(), which can differ when the inherited temp path has a dot-prefixed element. A probe file whose removal fails is retained on the doctor resource owner so doctor shutdown retries the removal; the result still reports its path.
* fix(cli): normalize exit status on worker cleanup failure (#8049) (#8050)
* fix(cli): normalize exit status on worker cleanup failure (#8049)
* test(cli): verify SDK output status and finalization rejection on exit
* ci: retrigger checks
---------
Co-authored-by: Sarthak Pandey <[email protected]>
* fix(gdb): keep inferior-tty verification for read-only queries (#5539)
* fix(gdb): keep TTY verification across read-only inferior-tty inspection
The console command path cleared `inferiorTtyVerified` before dispatching
any command whose text merely mentioned `inferior-tty`, so the read-only
`show inferior-tty` query permanently revoked a persistent session's
verified output sink; no in-session path ever restored the flag, and a
later close of a live session failed detach verification with
capability_unavailable.
Exempt the exact read-only query forms (`show inferior-tty` and the MI
`-inferior-tty-show`) from the output-mutation predicate; every other
command mentioning the TTY setting keeps revoking, so mutating commands
still fail closed and detach keeps re-reading the live TTY before
detaching. Cover the query forms, composite commands, and the
inspect-then-close detach path with regression tests.
Fixes#5099
* test(gdb): classify show inferior-tty in the one-shot table and rename the TTY classification table
- Add a 'show inferior-tty' row to the 'classifies one-shot GDB
commands' table: the read-only query no longer invalidates the detach
proof, a user-visible one-shot behavior change.
- Rename the 'classifies read-only inferior TTY queries as non-mutating'
table to 'classifies inferior TTY commands' since two of its rows are
mutation cases.
- Assert via the existing recorded-commands array that the inspected
'show inferior-tty' text is actually dispatched, and add an
execute-path assertion that the MI '-inferior-tty-show' form leaves
the verified-sink flag intact (defensive exemption: REA's own MI calls
do not route through this gate).
---------
Co-authored-by: akushonkamen <[email protected]>
* fix(browser): report an out-of-range generated position as a selection fault (#7133)
trace_web_source_location reported a well-formed generated_position past
the end of the selected script as invalid_format on generated_position,
with no bounds, so callers were told their input was malformed and not
which coordinate to change. Report out_of_range on the line or column
that is past the end, with its valid bounds, as export_web_scripts and
inspect_web_network_capture now do for their selections.
The position scan now returns the bound it ran past, so the offset and
the reported limit come from one pass and cannot disagree on line
terminators.
Fixes#7132
* fix(captures): classify unresolvable capture paths as capture_path input (#7814)
* fix(captures): classify unresolvable capture paths as capture_path input
Fixes#7636
* test(captures): cover unresolvable descriptor paths
---------
Co-authored-by: Abelo9996 <[email protected]>
Co-authored-by: morluto <[email protected]>
* fix(apple): preserve exact integer precision with post-table plist objects (#8910)
* fix(javascript): resolve electron findings through aliases of interop results (#8914)
* fix(javascript): resolve electron findings through aliases of interop results
* fix(javascript): resolve interop aliases iteratively and check target mutations
* fix(javascript): retain possible writes through uncertain aliases and guard positive reads
* perf(javascript): cache alias change status across semantic interop lookups
* fix(javascript): transient Windows private-runtime source-transfer (#8769)
* fix(javascript): transient Windows private-runtime source-transfer
Parent source-transfer into the Windows private runtime root failed with
EPERM on path-based exclusive create. Write through a native transient
private-runtime path that closes the handle after flush so per-job rm
still works, and yield around the sync write so abort deadlines can fire.
* fix(javascript): yield timer phase before Windows source-transfer write
* fix(il2cpp): reject module image references outside retained inventory (#8956)
* fix(ida): admit Docker host-gateway names as local MCP hosts (#8932)
Containerized REA reaches an IDA MCP server on the container host through
host.docker.internal (Docker, Podman compatibility) or
host.containers.internal (Podman). The HTTP registration refinement only
admitted localhost, 127.0.0.1 and [::1], so doctor reported the valid
gateway registration as invalid_configuration.
Admit both gateway names in the registration's local-host set. The
refinement still requires http or https, no URL credentials, and refuses
every other host.
Fixes#8925
* fix(mcp): report target after failed open (#8613)
* fix(mcp): report target after failed open
* test(session): cover failed open receipt ownership
* test(session): verify restored target receipt
* test(mcp): retain target on snapshot mismatch
* test(mcp): retain oversized open failure receipts
* test(cli): avoid continuing session receipt in one-shot errors
* fix(mcp): report provider after failed switch
* test(ghidra): verify failed open preserves annotations
* test(mcp): join queued receipt request before cleanup
* test(session): assert open failure receipts in lifecycle suites
* test(session): keep open-failure receipt checks within function limits
Move the provider-collision case and snapshot fixture setup out of the
functions that grew when both failure receipts were asserted.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(prompts): include existing static artifact investigation routes (#8972)
* fix(prompts): include existing static artifact investigation routes
* fix(prompts): preserve workflows in existing family scopes
* fix(prompts): require readers for Apple resource routes
* fix(rizin): keep debug targets off the debugger command stream (#8647)
* fix(rizin): keep debug targets off the debugger command stream
A Rizin debug target inherited Rizin's stdin, which is REA's command
stream. Once resumed, a target that reads stdin consumed REA's commands,
so close returned cleanup_incomplete and the one-shot CLI hung.
Route the target's stdin to the caller-selected TTY alongside stdout and
stderr. Before detaching, refuse a target whose stdin is still the command
stream, so a Rizin or backend that ignores the key gets a specific,
retained failure instead of a resumed target and a timeout.
Fixes#8642
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(rizin): keep the stdin test launcher's descriptors under dash
The launcher gated its target with `cat "$2" >/dev/null`. dash applies a
simple command's redirection to the shell itself until the command returns,
so on Ubuntu the waiting shell's fd 1 was /dev/null, and that shell is the
target REA reads in /proc. All 8 Linux-gated tests then failed the existing
output-descriptor check. macOS /bin/sh is bash, which redirects only in the
forked child, so this did not show locally.
Gate without any redirection. Production code is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(rizin): reject unmodeled commands and own cleanup at acquisition
Address review of the stdin regression tests.
The launcher accepted any unknown line, so the marker that followed could
complete it, and assertions only checked that commands were present. An
added `dc` or a repeated `olj` still passed. It now records an unknown line
and exits, and the tests pin the exact command sequence.
Cleanup hung off module state assigned after setup finished, so a failed
setup left its directory and terminal child behind. Each resource now
registers its own finish hook when acquired, using createTestTempDirectory.
Success checks throw the typed error instead of asserting `.ok`, and the
refusal probe no longer sits inside a loop and conditional.
Production code is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(ci): align tests with current evidence contracts
* feat(native): report binary hardening for ELF, PE and Mach-O (#8524)
* feat(native): report binary hardening for ELF, PE and Mach-O
Documents inspect_binary_hardening and its CLI equivalent, including
per-property states, supporting evidence, read budgets, and one report
per Mach-O slice. Explains that missing protection imports remain
unknown, signature presence does not establish validity, and hardened
runtime requires inspect_signature. Adds hardening links throughout the
documentation. The supplied diff also removes the advanced CodeQL
workflow and configuration, along with snapshot-binding mismatch
guidance.
* fix(native): validate declared payloads and dynamic tables before hardening rows
Run the shared range checks for ELF, read imports from the dynamic tables
rather than section metadata, and keep an unresolved symbol count apart from
an absent table. Check every Mach-O command before building rows and retain
only the offsets the rows need.
Make the verifier's readelf output wide, check the emitted format before
parsing Mach-O, and place the sources before the libraries in the link.
* fix(native): share Mach-O segment admission with hardening reads
A segment with a section count its cmdsize cannot hold passed the hardening
reader and could enable restrict_segment. Move the size, section count and
file range checks to a shared admission that readMachoImage and the hardening
reader both use, and pin the ELF load-size case with a regression test.
* fix(hardening): reject Mach-O section payloads past the slice and empty ELF interpreters
Segment admission never checked each section's payload range, so a
file-backed section past the slice still produced rows. Decode sections with
the section header owner and mark the slice malformed when a payload leaves
it; zero-fill and empty sections own no file bytes and are exempt.
admitInterp accepted a NUL first byte in a longer extent, which counted an
empty interpreter as PIE evidence. It now requires a nonempty path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KuWHGvzJkrhCqjtEHUwqyH
* fix(native): read CFG instrumentation from decoded GuardFlags
Use the IMAGE_GUARD_CF_INSTRUMENTED flag that guardFlagRecord already
decodes instead of masking the raw GuardFlags word a second time.
* fix(native): reject ELF table spans a second PT_LOAD remaps
The hardening reader mapped DT_STRTAB, DT_SYMTAB and hash tables through
the one PT_LOAD containing the table's first address. A second PT_LOAD
starting inside the table could map the rest of it from other file bytes
and the reader still reported parsed import evidence.
Cap each mapping at the next PT_LOAD that starts inside it, reject a
requested range that crosses that boundary, and stop hash reads at the
same limit, matching the conflicting-mapping refusal in readElfSymbols.
* fix(native): dedupe ELF note tables only on shared boundaries
The hardening reader dropped any note table whose bytes another table
already covered. A covering SHT_NOTE can hold a non-GNU note whose
descriptor is itself a complete GNU property note, while PT_NOTE names
that inner note directly. Walking only the outer stream missed the GNU
marker and reported CET disabled.
Drop an alias only when it starts and ends on boundaries of admitted
tables, which are note boundaries of the walked streams. Any other
covered alias leaves the feature rows unknown, as a partial overlap
already does.
* fix(native): admit every Mach-O symbol record after a canary match
The Mach-O stack protector scan returned as soon as it found an
undefined ___stack_chk_fail, so a later undefined symbol whose n_strx
lay outside the string table was never checked. The same damaged table
parsed or failed as malformed depending only on record order.
Keep the match and finish the bounded record walk. Records after the
match are only checked against the string table bounds, so their names
are not read or charged to the scan budget. The ELF reader already
admits its whole symbol table before matching names.
* fix(native): accept ELF load aliases with the same file translation
The hardening reader treated every PT_LOAD that starts inside a table,
or that also contains the table's address, as a conflicting mapping.
A load with the same address-to-file translation names the same file
bytes, so tables it overlaps still have one interpretation, and the
canonical ElfLoadMap already merges such spans.
Treat equivalent loads as one interpretation. A load with a different
translation, or the zero-filled tail of an equivalent load, still ends
the usable range, so a remapped table is still rejected.
* fix(native): keep same-span ELF note tables with different alignments unknown
A section and a program header that share endpoints were treated as one
note stream even when their effective note alignments differed. Walking the
same bytes at 4 and 8 can frame different notes, so dropping the second table
could hide a GNU property word and report CET or BTI as disabled.
Require matching effective alignment at both endpoints, and no alignment
change inside the covered span, before treating a table as an alias.
Otherwise mark the feature rows unknown. The fixture's .note.gnu.property
section now declares the class's note alignment, as toolchains emit it.
* refactor(native): read LC_SYMTAB through the shared fixed-word decoder
Move the Mach-O fixed uint32 command layouts out of MachoCommandNames
into domain/apple/machoLoadCommands so there is one owner of those
offsets. MachoCommandNames and the hardening reader now both decode
through it; the hardening reader keeps its own bounds and import policy.
* fix(native): skip Mach-O common definitions in canary imports
A common definition sets N_UNDF|N_EXT like an import but stores its
allocation size in n_value. An object holding only
`int __stack_chk_fail;` therefore reported stack_protector: enabled.
Require a zero n_value before an undefined external counts as an
import, for both nlist widths.
* refactor(native): read linkedit data commands through the shared fixed-word decoder
readMachoLinkeditDataCommand reproduced the dataoff/datasize offsets that
FIXED_WORD_FIELDS already owns. Take the command and decode through
decodeMachoFixedWordFields, as readMachoSymtabCommand does, keeping the
truncation and endianness behavior.
* fix(native): skip empty SHT_NOTE sections in ELF hardening
A zero-sized SHT_NOTE owns no bytes, so the section directory marks it
not file-backed and the hardening reader rejected the whole image as
malformed, while readElfImage accepted the same bytes. Skip empty note
sections before the file-backing check; they have nothing to walk.
* fix(scripts): run hardening verifier commands through the shared owner
The binary hardening verifier started its probes and compiler drivers with
raw spawnSync, which bypassed the src/process/ supervision that AGENTS.md
requires. Every launch now goes through createVerifierCommands, and the
temporary root is removed only after the owner closes cleanly.
* fix(scripts): clean up hardening verifier commands on Ctrl-C
SIGINT or SIGTERM ended Node before withVerifierCleanup ran, so detached
compiler trees and the temporary root could outlive the verifier. The
verifier now turns those signals into an abort that every owned command
receives, so cleanup closes the owner and removes the root before exit.
* fix(scripts): fail the hardening verifier when a signal lands outside a command
Only the owned commands read the interruption signal, so a SIGINT or
SIGTERM during the final cleanup or between commands was recorded and
then dropped, and the verifier exited 0. After cleanup settles, the
verifier now throws the interruption reason. A failure the operation or
its cleanup already raised still leaves first and is not replaced.
* fix(native): fail Mach-O slices with a malformed encryption command
The hardening reader turned every malformed status from the shared
encryption reader into an unknown `encrypted` row, so a short
LC_ENCRYPTION_INFO_64 or an encrypted range past the slice still left the
slice parsed with a full report.
Only two conflicting encryption commands stay an unknown row now. Any other
encryption defect fails the slice like other structural command failures.
* fix(native): ignore STB_LOCAL undefined ELF symbols as imports
A local undefined __stack_chk_fail or __memcpy_chk is never resolved
against another object, so it does not establish an external import.
Skip STB_LOCAL bindings when reading protector signs, and give the
fixture's positive symbols STB_GLOBAL binding.
---------
Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---------
Signed-off-by: Rudy Celekli <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Edgar Arakelyan <[email protected]>
Co-authored-by: ShuaixinHuang <[email protected]>
Co-authored-by: highmachs <[email protected]>
Co-authored-by: hhang <[email protected]>
Co-authored-by: hhang <[email protected]>
Co-authored-by: Paritosh Lahre <[email protected]>
Co-authored-by: Adolanium <[email protected]>
Co-authored-by: 김준호 <[email protected]>
Co-authored-by: t <t@t>
Co-authored-by: Hasan Zayni <[email protected]>
Co-authored-by: Salman Almutairi <[email protected]>
Co-authored-by: Lucien <[email protected]>
Co-authored-by: RRFZ <[email protected]>
Co-authored-by: 2SinceJuly <[email protected]>
Co-authored-by: Wade <[email protected]>
Co-authored-by: Martin Klein <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: Rudy Mizrahi Celekli <[email protected]>
Co-authored-by: xin zhuang <[email protected]>
Co-authored-by: Beqa <[email protected]>
Co-authored-by: NoThing <[email protected]>
Co-authored-by: Sarthak Pandey <[email protected]>
Co-authored-by: akushonkamen <[email protected]>
Co-authored-by: akushonkamen <[email protected]>
Co-authored-by: amyanger <[email protected]>
Co-authored-by: Abel Yagubyan <[email protected]>
Co-authored-by: Abelo9996 <[email protected]>
Co-authored-by: Jiabin Yin <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Mohamed Ayoub Eleuch <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
* fix(cli): reject project-scoped Amp registration
Incur's mcp add parses --no-global but its Amp writer always writes the
global settings file, so a project-scoped Amp request exited 0 reporting
success while changing global config. Fail closed in the entrypoint before
Incur dispatches: project-scoped Amp registration now exits 1 with a
diagnostic and leaves global settings untouched. Global Amp registration
is unchanged.
Fixes#7535
* fix(cli): exempt discovery requests from the Amp project-scope guard
Discovery requests are not registrations: mcp add --no-global with a
discovery flag (--help, -h, --llms, --llms-full, --version, --schema) now
passes through to normal handling instead of exiting 1 with the scope
error, matching the discovery set mcp add itself exempts. Real
project-scoped Amp registrations are still rejected before Incur runs.
Also restores the executable bit on scripts/rea.mjs, and adds launcher
dispatch tests proving the guard rejects a real registration before the
CLI runtime loads and passes discovery requests through.
* fix(cli): share the mcp add discovery classification between guard and routing
The Amp project-scope guard kept its own copy of the mcp add discovery
flags, which could drift from the discovery routing in cli.ts.
classifyMcpAddDiscovery in cliBuiltinArguments.ts is now the single
owner: the guard and the cli.ts mcp add special-casing both consume it,
with bypass/version/schema precedence matching the routing. No behavior
change; covered by classification unit tests and the existing mcp add
discovery serve test.
* fix(cli): emit structured output for Amp project-scope refusal
The Amp project-scope guard wrote a plain message to stderr and left
stdout empty when the caller asked for a structured document (json,
jsonl, yaml), contradicting the failure framing documented for the
output argument. Structured formats now render the refusal through the
same pre-execution error renderer the neighboring output-argument
branch uses, with code AMP_PROJECT_SCOPE_UNSUPPORTED on stdout; every
other format keeps the byte-identical plain stderr message. The guard
still exits 1 before dist/cli-runtime/cli.js is imported.
Co-authored-by: Saim Shafique <[email protected]>
* test(cli): cover Amp project-scope guard through the launcher
The guard tests only called isProjectScopedAmpRegistration, so they
still passed with its call in scripts/rea.mjs removed. Add a launcher
test with an isolated home and working directory that checks the exit
code, the diagnostic, the unchanged global settings, and the absence
of project files for both explicit Amp and default-agent requests.
Co-authored-by: Saim Shafique <[email protected]>
* test(cli): assert Amp project-scope refusal by error code
The --no-global refusal tests locked the full English sentence. Structured output now asserts AMP_PROJECT_SCOPE_UNSUPPORTED and a message field.
* fix(cli): share normalized mcp add agent selection with the scope guard
The project-scope guard scanned raw arguments and only recognized the spaced --agent form, while dispatch expanded --agent= equals forms first: --agent=claude-code was wrongly refused as default Amp, and --agent claude-code --agent=amp slipped Amp past --no-global. The equals expansion and agent scan now live once in cliBuiltinArguments.ts and both callers consume them, so guard and dispatch read the same selection.
* ci(environment): pin the real agentenv lane to agentenv-framework 0.9.1431
The real-agentenv lane, its identity check and the docs pinned
agentenv-framework 0.9.1334, 97 releases behind the current 0.9.1431.
Recorded verification notes keep the versions they ran with; the website
catalogs change only the placeholder comment that shows the version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(environment): describe 0.9.1431's pulled infra images; keep the site on the release
0.9.1431 pulls its gateway and service-db images from ghcr.io by the
digests its release records and builds one only when that pull fails,
so the lane docs, the export guide and the verifier's comments now say
so, and the mirror step fetches only the bases those builds name. The
public site describes the released package, which still runs 0.9.1334,
so its guide and evidence page keep that version until a release ships
the new pin.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(discovery): advertise attached IDA navigation effects
The canonical tool audit now includes the UI and network effects of the attached IDA legacy profile for the four navigation operations. Session inventory still starts from the provider-neutral evidence base and composes the selected provider descriptors, so headless and native sessions do not inherit attached UI state.
Adds SDK/session boundary coverage for all four operations and preserves attached, headless, and native distinctions.
* fix(discovery): cover IDA provider effects across tools and workflows
* test(discovery): isolate IDA metadata from host tool probes
* test(discovery): compare live identity with session effect bases
* fix(discovery): audit complete IDA provider effect union
* fix(discovery): exclude unreachable provider workflow effects
* fix(discovery): propagate IDA effects through composed tools
* fix(artifacts): deduplicate keyed archive path diagnostics
* test(artifacts): use real bundle in provider binding fixture
* test(mcp): align overview inventory fixtures with row contracts
* fix(javascript): align Electron path and owner identities
Refresh archive close and function CLI fixtures for current admission and Result contracts.
* test: align boundary fixtures with runtime prerequisites
* test(javascript): preserve unknown Electron IPC ownership in fixture
* fix: audit IDA network effects during lifecycle cleanup
* test: align close lifecycle hint with network audit
* style(website): format merged Wrangler watch paths
* fix(discovery): retain retry and cleanup effects
* style(website): drop unrelated Wrangler formatting
The watch_dir reflow is whitespace only. The IDA navigation-effect fix stays.
---------
Co-authored-by: dhruvac29 <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
* fix(signals): report a failed sigrok version probe as a configuration error
* fix(signals): attribute a failed version probe to the limited probe
* fix(signals): name the limiter as a possible cause of a failed version probe
The version probe runs under the resource limiter, so a nonzero status
can come from prlimit as well as sigrok-cli. Say so in the configuration
refusal instead of blaming sigrok-cli alone, and cover a limiter that
refuses to start the tool for both decode and discovery.
* test(signals): assert limiter probe failures by typed fields
The limiter-refused recording test locked English probe wording. Assert the capability-unavailable code, provider, operation, and captured limiter output instead.
* fix(signals): record an unattributed version-probe status owner
A nonzero resource-limited version probe now reports version_probe_status_owner limiter-or-tool. The limiter-refusal test requires that field, so the old sigrok-cli-only failure no longer passes.
---------
Co-authored-by: Abelo9996 <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
* feat(native): report binary hardening for ELF, PE and Mach-O
Documents inspect_binary_hardening and its CLI equivalent, including
per-property states, supporting evidence, read budgets, and one report
per Mach-O slice. Explains that missing protection imports remain
unknown, signature presence does not establish validity, and hardened
runtime requires inspect_signature. Adds hardening links throughout the
documentation. The supplied diff also removes the advanced CodeQL
workflow and configuration, along with snapshot-binding mismatch
guidance.
* fix(native): validate declared payloads and dynamic tables before hardening rows
Run the shared range checks for ELF, read imports from the dynamic tables
rather than section metadata, and keep an unresolved symbol count apart from
an absent table. Check every Mach-O command before building rows and retain
only the offsets the rows need.
Make the verifier's readelf output wide, check the emitted format before
parsing Mach-O, and place the sources before the libraries in the link.
* fix(native): share Mach-O segment admission with hardening reads
A segment with a section count its cmdsize cannot hold passed the hardening
reader and could enable restrict_segment. Move the size, section count and
file range checks to a shared admission that readMachoImage and the hardening
reader both use, and pin the ELF load-size case with a regression test.
* fix(hardening): reject Mach-O section payloads past the slice and empty ELF interpreters
Segment admission never checked each section's payload range, so a
file-backed section past the slice still produced rows. Decode sections with
the section header owner and mark the slice malformed when a payload leaves
it; zero-fill and empty sections own no file bytes and are exempt.
admitInterp accepted a NUL first byte in a longer extent, which counted an
empty interpreter as PIE evidence. It now requires a nonempty path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KuWHGvzJkrhCqjtEHUwqyH
* fix(native): read CFG instrumentation from decoded GuardFlags
Use the IMAGE_GUARD_CF_INSTRUMENTED flag that guardFlagRecord already
decodes instead of masking the raw GuardFlags word a second time.
* fix(native): reject ELF table spans a second PT_LOAD remaps
The hardening reader mapped DT_STRTAB, DT_SYMTAB and hash tables through
the one PT_LOAD containing the table's first address. A second PT_LOAD
starting inside the table could map the rest of it from other file bytes
and the reader still reported parsed import evidence.
Cap each mapping at the next PT_LOAD that starts inside it, reject a
requested range that crosses that boundary, and stop hash reads at the
same limit, matching the conflicting-mapping refusal in readElfSymbols.
* fix(native): dedupe ELF note tables only on shared boundaries
The hardening reader dropped any note table whose bytes another table
already covered. A covering SHT_NOTE can hold a non-GNU note whose
descriptor is itself a complete GNU property note, while PT_NOTE names
that inner note directly. Walking only the outer stream missed the GNU
marker and reported CET disabled.
Drop an alias only when it starts and ends on boundaries of admitted
tables, which are note boundaries of the walked streams. Any other
covered alias leaves the feature rows unknown, as a partial overlap
already does.
* fix(native): admit every Mach-O symbol record after a canary match
The Mach-O stack protector scan returned as soon as it found an
undefined ___stack_chk_fail, so a later undefined symbol whose n_strx
lay outside the string table was never checked. The same damaged table
parsed or failed as malformed depending only on record order.
Keep the match and finish the bounded record walk. Records after the
match are only checked against the string table bounds, so their names
are not read or charged to the scan budget. The ELF reader already
admits its whole symbol table before matching names.
* fix(native): accept ELF load aliases with the same file translation
The hardening reader treated every PT_LOAD that starts inside a table,
or that also contains the table's address, as a conflicting mapping.
A load with the same address-to-file translation names the same file
bytes, so tables it overlaps still have one interpretation, and the
canonical ElfLoadMap already merges such spans.
Treat equivalent loads as one interpretation. A load with a different
translation, or the zero-filled tail of an equivalent load, still ends
the usable range, so a remapped table is still rejected.
* fix(native): keep same-span ELF note tables with different alignments unknown
A section and a program header that share endpoints were treated as one
note stream even when their effective note alignments differed. Walking the
same bytes at 4 and 8 can frame different notes, so dropping the second table
could hide a GNU property word and report CET or BTI as disabled.
Require matching effective alignment at both endpoints, and no alignment
change inside the covered span, before treating a table as an alias.
Otherwise mark the feature rows unknown. The fixture's .note.gnu.property
section now declares the class's note alignment, as toolchains emit it.
* refactor(native): read LC_SYMTAB through the shared fixed-word decoder
Move the Mach-O fixed uint32 command layouts out of MachoCommandNames
into domain/apple/machoLoadCommands so there is one owner of those
offsets. MachoCommandNames and the hardening reader now both decode
through it; the hardening reader keeps its own bounds and import policy.
* fix(native): skip Mach-O common definitions in canary imports
A common definition sets N_UNDF|N_EXT like an import but stores its
allocation size in n_value. An object holding only
`int __stack_chk_fail;` therefore reported stack_protector: enabled.
Require a zero n_value before an undefined external counts as an
import, for both nlist widths.
* refactor(native): read linkedit data commands through the shared fixed-word decoder
readMachoLinkeditDataCommand reproduced the dataoff/datasize offsets that
FIXED_WORD_FIELDS already owns. Take the command and decode through
decodeMachoFixedWordFields, as readMachoSymtabCommand does, keeping the
truncation and endianness behavior.
* fix(native): skip empty SHT_NOTE sections in ELF hardening
A zero-sized SHT_NOTE owns no bytes, so the section directory marks it
not file-backed and the hardening reader rejected the whole image as
malformed, while readElfImage accepted the same bytes. Skip empty note
sections before the file-backing check; they have nothing to walk.
* fix(scripts): run hardening verifier commands through the shared owner
The binary hardening verifier started its probes and compiler drivers with
raw spawnSync, which bypassed the src/process/ supervision that AGENTS.md
requires. Every launch now goes through createVerifierCommands, and the
temporary root is removed only after the owner closes cleanly.
* fix(scripts): clean up hardening verifier commands on Ctrl-C
SIGINT or SIGTERM ended Node before withVerifierCleanup ran, so detached
compiler trees and the temporary root could outlive the verifier. The
verifier now turns those signals into an abort that every owned command
receives, so cleanup closes the owner and removes the root before exit.
* fix(scripts): fail the hardening verifier when a signal lands outside a command
Only the owned commands read the interruption signal, so a SIGINT or
SIGTERM during the final cleanup or between commands was recorded and
then dropped, and the verifier exited 0. After cleanup settles, the
verifier now throws the interruption reason. A failure the operation or
its cleanup already raised still leaves first and is not replaced.
* fix(native): fail Mach-O slices with a malformed encryption command
The hardening reader turned every malformed status from the shared
encryption reader into an unknown `encrypted` row, so a short
LC_ENCRYPTION_INFO_64 or an encrypted range past the slice still left the
slice parsed with a full report.
Only two conflicting encryption commands stay an unknown row now. Any other
encryption defect fails the slice like other structural command failures.
* fix(native): ignore STB_LOCAL undefined ELF symbols as imports
A local undefined __stack_chk_fail or __memcpy_chk is never resolved
against another object, so it does not establish an external import.
Skip STB_LOCAL bindings when reading protector signs, and give the
fixture's positive symbols STB_GLOBAL binding.
---------
Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(rizin): keep debug targets off the debugger command stream
A Rizin debug target inherited Rizin's stdin, which is REA's command
stream. Once resumed, a target that reads stdin consumed REA's commands,
so close returned cleanup_incomplete and the one-shot CLI hung.
Route the target's stdin to the caller-selected TTY alongside stdout and
stderr. Before detaching, refuse a target whose stdin is still the command
stream, so a Rizin or backend that ignores the key gets a specific,
retained failure instead of a resumed target and a timeout.
Fixes#8642
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(rizin): keep the stdin test launcher's descriptors under dash
The launcher gated its target with `cat "$2" >/dev/null`. dash applies a
simple command's redirection to the shell itself until the command returns,
so on Ubuntu the waiting shell's fd 1 was /dev/null, and that shell is the
target REA reads in /proc. All 8 Linux-gated tests then failed the existing
output-descriptor check. macOS /bin/sh is bash, which redirects only in the
forked child, so this did not show locally.
Gate without any redirection. Production code is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(rizin): reject unmodeled commands and own cleanup at acquisition
Address review of the stdin regression tests.
The launcher accepted any unknown line, so the marker that followed could
complete it, and assertions only checked that commands were present. An
added `dc` or a repeated `olj` still passed. It now records an unknown line
and exits, and the tests pin the exact command sequence.
Cleanup hung off module state assigned after setup finished, so a failed
setup left its directory and terminal child behind. Each resource now
registers its own finish hook when acquired, using createTestTempDirectory.
Success checks throw the typed error instead of asserting `.ok`, and the
refusal probe no longer sits inside a loop and conditional.
Production code is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(prompts): include existing static artifact investigation routes
* fix(prompts): preserve workflows in existing family scopes
* fix(prompts): require readers for Apple resource routes
* fix(mcp): report target after failed open
* test(session): cover failed open receipt ownership
* test(session): verify restored target receipt
* test(mcp): retain target on snapshot mismatch
* test(mcp): retain oversized open failure receipts
* test(cli): avoid continuing session receipt in one-shot errors
* fix(mcp): report provider after failed switch
* test(ghidra): verify failed open preserves annotations
* test(mcp): join queued receipt request before cleanup
* test(session): assert open failure receipts in lifecycle suites
* test(session): keep open-failure receipt checks within function limits
Move the provider-collision case and snapshot fixture setup out of the
functions that grew when both failure receipts were asserted.
---------
Co-authored-by: Cursor Agent <[email protected]>
Containerized REA reaches an IDA MCP server on the container host through
host.docker.internal (Docker, Podman compatibility) or
host.containers.internal (Podman). The HTTP registration refinement only
admitted localhost, 127.0.0.1 and [::1], so doctor reported the valid
gateway registration as invalid_configuration.
Admit both gateway names in the registration's local-host set. The
refinement still requires http or https, no URL credentials, and refuses
every other host.
Fixes#8925
* fix(javascript): transient Windows private-runtime source-transfer
Parent source-transfer into the Windows private runtime root failed with
EPERM on path-based exclusive create. Write through a native transient
private-runtime path that closes the handle after flush so per-job rm
still works, and yield around the sync write so abort deadlines can fire.
* fix(javascript): yield timer phase before Windows source-transfer write
* fix(javascript): resolve electron findings through aliases of interop results
* fix(javascript): resolve interop aliases iteratively and check target mutations
* fix(javascript): retain possible writes through uncertain aliases and guard positive reads
* perf(javascript): cache alias change status across semantic interop lookups
trace_web_source_location reported a well-formed generated_position past
the end of the selected script as invalid_format on generated_position,
with no bounds, so callers were told their input was malformed and not
which coordinate to change. Report out_of_range on the line or column
that is past the end, with its valid bounds, as export_web_scripts and
inspect_web_network_capture now do for their selections.
The position scan now returns the bound it ran past, so the offset and
the reported limit come from one pass and cannot disagree on line
terminators.
Fixes#7132
* fix(gdb): keep TTY verification across read-only inferior-tty inspection
The console command path cleared `inferiorTtyVerified` before dispatching
any command whose text merely mentioned `inferior-tty`, so the read-only
`show inferior-tty` query permanently revoked a persistent session's
verified output sink; no in-session path ever restored the flag, and a
later close of a live session failed detach verification with
capability_unavailable.
Exempt the exact read-only query forms (`show inferior-tty` and the MI
`-inferior-tty-show`) from the output-mutation predicate; every other
command mentioning the TTY setting keeps revoking, so mutating commands
still fail closed and detach keeps re-reading the live TTY before
detaching. Cover the query forms, composite commands, and the
inspect-then-close detach path with regression tests.
Fixes#5099
* test(gdb): classify show inferior-tty in the one-shot table and rename the TTY classification table
- Add a 'show inferior-tty' row to the 'classifies one-shot GDB
commands' table: the read-only query no longer invalidates the detach
proof, a user-visible one-shot behavior change.
- Rename the 'classifies read-only inferior TTY queries as non-mutating'
table to 'classifies inferior TTY commands' since two of its rows are
mutation cases.
- Assert via the existing recorded-commands array that the inspected
'show inferior-tty' text is actually dispatched, and add an
execute-path assertion that the MI '-inferior-tty-show' form leaves
the verified-sink flag intact (defensive exemption: REA's own MI calls
do not route through this gate).
---------
Co-authored-by: akushonkamen <[email protected]>
* fix(ghidra): report restricted-host temp write denial on Windows
Add a Windows doctor check that creates and deletes a file under os.tmpdir(), and classify a denied java_home.save in Ghidra startup output with its path, Win32 error 5, and TMP/TEMP remediation. The probe covers REA's own process only; a host that restricts only child processes is reported from the child's output at startup.
Fixes#7075
* fix(ghidra): probe the session root and retain failed probe cleanup
The Windows doctor temp_directory probe now writes under ghidraSessionRoot(), the same root Ghidra session runtimes use, instead of os.tmpdir(), which can differ when the inherited temp path has a dot-prefixed element. A probe file whose removal fails is retained on the doctor resource owner so doctor shutdown retries the removal; the result still reports its path.
* fix(jeb): reconnect after stale MCP sessions
* fix(jeb): preserve failures for overlapping MCP requests
* fix(jeb): make cleanup operations explicit and stabilize timeout fixtures
The Windows bootloader sets no execute bit on extracted ARCHIVE_ITEM_BINARY
members (the S_IXUSR fchmod in pyi_archive.c is #ifndef WIN32). Mark type b
members executable only when the carrier dialect is posix.
* fix(decompile): retain inventory limitations with the latest selection
The manifest and decompile index dropped procedure-inventory
limitations, so a directory separated from its Evidence response could
not explain why functions were selected or omitted. Keep those
limitations on the manifest and in a latest_run index record that also
names the filter and counts that produced them. Older runs remain
represented only by their retained function records.
Fixes#8355
* test(decompile): surface export errors and check function caveats
Address review findings on the inventory-limitation regression:
- Throw the typed export error instead of returning from a failed Result.
- Assert historical function records keep their own pseudocode caveats
and do not receive the latest inventory warnings.
* fix(artifacts): own source errors during output setup
* fix(artifacts): join silent source destruction before returning
* fix(artifacts): retain source cleanup failures with primary errors
* fix(artifacts): join source finalization and classify secondary failures
* fix(firmware): separate source cleanup from output rollback
* fix(artifacts): retain live source cleanup dependencies
* fix(artifacts): preserve source failure completion and semantics
* fix(artifacts): retain scope cleanup owners and typed failures
* fix(artifacts): keep cleanup classification and admission recovery
When the operation completed and only scope cleanup failed, keep the cleanup failure's own reason instead of reporting io, matching main (fixes artifactProvider.cleanupRetry). When a typed primary is rebuilt with pending scope cleanup, carry its userMessage and ASAR header admission so callers keep the configured ceiling and setting.
* fix(artifacts): translate plain file-close failures through the reader path
The outer file-cleanup branch rebuilt every ArtifactFileCleanupFailure as a
ProviderAdapterError, so dyld reads lost their io, unavailable, format and
cancelled reasons and their exact file-cleanup cause. Compose separately only
when the failed primary owns its own translation (a domain error, source
failure or scope cleanup failure); raw and reader primaries are already
projected by the file failure with its exact lease.
* fix(artifacts): bound source destruction by cancellation and keep wrapper admission
After cancellation, SafeOutputTree.write could wait indefinitely for a source
whose destruction did not settle, both in the destroy join and in the
iterator's wait for close. Both waits now end at cancellation plus a fixed 1 s
grace, modeled on the process-output closure grace. A destruction that
outlives it is reported as incomplete source cleanup under an exact lease that
releases only once closure is observed without a late error.
ArtifactResourceCleanupFailure and SafeOutputSourceFailure now keep the
primary's admission, matching withCleanup.
* fix(artifacts): keep one settled wait while observing a stuck source
observeClosure raced the same pending close promise against a new timer every
10 ms, so a source whose destruction never settled accumulated one reaction per
poll for as long as it stayed open. Each poll now waits on its own promise that
the close listener or the unref'd timer settles, leaving one pending wait and
one close listener. The lifecycle test also asserts that a late close releases
the retained owner without an explicit retry.
* fix(artifacts): settle pending reads before cancellation
* test(artifacts): observe the consumed ZIP iterator on cancellation
* test(artifacts): validate projected cleanup resources before sorting
* test(mcp): reserve parent capacity for timeout recovery
* fix(artifacts): contain ASAR cancellation before output consumption
* test(artifacts): hash ASAR cancellation fixture bytes
* fix(process): report a deadline that elapses during final cleanup as a timeout
runOwnedCommand checked its absolute command deadline before awaiting
process cleanup, but on the ordinary successful path it consulted only
the caller's cancellation signal afterwards. A command whose deadline
expired while successful cleanup was still awaited therefore resolved
as success.
After cleanup, an elapsed deadline now throws OwnedCommandFailure with
reason "timeout" and the completed snapshot, as docs/tool-design.md
requires a final signal check after the last awaited finalization step.
Existing primary failures, caller cancellation and incomplete-cleanup
classification keep their precedence.
Fixes#7369
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(process): drive the cleanup-deadline regression with controlled time
The regression test gave output collection 100 ms of real time before
the deadline. On a busy worker the deadline could expire during
collection instead, taking a different failure path and failing the
test even with the fix. Use fake timers, wait until cleanup has
started, then advance past the deadline and release cleanup, so the
ordering no longer depends on worker speed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(process): reject captures whose top-level filesystem facts contradict their checkpoints
A process capture records its filesystem state twice: files_before,
files_after and filesystem_effects at the top level, and the initial
and final filesystem_checkpoints. Validation never reconciled the two,
and comparison reads only the checkpoints. A caller-authored capture
recording a created file at the top level while its final checkpoint
was empty was therefore accepted and compared as unchanged.
Semantic validation now requires the initial checkpoint to match
files_before with no effects, and the final checkpoint to match
files_after and filesystem_effects. The native producer builds both
representations from the same snapshots, so produced captures are
unaffected; consistent created-file captures still compare as changed.
Fixes#7371
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(process): cover file-list checkpoint mismatches independently of effects
Every rejected case in the checkpoint-consistency test also disagreed on
effects, so removing both file-list comparisons left it passing. Add
initial and final checkpoint cases whose file lists alone disagree while
effects and coverage accounting stay valid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
find_changed_behavior derived behavior_status from each process
comparison's aggregate status alone. An unchanged comparison whose
first_divergence was unknown (residual unknowns prevent proving no
divergence) and a consistent contradiction comparison both fell
through to observed_unchanged, with no unresolved finding and no
changed-behavior registry unknown.
An unchanged comparison with unknown divergence now counts as unknown
and adds one comparison-level unresolved first_divergence finding
carrying its reason, as artifact comparisons already do for coverage.
A contradiction status makes the aggregate unknown, and contradiction
dimensions are classified and counted as contradictions instead of
unresolved branches. Complete unchanged comparisons stay
observed_unchanged and changed comparisons with unknown localization
stay observed_changed.
Fixes#7384Fixes#7383
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(uninstall): keep a managed cache namespace whole when it holds a caller-selected Kuna cache
purge --purge-data reported a REA_KUNA_CACHE_DIR selection below the default Kuna cache as retained, then removed it together with its parent namespace. A namespace that contains the selection is now retained and reported, so the retained report is true.
Fixes#7344
* fix(uninstall): also keep a namespace equal to, or reached by symlink traversal from, the selected Kuna cache
Compare the namespace and the selection with the same normalization, treat an equal path as contained, and resolve existing paths before planning so a symbolic link followed by .. is judged by where it leads.
* fix(uninstall): judge a selection with parent traversal where the filesystem resolves it
Collapsing .. lexically contradicts the existing rule that parent traversal after a symbolic link leads elsewhere, and broke the retained-through-symlink-traversal test. Compare spellings without .. directly and resolve existing paths for the rest, so the symlink-parent case still purges the default cache while a selection that resolves into a managed namespace is retained.
* fix(uninstall): keep a namespace whole when the selection's location is unknown
A realpath failure other than ENOENT/ENOTDIR (for example EACCES under an
inaccessible parent) no longer counts as "absent". Each namespace that may
contain the caller-selected Kuna cache is retained instead of purged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(adb): name unexecutable adb paths in readiness refusals
When REA_ADB_PATH or the PATH-resolved adb is a directory or a file the
host refuses to execute, the version probe fails at spawn with EACCES or
EPERM. That fell through to the generic "did not report a usable version"
refusal, which named neither the path nor the cause.
Classify those spawn errors as binary_not_executable (provider rejection
executable_missing), name the selected path and errno, and give
remediation for the environment, PATH and host-policy cases. The generic
fallback now names the selected path too.
Fixes#5689
* fix(process): admit synchronous spawn errors as captured failures
Node throws spawn errors other than EACCES, EAGAIN, EMFILE, ENFILE and
ENOENT synchronously from execFile. execFileBytes called execFile inside
its Promise executor, so ENOTDIR or EPERM became a rejection without
capture fields and execFileOutputFailure returned undefined. Consumers
that classify by failure code, such as the adb version probe, could not
see the code, which left the adb EPERM branch unreachable.
Catch synchronous spawn errno errors at the capture boundary and attach
the same empty capture an asynchronous spawn failure carries. Argument
validation errors stay unadmitted.
The adb version probe now reports an ENOTDIR selected path, such as a
REA_ADB_PATH whose parent is a regular file, as a missing binary with
the filesystem error code instead of the generic version refusal.
* fix(process): expose spawn failures from the capture owner
execFileBytes knows when a command never started, but
ExecFileOutputFailure did not say so, so consumers reread raw syscall
and code fields and disagreed. Mark both the synchronous throw and an
asynchronous `spawn <path>` error with `spawnFailed: true`, and set
`path` on the synchronous error to match the asynchronous shape.
Argument validation errors stay unadmitted.
Consumers now use the owner field:
- inspectAdbClient never reports a spawn errno as `exit_code`. ENOENT,
ENOTDIR, ELOOP and ENAMETOOLONG are `binary_missing`; EACCES and EPERM
stay `binary_not_executable`; any other spawn errno, such as EINVAL,
is `binary_not_executable` with a path-naming "could not be started"
message. Each records `filesystem_error` and `path_source`. EACCES
remediation also names parent directory search permission, and EPERM
remediation no longer names host policy as the only cause.
- DarwinProcessRunTokenReader tags every not-started compiler as
`spawn_failed`, not only ENOENT. EACCES, EPERM and ENOTDIR were
previously `failed`.
- NativeDmgArtifactReader uses one not-started rule for both
`processNotStarted` (previously `spawn ` only, so synchronous errors
were missed) and the ENOENT unavailable/io split (previously any
`spawn` prefix). Its fake hosts now reject with real execFileOutput
spawn failures.
* fix(process): classify spawn failures consistently across consumers
Admit synchronous and asynchronous spawn errnos through one helper that
requires an errno and a `spawn` or `spawn <path>` syscall. The
synchronous shape now also gains `spawnargs`, so both shapes match.
Use the owner's `spawnFailed` fact in the remaining consumers:
- inspectAdbClient reports exhausted host capacity (EAGAIN, EMFILE,
ENFILE, ENOMEM) as a host failure to retry and no longer tells the
caller to change REA_ADB_PATH. Path-resolution errnos come from a
predicate the filesystem owner exports.
- runAdbCapture says adb could not be started instead of claiming an
exit code for a process that never ran.
- The Darwin compiler probe words every spawn_failed outcome as a start
failure, not a compilation failure.
- Asset catalog inspection treats any assetutil that never started as
unavailable, not only ENOENT.
* fix(process): report host spawn capacity as a retryable constraint
Spawn errnos that report exhausted host capacity (EAGAIN, EMFILE, ENFILE,
ENOMEM) were classified by adb readiness alone. assetutil reported them as
an unavailable tool, which marked inspect_asset_catalog unavailable in the
session, and adb commands reported them as protocol failures.
Classify them once in the process capture owner and report a retryable
capacity constraint with recovery advice from assetutil and adb commands.
* docs(adb): describe host capacity failures from device operations
* fix(adb): report exhausted host spawn capacity as runtime unavailability
A host that cannot start adb for lack of process, memory or descriptor
capacity is not a configuration problem. Give it its own failure code and
map it to the runtime_unavailable provider rejection instead of
not_configured.
* fix(adb): report probe-time host capacity as a retryable constraint
A device operation whose adb version probe could not start for lack of host
capacity was refused as capability_unavailable without retryable. It now
reports the same retryable capacity constraint as a later adb command.
* fix(adb): keep the selected adb path in probe-time capacity errors
The retryable capacity constraint raised at the version probe dropped the
selected_binary and path_source diagnostics the refusal used to carry.
* refactor(adb): keep the spawn-failure fact to adb consumers
Restore the DMG reader, Darwin compiler probe and assetutil to main. The
capture owner still marks a never-started command, including the spawn
errors Node throws synchronously, and only the adb readiness probe and
adb commands read that fact.
* test(adb): assert spawn-failure facts instead of refusal wording
The new adb tests compared complete refusal sentences and banned an old
phrase. They now check the errno, selected path, path source and the
owner's spawn-failure marker, so wording edits do not break them.
* fix(adb): own one host-capacity remediation and cover the spawn route
The host-capacity constraint and the configuration failure each carried their own remediation sentence. AdbConfiguration now owns the text and both use it, so the constraint's remediation.action equals diagnostics.remediation.
The synchronous spawn-throw test now runs on Node's execFile route and on the owned spawn route adb uses (stopSignal). The provider test covers inspect_adb_client under EMFILE next to list_adb_devices, and the docs say which operations report the retryable capacity constraint and which report runtime_unavailable.
* fix(process): report EMFILE and ENFILE spawn failures without stdio
After an EMFILE or ENFILE spawn failure Node creates no stdio, so the
owned stop-signal route threw a TypeError while reading the streams and
the later spawn error went unhandled. Attach the error listener first and
capture absent streams as empty, so adb reports retryable host capacity.
* fix(adb): keep a completed push when host capacity stops its digest check
A capacity failure starting sha256sum came back as a retryable error even
though adb push had already written the file. Repeating the request with
overwrite=false is then refused because the destination exists.
Return the receipt with an unavailable device digest and name the capacity
failure in its limitations, so the caller is told the transfer is unverified
rather than advised to retry.
* docs(adb): list host capacity as an unverified push case
---------
Co-authored-by: t <t@t>
* fix(cli): parse environment-serve --port and --host like rea mcp --http
environment-serve declared --port as a number, so incur coerced the flag text with Number(): an empty --port= bound an ephemeral port instead of failing, and 0x1F90 or 1e3 were reinterpreted. --host accepted whitespace. docs/cli.md says these flags resolve like rea mcp --http, which accepts only decimal port text and a non-empty trimmed host. The flags now use the same rules and are refused before binding.
* refactor(cli): reuse the shared MCP listener port and host schemas
* fix(setup): replace the fixed Hopper download deadline with a stall timeout
Hopper package downloads aborted after a fixed 120 s, which was also the
largest deadline the download owner accepted. The current 32 MB Linux
package needs about 132 s at the reporter's sustained 245 KB/s, so setup
could never finish on that connection, and the timeout surfaced only as
Node's generic abort message.
Abort a download when no response bytes arrive for 30 s, or when it
outlives a 15-minute overall bound, with a tagged
HopperDownloadTimeoutError that names the URL, the limit reached and the
bytes received. Linux and macOS setup already relay a thrown download
error's message; the Linux publisher now keeps it for release metadata
and public key fetches instead of a generic verification failure.
Metadata and key fetches keep their 10 s bound.
Refs #7077
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(setup): keep the macOS release feed on the metadata limit
The stall-timeout change left the macOS release feed fetch on the
package download bounds. That fetch could then wait up to 15 minutes
instead of the 120 s it had, while the installation guide says release
metadata keeps a 10 s limit. Only the Linux publisher owned that limit.
Move the 10 s metadata limit into HopperDownload.ts beside the package
bounds. The Linux feed and key fetches use it, and the macOS installer
now passes it for its feed fetch through the installer host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture a Linux desktop application in use
Add `capture-desktop-application`, a CLI-only, Linux and X11 capture that
launches an installed desktop application, watches it while it is used and
writes capture Evidence plus SHA-256 addressed content:
- inventory: desktop entry, launch command, executable, package and its
apt sources (or snap, flatpak, AppImage), Electron markers and candidate
state directories;
- session: the launched process tree, client windows from xprop with the
first application window time and a window match, and files held open;
- state: a stat walk of the home directory before launch, at checkpoints
read from stdin, at stop and after exit, with created, modified, deleted
and retyped paths and the content of changed files and state directories.
It refuses non-Linux hosts, missing DISPLAY or xprop, a non-empty output
folder and an application whose window is already open, and signals only
processes it started.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): own launched processes, confirm walk coverage and keep cancelled captures
- Launch through the owned provider path with a run token; release with
SIGTERM, then the token-verified group cleanup, after every session.
Processes without the token are never signalled; each records whether it
carries it, and the session records whether cleanup was verified.
- Report creations and deletions only where a walk listed the nearest
existing directory; count the rest as unconfirmed.
- Retain a file only when it is still the walked version, through owned
no-follow reads; charge the budget only for new content; create files
exclusively in a private output folder owned by the caller.
- Walk content roots outside the home directory, compare paths in their
physical spelling, and run executables from the caller's directory.
- Refuse when the window list cannot be read, re-evaluate window ownership
each sample, handle one queued request per sample with a bounded stdin
queue, and write the capture with end reason `cancelled` on interruption.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep owned cleanup failures and fresh digests
- Allow a capture cancelled before any requested checkpoint to hold only the
baseline.
- Retry an incomplete stop once; if cleanup is still unverified, write the
capture and fail with the run token and process group as cleanup
resources.
- Classify path changes after a walk (ENOTDIR and other selected-file
errors) as changed_since_walk instead of failing the capture.
- Treat a non-directory ancestor as proof that nothing existed beneath it.
- Report a before digest only for the file version it was read from.
- Derive stderr_truncated from stderr's own byte counts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* wip(desktop): state view over a capture folder
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep failed file-handle closes owned for retry
Content reads pass retainFailedClose, so a handle whose close fails stays
owned by the content store; the capture retries those closes after it is
written and fails with the output folder as the cleanup resource if one
still cannot be closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): read a captured application's state
Add inspect-desktop-application-state, a CLI command that reads a capture
folder into the application's state: every retained or changed file with
its role and reasons, a typed JSON view of each version (SQLite through the
shared worker, dconf through a private dconf dump, JSON, XML, key files,
text) and the kinds of change between versions (lines, values, keys, rows).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report process and handle cleanup failures together
When both the launched processes and a retained file's handle remain
unresolved after the capture is written, one cleanup error now lists both
reasons and both resource sets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): export a captured application as an agentenv computer app
Add export-desktop-application, which writes an agentenv bundle with one
computer_app environment from a capture folder and its state view: the
apt package, a boot launch matched on the captured window class with a
window ready check, owned paths from the state roles, a seed of the
baseline files, data hooks that call agentenv's add/get/reset helpers
with a typed JSON view on get, a skill, a README and a deploy smoke task.
The desktop CLI commands now load their services lazily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep state views faithful to the capture
- Read key files, XML attributes, dconf groups and SQLite tables into
dictionaries without inherited properties, so captured keys such as
__proto__ stay data.
- Version databases with their write-ahead log as of each checkpoint, and
stage only digest-verified bytes for the SQLite and dconf readers.
- Detect the format per version, report format changes, keep JSON null and
empty containers as values, keep XML text and preserved whitespace, and
mark truncated views (including XML depth) and comparisons of them.
- Number versions and changes by checkpoint index, decide baseline
presence from the captured type, and keep tagged SQLite integers, text
bytes and blobs distinct.
- Carry the capture's limitations and omitted content forward, match state
directories by their home path, index changes once per capture, keep
failed handle closes owned, and finish the CLI through the shared owner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep coverage, logs and handles honest in the state view
- Read coverage flags only from formats whose readers write them, so JSON
fields named truncated stay data.
- Fail a SQLite version whose write-ahead log existed but was not retained
or does not match its digest, instead of reading the database alone.
- Close retained content on every exit, including cancellation, and report
a close that still fails together with the primary failure.
- Finish the CLI with the canonical operation name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export only what the computer can install and seed
- Refuse packages not from the Ubuntu 24.04 archive, since the computer
installs by name from that archive.
- Start the launch command in the captured working directory.
- Respell the home path only in files the state view read as text.
- Report files that were not retained instead of dropping them silently.
- Load captured dconf keys for apps that own no files.
- Keep home-level files as owned paths; rename reserved Python class names.
- Mark truncated SQLite and text views in the class's get view, and read
file types the way the state view does.
- Release the output tree through a resource scope, and read seeds through
an owner closed on every exit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep staged folders owned, symlinked state roots and XML values
- Stage database bytes in a private runtime root released through a
resource scope, so a failed removal keeps its owner and is reported with
the read's outcome.
- Report cleanup facts and owners from desktop failures instead of
wrapping them as a plain adapter error.
- Record the state directories as walked, in their physical spelling, and
match roles against both spellings, so files in a symlinked state
directory are not mistaken for selected documents.
- Keep whitespace-only XML leaf values; drop only formatting between
elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read XML in the exported app the way the state view does
Keep whitespace-only leaf values and xml:space="preserve" text in the
generated get view; drop only formatting between elements.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): release retained handles with their owners, read older captures
- Release retained-content handles whose close failed through a resource
scope on every exit; a release that fails again reports the exact handle
owners. A read that finished keeps its bytes when only its close failed.
- Accept captures written before state directories were recorded, and say
in the state view's limitations that symlinked state directories may be
misclassed for them.
- Compute each XML element's whitespace context once instead of scanning
siblings for every text run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): boot without the working folder, bound XML, accept mirrors
- Try the captured working directory only when an argument is relative,
and start the app even when that folder is missing.
- Cut the generated XML view at REA's node and depth limits, and keep one
unreadable file from failing data/get.
- Accept Ubuntu 24.04 mirrors, not only the canonical hosts; PPAs and
other repositories are still refused.
- Read seeds through the retained-content owner's new signature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): state view review round
- Stage a retained -journal beside its database so the SQLite worker
accepts an inactive journal and refuses a hot one; a -wal or -journal
that existed but was not retained (including one omitted at the
baseline) makes the version unreadable.
- Keep tables that read when another fails, and let cancellation and
unfinished cleanup propagate instead of becoming view errors.
- Read text as a key file only when every line parses as one.
- Rank a file named in a window title or selected by the caller as a
document even when the application holds it open; match titles on
file-name boundaries; leave the display name out of dconf matching.
- Bound JSON and key file views (4 MiB, 500 levels, exact numbers) and
report too-large, missing and altered retained content separately.
- Add no version or change while a database stays absent.
- Read staged databases in place instead of copying them again.
- Give Evidence the same limitations as the result.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): export review round
- Load captured dconf keys without D-Bus: install dconf-cli, compile the
existing and captured key files into the user database, and warn on
failure instead of stopping the server.
- Start relative-argument launches in the captured working directory,
the home included, since the framework launches from the app folder;
respell the home itself and option values under it.
- Read views in a thread, skip files over 64 MiB, align the generated
readers with the state view's, and close SQLite connections.
- Never own the home or a shared base folder; map a symlinked state
directory's files back to their home spelling.
- Report omitted files and unretained opened folders.
- Name the module so it cannot shadow another, and keep the class off
the module's own names; derive names the bundle check accepts.
- Write seeds as they are read, and remove an unpublished bundle on
failure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): capture review round
- Ask the launch process group and token carriers to exit before
killing them, on errors too; report incomplete cleanup with its facts.
- Count descendants of the launch as started by the capture even when
their environment cannot be read; treat a reused pid as a new process.
- Follow symlinked desktop entries; name a flatpak's state after its
application id; match flatpak windows by name only.
- Stop on end of input; refuse empty, duplicate and reserved checkpoint
names; require whitespace after the checkpoint keyword.
- Stay on each walk root's device, and never walk or retain `/` or a
directory containing the home; say so in the limitations.
- Refuse a window manager that does not publish _NET_CLIENT_LIST.
- Remove retained content when a capture fails before it is written.
- Skip reading a file larger than the remaining content budget.
- Record apt's archive origin for each package source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read an unset client list as no windows when supported
xfwm4 leaves _NET_CLIENT_LIST unset while it manages no windows, so the
capture refused an ordinary desktop with nothing open. Read
_NET_SUPPORTED with the list: an unset list means no windows when the
window manager supports it, and the capture is refused only when it
does not. `xprop`'s "no such atom" output now reads as unset too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): accept packages by apt's archive origin
Accept a package source when apt reports its origin as Ubuntu for a
noble suite, so mirrors count and PPAs or other repositories that merely
have "ubuntu" in their URL do not. Captures made before origins were
recorded fall back to Canonical's own archive hosts. Seed reads report
why retained content could not be used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps from vendor apt repositories
A closed-source app installed from its vendor's apt repository, such as
Typora, can now be captured and exported as a computer app.
- The capture records each non-Ubuntu repository listing the installed
version (uri, suite, components from apt's sources files, one-line or
deb822) and the key that signs it: the signed-by key, or the trusted
key that gpgv shows verifies the repository's signed index. The key
is retained with the capture.
- The capture records the packages owning the libraries the executable
loads (DT_NEEDED with RUNPATH and the linker cache, through REA's ELF
readers), since a vendor package may not depend on them.
- The export declares a verified vendor repository as an AptSource with
its key, installs a vendor package's library packages, and adds
--no-sandbox to an Electron app's launch, since a container denies
Chromium's sandbox.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): report a locked database at once in data/get
A database the running app holds locked made each data/get wait sqlite's
five-second busy timeout before reporting it. Record the Typora run in an
agentenv computer in the verification.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): detect key files without splitting the whole text
Read lines one at a time and stop at the first that a key file view
would drop, instead of splitting a file of up to 64 MiB into arrays
before its format is known.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep symlinked homes and state folders under the home in exports
- Respell a path under its state folder's logical spelling only when that
spelling is under the captured home, so a symlinked HOME keeps its
files; pair walked and logical state folders only when the capture
lists the same number, and match the innermost folder first.
- When two files map to one home path, keep the one present before the
capture and name the other in the limitations.
- Read key files in the generated data/get with the state view's rule,
checked against it by running the helper under python3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): end captures on zombies and keep cleanup owners
- A zombie or dead process no longer keeps the session open.
- Remove only the partial capture file this run created.
- Keep the exact owners of handles and processes whose cleanup failed on
the returned error, so a retry can release them; test the published
capture's cleanup-failure branch with mixed outcomes and retries.
- Release the application once even when the exit checkpoint fails.
- A cancellation before the capture is written removes what it retained.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): vendor package review round
- Match apt sources through `apt-get indextargets`, which covers flat `./`
and `/` suites and gives each release file and sources entry.
- Accept a key whose signature verifies even when the release carries
another signature without a key here; keep only the verifying key from
a keyring holding several (OpenPGP packet splitter).
- Install the package from its repository's release (`name/<release>`),
declare one source per URI and suite, support only the capture host's
architecture, and refuse Ubuntu Pro repositories.
- List only library packages the computer can install; walk libraries only
for vendor installs, with RPATH inherited as ld.so does, normalized
paths, and DT_NEEDED bounded while reading.
- Put --no-sandbox after the program an env wrapper launches; keep the
gpgv folder's owner when its removal fails; respect --max-file-bytes for
keys; wait 0.25 s on a busy SQLite database in data/get.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): keep the vendor spec's gpgv command on one line
A code span broken across lines left `<binary keyring>` for VitePress to
read as an unclosed HTML tag, so the docs build failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): add a validated task to exported desktop applications
Each capture can now yield one agentenv task in its exported bundle. An
agent writes the task (a prompt plus declarative checks over the app's
data/get views); REA validates it and stays model-free.
- `describe-desktop-application-task` summarises a capture's net change
per file (role, format, views before and after) and drafts checks.
- Checks: file presence, lines present/absent/counted, JSON-pointer values,
SQLite rows and counts. REA keeps a task only if a check fails on the
seed and every check passes on the captured end state; a file that was
not retained at the baseline counts as absent, as the computer starts.
- `export-desktop-application --task` writes tasks/NAME.json (deploy,
agent, skills, recording, prompt, verifier), a compiled verify.py that
reads data/get, and an eval. A boundary test runs the verifier under
python3 and checks it agrees with REA's evaluator on every check kind.
- Docs, spec and skill guidance, with the solve-rate recipe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the seed test in its own block
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task verifier driven in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the Typora task run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep task checks bounded, complete and attributable
- line_count counts lines holding a substring (contains) instead of
matching a regular expression, which could stall the verifier on
agent-written text and read differently in Python.
- value_equals of a whole object or array is undecided on a truncated
view.
- The draft keeps a database whose rows changed under an unchanged hash,
and a file whose truncated views match while it changed.
- A task file that cannot be read keeps the reader's detail, and a failed
cleanup keeps the read failure as its primary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): leave a file with one version out of the task draft
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): update the task verification's test counts
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record the task solve rate with Claude Code and Sonnet 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(desktop): capture and export apps installed from a downloaded .deb
capture-desktop-application --package-file reads the file's control fields
with dpkg-deb and records its SHA-256, size and dependencies when it holds the
installed package; the file itself is never kept. The library walk now also
runs for local installs.
export-desktop-application --package-url declares agentenv's
Download(url, sha256) for that file, its dependencies and library packages as
apt packages, and a run script that installs it with dpkg.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): seed app-named dotfiles and respell UTF-16 seeds
The capture now treats dotfiles in the home named after the application
(.varicad-cfg.ini, .notesrc) as state roots, so a file the app only reads
during the capture is still seeded. The exported seeding respells the
captured home in UTF-16 text files with a byte-order mark, keeping the mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): hold readiness until the app's own window shows
An exported app is ready only once a window of its class that the taskbar
lists is shown, so a splash screen of the class, which skips the taskbar, no
longer counts as the app being up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(desktop): record VariCAD captured, exported and run in an agentenv computer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep exported views strict JSON for infinite and inexact numbers
The exported data/get spells infinite and negative-zero SQLite reals as REA's
SQLite reader does ("Infinity", "-Infinity", "-0"), and refuses a JSON file
holding a number that reading would change (1e999, integers past 2^53, -0,
rounded fractions) with the state view's own error, instead of a view with
an infinity in it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep package-file facts tied to one file and URLs unechoed
- A --package-url refusal never repeats the URL, and credentials are checked
before the scheme.
- The package file's identity must hold between dpkg-deb's read and the hash.
- A failed close keeps its cleanup owner even when the capture is cancelled.
- Readiness also ignores windows typed as splash screens.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): record database logs and keep state changes distinct
- A selected path that is not a directory brings its -wal and -journal as
content roots, so the capture records whether they exist and retains them.
- The state view treats a file as absent only under recorded content roots
and refuses a database whose log presence is unknown.
- JSON changes list containers too, so an array that becomes an object with
the same members is a change.
- Key file entries escape the key as a JSON pointer segment, so [a/b] c and
[a] b/c stay apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep watching through unread samples and late workers
- An unreadable window list is unknown, not zero windows, and is named in
the limitations.
- Before ending the session, live run-token carriers no parent link reached
are tracked; an unreadable process table leaves the exit unknown.
- An env prefix's assignments find the program and name its XDG folders.
- flatpak run options that take a value are skipped with it, -- is honoured
and a /arch/branch suffix is dropped from the id.
- State directories include the entry's full id.
- Path options refuse empty values; roots count toward the entry limit.
- A capture file whose close failed stays owned and is retried on discard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): bound apt sources, keep older captures and cleanup owners
- Sources files parse into one stanza per line or deb822 paragraph with its
URIs and suites kept as lists, so no URI x suite product is built.
- Repository architectures and release are optional, so captures written
before they were recorded parse; the export treats them as unrestricted
and unknown.
- Library sources are matched against every index of a declared
repository's URI, suite and components.
- Two failures side by side keep both causes and both cleanup owners, and a
host file whose close fails reports a lease on its handle.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): match escaped key file entries and suggest containers through members
- The draft's key file lookup uses the state view's escaped `group/key`, so a
key holding `/` or `~` still gets its suggestion.
- JSON and XML changes now list containers too; the draft suggests an added
or removed container through its listed members, and a container only
when none of its members is listed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): read relative PATH entries from the launch folder
A program, or an empty or relative PATH entry, is resolved from the folder the
program starts in, as the launch does, so `env PATH=. app` with the entry's
Path still finds the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): compare JSON views on one shared path
JSON and XML changes are found by walking both trees with one shared pointer
path, building a pointer only for a listed value, so memory follows depth
rather than every container's full path. A pointer over 4096 characters is
counted but not listed. A 256-level document with 4000-character keys went
from about 251 MiB of heap to under 1 MiB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep JSON Lines as text in the exported reader
The exported reader parses the whole text before an inexact number makes it
JSON with an error, so JSON Lines with an integer above 2^53 stay text as in
REA. Over-long integer spellings are unsafe without converting them. The
guide no longer claims large integers keep their value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep the state-path help free of dash-led words
The CLI alias check reads `-wal` and `-journal` in help text as short flags,
so the help names the write-ahead log and rollback journal instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never record or export apt source credentials
- Source URIs lose any user name and password as they are parsed, the way apt
splits them, and so do index-target URIs and descriptions and the sources
apt-cache policy lists, so matching works whether or not apt prints them.
- A repository whose entry held credentials is marked
`uri_credentials_removed`, and the export refuses it, or an older capture
whose URI still holds them, with a reason that names no URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up without normalizing their paths
Program paths and PATH entries are joined rather than resolved, so `..` after
a symbolic link means what it means to the launch. The lookup moves to its own
module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): note inexact JSON numbers with a flag, not a list
The exported reader only needs to know whether any number was inexact, so it
keeps a boolean instead of every rejected spelling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): refuse older captures that still hold apt credentials
A capture written before credentials were removed counted the credentialed
repository's libraries as installable, so the export refuses it whole and asks
for a new capture rather than declaring packages no source offers. Recorded
policy sources are redacted in export messages too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(desktop): keep the credential fixture on an example host
A login on a real host reads as a secret to the commit scanner, so the older
capture's fixture uses apt.example.com like the other credential tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): tidy looked-up paths and search relative entries only for env
Empty and `.` segments are dropped from a looked-up program path, which keeps
`/snap/bin/` entries recognisable as snaps, while `..` is still left for the
kernel. Relative and empty PATH entries are searched only in the PATH an `env`
prefix sets, which travels with the exported launch; the host's own PATH does
not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): keep excluded and unwalked database logs unknown
The capture records the paths every walk left out, and the state view treats
a log as absent only under content roots the baseline walked completely and
outside those exclusions, so an excluded or unvisited log leaves the database
unread rather than read without it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): judge JSON numbers after duplicate keys settle
The exported reader marks an inexact number instead of flagging it during the
parse, then looks for marks in the parsed value, so a number a later duplicate
key overwrote no longer refuses the file, as in REA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): redact a repository's policy source when it names it
An older capture whose policy source matched no sources entry kept that text
whole, and the export named the repository by it; the export now names it
without any user name or password.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): never signal the launch group on unknown membership
When the current read cannot observe a carrier's process group, the carrier
is signalled on its own instead of letting an older process-table row grant
a group-wide SIGTERM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): look programs up in the launch's order and refuse host-relative finds
The lookup searches PATH as the launch does, an empty entry meaning the
launch folder, so inspection and launch name the same program. A program found
only through a relative or empty entry of the host's own PATH is refused,
since the exported computer would not have it; an env prefix's PATH travels
with the command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): count unchecked paths and keep logs behind them unknown
Each walk counts the paths it could not stat and the directories on another
device it did not enter, and the checkpoint records it as `unchecked_paths`.
The state view treats a log as absent only when the baseline left nothing
unchecked, so a log whose stat failed stays unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* perf(desktop): scan parsed JSON with one iterator per level
The check for marked numbers walks the parsed value with an iterator per
nesting level instead of copying each container's children; a million-entry
array went from about 15.7 MiB to 8.1 MiB of peak traced memory.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(desktop): start a relative program from its captured folder
An exported launch whose program path is relative, such as `./app` from an
entry's Path, now changes to the captured working directory first, as it
already did for relative arguments.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(mcp): guide retained evidence input recovery
* style(mcp): format catalog and server types
* test(mcp): verify HTTP capacity recovery guidance
* test(mcp): model catalog recorder imports in cleanup fixtures
record-mcp-catalog.mjs loads tool contracts and the Evidence-input
contract before it registers cleanup owners. The cleanup fixture still
sentineled only the previous imports, so those loads failed before any
owner wrote cleanup.jsonl. Track the script's read() calls and replace
the new modules with the empty contract list this fault injection uses.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(mcp): report selection after failed target opens
* fix(mcp): retain resource root selection in open failures
* test(session): assert selection during cleanup failures
* test(mcp): delay failed open response across target switch
* test(session): distinguish no selection from pending cleanup
* test(session): assert selections during cleanup and cancellation
* test(mcp): join the failed-open gate before restoring its spy
The snapshot-switch gate waited only on its acknowledgement, so a request
that settled early hung until the runner timeout, and a later assertion
failure restored the spy while that request was still in flight. Race the
acknowledgement and join the launched open in finally before mockRestore.
---------
Co-authored-by: Cursor Agent <[email protected]>
* feat(native): page retained feature traces
* fix(snapshot): retain linked source evidence for replay
* test(snapshot): verify linked foreign evidence stays scoped
* docs(mcp): clarify imported residual questions
* docs(mcp): guide trace reference classification
* fix(cli): report result sources when Evidence publication fails
A trace keeps linked sources on the result. Use those records for the
undelivered source closure as well as the bundle, so a refused output
does not report them as not captured.
* ci: rerun pull request checks
The merge of origin/main did not start the CI workflow for this head.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(native)!: preserve value-flow absence semantics
* test(native): compare value-flow reasons with their owners
Stop freezing the unrecorded and Kuna unavailable sentences. Assert the
procedure-scoped unknown and that an explicit provider reason is preserved.
---------
Co-authored-by: Cursor Agent <[email protected]>
* feat(artifacts): inventory and extract ESP32 firmware images
ESP32-family app and bootloader images (magic 0xE9) had no reader, so
open_binary refused OTA downloads, app partitions and bootloaders.
Add the esp-image container format behind inspect_artifact and
extract_artifact. A pure decoder reads each byte once and keeps only
fixed-size fields. Derived facts cover the chip, from a table cited to
ESP-IDF v6.1; segment classification as ESP-IDF's should_map and
should_load; checksum and appended SHA-256 results; app and bootloader
descriptors; Secure Boot v2 signature blocks, observed and unverified;
and undescribed trailing byte ranges. Truncation and undescribed bytes
make coverage unknown. ESP8266 images are refused with a specific
reason, and ciphertext stays unrecognized.
Add the format to every target, Evidence, snapshot, live-summary and
artifact vocabulary, document it, and add an optional verify:esp-images
lane that compares esptool-built images with esptool image-info and
espsecure signature-info-v2.
Refs #3615
* test(artifacts): run the ESP image lane on esptool v5.6.0
The lane compared descriptor fields only when esptool image-info printed
one, so a descriptor that REA reports and esptool does not print passed
without comparison. Compare the descriptor kind in both directions.
Record that the lane passed for all 13 images with esptool and espsecure
v5.6.0, the version the layout facts cite, as well as with 4.12.0.
* refactor(artifacts): define ESP image layout sizes once
The decoder and the derived facts each defined the segment-header,
appended-digest and flash-sector sizes, the erased-byte scan and the
offset formatter. Define them once with the other fixed-layout structures
and import them in both places. The signature slot count is the one
constant that drives the slot scan.
Correct the observedEnd comment: it is the end of the last chunk read.
* fix(artifacts): report ESP-IDF's segment length limit by chip
ESP-IDF's verify_segment_header rejects a segment of 16 MiB or more
unless the chip maps flash through 32-bit cache addresses
(SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP: ESP32-S3, P4, C5, C61, H4 and
S31 in v6.1). Record that capability in the chip table and add a
limitation for such segments; unknown chips get none.
`loaded` repeats should_load. It does not mean the bootloader accepts the
segment: verify_load_addresses still rejects one outside the chip's RAM,
which REA does not model. Say so in the docs, the skill reference and the
inventory limitation.
* test(artifacts): check ESP lane tools first and verify extraction
The lane started work before checking its tools, so a missing RISC-V
target, ld.lld, esptool or espsecure surfaced as a raw spawn or command
error. Check each one before building and name the variable to set. Warn
when esptool is not v5.6.0, the release the layout facts cite.
Assert each segment's class for the ESP32-C3 app, its variants and the
bootloader, as the Xtensa image already did. Extract the app and the
signed image through the CLI and compare every file with the bytes at
esptool's segment offsets or in the appended signature sector; the
source's SHA-256 must be unchanged and equal the reported root_sha256.
* docs(artifacts): state what the ESP image lane does not establish
The Verification section listed the lane's checks but not its gaps: only
ESP32-C3 and ESP32 images come from a real producer, no real project
build, OTA download or flash dump is inspected, no signature is verified,
and the lane does not run in CI.
Record the timing of the final lane run on esptool v5.6.0.
* fix(artifacts): compare ESP segment declarations
Segment entries hash only their data, so compare_artifacts reported a
segment that moved to another load address with the same bytes as
unchanged; only the image itself changed. Compare each segment's
load_addr and data_len as its declaration, as Node SEA and PyInstaller
members are compared, and count a changed declaration as a metadata
change, also when content is unavailable.
Header offsets are layout and the classification is derived, so neither
is compared.
* test(artifacts): run ESP lane commands as owned process trees
The lane ran its tools through execFile. A timeout killed only the direct
child, so a linker or assembler that Clang or GCC had started could keep
running while the lane removed its workspace. Run every command through
createVerifierCommands, check the tools inside withVerifierCleanup, and
remove the workspace only after commands.close() has stopped every tree;
a failed close keeps the workspace.
Only a nonzero exit now counts as a refusal or an expected esptool
failure; timeouts and spawn failures propagate. Supervising each command
roughly doubles the lane's run time.
* test(artifacts): cancel ESP decoding after its first decoded chunk
The cancellation test requested cancel or close on setImmediate, which
can land while the file is still being opened and checked, so it never
showed that decoding had started. It also passed against a reader that
ignored both requests while decoding. Acknowledge the decoder's first
chunk, then cancel or close, join the operation, and assert that no
later chunk reaches the decoder and that the reader ends closed.
* docs(artifacts): record which ESP metadata comparison ignores
Segments are compared by their declaration. The image's and the
signature blocks' format_metadata are not: every field comes from their
own bytes, which content comparison already covers, and a block's
image_digest_matches follows the image's content.
* feat(artifacts): inventory and extract ESP flash layouts
A whole-flash dump of an ESP32-family chip was read as its bootloader
image, with the partition table, apps and data left as undescribed bytes,
or refused when it began with erased flash. Read it as an esp-flash-image
container instead.
The partition table is decoded as esp_partition_table_verify accepts it
and is found at the caller's partition_table_offset, at the default
0x8000, or by scanning 0x1000-aligned sectors for a table whose MD5
verifies, which the inventory reports as derived. The bootloader is
located at its chip's ROM bootloader offset. Each partition is an entry
with its declaration in flat format_metadata; the bootloader and app
partitions carry ESP image facts from the existing streaming decoder; the
otadata records are reported with a selection derived without rollback or
anti-rollback, and the running partition stays unknown. One unreadable
partition does not hide the others, and extraction writes every partition,
nvs_keys included, as ordinary data.
Flash layouts are detected before single ESP images. The scan runs only
when an ESP image sits at a ROM bootloader offset, and is refused when a
bootloader at another chip's offset shows that the dump does not start at
flash address 0. open_binary gains flash_offset and partition_table_offset,
which require format esp-flash-image and are part of the snapshot target
identity; the CLI takes them with --target-format esp-flash-image.
Comparison treats a partition's label, type, subtype, range and flags as
its declaration.
* test(artifacts): verify ESP flash layouts with gen_esp32part, merge-bin and otatool
Add the optional verify:esp-flash lane. It builds default and relocated
partition tables with ESP-IDF's gen_esp32part.py, assembles dumps with
esptool merge-bin, including one written from flash 0x1000, and writes
OTA records with otatool through flash_file_tool.py, which replaces
parttool's esptool calls with reads and writes of the dump file.
It compares REA's bootloader and app facts with esptool image-info, every
partition with gen_esp32part.py's decoding of the extracted table, and the
OTA records and derived slot with otatool read_otadata after each switch.
It also checks extracted bytes and source integrity, the scan and caller
table offsets, flash_offset on the 0x1000 dump, a corrupted app beside
valid partitions and a truncated dump. ESP32 linker scripts place the
conformance bootloader and app in ESP32 address ranges.
* fix(artifacts): read ESP flash layouts as the bootloader does
Read the partition table as bootloader_utility_load_partition_table
does: the last factory, otadata and same-subtype OTA declaration wins,
and a limitation names repeats. Plan each otadata record within its
partition and the dump. An otadata partition smaller than two sectors
selects none, the bootloader's INVALID_INDEX; a record the dump lacks
leaves the selection unknown while the other record stays reported. The
fallbacks follow the bootloader's indexes: ota_0-fallback and
factory-fallback name no partition when the table declares none, and the
assumptions add GPIO holds and deep-sleep wake.
Refuse a dump whose bootloader contradicts its origin before the default
table offset is tried, whether flash_offset was omitted or given, and keep
the ESP image reader's ESP8266 refusal. A table never lies inside the
located bootloader image, so single app images are no longer scanned. An
explicit esp-flash-image selection is decided before other container
signatures, and the native commands' --target-format no longer takes it.
Say when no bootloader confirms the default origin.
Exempt the primary bootloader and partition_table entries of ESP-IDF 5.3
from placement limits, report every region inside a longer one, and name
partitions by index when labels repeat ignoring case. Keep an undecodable
label's bytes in label_hex and compare them. Word undescribed ranges as
starting at a byte that is not erased, and treat a short erased region as
erased.
Add an MCP compare_artifacts test for partition declarations, reader
identity and close tests, scan edge cases, the 0x2000 ROM offset and a
literal otatool CRC.
* test(artifacts): verify ESP primary layout entries and the otadata fallback
Build a table with the primary bootloader and partition_table entries
that gen_esp32part.py fills from --primary-bootloader-offset, and check
that REA reads it with complete coverage, no overlap or misplacement
limitation, and a table that agrees with gen_esp32part's decoding. Erase
the otadata with otatool after the slot switches and check the records
against read_otadata and the factory fallback.
* refactor(artifacts): share positional entry lifecycle
The ESP image and ESP flash-layout readers carried the same 138 lines of
lifecycle code. PositionalArtifactReader now owns it: one preparation
under the reader's scope, a carrier recheck before each traversal,
refusal of forged entries, range streams that check their length and
recheck the carrier when they end, and a close that retires open
streams before it releases the carrier.
Each reader keeps its location, decoding, entries and metadata, and
returns its entries and coverage from prepareEntries(). Entries carry a
required byte range, which removes the unreachable null-range checks in
open(). Failure reasons, messages, order and cancellation do not change.
The Godot PCK reader keeps its own copy. It reports a member that ends
early as a format failure, where the ESP readers report integrity, and
the helper cannot express that without an option. The Node SEA reader
has the same difference.
* fix(artifacts): report Godot PCK and Node SEA members that end early as integrity
Behavior change: a Godot PCK or Node SEA member stream that delivers
fewer bytes than its entry declares now fails with reason "integrity"
and the message "<label> entry ended early: <path>". Before, it failed
with reason "format" and "<label> is malformed: member ended early:
<path>". Callers see the category integrity_mismatch instead of
execution_failure. The remediation text does not change.
Only a carrier that changes after open() rechecks its identity reaches
this path. The directory was valid when the inventory was taken, so a
short member means that the carrier changed, not that it is malformed.
The identity recheck that runs next also reports integrity, and the ESP
and ASAR readers already report a short member as integrity.
The new tests end the admitted descriptor's reads inside a member after
open() and check the reason and message for each reader.
* refactor(artifacts): move Godot PCK and Node SEA onto the positional lifecycle
The Godot PCK and Node SEA readers carried their own copies of the
positional entry lifecycle. They now extend PositionalArtifactReader and
keep only their location, decoding, entries and metadata.
prepareEntries() returns the entries and the coverage that each reader
reported before. Godot reports unknown coverage with one limitation for
an encrypted or sparse-bundle directory. Node SEA reports complete
coverage with its fuse contradiction as a limitation.
The base class takes two rules from these readers:
- open() refuses an encrypted entry as unavailable, after the
forged-entry check and before the carrier recheck: "Encrypted <label>
member is unsupported: <path>". ESP entries are never encrypted.
- Traversal yields to the event loop every 256 entries, starting before
the first entry. The ESP readers gain one yield before their first
entry. This changes scheduling only.
Failure reasons, messages, order and cancellation do not change. A
member that ends early has failed as integrity since the previous
commit. Godot's directory parse still checks only cancellation, so close
does not stop it early. Node SEA's close still drops an admitted decode
that no completed preparation released. Each reader now stores its
entries only after preparation succeeds; a failed preparation never
served them before either. PositionalArtifactEntry requires a byte
range, so the unreachable null-range checks in open() are gone.
* fix(artifacts): keep ESP flash coverage complete for a dump cut short
An esptool merge-bin dump ends after the last part it wrote, usually inside
its last partition. Such a partition made the layout's coverage unknown, so
artifact comparison of two merge-bin dumps reported every unchanged
partition as unknown.
The partition table names every partition whatever the file's length, so a
partition cut short or left out by the end of the file hides no entry. As
the ESP image reader does for a cut-short segment, its entry holds the bytes
the file has and an entry limitation gives the declared size; a partition
with no bytes stays in partitions_outside_dump. Coverage stays complete
unless undescribed bytes remain or no bootloader is located. An MCP
compare_artifacts test of two dumps that end inside ota_0 now classifies
every partition, and fails on the old rule.
* fix(artifacts): report written bytes in ESP partitions, erased slots included
An app slot is erased for ESP-IDF's bootloader when its image header bytes
are erased flash, and REA reported only that status. A slot can still hold
data further in: a dump read from a device held a complete older app image
1 MiB into an OTA slot reported as erased, with no limitation.
Each partition entry now records where the bytes the dump holds are
written, relative to the partition: written_offset, the first byte that is
not 0xFF, written_end, just past the last, and written_bytes, how many are
not 0xFF. They are null for a partition outside the dump. The status stays
erased, which is the bootloader's view, and a limitation names an erased
app slot that holds written bytes and says they are not read as an image.
The reader reads every partition byte once more to find them.
* fix(artifacts): derive the ESP boot selection for every partition table
The boot selection lived inside otadata, so a table without an otadata
partition, the common factory-only layout, had none. ESP-IDF's
bootloader_utility_get_selected_boot_partition returns the factory index
when the table declares no otadata partition.
Move it to a top-level boot_selection that every layout has. Its basis
names the bootloader's branch: otadata, factory-fallback and ota_0-fallback
from the records, no-otadata for a table without otadata, none for the
bootloader's INVALID_INDEX, and unknown when the dump lacks a record the
bootloader reads, which replaces the null selection. running_partition
moves with it, and otadata keeps its partition index and records. The
assumptions name what each branch leaves out: no records assumption
without otadata, and none for none or unknown. The real-tool lane checks
no-otadata on a table gen_esp32part built without otadata.
* fix(artifacts): say when an ESP image is a flash dump read from 0x1000
An ESP32 dump read from flash 0x1000, as read-flash 0x1000 or merge-bin
--target-offset 0x1000 writes it, opened automatically as its bootloader
image with a megabyte of undescribed bytes and no next probe. The flash
reader refuses that origin, but nothing told an agent which flash_offset to
pass.
espFlashDumpOrigin owns the inference: an image of a chip whose ROM loads
the bootloader from flash R, at file offset F, is the bootloader of a dump
read from R - F when a partition table sits where that origin puts 0x8000.
The ESP image reader asks it for a non-app image at file offset 0, so the
result does not depend on how the reader was created, and records
flash_dump {flash_offset, table_file_offset}, null otherwise, with a
limitation that says to reopen the file as esp-flash-image with that
flash_offset. inspect_artifact proposes the same open_binary call as a next
probe, and the flash reader's misplaced-origin refusal names the offset.
* perf(artifacts): summarize nested ESP images in the flash layout summary view
The summary view returned an ESP flash layout's root facts whole, so each
app partition and the bootloader carried every segment and signature
block, about half of a dump's summary.
The summary view now gives each nested image a summary instead of its
facts: status, chip, entry point, segment count, load ranges (each mapped,
loaded or unclassified segment's address and declared length), the
checksum and appended SHA-256 statuses, coverage and limitations. Complete
detail keeps the full facts, every segment included, and the retained
Evidence is unchanged. On a 16 MiB device dump the summary's root falls
from 5,141 to 2,657 bytes, and the summary Evidence from 10,038 to 7,554.
* test(artifacts): expect the misplaced image the ESP bootloader search reports
8a748d3cc added misplacedImage to the bootloader search, which the
locator uses to name the origin a misplaced bootloader implies, but one
test still compared the search result without it. Expect the image's
chip and file offset, and no image for an unknown chip.
* fix(artifacts): give every ESP partition from the dump's start an entry
After ecbc1a772 kept coverage complete for a dump cut short, a partition
wholly past the end of the file still had no entry, only a
partitions_outside_dump row. Comparing a merge-bin dump with a full device
read then reported that partition as added or removed, as if one table
did not declare it.
Every partition from the dump's first byte on is now an entry holding the
bytes the file has: none for one past the end, with a limitation saying
the file holds none of its bytes, no image facts and null written fields.
partitions_outside_dump keeps only partitions that begin before the dump's
first byte, and coverage is unknown when one does, since it has no entry.
Two dumps that lack the same partition now compare it as unchanged, and a
dump against a longer one reports the partition as changed; an MCP test
of a merge-bin dump against a full one checks that no path is added or
removed.
Unlike the ESP image reader, which marks a truncated image's coverage
unknown because later segments and signature blocks may be hidden, the
flash reader can list every partition from the table whatever the file's
length; only the entry limitation follows the image reader.
* fix(artifacts): select no ESP app from a partition table the bootloader rejects
boot_selection followed bootloader_utility_get_selected_boot_partition but
not the table check before it. For a table whose MD5 does not match, or
that has a second MD5 entry, esp_partition_table_verify fails, the
bootloader selects no app and resets. REA still derived a slot from the
otadata records, although the table's own limitation said the bootloader
rejects it.
The selection now has basis none, with no assumptions, for a table the
decoder marks invalid. It also follows two more branches of the
bootloader: an otadata partition at offset 0, which the bootloader takes
for none, selects the factory index as no-otadata, and a table without an
OTA app selects its fallback even when the dump lacks a record, since the
bootloader then ignores the records.
The bootloader also rejects a table whose partitions end past the flash
size in its header. REA does not model that check; the format guide says
so.
* fix(artifacts): require the ESP dump origin's table to follow the image
espFlashDumpOrigin named a later flash origin whenever a partition table
decoded where that origin puts 0x8000. Bytes inside the image's own
segments could be read that way, so an ESP32 bootloader whose segment data
held table-shaped bytes at file offset 0x7000 was reported as a dump read
from 0x1000.
The table must now start at or after the image's end, the rule the locator
already applies to the default table. The ESP image reader passes the
image_end it decoded, or the file size for a truncated image; the flash
reader's refusal passes the end of the misplaced image's segments. The
bootloader search drops its misplaced flag, which only restated
misplacedImage, and the next probe formats offsets with offsetText.
Reader tests cover the 0x1000 and 0x2000 ROM offsets, a standalone
bootloader, bytes that begin like a table, an app with a table at 0x7000
and a table inside a segment; without the boundary check, three of them
fail.
* fix(artifacts): give the ESP flash summary its own kind and each image's identity
The summary view's ESP flash root kept kind esp-flash-image, so a consumer
bound to kind could not tell an image's summary from its full facts, and
the advertised schema held two objects with the same kind const. It is now
esp-flash-image-summary.
The per-image summary also dropped the descriptor, which is what tells two
app slots apart, and named each image only by its partition index, which
the summary view, having no entry rows, could not resolve. Each nested
image now carries its entry path from the layout plan, in both views. The
summary adds the descriptor kind with its identifying fields (an app's
project_name, version, idf_ver, secure_version and app_elf_sha256; a
bootloader's version, idf_ver and secure_version), the signature block
count and each load range's classification. Its enums and fields derive
from the schemas that own them.
An MCP test reads a dump with an app in ota_0 and an erased ota_1 that
holds older bytes: it checks both summaries, that the summary's inventory
Evidence is the one the complete view returns with full facts, and the
erased slot's written range.
* test(artifacts): cover cancel and close during the ESP written-bytes scan
The written-bytes pass reads every byte of each partition the dump holds,
checking that the reader is open before each chunk, but only app decoding
had a cancellation and close test. A data partition, which only that pass
reads, now holds 9 MiB; once the scan has examined its first chunk, a
cancel rejects preparation with the caller's reason and a close with
unavailable, and the scan examines no further chunk.
The erased-slot test now asserts the whole set of erased-slot limitations,
so factory, ota_0 and nvs, which the bootloader reads or which are not app
slots, are shown to have none.
* docs(artifacts): re-time the ESP flash lane and state only observed causes
The lane ran again after its assertions changed; both esptool runs passed,
and the timed run's figures replace the earlier ones. The erased-slot
paragraph no longer names a cause for the older bytes, which REA cannot
observe, and the skill reference states that every table has a
boot_selection in its own sentence, with the none basis for a table the
bootloader rejects.
* test(artifacts): open an ESP partition past the end through the positional reader
A partition that lies past the end of a dump is an entry with no bytes,
and the merge with the positional lifecycle gives it the empty range at
the end of the file. The inventory tests listed that entry but never
opened it. Opening it through the shared reader now yields no bytes, and
its range is the file's size with a declared size of 0.
* fix(artifacts): retire ESP flash members as the ESP image reader does
#7498's maintainer commits made the ESP image reader keep a member owned
until its consumer closes it, retire fully buffered bytes when
cancellation arrives after production, and refuse to finish an inventory
paused across close. The ESP flash reader was copied from the earlier
reader. Main's shared stored-member lifecycle cases failed two of eight
against it: a paused inventory completed after close, and buffered member
bytes stayed readable after cancellation.
Give the flash reader the same lifecycle and run the shared cases against
it.
* docs(artifacts): re-time the ESP flash lane after merging main
The lane passed again with esptool v5.6.0 and 4.12.0 on the merge of upstream main. Record the v5.6.0 run's time and peak resident set.
* fix(artifacts): read ESP image Evidence recorded before flash_dump
The flash layout work made `flash_dump` a required field of the
`esp-image` root metadata. Evidence recorded before it has no such
field, so newer REA rejected records that were valid when they were
made: `compare_artifacts` and `inspect_analysis_view` reported
`evidence_integrity_mismatch` for them.
The field is now optional when read. A record without it parses as it
is, so the graph digest it commits to still matches; nothing fills the
field in. The image reader still writes it for every image, and the
inspection probe reads a missing field as no hint.
A regression test recommits an image inventory without the field and
passes it inline to `compare_artifacts` and `inspect_analysis_view`.
Against a server built with the required field, `compare_artifacts`
rejects it.
* fix(artifacts): stop the ESP flash gap scan when the reader closes
The scan for the first written byte in each undescribed gap checked
only the caller's signal. `close()` marks the reader closed and waits
for preparation, so it waited while the rest of a long erased gap was
read.
The gap scan now takes the reader's open check, as the written-bytes
scan and the image decoder do, and checks it before each chunk. Tests
cancel or close the reader after the scan's first chunk of a 9 MiB
erased gap and confirm that no later chunk is examined.
* fix(artifacts): name ESP partitions with Windows device labels by index
The safe-label rule accepted labels such as `NUL`, `con` or `COM1`, so
extraction wrote `partitions/NUL.bin`. Windows reserves those names
before any extension, and ordinary Windows tools cannot use such a
file.
A label that is a Windows device name (`CON`, `PRN`, `AUX`, `NUL`,
`COM0`-`COM9` and `LPT0`-`LPT9`, any case, alone or before a dot) now
takes the existing index fallback. The partition metadata and the
entry's source path keep the label.
* fix(artifacts): read ESP flash partitions' written bytes in one pass
Each partition got its own scan of the bytes the dump holds, so
partitions declared over the same bytes read them again and again.
Overlaps only add limitations, so a crafted table reached this normally:
94 overlapping partitions over a 32 MiB dump would examine about 3 GiB
before the first entry.
The reader now reads the bytes the partitions cover once. Partition
boundaries cut them into segments; one pass counts each segment's
written bytes and bounds, and each partition's extent is the sum of the
segments inside it. Every partition keeps its own declaration, extent
and limitations. The pass checks the reader's open state before each
chunk, as the per-partition scan did.
A test declares 60 partitions over one MiB, 40 over all of it and 20
over its first half, and checks each partition's extent and the
overlap limitations. The reader reads 1,113,256 bytes of the
1,114,112-byte dump; one scan per partition read 52,493,480.
* test(artifacts): fail the ESP lanes on failed commands and refusals
`otatool` turned a nonzero exit into returned text, which its callers
discarded, and `inspect()` returned `{ refused }`, which the flash lane
never checked. A failed command then surfaced later as a slot
assertion, an output-parser error or a TypeError, hiding its cause.
Commands the lanes need to succeed now reject with the output they
exited with: `otatool` and `inspect()` do so through a shared
`failedCommand`. An expected refusal is opt-in: `refusal()` returns
REA's output and fails the lane when REA opens the file. The ESP image
lane's two ESP8266 cases use it.
Checked by calling `otatool` on a file without a table, `inspect()` on a
file REA refuses and `refusal()` on a file REA opens; each rejects with
its cause.
* fix(artifacts): require a partition entry in an ESP partition table
The decoder counted the slot, not the partitions, so a lone MD5 entry
over the empty prefix, followed by the terminator, decoded as a table
with no partitions. Default and caller-selected detection then accepted
that empty layout, although the guide requires one partition.
A table now needs one partition entry before its terminator. An MD5
entry alone is refused as `entry 1 at 0x20 ends the table before any
partition entry`. The cases cover the decoder and both automatic and
caller-selected location.
* fix(artifacts): decode an image shared by ESP app partitions once
Each app partition got its own decode, so app partitions declared over
the same bytes decoded and hashed the same image again and again, the
same pattern the written-bytes scan had. With 60 app partitions over
one 960 KiB image the reader read 64,029,736 bytes of a 1,114,112-byte
dump.
Regions with the same file range now share one decode; each keeps its
own copy of the facts. Partitions with different ranges still decode
separately, because truncation can change the facts: at most one decode
per partition the table holds, each within its partition.
A test declares 60 app partitions over one image, 40 holding all of it
and 20 cutting it short, and expects three decodes: the bootloader, the
whole image and the cut one. The 60-partition dump now reads
2,161,864 bytes, as a dump with one app partition does.
* test(artifacts): run the shared member lifecycle cases on Godot PCK and Node SEA
The Godot PCK and Node SEA readers now extend PositionalArtifactReader,
whose member lifecycle comes from #7498's ESP reader. Main's shared
stored-member cases ran only for the ESP and uImage readers. Run them for
these two readers as well.
On main `6e9ee97a6`, both readers fail two of the eight cases: a paused
inventory finishes after `close()`, and member bytes buffered before
cancellation stay readable. On the positional base, all eight pass.
---------
Co-authored-by: morluto <[email protected]>
* feat(artifacts): read WeChat mini-program wxapkg containers
Add a read-only reader for plaintext wxapkg packages: the 0xBE/0xED
18-byte big-endian header, the bounded name/offset/size member table,
and verbatim member bytes at absolute carrier offsets.
- Members retain declared names, absolute offsets and sizes, so
source_location reports real carrier positions.
- V1MMWX PC-client packages are detected as encrypted coverage with
null member facts; extraction refuses with a typed error and no
decryption is attempted.
- Bounded table and directory budgets are admitted before allocation;
malformed marks, truncated tables, invalid UTF-8 names, member
extents inside the header or beyond the file, and counts that cannot
fit their table are typed format or limit failures.
- Member reads stream in bounded 64 KiB chunks with carrier freshness
checks; the reader joins outstanding streams before close and bounds
active streams and repeated member I/O.
Refs #8121.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(artifacts): bound wxapkg members to the declared data region
Normalize the two indexLength conventions before the table bounds check
and the table read. Member ranges must then lie in that declared data
region, which has to fit in the file.
Record wxapkg format metadata as not compared: header facts repeat the
package bytes, and entry source paths keep a producer root prefix.
* fix(artifacts): choose the wxapkg index after member ranges fit
Four trailing bytes made the rows-only layout end on the file and dropped
the count-inclusive layout. Keep every admitted layout until the parsed
rows and member ranges select one.
---------
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <[email protected]>
* fix(native): retain value trace source evidence
* fix(native): build trace error evidence only on failure
* test(mcp): expect unowned value trace refusal
* test(mcp): avoid freezing trace error wording
* test(native): keep value-trace stop coverage with source evidence
Main's stop and continuation cases now meet the trace's evidence-owner
precondition: direct calls supply a source id, and a completed MCP trace
runs in a session that can retain it.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(browser)!: keep the Chromium sandbox for REA-launched browsers
Playwright appends --no-sandbox unless a launch passes
chromiumSandbox: true. Neither the browser scenario launch nor the native
module resolver passed it, so every REA-launched Chromium ran without its
sandbox and REA_BROWSER_NO_SANDBOX had no effect.
Both launch sites now take the sandbox decision from ChromiumSandbox.ts:
off on Android or when REA_BROWSER_NO_SANDBOX=true, on otherwise. When
Chromium reports that it cannot start its sandbox, the launch failure's
remediation names the opt-out.
Fixes#8722
BREAKING CHANGE: hosts where Chromium cannot create its sandbox, such as
root users and Linux containers without user namespaces or a setuid
sandbox, now fail to launch until REA_BROWSER_NO_SANDBOX=true is set.
* fix(browser): name the sandbox opt-out on any sandboxed launch failure
Chromium reports an unusable sandbox through several unrelated startup
messages, including a misconfigured setuid helper and a refused namespace
move, and Playwright recognizes only some of them. Matching individual
messages missed real failures, so a sandboxed launch failure now always
names REA_BROWSER_NO_SANDBOX=true as a conditional remediation and the
retained startup diagnostics show whether the sandbox was the cause.
* fix(browser): keep default recovery for resolver failures after launch
The module resolver's outer catch also covers context, navigation and
evaluation failures after the browser launched. Attaching the sandbox
remediation there replaced their default recovery guidance. Attach the
shared launch remediation only when a sandboxed launch itself fails.
* docs(browser): note the Android sandbox exception for module traces
* feat(artifacts): inventory and extract ESP32 firmware images
ESP32-family app and bootloader images (magic 0xE9) had no reader, so
open_binary refused OTA downloads, app partitions and bootloaders.
Add the esp-image container format behind inspect_artifact and
extract_artifact. A pure decoder reads each byte once and keeps only
fixed-size fields. Derived facts cover the chip, from a table cited to
ESP-IDF v6.1; segment classification as ESP-IDF's should_map and
should_load; checksum and appended SHA-256 results; app and bootloader
descriptors; Secure Boot v2 signature blocks, observed and unverified;
and undescribed trailing byte ranges. Truncation and undescribed bytes
make coverage unknown. ESP8266 images are refused with a specific
reason, and ciphertext stays unrecognized.
Add the format to every target, Evidence, snapshot, live-summary and
artifact vocabulary, document it, and add an optional verify:esp-images
lane that compares esptool-built images with esptool image-info and
espsecure signature-info-v2.
Refs #3615
* test(artifacts): run the ESP image lane on esptool v5.6.0
The lane compared descriptor fields only when esptool image-info printed
one, so a descriptor that REA reports and esptool does not print passed
without comparison. Compare the descriptor kind in both directions.
Record that the lane passed for all 13 images with esptool and espsecure
v5.6.0, the version the layout facts cite, as well as with 4.12.0.
* refactor(artifacts): define ESP image layout sizes once
The decoder and the derived facts each defined the segment-header,
appended-digest and flash-sector sizes, the erased-byte scan and the
offset formatter. Define them once with the other fixed-layout structures
and import them in both places. The signature slot count is the one
constant that drives the slot scan.
Correct the observedEnd comment: it is the end of the last chunk read.
* fix(artifacts): report ESP-IDF's segment length limit by chip
ESP-IDF's verify_segment_header rejects a segment of 16 MiB or more
unless the chip maps flash through 32-bit cache addresses
(SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP: ESP32-S3, P4, C5, C61, H4 and
S31 in v6.1). Record that capability in the chip table and add a
limitation for such segments; unknown chips get none.
`loaded` repeats should_load. It does not mean the bootloader accepts the
segment: verify_load_addresses still rejects one outside the chip's RAM,
which REA does not model. Say so in the docs, the skill reference and the
inventory limitation.
* test(artifacts): check ESP lane tools first and verify extraction
The lane started work before checking its tools, so a missing RISC-V
target, ld.lld, esptool or espsecure surfaced as a raw spawn or command
error. Check each one before building and name the variable to set. Warn
when esptool is not v5.6.0, the release the layout facts cite.
Assert each segment's class for the ESP32-C3 app, its variants and the
bootloader, as the Xtensa image already did. Extract the app and the
signed image through the CLI and compare every file with the bytes at
esptool's segment offsets or in the appended signature sector; the
source's SHA-256 must be unchanged and equal the reported root_sha256.
* docs(artifacts): state what the ESP image lane does not establish
The Verification section listed the lane's checks but not its gaps: only
ESP32-C3 and ESP32 images come from a real producer, no real project
build, OTA download or flash dump is inspected, no signature is verified,
and the lane does not run in CI.
Record the timing of the final lane run on esptool v5.6.0.
* fix(artifacts): compare ESP segment declarations
Segment entries hash only their data, so compare_artifacts reported a
segment that moved to another load address with the same bytes as
unchanged; only the image itself changed. Compare each segment's
load_addr and data_len as its declaration, as Node SEA and PyInstaller
members are compared, and count a changed declaration as a metadata
change, also when content is unavailable.
Header offsets are layout and the classification is derived, so neither
is compared.
* test(artifacts): run ESP lane commands as owned process trees
The lane ran its tools through execFile. A timeout killed only the direct
child, so a linker or assembler that Clang or GCC had started could keep
running while the lane removed its workspace. Run every command through
createVerifierCommands, check the tools inside withVerifierCleanup, and
remove the workspace only after commands.close() has stopped every tree;
a failed close keeps the workspace.
Only a nonzero exit now counts as a refusal or an expected esptool
failure; timeouts and spawn failures propagate. Supervising each command
roughly doubles the lane's run time.
* test(artifacts): cancel ESP decoding after its first decoded chunk
The cancellation test requested cancel or close on setImmediate, which
can land while the file is still being opened and checked, so it never
showed that decoding had started. It also passed against a reader that
ignored both requests while decoding. Acknowledge the decoder's first
chunk, then cancel or close, join the operation, and assert that no
later chunk reaches the decoder and that the reader ends closed.
* docs(artifacts): record which ESP metadata comparison ignores
Segments are compared by their declaration. The image's and the
signature blocks' format_metadata are not: every field comes from their
own bytes, which content comparison already covers, and a block's
image_digest_matches follows the image's content.
* fix(artifacts): decide declaration comparison for every metadata kind
compare_artifacts listed the format_metadata kinds it compared by hand,
so a new kind went uncompared without notice; ESP segment load addresses
were first missed this way. One table beside the schemas now gives every
kind a declaration projection or the reason comparison uses none of its
facts, and a kind without an entry does not compile. Consumers read one
result per pair instead of naming formats.
- Godot PCK entries stay uncompared. Godot looks members up by the
simplified path without res://, which logical_path holds apart from
Unicode normalization. Godot 4.3 exports store res:// and 4.4.1
exports do not, so comparing source_path would mark every member
changed. The reader admits only the encryption flag, which the
occurrence's encrypted field records.
- A changed PyInstaller declaration is now a metadata change also when
the member's content is unavailable, as for ASAR, Node SEA and ESP
segments. It was unknown before, for no recorded reason.
- Root kinds and ESP signature blocks stay uncompared: their fields come
from their own bytes, which content comparison covers.
- A declaration that only one side records now adds one generic
limitation for every compared kind. Only Node SEA added one before,
with its own text.
* fix(artifacts): keep content comparison for metadata records with content
The record rule compared two metadata occurrences by their declaration
alone. A hand-built record that names content, with an equal declaration,
then hid a content change; PyInstaller records already did so before the
table. The rule now needs both records without an artifact_id, which is
every metadata record a reader emits.
Review changes in the same area:
- The unknown-declaration limitation also covers declarations of different
kinds and non-container observations, and tests compare it with its
constant.
- docs/godot-pck.md gives each Godot version's lookup path, the backslash
case, and that a member that becomes encrypted compares unknown. The
table points to it instead of restating it.
- The PyInstaller regression moves to an acceptance test whose MCP client
validates the advertised output schema, and the domain tests keep one
representative case per behavior.
* fix(artifacts): compare encryption changes when content is unavailable
An encrypted member has no content. Comparison then took the
content-unavailable fallback, which counted only ASAR storage and format
declarations, so a Godot PCK, ZIP or PYZ member that became encrypted
compared unknown although both sides record `encrypted`. The record rule
compared node-less metadata records by format declaration alone, so a PYZ
namespace record whose encryption flipped compared unchanged.
`OccurrenceMetadata` now owns the encryption comparison beside ASAR
storage and the format declaration. Every occurrence records `encrypted`,
so a pair's comparison is never unknown. The fallback, the record rule and
the metadata dimension read it there; `metadataChanged` no longer compares
`encrypted` itself. The record rule compares all of `OccurrenceMetadata`:
a changed value is changed in metadata, with availability when another is
unknown.
Decision: occurrence limitations still do not count for node-less
records. Readers derive them from the declared facts (the PYZ caveat
follows `encrypted`), and their wording can change between REA versions
while the record stays the same. The fallback does not compare them
either.
Behavior changes:
- A pair with unavailable content whose `encrypted` differs is changed
[metadata, availability], not unknown [availability].
- A node-less record whose `encrypted` differs is changed [metadata], not
unchanged.
- A hand-built node-less record that records ASAR storage now compares
it; no reader emits such records.
Remaining:
- The fallback still ignores `entry_kind` and `executable`, which readers
also declare, so a file that becomes a symlink, or a mode change on an
encrypted ZIP member, still compares unknown. `executable` has its own
dimension, so its fix needs content-free checks split from node checks.
Each is its own behavior decision.
- Node-bearing pairs still compare limitation wording; records do not.
Whether limitation wording is ever a comparison fact needs one decision.
* fix(artifacts): compare entry kind and executable when content is unavailable
When either side lacked verified content, comparison took a fallback that
counted only ASAR storage, encryption and format declarations. It compared
`entry_kind` only in the metadata check of pairs with content, and
`executable` only beside the node checks. A ZIP member that became a
symlink, whose target REA does not read, and an encrypted ZIP member whose
Unix mode gained an execute bit therefore compared unknown, although
readers declare both fields on every occurrence.
`OccurrenceMetadata` now owns the entry kind comparison beside encryption;
it is never unknown. Comparison checks fall into two groups: declared facts
that need no content (`executable` and the metadata declarations), and
facts that need both content nodes (content, kind, format, size, relations
and node metadata). The fallback, the record rule and pairs with content
share the declared checks and one rule for unknown declarations. Kind and
format stay content checks: without content they are guesses from the path.
Behavior changes:
- Unless both sides have content, a pair whose entry kind differs is
changed [metadata, availability], not unknown [availability].
- Such a pair whose `executable` differs is changed [executable,
availability], with metadata when another declared fact also differs.
- A hand-built node-less record whose `executable` differs is changed
[executable]. No reader emits such records.
Remaining: declared sizes and integrity digests still compare only through
observed bytes, so they do not count when content is unavailable.
* fix(artifacts): never mark a symlink executable
On POSIX hosts the directory reader recorded a symlink's own permission
bits as `executable`. Linux reports 0777 for every symlink and macOS
usually 0755, so directory, app bundle and DMG symlinks read true. zip.js,
the PyInstaller reader and the Windows branch record false. The previous
commit compares `executable` without content, so a symlink that two
readers or two hosts record differently compared changed in `executable`,
and a non-executable file that became a symlink also reported `executable`.
`hostExecutableMarking` now gives a symlink no marking on any host:
running a symlink runs its target, and the link's own bits do not control
that.
Behavior change: directory, app bundle and DMG inventories record
`executable: false` for symlinks. Occurrence IDs do not change, but the
graph digests and manifest IDs of inventories with symlinks do. Comparing
an inventory stored before this change with a new one therefore reports
changed [executable, availability] once for each such symlink.
* fix(artifacts): stop comparing limitation wording
Pairs with content compared node and occurrence limitations as metadata,
while node-less records, the content-unavailable fallback and inventory
limitations did not. Limitations explain what an observation could
establish. Readers mostly derive them from fields that comparison already
reads, from other occurrences or from what REA could read, and their
wording changes between REA versions. So adding `A` beside an unchanged
ZIP member `a` marked `a` changed in metadata through its case-collision
note, and an older and a newer inventory of equal bytes could differ
wherever a reader reworded a note.
No comparison path reads limitations now. Inventory limitations still
appear in the result, and occurrence limitations stay in the inventory
Evidence. Readers write no node limitations, so only hand-built inventories
compared them.
Behavior change: for a pair with content, limitation text no longer adds
metadata. A pair that differs only there compares unchanged, or unknown
[availability] when a declaration is unknown.
Remaining: a few facts appear only in limitation text, or also in fields
that comparison does not read, so their changes are no longer reported
while the compared bytes stay equal: an ASAR block integrity table or an
unsupported integrity declaration, a Godot member's all-zero MD5, a ZIP
name's leading `./` (also in `source_path`), and a PyInstaller PYZ image
digest (also in `pyz_sha256`; the archive root still reports the change).
Each needs a field or a comparison decision of its own if it matters.
* fix(artifacts): compare declared digests when content is unavailable
Without observed bytes on both sides, comparison ignored the containers'
declared integrity claims. Two encrypted Godot PCK members therefore
compared unknown even when their declared MD5s showed that the script had
changed. Godot's exporter stores the MD5 of a member's original bytes, so
the MD5 does not depend on the encryption IV. This comes from the 4.3,
4.4.1 and 4.7.1 exporter sources; no real encrypted export was compared.
When either side has no content, claims of one algorithm that differ now
make the path changed in metadata and availability. Equal claims add no
change, because they are unverified, and observed bytes still decide when
both sides have them. Godot PCK and ASAR readers record such claims.
For a readable member, a directory that declares or omits an MD5 only
decides whether REA can check its bytes, so such a member compares
unchanged. An encrypted member with an MD5 on only one side stays unknown.
Remaining: declared sizes still compare only through observed bytes.
* docs(artifacts): check the contract's compared kinds against the table
docs/mcp-contracts.md named the format_metadata kinds that
compare_artifacts compares in prose, by hand, so a kind added to the
declaration table could go unnamed. The integration branch already omits
esp-flash-partition, which the ESP flash layout branch adds to the table.
The paragraph now names each compared kind by its identifier. A
conformance test requires it to name exactly the kinds whose table entry
has a declaration, which artifactFormatMetadata.ts exports as
COMPARED_FORMAT_METADATA_KINDS, and no kind that comparison ignores.
* test(artifacts): share the comparison acceptance MCP fixture
Six artifact comparison acceptance tests each copied the same MCP
steps: open_binary, inspect_artifact, the inventory_artifact substep
Evidence, then compare_artifacts. The coverage test repeated the
inspect steps three times and kept its own inventory lookup.
tests/fixtures/artifactComparisonMcp.ts now owns these steps on the
schema-validating connectLocalToolsMcp client.
connectArtifactComparisonMcp() returns call, inspect(path) and
compare(left, right). inventoryEvidence() reads the inventory Evidence
that an inspection retains, from MCP or CLI output. Each test still
writes its own authored input and keeps its expectations.
The coverage lookup matched evidence.operation, not the substep
operation. The inspection schema fixes both to inventory_artifact, so
both select the same substep.
The plist acceptance test does not change. It compares the inspection
Evidence itself across CLI and MCP and passes the comparison Evidence
to verify_reconstruction and find_changed_behavior.
No assertion or scenario changes.
* fix(artifacts): leave .NET bundle member declarations uncompared
Upstream main compares no .NET bundle member facts. Comparing `file_type`
changed `compare_artifacts` output beyond this branch's fixes, so the kind
stays uncompared until that is decided. A changed manifest still shows as a
content change of the host.
* fix(artifacts): leave HEX region declarations uncompared
The comparison table compared a region's start_address and length. A
region's path names its start and end addresses, and its length follows
from them, so two regions that pair at one path always declare the same
facts: the entry could never find a change. It only made a region with
equal bytes unknown when the other side recorded no metadata, such as a
directory of extracted regions. The kind is not compared, as on main.
The contract paragraph also stops naming .NET bundle members, which
3e51a7760 left uncompared.
* test(artifacts): compare node-less records of any compared kind
The record rule follows the comparison table, not PyInstaller, but only
PyInstaller records exercised it. An ESP segment record shows both
outcomes: an equal declaration compares unchanged and a moved one changed
in metadata, without the availability that the fallback for unread
content would add.
* test(artifacts): compare a node-less record's recorded ASAR storage
The record rule compares ASAR storage beside the declaration and
`encrypted`, but no test recorded it on a metadata record. A changed
value is changed in metadata, and a value that only one side records
is unknown in availability, as for any occurrence.
* test(artifacts): compare UnityFS member declarations through MCP
The table compares a UnityFS member's declared source_path and raw
flags, but no comparison test covered the kind, so dropping either field
from the projection left every test passing. Bundles with equal member
bytes now show both: other flags, or a stored path in decomposed form,
compare changed in metadata, and reordered members, whose index and
stream offset are layout, compare unchanged.
* test(artifacts): use the shared comparison fixture for UnityFS
The UnityFS comparison test arrived from the bottom branch with its own
copy of the open_binary, inspect_artifact and compare_artifacts steps,
which tests/fixtures/artifactComparisonMcp.ts owns here. It now uses
connectArtifactComparisonMcp() like the other comparison acceptance
tests. No assertion changes.
* test(artifacts): find the contract's compared kinds by marker
The docs conformance test found its paragraph by an exact English phrase,
so rewording "also compares" failed it while the named kinds stayed
right. The paragraph now sits between BEGIN and END markers, as the
allocation report's sections do, and the test reads the kinds between
them. It still requires exactly the kinds whose table entry has a
declaration.
* fix(javascript): remove duplicate childNodes import
The squash of #7967 re-added the import already introduced in afc18131f,
so typecheck failed with TS2300.
* test(gdb): expect the command-output cap limitation
The retained-record limitation now names the command-output setting.
The coverage assertion still required the previous fixed-cap sentence.
* test(cli): keep rejected mcp add values off Amp settings
mcp add now rejects a flag in the --command value position. The Amp
registration test still treated that invocation as a successful selection.
---------
Co-authored-by: Cursor Agent <[email protected]>
* fix(cli): export source Evidence for native value traces
* fix(cli): export source Evidence for native value traces
* fix(cli): preserve trace completeness in summaries
* fix(firmware): keep producer reports writable under small staging budgets
extract_firmware passed max_output_bytes straight to prlimit --fsize. RLIMIT_FSIZE
applies to every regular file the producer writes, including the mandatory
report.json and unblob.log outside the staging tree, so a staging budget smaller
than the report truncated it and failed the extraction even when every staged
file fit. Derive the per-file ceiling as the larger of the staging budget and the
8 MiB report budget; polling and the final inventory still enforce the aggregate
staging budget.
Fixes#3888
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(firmware): keep files stopped at the size ceiling over budget
The per-file RLIMIT_FSIZE ceiling equalled the largest size its readers
accept, and both the staging inventory and the report reader accept a
file of exactly their budget. A write the kernel stopped at the ceiling
therefore left a truncated file that passed as complete. Raise the
ceiling by one byte so such a file exceeds its budget and is rejected.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(firmware): report the effective file-size ceiling on limit failures
The exit path re-derived the staging budget for SIGXFSZ and process-limit
recovery, so a small-budget run killed at the 8 MiB + 1 ceiling reported
file_size_bytes 64 and said the cap was max_output_bytes. Pass the
ceiling runFirmwareCommand installs and describe how it is derived.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(firmware): reject staged files cut off at the per-file limit
RLIMIT_FSIZE stops a write at the limit without extending the file, and
the staging inventory admits a file of exactly max_output_bytes. With a
budget of 8 MiB or more the producer limit equalled the budget, so a
staged file the kernel cut short published as a complete extraction;
an inherited ceiling below the budget did the same.
Derive the per-run limit once in firmwareFileSizeLimit as one byte above
the largest budget, still capped by the inherited ceiling, so a file
stopped at the limit exceeds its budget. When the inherited ceiling
removes that byte, both staging inventories refuse a regular file that
reaches the installed limit with a nonretryable file-size constraint
naming the inherited limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(firmware): derive limit adjustability from the per-run limit
The one byte of headroom makes the per-run file limit equal an
inherited ceiling of exactly max_output_bytes + 1, but adjustability
still compared that ceiling with the raw budget. A SIGXFSZ then advised
raising max_output_bytes, which the same ceiling clamps back to the
same limit.
Decide adjustability in firmwareFileSizeAdjustable from
firmwareFileSizeLimit itself: recommend a larger budget only when it
raises that limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(firmware): compare file-size recovery with its owners
The SIGXFSZ adjustability cases and the inherited-ceiling workflow matched
fragments of remediation prose, so a harmless rewrite would fail them.
Compare the emitted guidance with firmwareSignalLimitError for the expected
branch, check that the two branches stay distinct, and compare the inventory
refusal with firmwareFileSizeLimitReached. The selected limit, resource and
retryability stay asserted as structured facts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
* fix(artifacts): keep inventorying an ASAR whose member declares an invalid size
A member whose declared size is not a non-negative safe integer, such as
Figma Desktop's .codesign placeholder (size -1000), made readAsarListing
throw and failed inventory of the whole archive without naming the entry.
Listing now keeps such a member with a null size and a limitation naming
the parsed value, as it already keeps an unreadable offset. Opening a
packed member with a null size raises ArtifactEntryContentUnavailable, so
inventory records it as unavailable and continues with the other members.
ElectronFileScope keeps the strict size rule.
Member metadata that still stops inventory now names the member and the
rejected fields instead of reporting a generic malformed archive.
Fixes#8715
* fix(artifacts): bound and preserve ASAR listing diagnostics
Quote an unsafe path component or a string size through a 256-unit
prefix plus its length, so one oversized header key cannot inflate the
failure or the retained limitation. Render numeric sizes with String so a
size that JSON.parse turned into Infinity is reported as Infinity rather
than null.
* fix(artifacts): keep ASAR listing diagnostics exact and located
Report a numeric size beyond the safe-integer range as having no
retained exact value instead of the number JSON.parse rounded or
overflowed, name object and array sizes correctly, and interpret each
declared size once without validating absent directory sizes.
Quote member and parent paths through the bounded source quote, name the
rejected root fields as member failures already do, and append the
archive path to listing failures so a nested archive stays identifiable.
* test(artifacts): match ASAR listing archive paths that contain spaces
* feat(desktop): suggest managed members for Unity predefined script assemblies
Unity Mono triage listed only inspect_managed_artifact probes, mostly for
engine and framework assemblies. Assembly-CSharp.dll and
Assembly-CSharp-firstpass.dll directly in a managed directory with a
UnityEngine reference witness now get an inspect_managed_members probe,
ordered before every managed artifact probe.
* docs(desktop): describe Unity Mono managed member probes
* refactor(desktop): read Unity managed directory from the witness fact
* refactor(desktop): reuse the managed directory index for predefined assemblies
* feat(desktop): point Unity member probes at summary delivery first
* test(desktop): no member probes without a Unity reference witness
* docs(desktop): trim Unity member probe note
* feat(desktop): suggest managed members in Unity IL2CPP backup folders
Unity IL2CPP builds write <Product>_BackUpThisFolder_ButDontShipItWithYourGame
next to the player. When shipped, its Managed/ holds pre-IL2CPP assemblies.
A path convention now records the folder on Assembly-CSharp*.dll directly in
its Managed/, including when the admitted root is the folder itself, and
triage offers inspect_managed_members for those files. Correspondence to
the shipped native code stays unknown.
* docs(desktop): note IL2CPP backup folder member probes
* docs(desktop): list IL2CPP metadata versions 24 and 27 for the metadata probe
* style(desktop): merge stacked doc comments on desktopPathConventions
* test(desktop): cover member probes in a macOS bundle managed directory
* docs(desktop): note that member probes need the game folder as root
* test(desktop): no member probe when the root is the Unity data folder
* test(desktop): check the data-folder root still gets managed probes
* test(desktop): check the IL2CPP backup convention against the probe bound
---------
Co-authored-by: gensokyo-dev <[email protected]>
* feat(setup): register LM Studio during setup
Register rea in mcp.json in the LM Studio home through a new lm_studio
client. The home follows LM Studio's own lookup, read-only: the path in
~/.lmstudio-home-pointer, else an existing ~/.cache/lm-studio, else
~/.lmstudio. An unreadable or relative pointer reports a path error
instead of a guessed home.
LM Studio parses mcp.json with strict JSON.parse, so comments, trailing
commas and a BOM are rejected before any backup or write. The entry
carries command, args and env with no type or timeout. Local models have
small context windows, so the registration selects
REA_MCP_INPUT_SCHEMA_PROFILE=compact unless a profile is set explicitly,
and doctor reports an entry without a profile as stale. One predicate now
owns that default for Kimi Code and LM Studio.
* fix(doctor): resolve provider budgets for LM Studio as setup does
* fix(mcp): report session tool target requirements accurately
- Declare requiresActiveTarget on inspect_address_context in sessionToolContracts.ts so closed-session discovery stops advertising an address query that fails with target_unavailable.
- Route session-kind tools through targetAvailability in CapabilityInventory.ts before the available fallback, and report target_unsupported with mode-level remediation for get_navigation_context and inspect_address_context on artifact and archive targets.
- Keep each navigation mode's missing_operations and remediation aligned with the target restriction so recommendations name the target instead of a provider.
* test(mcp): cover closed-session and artifact target requirements
- Add CapabilityInventory controls for closed-session address and explicit-path APK availability, artifact and archive restrictions with per-mode navigation flags, and structured reasons instead of pinned remediation sentences.
- Add a production stdio MCP regression in sessionTargetRequirements.test.ts that observes both binary_session entries before and after the calls, the target_unavailable address error, the successful unsigned-ZIP APK call, and the sibling navigation prerequisite.
Default page-origin scope excludes cross-origin APIs. Point agents at
JavaScript analysis for API-origin discovery, require page origin plus API
origins in allowed_origins (list replaces default), and bump the packaged
skill to version 60.
* fix(native): select versioned arm64e slices for codesign by family name
Current clang marks arm64e slices with pointer-authentication ABI
version 1 (subtype 0x81000002, lipo name arm64e.v1). The codesign
selector table accepted only ABI version 0, so inspect-signature failed
with capability_unavailable whenever such a slice was selected, for
example on any universal arm64e + x86_64 binary built today.
codesign has no versioned name: -a arm64e.v1 is "unknown architecture
name", and -a arm64e resolves to the first FAT slice with the arm64e
base subtype, whatever its ABI version. A selector is therefore exact
only for that first slice. Accept any versioned pointer-authentication
ABI for arm64e, and return a selector only when the slice is the first
in table order to carry that name; a later slice sharing it stays
unknown instead of being inspected through the earlier one. This also
removes the separate duplicate-name check from signature coverage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): keep arm64e selectors unknown behind unmapped variants
codesign resolves a name to the first FAT slice with the same CPU type and
base subtype, so an earlier capability variant whose own name is unknown may
shadow a later arm64e slice. Compare identities rather than successful name
mappings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(javascript): emit no timer for a literal non-function callback
Node validates the timer callback before creating a handle:
if (typeof callback !== 'function')
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
The throw is synchronous, before any timer exists, but the analysis reported a
`schedule` operation for such calls.
Measured on Node 24.22.1:
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(`code`, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1n, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(true, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(void 0, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout([], 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout({}, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout() -> THROWS ERR_INVALID_ARG_TYPE
setImmediate("code") -> THROWS ERR_INVALID_ARG_TYPE
setInterval("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
setTimeout(function(){}, 0) -> OK
setTimeout(() => {}, 0) -> OK
setTimeout(fn, 0) -> OK
setTimeout(class {}, 0) -> OK (fails only when the class is invoked)
So a schedule is dropped only for an exactly readable literal that is not a
function, or for a missing argument. An identifier, a call result or a spread may
still be a function at runtime and keeps its schedule.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): scope the timer callback rule to Node's timers module
greptile flagged two problems with the first version.
P1 - browser timers disappeared. The rule was applied to bare globals, but
browser and Electron renderer sources reach this analysis without a Node/browser
distinction, and there `setTimeout("refresh()", 0)` is a real timer: the host
evaluates the string. The rule now applies only when the timer is imported from
`node:timers`, where that form cannot exist. A bare global schedule keeps its
fact whatever argument 0 holds.
P2 - unused import. `readExactJavaScriptLiteral` was imported but never used; the
helper reads shapes through `unwrapJavaScriptExpression`. Removed.
Measured on Node 24.22.1 (the throw is what makes a node:timers schedule
impossible; the bare-global case is intentionally left alone):
import { setTimeout } from "node:timers";
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(fn, 0) -> OK
--- bare global, host may be a browser ---
setTimeout("code", 0) -> kept as a schedule
24 cases cover both sides plus the cancellation link.
* fix(javascript): reject every non-function global and unary timer callback
Reviewing the timer callback guard against Node 24.22.1 found two gaps. Both are
in the same "a call that always throws must not emit a resolved fact" family.
**An unmodified global that is not a function was accepted.** The guard read
literal nodes only, so a bare global identifier passed through even though Node
rejects it before creating a handle:
setTimeout(undefined) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(NaN) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(Infinity) -> THREW ERR_INVALID_ARG_TYPE
`isUnmodifiedGlobalReference` keeps a shadowed binding out of the rule, so a
local `const NaN = () => {}` keeps its schedule.
**Only `void` was treated as a non-function unary operator.** Every unary operator
yields a primitive, and Node rejects a primitive callback:
setTimeout(!0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(~0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(-1) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(+"1") -> THREW ERR_INVALID_ARG_TYPE
setTimeout(typeof 1) -> THREW ERR_INVALID_ARG_TYPE
A class expression is still kept, because it passes Node's own `typeof` check and
only fails when invoked.
Fifteen regressions cover the rejected forms plus arrow, identifier, class,
spread and shadowed-global cases; ten fail on the previous commit.
* fix(javascript): let sloppy eval shadow the non-function global callback
Review finding. The new global check read `undefined`, `NaN` and `Infinity` as
unmodified globals, but sloppy `eval` can introduce a local function under one of
those names, which then shadows the global. Measured on Node 24.22.1 in script
scope:
function g() { eval("var undefined = () => {}"); setTimeout(undefined, 0); }
-> schedules a timer
function g() { eval("var NaN = () => {}"); setTimeout(NaN, 0); }
-> schedules a timer
`semanticOpaqueNameMayHaveChanged` now has to agree that no visible activation can
introduce such a name before the global reading is conclusive. In a module the
same `eval` is strict and introduces no outer binding, so the global reading still
holds and the timer is still dropped.
Three regressions cover the sloppy forms, and a fourth pins the no-eval cases as
still dropped. The three sloppy cases fail without the guard.
* feat(harmony): disclose unclassified inventory files
* test(harmony): let createEvidence own JSON admission in the partial-inventory case
* test(harmony): extract the projection assertion from the archive journey
* docs(harmony): qualify the unclassified disclosure rules and pin exact-case manifests
* test(harmony): replace limitation prose with structured coverage facts
The partial-inventory control froze the sentence "Source inventory coverage is
partial; absence is unknown." verbatim. docs/testing.md asserts structured
limit facts instead of freezing explanatory sentences and puts documentation
wording in review, so the sentence is no longer substring-matched.
`coverage.status` is a disjunction of independent causes in this projection:
incomplete inventory scope, unavailable content, and an exceeded bridge budget.
Asserting only `status: "partial"` left that attribution unstated, so the two
other causes are now pinned as well. `unavailable_components` being empty and
`bridge_candidate_coverage` being complete with zero candidates make the partial
result attributable to the missing occurrence alone.
Also drops an `artifact_id` comparison that could not fail: the fixture's four
files carry the same bytes, the preceding assertion already pins that shared
digest, and `artifact_id` is derived from the digest. Distinct paths sharing
content stay covered by the path array and the independent digest expectations.
Narrows a comment that claimed exact-case matching for every name in the
control; only the manifest names are case-sensitive, because the App Pack child
suffix match is case-insensitive.
* docs(harmony): name the disclosure location for a nested child package
The unclassified-disclosure paragraph used "there" to refer back to a clause
that had just named `app_pack_children`, so a reader could take a nested `.hap`
or `.hsp` member of a HAP or HSP root to be reported as an App Pack child. It is
reported in `unclassified_components`: the child list is populated only for an
App Pack root. Names the location explicitly.
* fix(javascript): emit no fs acquisition for a literal non-flag string
`fs.open` and `fs.openSync` validate a literal flag before touching the
filesystem:
if (!isFd(flags) && !FLAGS_SET.has(flags))
throw new ERR_INVALID_ARG_VALUE('flags', flags, ...);
The throw is synchronous, before a descriptor exists, but the analysis reported
an `acquire` operation for those calls.
Measured on Node 24.22.1 against a file that does not exist, so each valid flag
is exercised without an EEXIST masking it:
ACCEPTED as a flags string:
a a+ as as+ ax ax+ r r+ rs rs+ sa sa+ sr sr+ w w+ wx wx+ xa xa+ xw xw+
REJECTED with ERR_INVALID_ARG_VALUE:
"" A R W rb wb s s+ x r,x
A Python-style `"rb"`/`"wb"` is the common case: those are valid in Python and
fatal at the Node call site.
`FS_OPEN_FLAGS` in `javascriptStaticAnalysisHelpers.ts` already holds exactly
those 22 accepted flags. It is now exported and reused so the accepted set has
one owner. A numeric flag is a platform bitmask and a computed value may still be
valid, so only an exactly readable non-flag string is dropped.
`fs.createReadStream` keeps its acquisition: it does not validate the flag
synchronously, it returns a stream and reports the failure on the error event.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject every fs flag literal Node rejects
The first version only decided string flags. Node's check covers every flag
shape, and each one below throws `ERR_INVALID_ARG_VALUE` or `ERR_OUT_OF_RANGE`
synchronously, before a descriptor exists:
openSync(p, 1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, 1e400) -> THREW ERR_OUT_OF_RANGE
openSync(p, NaN) -> THREW ERR_OUT_OF_RANGE
openSync(p, Infinity) -> THREW ERR_OUT_OF_RANGE
openSync(p, 0n) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, true) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, false) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, {}) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, []) -> THREW ERR_INVALID_ARG_VALUE
--- accepted ---
openSync(p, 0) / 1 / 65536 / +5 -> ok
openSync(p) / undefined / null -> ok
openSync(p, "r") / "wx" / "sr" -> ok
`NaN` and `Infinity` are identifiers rather than literals, so they are decided
only through `isUnmodifiedGlobalReference`; a shadowed binding keeps the
acquisition.
* fix(javascript): judge an fs flag by its value, and ignore a shifting spread
Two P1 findings on the first version.
**A unary operator was judged by its operand.** `openSync(p, void 1.5)` passes
`undefined`, so Node applies the default read flag, but the branch saw a
fractional operand and dropped a real acquisition. `openSync(p, -0)` is the valid
zero bitmask and was also dropped. `staticFlagBits` now evaluates the operator:
openSync(p, void 1.5) -> ok (keeps the acquisition)
openSync(p, void 0) -> ok
openSync(p, -0) -> ok
openSync(p, -0.0) -> ok
openSync(p, +0) -> ok
openSync(p, ~-1) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, +1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, !0) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, ~0) -> THREW EINVAL
openSync(p, typeof 1) -> THREW ERR_INVALID_ARG_VALUE
**A spread before the flag slot shifts the runtime position.** With
`args = [path, "w"]`, `openSync(...args, "0600")` opens with mode `"0600"`, but
argument 1 is then the mode rather than the flag. The check now keeps the
acquisition whenever argument 0 is a spread, because the flag it inspects is not
provably the flag at runtime.
* fix(javascript): leave a signed fs bitmask undecided
greptile caught that the flag check rejected every negative integer, but Node
accepts some signed bitmasks. Measured on Node 24.22.1 against an existing file:
openSync(p, -2147483648) -> ok
openSync(p, -1073741824) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -2) -> THREW EINVAL
openSync(p, 2147483647) -> THREW EINVAL
openSync(p, 2147483648) -> THREW ERR_OUT_OF_RANGE
openSync(p, -2147483649) -> THREW ERR_OUT_OF_RANGE
So the sign does not decide: some negative values open a descriptor and others do
not, and which is which is a platform bitmask question this analysis cannot
settle. The check now rejects only a non-integer or a value outside the signed
32-bit range, and leaves an in-range negative undecided — the same choice
`greptile` asked for, on the principle that a call which may succeed must keep
its acquisition.
`!`, `typeof` and `delete` are still rejected, because those yield a boolean or a
string regardless of the operand.
* fix(javascript): read a no-expression template literal flag
Review finding. A no-expression template literal is exactly readable, so it is the
same flag string Node receives, but the guard only read `StringLiteral`:
fs.openSync(p, ` + "`rb`" + `) -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, ` + "`r`" + `) -> ok
The flag slot now accepts an interpolation-free template; an interpolated template
stays undecided.
Four regressions cover both directions; two fail without the change.
* fix(artifacts): classify bundle executables by their own name
A directory bundle is selected for its executable, so the executable's
canonical name classifies it; the bundle directory spelling is not an
artifact alias. Bundle facts now reach artifact, archive and database
targets so the admitted-source digest binds to the executable.
Fixes#7362
* fix(artifacts): share the artifact identity across every artifact route
The trailing-archive fallback also carries the bundle facts now, so a
bundle whose executable is a script stub with an appended ZIP keeps its
digest bound to the executable.
* fix(artifacts): name the classification spelling at the resolver boundary
The resolver already knows whether a bundle directory or a file was
selected, so it hands the executable or the caller's spelling to the
reader instead of inferring it from the Info.plist. Every admitted route
now shares one identity, and the .app bundle operations accept a bundle
whatever its executable admitted as.
* fix(artifacts): route bundle keyed archive reads by bundle facts
A bundle whose executable is a plist now carries bundleInfoPlist, so
inspect_keyed_archive must read bundle-relative archives instead of the
standalone route that compares the selected bundle directory with the
executable.
* refactor(artifacts): build the selected ESP flash identity from the shared owner
The selected ESP flash route from main built path, source path and digest
inline. Use the resolver's shared identity so it carries the same facts as
every other admitted target.
* fix(web): omit importer integrity for the trusted resolver stub
Import-map integrity that covers the selected importer was applied to REA's
replacement resolver script. Strip only those integrity entries from the
installed map so native resolution can proceed while preserving dependency
integrity and the caller-selected map identity.
Fixes#8544
* fix(web): keep __proto__ import-map integrity keys on a null prototype
* fix(cli): emit compact JSON validation errors for demangle-swift
Piped and redirected --json argument diagnostics went through Incur's
always-indented Formatter, bypassing REA's destination-aware writer.
Buffer non-TTY SDK JSON like JSONL and rewrite through writeJsonOutput.
* fix(cli): keep token-count for piped SDK JSON documents
Compact JSON rewrite for non-TTY SDK output returned early before
token controls, so --llms/--schema --format json --token-count printed
the document instead of its count.
* fix(cli): do not token-count non-document JSON captures
Completion hooks and other non-JSON --json pipe output must stay verbatim;
token-count on non-JSON SDK captures applies only with sdkDocumentControls.
* test(cli): assert demangle JSON validation by code, not wording
* fix(javascript): emit no child process for a literal non-object options slot
Node validates the child_process options object before creating anything, so a
literal primitive or a literal array in that slot throws ERR_INVALID_ARG_TYPE
synchronously and no child process exists. The analysis still reported a complete
spawn for them.
Measured on Node 24.22.1:
spawn(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], null) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], []) -> THROWS ERR_INVALID_ARG_TYPE
execFile(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
spawn(cmd, args, undefined) -> SPAWNED
spawn(cmd, args, {}) -> SPAWNED
execFile(cmd, [], null) -> SPAWNED
fork(worker, [], null) -> SPAWNED
exec(cmd, "inherit") -> SPAWNED
The asymmetries matter: `spawn` rejects `null` and exempts only `undefined`,
while `fork` and `execFile` skip any nullish options, and `exec` spreads its
options and validates none of them. Drop a candidate only when the options slot
is a provable literal primitive or array, so an unresolved or non-literal value
stays a candidate.
`spawn(cmd, [], null)` is the sharpest case: it was indistinguishable from the
valid `undefined` form, including a resolved connects-stdio relation, for a call
that dies before any child exists.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject literal options Node shall reject, and drop a stray file
Two review findings on #8019.
greptile: `rejectsOptionsShape` still kept `spawn("tool", [], 0n)`, `-0` and
`1e400`, which Node also rejects before creating a child
(`ERR_INVALID_ARG_TYPE: The "options" argument must be of type object`). Those
forms are exactly the ones `readExactJavaScriptLiteral` refuses to price — a
BigInt, a non-finite numeric literal and a negated numeric literal — so read the
shape directly instead of widening the shared exact-value reader. `NaN` is an
identifier rather than a literal, so it still needs scope resolution and is left
as a documented gap.
Also remove `.rea-hopper-stage-YOryZ6/`, a local Hopper installation stub that an
`add -A` swept into the commit. It is unrelated to this change.
* fix(javascript): keep a void options slot, reject ! and ~
greptile flagged that `isUnpricedPrimitiveLiteral` dropped `spawn("tool", [], void 0)`,
`fork(..., void 0)` and `execFile(..., void 0)`. It checked the unary operand without
the operator, but `void <expr>` evaluates to `undefined`, which is the one options
value Node accepts in place of an object.
Measured on Node 24.22.1:
spawn(cmd, [], void 0) -> SPAWNED
spawn(cmd, [], void 5) -> SPAWNED
spawn(cmd, [], -0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], -5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], +5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], !0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], ~0) -> THROWS ERR_INVALID_ARG_TYPE
So the operator now decides: `void` keeps the spawn, `!` and `~` reject whatever
their operand, and `+`/`-` reject only when the operand is a numeric literal.
* fix(javascript): reject a non-finite global in the child-process options slot
An unshadowed `NaN` or `Infinity` in the options slot is a number, and Node's
options check rejects a number exactly as it rejects any other primitive. The
guard read only literal nodes, so the global identifier passed through. Measured
on Node 24.22.1:
spawn("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
spawn("e", Infinity) -> THREW ERR_INVALID_ARG_TYPE
execFile("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
fork("x.js", NaN) -> THREW ERR_INVALID_ARG_TYPE
--- accepted ---
exec("e", NaN) -> ok, because exec validates no options
spawn("e", undefined) -> ok, the one accepted global
spawn("e", null) -> ok
spawn("e", []) / ({}) -> ok
`isUnshadowedGlobal` keeps a local binding named `NaN` out of the rule, so a
shadowed value keeps its fact. Seven regressions cover the rejected forms plus
`exec`, `undefined`, `null`, an array, an object and a shadowed `NaN`; all seven
fail on the previous commit.
* fix(javascript): emit no promise for a literal non-callable executor
Node's `Promise` constructor calls the executor synchronously and throws a
`TypeError` when it is not callable, before any promise object exists. The
analysis reported a `constructor` promise operation with a hardcoded
`sourceResolution: "complete"`, which the graph projects as
`creates-promise` `resolved`.
Measured on Node 24.22.1:
new Promise() -> THREW TypeError
new Promise(1) / (-1) -> THREW TypeError
new Promise("work") / (`work`) -> THREW TypeError
new Promise(true) / (!0) -> THREW TypeError
new Promise(null) / (void 1) -> THREW TypeError
new Promise(1n) / (/re/) -> THREW TypeError
new Promise([]) / ({}) -> THREW TypeError
--- accepted ---
new Promise(() => {}) -> OK Promise
new Promise(async () => {}) -> OK Promise
new Promise(function () {}) -> OK Promise
new Promise(class {}) -> OK Promise
A class passes Node's own `typeof` check, so it keeps its operation. An
identifier, a call result or a spread may still supply a function and also keeps
its operation.
The filter gates the candidate push rather than `promiseCandidateDetails`, which
also decides whether an `await` becomes an `awaited-expression`: gating there
made `await new Promise(1)` emit a spurious task fact, whereas gating at the push
site makes it emit nothing, which is the honest answer.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject a non-finite executor, not only a literal
greptile caught that `new Promise(1e309)` still emitted a promise. Babel stores
that literal as `Infinity`, and `readExactJavaScriptLiteral` refuses to price a
non-finite number, so the guard missed it.
Measured on Node 24.22.1 — every one throws before a promise exists:
new Promise(1e309) -> THREW TypeError
new Promise(1e400) -> THREW TypeError
new Promise(Infinity) -> THREW TypeError
new Promise(-Infinity) -> THREW TypeError
new Promise(NaN) -> THREW TypeError
A numeric literal is now read directly rather than through the exact-value
reader. `Infinity` and `NaN` are identifiers rather than literals, so they are
decided through `isUnmodifiedGlobalReference`; a shadowed binding keeps its
promise, which is the same scoping rule the timer fix uses.
* fix(javascript): link a response body reader only to a settled fetch
`collectResponseConsumers` treated any `.json()`/`.text()`/`.arrayBuffer()` on a
binding holding a request result as a response consumer, and gave it the
request's endpoint at `resolution: "complete"`. That holds only for a settled
`fetch`.
Measured on Node 24.22.1:
await fetch(u) -> Response json/text/arrayBuffer = function
fetch(u) unawaited -> Promise json = undefined; pending.json() THROWS TypeError
http.get(u) -> ClientRequest json/text/arrayBuffer = undefined
http.request(u) -> ClientRequest json = undefined; request.text() THROWS TypeError
new WebSocket(u) -> WebSocket socket.json() THROWS TypeError
await http.get(u) -> ClientRequest (await does not help)
So four shapes emitted a resolved "this endpoint's body is read here" fact for a
call that throws before any body is read: an un-awaited fetch, `http.get`,
`http.request`, and a `WebSocket` handshake.
Link only requests that settle to a `Response`: a `fetch` call whose value is
awaited through expression wrappers. A binding that also holds a request kind
without a body reader keeps the consumer a candidate (`partial`) rather than a
proven one, and a binding with no settled fetch emits no consumer at all, which
matches the `collectReleases` / `forkRejectsStdioArray` precedent — the receiver
provably has no such member, so there is no fact to report.
* chore: retrigger title and readiness checks after the title was corrected
* fix(ghidra): keep a changed existing project's refusal typed through launch
When the selected REA_GHIDRA_PROJECT_PATH project changes after
open_binary commits its profile, the launcher's verified copy refuses it
with AnalysisArtifactChangedError. GhidraHeadlessLauncher wraps that in
GhidraLaunchError, and the session error's cause became the wrapper, so
the projection missed the existing artifact_changed branch and returned
generic execution_failure with rea doctor advice.
Read the launcher's primary failure when it is an
AnalysisArtifactChangedError, so the refusal reaches callers as
artifact_changed with the changed path and the existing reopen guidance.
The refusal stays fail-closed: no automatic reopen or fresh-import
fallback. Other launch failures keep their current projection.
Fixes#8217
* fix(ghidra): describe a project profile mismatch without a timing claim
When the owned copy of a selected Ghidra project no longer matches the
fingerprint committed by open_binary, copyExistingGhidraProject threw
RegularFileChangedError, so the artifact_changed reason said the input
"changed while being read". A digest mismatch only shows that the
project differs from its committed profile; the change may have happened
before the copy started.
Throw AnalysisArtifactChangedError directly with a reason that states
the mismatch and leaves the time of the change unestablished. Changes
observed while files are being read keep their existing reason.
* fix(cli): seal in-process Evidence before publishing
The CLI rebuilt a plain bundle from records it had already authenticated,
so writeEvidenceBundle cloned and authenticated the complete analyzed
graph again at the final checkpoint. On a default heap that second copy
could exceed available memory after minutes of analysis and discard the
whole run.
Seal the in-process bundle once so publication can stream the canonical
form without the redundant authentication copies. The regression test
fails on the previous behavior with resource_constraint under a one-byte
heap allowance.
Fixes#8411
* fix(cli): preserve the heap check for mutable Evidence
Only seal the publication bundle when every record is already immutable.
Mutable producer records keep the writer's existing heap check instead of
being copied before that check. The regression now covers the safe
fallback and lets the shared temporary-directory fixture own cleanup.
* test(cli): keep a distinct mutable source in the publication regression
Seal the main record and give the mutable source its own evidence ID so
the map cannot replace it before the bundle guard runs.
* fix(gdb): keep sessions usable after an oversized stream record
A single MI console, target or log record over the 16 MiB line limit
failed the whole session protocol, so close could not re-synchronize
and GDB plus its inferior stayed owned with no recovery path.
MI stream records carry only output and c-strings escape newlines, so
a raw LF always ends the record. #receive now drops the remainder of
an oversized stream record, marks pending command output and retained
records truncated, and keeps parsing later lines; oversized result
and async records still fail closed as before.
Fixes#8023.
* fix(gdb): keep earlier records in a chunk with an oversized stream record
* test(gdb): drop superseded oversized-record test copies
Artifact classification probes every ELF root for a Node SEA note. Each
PT_NOTE program header was scanned on its own, one read per note, so
headers that declare the same note bytes multiplied the work: a 295 KB
ELF with 4,096 such headers kept inspect-artifact busy for about three
and a half minutes.
Every note header takes at least 12 bytes, so disjoint tables hold at
most one note per 12 file bytes. The scan now stops there. Past that
count the remaining tables are unreadable: an ELF without a confirmed
blob keeps its native classification, and a confirmed blob fails closed.
* fix(wasm): name the compare_wasm_interfaces side of rejected Evidence
compare_wasm_interfaces parsed both sides inside one try and reported any rejection, such as tampered content, a different operation or a rebound subject, as an input issue at path [], which names no argument. Each side is now parsed separately and a rejection is reported at [left] or [right], as the function, native and managed comparers already do.
* test(wasm): cover both comparison sides through the MCP boundary
Android's package parser discards an activity, activity-alias or receiver
intent filter that declares no action, so such a filter neither makes the
component exported by default nor triggers the API 31 explicit-export
requirement. Services keep action-less filters. The manifest inventory
counted every declared filter, reporting exported_default true (or an
explicit-export validation error) for components Android does not export.
intent_filter_count still reports every declared filter.
Setup against npm latest already accepts --client freebuff. Replace the
stale proposed-integration wording while keeping the unverified native
client loading caveat.
Compact input schemas are the default advertisement since #5975, and the
profile drops every property description. On 23 of the 31 tools with a
guided `detail` option, including inspect_artifact and inspect_macho,
`detail` is advertised as a bare complete|summary enum, so callers take the
complete default. On a universal macOS app that reply was about 699k
characters for inspect_artifact and 336k for inspect_macho, against about
4.4k and 3.2k with summary.
Keep the canonical description of a root `detail` property in the inline
compact input form. The reduced form already keeps first-level guidance, and
compact output schemas are unchanged. Advertised compact input schemas grow
from 127,616 to 133,358 bytes across 233 tools; the largest schema stays at
4,107 bytes, within the 13,312-byte budget.
The new boundary test compares `detail` guidance between the full and compact
tools/list and fails before the change.
* fix(javascript): require exactly one fork IPC channel
Node's fork() rejects an explicit stdio array that carries more than one IPC
channel, and the throw is synchronous, before any child process exists:
if (ArrayPrototypeFilter(options.stdio, ...'ipc').length > 1)
throw new ERR_IPC_ONE_PIPE()
The analysis already dropped an array with no "ipc" (#7992) but still reported a
resolved fork for an array carrying it twice.
Measured on Node 24.22.1:
fork({stdio: ["ipc", "ipc"]}) -> THROWS ERR_IPC_ONE_PIPE
fork({stdio: ["pipe", "ipc", "ipc"]}) -> THROWS ERR_IPC_ONE_PIPE
fork({stdio: ["ipc"]}) -> SPAWNED connected=true
fork({stdio: ["pipe", "ipc"]}) -> SPAWNED connected=true
fork({stdio: ["pipe", "pipe", "pipe", "ipc"]})-> SPAWNED connected=true
So the required count is exactly one, not at least one. Count the resolved "ipc"
literals and reject the candidate unless the count is one; a spread or an
unreadable element may still change the count and is left alone, as before.
* chore: retrigger title and readiness checks after the title was corrected
The host no-return verifier, scripts/verify-real-ghidra-noreturn.mjs,
fails before its Evidence checks. ReaNoReturnProbe calls private bridge
methods through reflection, and two bridge changes left the probe behind:
- d4bd65c53 added a Function parameter to noReturnCallLimitations, and
225f26a5f renamed the method to functionBoundaryLimitations. The probe
still looked up noReturnCallLimitations(Iterable) and stopped with
NoSuchMethodException.
- 01a1b3fda made procedureReferences return a function observation
envelope. The probe read references and unresolved_calls from the top
level of that envelope instead of from its value.
Pass each function and its instructions to functionBoundaryLimitations,
and read procedureReferences results from the envelope value. The require
checks are unchanged. CI does not run this verifier.
These edits are the probe repair from robotaro's open draft that treats
demangled C++ runtime imports as returning, copied line for line, so
either change merges cleanly after the other.
Co-authored-by: robotaro <[email protected]>
The design guide said the setup allowlist does not copy
REA_MCP_MAX_RESPONSE_BYTES, but SELECTED_REGISTRATION_SETTINGS includes
it and setup forwards an explicit selection, as the client guide states.
Fixes#7420
An argument that accepts inline JSON or a JSON file path also reads the
request from standard input when it is "-". The behavior shipped without
documentation. Describe it next to the request-file example, with its
UTF-8, byte-order mark and 8 MiB limits.
Fixes#7138
The One analysis engine readiness row offered ghidra (or hopper, ida) while
the CLI accepts hopper, ghidra, ida, jadx and kuna for doctor --provider.
Add the missing IDs and keep the table aligned.
Related to #4624: docs/cli.md carries the same stale enumeration and is
updated separately in #4651.
* fix(ghidra): mark empty xrefs results as unresolved, not unreferenced
Ghidra 12.2 resolves no references to data addresses on some Mach-O
imports, and xrefs returned a bare empty list with nothing separating
'no references exist' from 'this provider resolved no references', so
callers concluded addresses were unreferenced.
projectExecution now attaches ghidraEmptyXrefsLimitation to an empty
xrefs result: the list reflects what this analysis resolved, not proof
the address is unreferenced. Non-empty results carry no caveat.
Covered by behavior tests for both branches; validated against
FM9-Edit-arm64 (expect: limitation) once the daemon session reopens.
* fix(ghidra): drop circular trace_feature cross-check from empty xrefs caveat
trace_feature calls xrefs internally, so recommending it as an independent
cross-check repeats the same lookup the caveat qualifies. Keep disassembly
as the single independent check.
* fix(ghidra): report actionable-reference absence and invalidate stale snapshots
Review follow-up on the empty xrefs caveat:
- The bridge filters synthetic entry-point references before observation, so
"resolved no references" could be false; the caveat now reports that no
actionable references were returned and names the omission.
- Drop the trace_feature cross-check: traceReferences observes xrefs per
matched string, repeating the same lookup instead of independent evidence.
- Bump instruction_references semantics to v3 so analysis snapshots captured
before the caveat replay no bare empty results; the profile commitment in
the snapshot binding no longer matches.
* feat(ghidra): mark empty function-reference facets as unresolved too
Extends the empty-xrefs caveat to the sibling ReferenceManager consumers
review follow-up identified: procedure_references and analyze_function
reference facets built on the same observation path could still present
empty arrays as collected facts.
- ghidraEmptyFunctionReferencesLimitation attaches when
procedure_references returns no references for the requested direction
and when a dossier observes no references in either direction;
callers/callees (function-manager derived, observed working on the
reported target) stay caveat-free.
- One emptyReferenceCaveat owner replaces the inline xrefs branch.
- function_references semantics bump to v3 so snapshots captured before
the caveat do not replay bare empty facets; raw profile digests
recomputed.
* fix(ghidra): cover annotation and instruction reference facets in the caveat
Review follow-up: annotate_native_function returns a dossier built by the
same analyzeFunction collection and inspect_native_instruction builds
incoming_references with visitObservedReferencesTo; both could present
empty arrays as collected facts under the reported ReferenceManager
failure.
- annotate_native_function attaches the caveat when its dossier observes
no references in either direction.
- inspect_native_instruction attaches it when incoming_references is an
explicit empty array (null or omission stays unavailable, matching the
field contract).
- Split the diagnostics describe to keep each block under the lint bound.
- Behavior tests cover attach and non-attach for both operations.
* fix(ghidra): name the honest cross-check limits in the reference caveats
Review follow-up: the motivating failure was a string address, so
'cross-check the disassembly at this address' pointed at nothing — data
and string addresses have no instruction to disassemble — and
inspect_native_instruction's incoming_references is built through the
same visitObservedReferencesTo path, so it was not independent either.
- xrefs caveat now states that no independent in-product cross-check
exists for the empty result, that surrounding disassembly only helps
at a code address, and that data or string addresses need an external
disassembly scan (otool -tV adrp/add pairing).
- Function-facet caveat separates outgoing references (own disassembly
is independent) from incoming references (no in-product check; scan
other code externally).
* fix(ghidra): use the address-scoped caveat for inspect_native_instruction
Greptile review follow-up: inspect_native_instruction checks incoming
references to one exact address, so the function-scoped caveat misdescribed
the scope; an instruction can be unreferenced while its function has
references elsewhere, and the address may have no containing function.
Attach the address-scoped xrefs caveat instead.
* fix(ghidra): qualify each empty reference direction independently
Analyze-function and annotate-native-function results attached the
unresolved-references caveat only when BOTH reference directions were
empty, so a dossier with one populated direction presented its empty
direction as a collected fact.
Split the combined function-references caveat into direction-scoped
limitations and attach one per empty direction (analyze_function,
annotate_native_function dossier readback, procedure_references by its
requested direction).
* fix(ghidra): qualify empty outgoing instruction references
inspect_native_instruction results carry two independent
ReferenceManager facets: the incoming list and the outgoing references
built from instruction.getReferencesFrom(). Only the incoming list was
checked, so an instruction with a populated incoming list and no
outgoing references presented the empty outgoing facet as a collected
fact, and any populated incoming list suppressed the caveat entirely.
Each facet is now qualified independently: the empty incoming list
keeps the address-scoped caveat; the empty outgoing list attaches the
new instruction-scoped outgoing limitation (this stream is not
entry-point filtered, so it claims no entry-point omission). Truth
table covered in describe("Ghidra empty instruction reference facets").
* fix(ghidra): gate the instruction outgoing caveat on decoded status
Non-decoded inspect_native_instruction results (data, not-in-listing,
not-instruction-boundary, outside-memory) return before the bridge ever
queries instruction.getReferencesFrom(), so their empty references list
is an unobserved facet, not an empty ReferenceManager observation. The
unconditional outgoing caveat claimed the ReferenceManager returned no
references from an instruction that does not exist at that address.
The outgoing limitation now attaches only when status is decoded; the
address-scoped incoming caveat still applies per the empty incoming
list, matching the original motivating data-address case.
* fix(ghidra): dossier incoming omission and external outgoing recovery
Two unqualified cases remained in the function-reference caveats:
- The analyze_function/annotation dossier excludes incoming references
whose source is inside the function's own body (a self-recursive call
is enough), so the projected empty list can mean 'no outside source
observed' or 'only in-body sources observed'. The dossier now uses a
dedicated incoming limitation naming the exclusion, pointing at the
outgoing edges/disassembly where in-body edges remain visible, and at
procedure_references(incoming) - the same ReferenceManager stream
without the in-body exclusion - as the in-product discriminator.
procedure_references keeps the generic incoming caveat: its incoming
stream is not in-body filtered.
- An external function has no local body, so the outgoing caveat's
'function's own disassembly' recommendation was impossible. Records
classified external now get an external-specific outgoing limitation
naming the defining artifact and this program's call sites instead
(procedure_references outgoing, analyze_function, annotation readback).
* fix(ghidra): qualify dossier incoming caveat observation
The dossier-specific incoming caveat claimed the ReferenceManager
observed no incoming references from outside the body, but
visitObservedReference (ReaGhidraBridge.java:3973-3974) drops
entry-point references before the body filter (1619-1621), so the
manager may have returned references the dossier never reports.
Reframe as 'returned no actionable' and name both omissions; correct
the recovery claim: procedure_references(incoming) lacks the in-body
filter but omits synthetic entry-point references as well.
* feat(elf): decode Linux core dump process and memory mapping notes
Inspect Linux core dump note tables and decode structured metadata without
requiring external tools or debuggers:
- NT_PRPSINFO (type 3): decode process ID, parent ID, process group,
session, UID, GID, execution state, filename, and command line arguments
bounded by the text budget.
- NT_FILE (type 0x46494c45): decode mapped memory ranges (start_address,
end_address, file_offset_pages) and mapped file paths with page size.
- NT_SIGINFO (type 0x53494749): decode faulting signal number, errno, and code.
- NT_AUXV (type 6): decode auxiliary vector pairs with known AT_* tag names.
- Support both 32-bit and 64-bit, little-endian and big-endian targets.
- Retain unknown core note types gracefully without partial success errors.
- Add comprehensive unit tests, fixtures, acceptance tests, and documentation.
* fix(elf): preserve raw path bytes and enforce auxv termination
* fix(elf): account for path hex text budget and enforce auxv trailing padding
* fix(elf): account for row and scan limits, ABI signal order, and lossless source IDs
* fix(elf): cache auxv padding scans, pre-charge entries, and isolate page-size test
* fix(browser): keep negative script columns Chrome reports
Chrome reported a Debugger.scriptParsed event with startColumn -60 and
endColumn -15 for an inline script in WordPress's helper frame. CDP declares
these columns as plain integers, but REA's admission required min(0), so one
script failed the whole observe-web-execution run with unreadable_output.
CDP resource columns now share one representation, cdpResourceColumnSchema,
for script start/end columns, initiator call frames, parser initiators and
event listeners. Positions on a script's first line include its start column,
so keeping the reported values keeps the documented script-relative
conversion consistent. The domain contract and web runtime docs state that a
negative column does not locate a column in the resource.
Fixes#8674
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(browser): apply the CDP column policy to passive capture
inspect_web_page and discover_webmcp_tools still read CDP columns through
cdpSourceCoordinates, whose own >= 0 check turned a negative column into
null and reported invalid_protocol_value. WebMCP discovery also dropped the
tool's whole registration_source.
cdpResourceColumnSchema now lives with the domain's CDP observation schemas.
Runtime admission, the passive coordinate helper and the runtime, console,
initiator and WebMCP output schemas all use it. Lines keep their
non-negative rule.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(reference): Exclude Git pointer files from source identities
Match .git pointer files in the default import policy after the existing
directory rules, preserving ordinary-checkout exclusion provenance and Git
HEAD discovery. Cover linked-worktree identities, repeated imports, nested
metadata, similarly named source files, and configured-secret precedence.
Independent review: parent independent review APPROVE for Draft CI on patch
61bddf108952c77858fa71c333b4bb8a05ab33e8b03f5c6bccd879cd19c7c379 and source
tree 2d2372a9ec. Review triggers: delegated
shipping output, source inventory/file I/O behavior, and more than 50 lines.
Validation: npm run check passed with Node 24.22.1/npm 11.16.0 on Windows;
tracked Vitest regression 1 passed/3 Windows skips; test discovery passed.
Eight offline Linux production imports passed on cached Node 22.19.0 as
supporting evidence only. Pinned Linux/macOS Vitest cases await hosted CI.
Fixes#7551
* test(reference): Isolate worktree fixtures from host Git settings
Route every fixture Git command through one wrapper with a filtered child
environment, owned empty global config/template/hook paths, and no system
config reads. Keep resource cleanup under the existing temporary owner.
These settings are scoped to synthetic child processes; host Git settings
and this repository's commit/push checks are unchanged.
Targeted independent review: parent review APPROVE on patch
4be9a2af3e40edac3d64f687170e966a87fcf762f1877f2c55016e120bcd90ac and tree
a2ba91ee9c. The reviewer confirmed the native
proof helper hash, complete fixture command coverage, and owned cleanup.
Review triggers: delegated follow-up and fixture file/process boundaries.
Validation: npm run check:fast passed; targeted Vitest 1 passed/3 native
Windows skips. On pinned Node 24.22.1, the actual helper rejects inherited
hooks/templates while commit, worktree creation, HEAD and status checks pass.
The unisolated control copies the injected template and its commit fails.
Refs #7551
Preserve the twenty-entry diagnostic prefix and report the full count,
omitted count and truncation fact at the owning report projection. Document
the recovery limitation and cover the public HAR-to-comparison workflow.
Fixes#7621
Validation: 121 source tests; three selected CLI regressions (five other
cases unselected); nine README conformance cases; full --noEmit typecheck;
documentation facts/metadata/schema/licenses; module boundaries and
architecture guards. VitePress rendering is blocked by Windows EPERM on its
worktree-local Vue symlink. Full aggregate coverage remains for Draft CI.
Independent code review: APPROVE, relayed by parent task
01a17716-6299-75fe-87e9-b0398b8c9019. Trigger: >=50 changed lines / >=3 files.
Reviewed tree: ba964efcd6
Reviewed full-index patch SHA256:
972dcfc219dd7b9606175b1848068f92210fa3f7d0a8a244c1ac7ce296ae32d9
* fix(flutter): read unlabeled Dart Version::String() from engines
Release libflutter.so embeds Dart's Version::String() without the
standalone VM's "Dart VM version:" label, so identify_flutter_build left
dart_version null on real APKs. Accept that channel-bearing form beside
the labeled scanner path.
* fix(flutter): qualify unlabeled version candidates
---------
Co-authored-by: morluto <[email protected]>
Chrome DevTools HAR exports declare creator WebInspector 537.36 and
report IPv6 serverIPAddress values in brackets: its HAR writer strips
the port with lastIndexOf(':') but its bracket removal,
replace(/\[\]/g, ''), only matches the literal pair '[]'. The pinned
schema then rejects the whole archive at the first such entry.
Extend the existing Playwright 1.58.2 adaptation to this declared
producer. Brackets are removed only in the disposable validation copy
and only around a valid IPv6 literal; reported spellings are retained
and the per-entry limitation names the declared producer.
Application analysis preserves the complete literal request endpoint,
including the empty string, and the event seed already accepts a
retained empty name. The endpoint seed still required a non-empty
value, so trace-javascript-semantics refused to select a retained ""
endpoint with invalid_request.
The endpoint seed now accepts any string and keeps exact matching.
Boundary-field seeds keep their minimum because boundary nodes are only
produced from method names.
Found while fixing #6458; the event half of that report landed in
cf5e4f6fa.
Co-authored-by: Claude Fable 5.1 <[email protected]>
* feat(binary-layout): admit ELF64 AArch64 and name the refused host
inspect_binary_layout refused every AArch64 ELF, and on a non-x64 host the
refusal read "supports Linux x64 only", which looked like a target refusal
(#8709 ran on arm64 WSL and suspected an e_machine check).
The pwntools adapter now admits little-endian ELF64 AArch64 EXEC/DYN/REL
objects alongside the existing profiles, pwntools must report aarch64/64/
little, and the Evidence subject names arm64. The host refusal now names the
detected platform/arch and says the target was not read; the host check
itself is unchanged.
verify:binary:layout cross-compiles an AArch64 executable and relocatable
object (never executed) and checks sections, symbols and relocations
against GNU readelf, decoding ELF64 r_info as a 32-bit type under a 32-bit
symbol index. The real-binary-layout workflow installs the toolchain.
Refs #8709
* test(binary-layout): refuse RISC-V as the unsupported machine and assert the host refusal path
verify:binary:layout used e_machine 183 (AArch64) as its unsupported
target, which this branch now admits; use EM_RISCV (243) instead.
The unsupported-host provider test now asserts the refusal names this
host's platform/arch and says the target was not read, so reverting the
decoder to the old message fails it.
* fix(native): stop calling an off-screen window absent
The native UI helper looked up its selected window with
`.optionIncludingWindow`, which returned nothing while that window was off
screen. An existing window owned by the selected process was reported as
`window-mismatch`, before any permission check, so the verifier could not reach
its permission boundary. Select from all windows instead; absent and foreign
windows still fail.
The verifier's changed-bytes check also read `error.message`, but since
cc90771af a selection failure carries its helper code in `error.issues`, so the
check could not pass. Read the issue. Together these let the permissions lane
complete on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(native): own UI test processes from the moment they launch
The off-screen test registered cleanup only after parsing the ready record, so a
startup failure left `/usr/bin/open` and its app running. The lane test killed
only the verifier on a timeout, leaving its launcher and detached fixture alive
until they expired.
Register the off-screen owner immediately after the launch; the app inherits the
run token, so the sweep needs no ready record. In the lane test, every stand-in
records itself and leaves on one release the test's cleanup awaits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(debugger): let REA exit when a debugger could not be stopped
`rea debug-with-gdb` and `rea debug-with-rizin` printed cleanup_incomplete
and set exit status 1, but the retained child and its pipes kept the event
loop alive, so the process never exited. A stdio MCP server whose client
disconnected hung the same way.
closeOwnedDebuggerSessions is the owner both managers finish through. It now
unreferences retained sessions, as supervised providers already do after
incomplete cleanup, restores the references on retry, and names the retained
process IDs. The one-shot commands report those IDs, since no later command
can retry. Debugger ownership, signals and detach behavior are unchanged.
GDB cleanup requests also no longer hold the host until their 120 s command
deadline, because cleanup already bounds its own wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(debugger): name retained debuggers when an MCP server shuts down
An MCP server that exits with a retained debugger logged only "REA provider
cleanup failed" and "could not close cleanly", so nothing named the process
left for the caller to check. The PIDs were on OwnedDebuggerCleanupError, but
each shutdown reporter read only the outer aggregate's message, and the debug
failure_cause description dropped the field too.
The failure type now lives in its own dependency-free module with one reader
that walks composed failures. The shutdown report, the server's onclose log,
the debug description and the one-shot CLI all use it, and the shutdown report
writes the same remediation the CLI returns.
The fake debuggers now model each request exactly, with real reply shapes, and
a request they do not model fails the test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(debugger): compare retained-debugger guidance with its formatter
The new checks froze the wording "Check process ... and its target".
docs/testing.md asks guidance checks to compare with the owning formatter,
alongside independent checks of the facts. They now do, and each still proves
the process ID is named, because a formatter comparison alone would pass if the
formatter dropped it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Static checkpoints were admitted against the same budget as semantic and
application results, half of the parent's free heap. Once those charges used
it, the next checkpoint stopped the analysis and every remaining source lost
its static facts, with most of the parent heap still free.
Semantic and application results still stop at that budget. Static checkpoints
may now continue to three quarters of the free heap while the live heap also
has room for another copy of what it holds. Without that room, admission falls
back to the result budget. The live condition is needed because the limit V8
reports counts young-generation capacity that retained objects cannot use:
without it, the parent aborted on heap exhaustion at small heap limits.
Reserved relationships are given up only when the larger share runs short. A
stop still fails with a typed error, which now reports the limit that applied,
what was available, the live heap in use and what the checkpoint needed.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(doctor): report provider settings a client registration does not record
Provider checks read the terminal running doctor, so a registration with no
env block looked ready while its server reported not_configured. Compare what
an available provider relied on with the registration's own file and report the
setting names in a non-blocking registration_environment check. The client's
effective environment stays unknown, so health and the exit status are unchanged.
Fixes#8202
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(doctor): write the Cordis fixture at its literal path
The test resolved the path through supportedClients, whose default environment
argument CodeQL traces to the fixture's writes even though the test passes {}.
It only needed the path, so spell it like the other fixtures in this file and
drop a leftover directory it never used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(evidence): discover missing Evidence links without transferring records
An agent could only learn that a retained record's source was absent by reading
complete records or by having a downstream analysis refuse. The summary said
nothing about links, and complete-record retention was easy to mistake for
source closure.
- Add detail: links to get_evidence_bundle and CLI inspect-evidence-bundle:
paged link rows with retained or missing status and no record payloads, for
selected records against the whole session or file.
- Add retained-closure traversal, the walk composed workflows require, reporting
complete or incomplete. Direct reads make no closure claim.
- Count direct and missing direct links in the summary.
Fixes#8454
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(evidence): count a repeated Evidence link once in every delivery
The Evidence schema does not deduplicate evidence_links, so the summary counted
a repeated link twice while link rows and counts reported it once. Both now read
one helper for a record's distinct direct links.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(evidence): validate link delivery against the advertised schemas
The test never called tools/list, so neither the shared validator nor the MCP
client checked its replies against what each profile advertises. A schema that
rejected the links result could have passed. Fetch the advertised definition and
validate every request and reply against it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(native): select retained batch decompile results through analysis views
A completed batch_decompile could only be read whole. When it did not fit the
MCP response budget, or an agent wanted one function, inspect_analysis_view
refused the parent as unsupported and the only recovery was exporting the
session.
- Own the batch result schema in the domain, replacing the contract's copy and
the workflow's hand-written item types.
- Add summary and batch-decompile views: body-free rows keyed by original index
with exact filters and opt-in bodies, and UTF-16 pages of one body.
- Report the decompiling provider from the upstream analysis profile, since the
Evidence provider is the composing workflow.
- Keep batch limitations visibly aggregated rather than attributing them to one
successful item.
Fixes#8498
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(native): deliver batch decompile as a retained summary
A batch that fits still floods the caller's context with every body, and one
that does not fit only named an export. Selecting a body that cannot fit named
an export too, even though its text pages can.
- Accept detail: summary on batch_decompile: retain the complete batch first and
return its counts, identities and sizes. The default stays complete, and the
selection never reaches the Evidence parameters.
- Offer a ready-to-call body-free first page when a complete batch overflows.
- When a selected batch page overflows, continue from the same retained parent
with fewer rows, then with one body's text pages.
Refs #8498
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): count every unavailable entry label in a batch summary
label_unavailable only counted failed lookups, so a batch whose provider
answered null for every label reported zero. The identity contract defines a
null name as an unavailable label in both cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): continue an oversized batch view from the caller's own source
A batch view read from an Evidence file or inline record advertised a
retained-evidence continuation for a parent the session never retained, so
following it failed. A view's continuation now repeats the caller's source and
only narrows the selection. An inline record offers none, because it cannot be
repeated within the reply it overflowed. Non-batch views skip the selection
parse, since delivery asks about every view it returns.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(native): require advertised batch schemas through the shared validator
A local validator map dropped any tool without an output schema, so removing an
advertised schema would have left these contract tests green. Require both named
schemas and validate through createMcpSchemaValidation, as docs/testing.md asks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): offer file view continuations on servers that retain nothing
A fixed-target server reads an Evidence file through inspect_analysis_view but
records nothing, so its overflow skipped the continuation and returned generic
CLI guidance. A view's continuation names the caller's source and needs no
session, so it no longer depends on recording. Retained references still do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(cli): keep captured source Evidence when one-shot delivery never commits
A completed observation that was cancelled at final close, failed cleanup, or
never reached bundle commit returned its evidence links but dropped the source
records the invocation had already captured. The caller could neither read those
records nor tell whether they were missing, captured or written.
Report the closure at publishCliAnalysisEvidence, the shared boundary every
--evidence_output command uses. details.source_closure carries authentic captured
records within 64 KiB and explicit omissions: not_captured for a link this
invocation holds no record for, and not_inlined for a captured record outside this
response. A committed bundle keeps its receipt instead, nothing is written after
cancellation, and successful delivery is unchanged.
Fixes#8589
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(cli): keep the typed failure when collecting source closure cannot finish
A captured source with a data-sized evidence_links array made
pending.push(...links) exceed V8's call-argument limit. On final-close
cancellation or a refused publication that RangeError replaced the original typed
failure, which is the one outcome this delivery step exists to preserve.
Walk the closure through a Set, which is a deduplicating breadth-first queue, so
no link becomes a call argument and each link costs one lookup however often it
repeats. undeliveredSourceClosure now owns authentication and never throws: if
examining the closure fails, it reports collection_failure with the bounded error
type and an unknown closure state, and the original failure is still returned.
Fault the final-close tests on the scope the command gave to matching, after
matching completed, and assert that close was reached, so a moved close fails
instead of passing on an earlier one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Preserve the dynamic symbol's visibility attribute (default or protected) when extracting ELF dynamic exports, and expose it across complete Evidence and projected analysis views.
Historical Evidence records lacking visibility normalize safely to 'unknown', and projected views omitting visibility do not inject default values into partial row projections.
Fixes#8517
* fix(javascript): emit no event operation for a literal non-function listener
`EventEmitter` validates a listener before registering or removing anything:
if (typeof listener !== 'function')
throw new ERR_INVALID_ARG_TYPE('listener', 'function', listener);
The throw is synchronous, before any listener exists, but the analysis reported a
`register` or `remove` operation for those calls.
Measured on Node 24.22.1:
emitter.on(1) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", 1) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", null) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", true) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", {}) / ([]) -> THREW ERR_INVALID_ARG_TYPE
emitter.once / addListener / prependListener / prependOnceListener /
off / removeListener with a literal -> THREW ERR_INVALID_ARG_TYPE
--- accepted ---
emitter.on("x", () => {}) -> ok
emitter.on("x", function () {}) -> ok
`rejectsEventListener` drops the operation only for an exactly readable literal
that cannot be a function. An identifier, a call result or a spread may still
supply a function and keeps its operation, and a class expression passes Node's
own `typeof` check.
`emit` and `removeAllListeners` are untouched: `emit` passes its remaining
arguments as data to listeners that already exist, and `removeAllListeners` takes
only an optional event name.
* fix(javascript): price a non-finite listener and keep a shifted spread
Two review findings on the event-listener guard.
**A non-finite numeric literal was missed.** Babel stores `1e999` as a
`NumericLiteral` holding `Infinity`, and the exact-value reader refuses to price a
non-finite number, so the guard never saw it:
e.on("x", 1e999) -> THREW ERR_INVALID_ARG_TYPE
A numeric literal is always a number and therefore never callable, so the guard now
decides on the literal node directly.
**A leading spread shifts the listener position.** `e.on(...args, 1)` does not pass
`1` as the listener: Node spreads the array and ignores the trailing argument once
the listener is satisfied. The guard read `arguments[1]` regardless, so it dropped a
real registration. It now leaves the position undecided when argument 0 is a spread.
Five regressions cover both forms; all five fail without the fixes.
* fix(process): re-read unreadable tokens before signaling and name the process
Darwin KERN_PROCARGS2 samples a process's argument length and stack top
before it reads the address space, and exec publishes the new length
before it copies the new strings out (xnu sysctl_procargsx,
exec_copyout_strings). A read during exec can return a zero-filled or
stale strings area, and exec keeps the PID and start time that bracket
the read. The final sweep already re-read such tokens, but the
pre-signal group check read each member's token once, so one transient
read of a live group member failed the whole cleanup.
Move the bounded re-read into one shared fact-reading helper and use it
for both the sweep and the pre-signal group check. The group check binds
re-reads to the generation it first observed, so a reused PID stays
unresolved; the sweep keeps classifying the PID's current process.
An unreadable-token failure now carries the native identity read after
the token and the summary names the candidate by its native name,
parent, group and start identity. The Darwin helper and Linux procfs
reader report the short native process name (p_comm/comm) for display.
Fixes#8337
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): refresh other pre-signal members after token re-read waits
The pre-signal group check re-read only members whose token was
unavailable, so a readable member kept the relation it had before the
retry waits. A member that moved to another process group during those
waits could be validated on its old group, and the old group signaled
while the moved member kept running.
After any retry wait, the shared re-read helper now reads every other
live pre-signal fact once more with the same generation binding. A moved
member then takes the existing changed-group path, which retains its
ownership evidence and fails cleanup. An inconclusive refresh replaces
the stale fact so it fails closed. Nothing changes when no re-read ran,
and the sweep keeps classifying current processes because every group
it finds is revalidated before signaling.
Also cover PID reuse that happens before the re-read starts, where both
re-read identities agree and only the first-read generation rejects the
replacement's token.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): keep known identity changes final across token re-reads
The pre-signal re-read treated a first read without a same-generation
identity as unbound and replaced it with the re-read. On Linux the first
read can already establish PID reuse (an ESRCH token whose exit check sees
another generation); the re-read then classified the replacement, so a
zombie replacement could let cleanup succeed.
A pre-signal check now re-reads only same-generation facts. Changed,
exited, exiting and foreign-token facts are final. Each attempt also
re-reads the group's owned members instead of one separate refresh after
the loop, so a member that moves groups or turns unreadable while another
waits is seen within the same bound. The caller's run token selects the
owned members, and the sweep keeps classifying current processes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(setup): refuse Hopper on macOS below the release's minimum
Hopper setup planning accepted any macOS 12+ host as supported, and the
installer never compared the host with the selected release. The current
publisher-signed Hopper 6.6.1-demo requires macOS 12.4 (feed
deployment_target and the app's LSMinimumSystemVersion), so setup on
12.0-12.3 installed an app the host cannot run.
The installer now takes the minimum from the release that owns it: the
feed's deployment_target before the package download, and the signed
app's LSMinimumSystemVersion (decoded in-process after signature
verification) before copying. A host below either fails with the new
incompatible_host_version code and an installer detail naming both
versions; an incomparable app value is a bundle failure. Planning keeps
REA's own macOS 12 floor but reports support as unknown, since the
release minimum is read only after approval, and the plan discloses the
check. Info.plist decoding is shared with app-bundle resolution.
Fixes#8502
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(setup): compare the macOS Hopper plan detail with its owning disclosure
The planning test froze an explanatory sentence from the setup plan. The
macOS install detail now lives in macHopperInstallDisclosure beside the
commands and origins the installer owns, the plan uses it, and the test
compares the plan with that constant while keeping its independent
support-state assertions. An installer test also pins that the signed
app's requirement is read only after both signature checks pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(setup): compare macOS Hopper refusal details with their owners
The installer's refusal details were template literals inside
installMacHopper, so tests copied whole sentences. The installer now
formats them through exported macHopperHostVersionDetail and
macHopperIncomparableMinimumDetail. Tests compare each diagnostic and the
setup remediation with those owners and SetupInstallFailure's recovery,
and separately assert the URL, versions, setting name, failure code and
request order. The Info.plist type refusal is checked by error type, key
and path instead of a sentence fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
The handshake check ran only inside startSession, which a query reached
only when the profile committed a startup input, for PE or for an existing
project. Any other session started lazily inside callTool, so analysis seeds,
disabled analyzers, extension results, MIPS soft-float storage or a RISC-V
calling convention that the profile did not commit were never checked and
the query returned its result.
Every query now runs the checked start first. The client shares one startup
attempt, so there is still one launch, and a provider restarted within the
session is checked again. That leaves startSession as the only start in the
provider client, so the PE data-type path reuses its session info.
Recovery guidance is unchanged: the former eager-start condition now selects
only whether a completed handshake counts as a running provider.
Fixes#8612
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(native): select retained batch decompile results through analysis views
A completed batch_decompile could only be read whole. When it did not fit the
MCP response budget, or an agent wanted one function, inspect_analysis_view
refused the parent as unsupported and the only recovery was exporting the
session.
- Own the batch result schema in the domain, replacing the contract's copy and
the workflow's hand-written item types.
- Add summary and batch-decompile views: body-free rows keyed by original index
with exact filters and opt-in bodies, and UTF-16 pages of one body.
- Report the decompiling provider from the upstream analysis profile, since the
Evidence provider is the composing workflow.
- Keep batch limitations visibly aggregated rather than attributing them to one
successful item.
Fixes#8498
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): count every unavailable entry label in a batch summary
label_unavailable only counted failed lookups, so a batch whose provider
answered null for every label reported zero. The identity contract defines a
null name as an unavailable label in both cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(native): require advertised batch schemas through the shared validator
A local validator map dropped any tool without an output schema, so removing an
advertised schema would have left these contract tests green. Require both named
schemas and validate through createMcpSchemaValidation, as docs/testing.md asks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
After a validated seed, trace_native_values turned a cleanup-incomplete
failure from a call-target read or a followed callee dossier into a
plain-text unknown, kept dispatching reads, and returned successful
Evidence without the cleanup_incomplete status, cleanup reason or owned
resources.
The trace now classifies child failures with the shared stopsTraversal
predicate that call graph traversals use. A cancelled child still ends
the trace with its cancellation. A child whose provider cannot confirm
cleanup stops the trace: the provider's own error is the outcome, so its
resources and bound cleanup retry stay intact, and the work admitted
before the stop is returned as the trace's normal shape, with traversal
truncated by cleanup_incomplete and a located unknown at the stopping
read. That stop stays primary over a concurrent caller abort and over
the output-size check. Ordinary child failures remain unknowns.
MCP already retains such partials as workflow Evidence. The one-shot CLI
dropped them, so runDirectAnalysis now retains the partial workflow
Evidence on the failure through the same shared owner.
Fixes#8371
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(cli): describe one-shot timeouts for an invocation whose session has ended
A one-shot CLI analysis that reached a provider deadline told its caller
that the Ghidra session remained usable and reported a runtime-editable
limit applying to the next operation, although the command had already
released its provider session and runtime.
The shared error projection now takes an explicit caller lifetime.
Providers keep guidance that holds only for a continuing caller (session
reuse, same-session runtime updates) apart from the request outcome, and
the presentation adds it only for a continuing caller. One-shot owners
(runDirectAnalysis, runProviderAnalysis, finishAnalysisCommand and the
JavaScript application command) project for the invocation: details.limit
reports scope rea-invocation, applies_at next_invocation and the launch
environment seed, and the remediation leads with that setting for a new
invocation. MCP output keeps its runtime-update and same-session guidance.
Covers the Ghidra decompile timeout, decompiler failure, regex stack
exhaustion and startup timeout, the shared regex deadline, the Kuna
decompile timeout and the Hopper startup timeout.
Fixes#8311
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(cli): project composed child failures for the caller lifetime
Cleanup composition embedded its primary and cleanup failures as
continuing-caller projections, and baked the primary's message and the
cleanup's remediation into the composed error. A one-shot command whose
Ghidra startup timed out while shutdown also failed therefore still
reported a rea-runtime limit and update_analysis_settings in
details.diagnostics.primary_error.
childFailureDiagnostics now snapshots each embedded child at composition
for both lifetimes when its projection depends on the caller, so cleanup
history keeps its composition-time state and the final projection selects
the caller's form. composedCallerGuidance re-attributes message and
remediation with their continuing-only parts kept separate. Error copies
and redaction keep the invocation diagnostics. The Hopper startup timeout
now carries its continuing guidance on the error like the other producers.
Invocation limits keep agent_editable and rea_restart_required, with
values that hold for a new invocation, so only scope and applies_at
change and environment is added.
In existing-project mode a decompile timeout or a cancelled session now
says the next session copies and opens REA_GHIDRA_PROJECT_PATH again
rather than repeating import and analysis.
Tests compare delivered guidance with the owning formatters and constants
instead of freezing prose.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(discovery): carry provider repair instructions into capability discovery
Provider readiness reported only a failure description, so capability
discovery copied that description into tool_availability remediation and
dropped repairs the owners already had: the JEB discovery-deadline setting,
the ADB/Apktool execute-permission repair kept in diagnostics, and the IDA
and Kuna configuration recovery.
Unavailable ProviderAvailability now requires the owner's remediation
next to its reason, and readiness-derived unavailable capability
descriptors carry it too. Every producer supplies it (ADB, Apktool, JADX,
JEB, Ghidra, Hopper, IDA, Kuna, pcap, owned-process prerequisites, profile
rejection); the inventory uses it verbatim, falling back to the reason
only for static capability facts that declare no repair. ADB/Apktool
configuration failures own remediation as a field instead of diagnostics,
and execution refusals project it as remediation.action. JADX doctor reads
the availability's remediation instead of re-deriving it.
binary_session complete candidate availability and unavailable capability
groups expose remediation (CLI capabilities/providers too); summary omits
it and lists it in omitted_fields. The superseded IDA
configuration_remediation and windows_native_failure.remediation
diagnostics are removed.
Fixes#8506
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(discovery): keep owner repairs through provider selection and capability maps
Address review findings on the readiness-remediation contract:
- Provider selection rejections carry the availability owner's
remediation, CLI/MCP error details project it, and an explicit
selection refused by readiness uses it as remediation.action. IDA's
unreadable registration and Ghidra's Windows readiness repair no
longer degrade to generic selection advice now that the diagnostics
copies are gone.
- Ghidra's Windows readiness and existing-project capability maps carry
the same repair as the availability.
- pcap readiness keeps the limiter owner's repair (now a typed
remediationAction) and falls back to Wireshark guidance only when the
failure has none.
- ADB/Apktool repairs are owner constants that are complete sentences.
The execution refusal keeps its previous message-plus-repair recovery,
which the compiled CLI contract asserts.
- Regression tests compare with the owning formatter or constant and
add public MCP and CLI coverage for an explicit IDA selection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(evidence): read selected complete records without the whole bundle
get_evidence_bundle could discover a record by exact ID, but reading it
required the complete canonical bundle: complete + filters was rejected,
and inspect_analysis_view refuses parents without a view, such as
annotations, pseudocode sources and native traces.
- Add detail: records, a page (offset, limit; default 0, 8) of the
complete matching records in canonical bundle order. Records keep their
original IDs, subjects, providers and readback; missing_evidence_links
names direct links of the returned records that are not retained.
- The selection is labelled as historical and not an importable bundle;
complete keeps returning the canonical bundle, and offset/limit
require detail: records.
- Filters keep one owner shared by summary and records delivery, for the
MCP session and the CLI's inspect-evidence-bundle.
- An oversized record page now advises a smaller limit or one
evidence_id; whole-bundle overflow guidance mentions record reads.
Fixes#8307
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(evidence): compare record-selection guidance with its owners
Assert the record-selection limitations and oversized-page guidance
against their owning constants, and check the reported limit facts
separately, instead of pinning explanatory phrases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(setup): report the failed Hopper request URL and HTTP status
Hopper download callers collapsed every transport failure into a bare
`ok: false`, so HTTP 404 and 503 from the macOS feed and DMG, the Linux
publisher feed, the public key and the launch-time package download produced
identical generic advice without the failed URL.
The shared downloader now returns a typed failure (url plus http_status,
timeout, network, response_too_large or url_refused) and keeps caller
cancellation as a rejection. Installers attribute it to the request
(release_feed, package or public_key); HopperReleaseError carries it, and
setup projects it once into `download` and a remediation naming the URL,
outcome and next step. Replaces the Linux-only "Package download returned
HTTP" special case.
Fixes#8503
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(setup): refuse credentialed Hopper URLs and keep known HTTP outcomes
Native Fetch rejects a URL with userinfo using a TypeError that echoes the
whole URL, so a credentialed redirect reached `download.detail`, setup
remediation and publisher messages as a network failure. The downloader now
refuses request and redirect URLs carrying credentials as
`url_refused: credentials` before requesting them, and reports the caller URL
through the shared userinfo-removing `sanitizeBrowserUrl`.
Releasing an unadmitted body no longer replaces the outcome it was released
for: an errored (truncated) body after HTTP 503, or a failed cancel at the
size limit, keeps `http_status` or `response_too_large`.
Tests compare delivered remediation with the owning formatters and assert
request, URL, status and recovery class independently.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(javascript): page retained feature traces through analysis views
A bidirectional trace_application_feature result can exceed one MCP
response, and inspect_analysis_view refused trace parents as unsupported,
so the only recovery was exporting the whole session.
- Add a shared feature-trace view owner: a summary with the trace's
identity, producer counts and coverage, and pages of paths, nodes,
edges, seed_matches and native_handoffs in retained order with exact
filters. Node rows are compact unless observations are requested.
Page coverage never upgrades trace coverage.
- Accept an optional MCP view on trace_application_feature that retains
the complete trace before returning the selected facts; the CLI keeps
complete JSON and reads saved traces with inspect-analysis-view.
- Offer a ready-to-call first paths page when a retained complete trace
exceeds the MCP response budget.
- Share the distinct authority/state/confidence basis helper with
module references.
Fixes#8296
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(javascript): name the retained parent when a selected view overflows
Review follow-up for the feature-trace views.
- Share one delivery owner between trace_application_feature and
compare_source_to_bundle: stage the selected view against the
completed parent, keep the established contract that a cancelled
request commits no session facts, then commit sources and the parent.
- A selected view that exceeds the response budget now references the
retained parent and offers the same selection at half its limit,
instead of naming the view record, which cannot be a view parent.
- Cover two native handoffs paged one at a time, the oversized selected
page, and cancellation after the trace completes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(javascript): bind feature-trace pages to their collection rows
Advertise each feature-trace page with exactly its own row schema, so a
paths page cannot validate with node, edge, seed-match or handoff rows.
The native-trace view in #8533 received the same review finding; the two
sibling views now share one strict page contract.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(javascript): mix valid rows from another collection into a trace page
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(native): find procedures by the secondary names exact lookup accepts
search_procedures matched only each procedure's primary name, while exact
lookup also accepts Kuna inventory aliases (versioned-symbol and generated
sub_ names) and Ghidra symbols at an exact function entry. A search for a
name procedure_address resolves returned nothing, and trace_feature
silently missed the procedure.
Both providers now match the same names their exact lookup accepts. A
procedure still appears once under its primary name; the optional
matched_aliases field lists the secondary names that matched, so aliases
never become procedures of their own and list_procedures is unchanged.
The shared domain helper owns that row rule for Kuna and for the Ghidra
host-side Unicode folding pass. The Ghidra analysis profile records the
new search semantics so older snapshots cannot replay alias-blind results.
Fixes#8330
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(ghidra): match leaf spellings of procedure names in search
procedure_address accepts each entry symbol's leaf or namespace-qualified
spelling, but procedure search tested only qualified names, so a regex
such as ^audit_alias$ missed a secondary symbol ns::audit_alias (and ^same$
missed a primary alpha::same) that exact lookup selects.
Lookup and search now share one bridge definition of the names that select
a procedure, so they cannot drift. Matching secondary symbols are still
reported by their qualified names; because a leaf ends its qualified
spelling, the host's Unicode literal pass stays exact over those names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(session): clear provider-inferred unavailability after recovery
A provider adapter or process failure marked every routed operation of
that provider unavailable, but a later success cleared only the operation
that succeeded. Sibling operations, and composed tools built on them, kept
advertising the stale failure until each was retried individually.
Move runtime availability into one owner that records whether each entry
was observed for the operation or inferred from another operation's
provider-wide failure. A success clears its own failure and that provider's
inferred entries while keeping failures observed on other operations or
providers and declared refusals. Outcomes are ordered by dispatch to the
provider client, so an older in-flight success no longer erases a newer
failure while a request queued behind a failure still clears it.
Fixes#8404
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(session): keep newer successes when older failures complete last
Runtime availability kept one merged entry per operation and forgot
successful dispatches. An older provider-wide failure that completed after
a newer success re-inferred unavailability for siblings the newer success
had already proved working, and a superseded refusal could survive its own
later success.
Record only the newest failure and newest success dispatch per operation
and per provider, and project unavailability from them: an operation is
unavailable while its own newest failure is newer than its own newest
success, or, as inferred, while its provider's newest provider-wide failure
is newer than any success from that provider. The projection no longer
depends on reply completion order.
The recovery tests now bound held-reply acknowledgements, release and join
every held call in finally, and compare remediation with the owning
formatters instead of copying their wording.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(enhanced): stop call graph traversals at unconfirmed provider cleanup
A relationship read whose provider could not confirm cleanup of resources it
owns was projected into a graph node or call-path failure, the traversal kept
dispatching queued reads, and get_call_graph / trace_call_path returned
success with the cleanup_incomplete status buried inside the result.
Both traversals now read relationships through one shared owner,
readCallRelationships, which classifies a cancelled read or a read with
unconfirmed owned cleanup as terminal. On such a cleanup stop the workflow
returns the provider's own error as the outcome, so its cleanup owner, retry
binding and live resolution stay intact, and keeps the work admitted before
the stop as the workflow's normal result shape with a caveat naming the
stopped procedure and the unexpanded count. The composed-workflow layer
turns that admitted result into partial workflow Evidence on the failure,
and the MCP adapter retains it through the shared partial-error delivery.
Ordinary read failures still produce successful partial results.
Fixes#8369
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(enhanced): retain every carried partial Evidence on enhanced failures
Enhanced-tool failures used the shared partial-error delivery only when this
PR's workflow partial was attached. A failing read that already carried its
own partial Evidence, and analyze_function dossier failures, were projected
inline and never recorded with the session writer, so readback and bundle
export could not recover them. Every enhanced execution failure now goes
through one delivery owner that uses toPartialErrorToolResult with the
session writer; it still falls back to inline when no writer or no valid
Evidence exists.
Tests compare the traversal caveat against its owning formatter instead of
copying the sentence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Scheduled deep static checks on main fail knip because the
DarwinHelperCache type alias added in #8318 has no consumer. Remove
the alias; callers already use createDarwinHelperCache's inferred
return type.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix: retain address context cleanup failures
* fix!: preserve address context input errors
BREAKING CHANGE: Strict clients that cached the previous full inspect_address_context output schema must refresh tools/list and accept the typed error field on unavailable facets.
* test: use session contract for address context
Reject native view sources whose predicate disagrees with the selected operation. Use the original predicate for existing Evidence; do not alter a record without recomputing its identity.
Resume refused every identity mismatch with one generic message and
generic input-schema recovery. Report the differing target, profile,
layout, or provider facts with the journal and current values so callers
can choose the matching provider/profile or a new output directory.
Fixes#8349
Bind typed service creation, observed availability and retryable cleanup in one composition owner. Reuse selected CLI/MCP factories and canonical contract groups without copying provider operation inventories.
Closes#8429. Refs #740.
Validated paired real application workflows, actual local Codex CLI Terra/high use, complete Evidence and Unknown checkpoint parity, compiler-backed import boundaries, SDK schemas and final real mixed routing. Automated review findings were fixed at their producing owners. Existing exact-base CodeQL alerts are documented on the PR under the owner-authorized unrelated-check exception; no global alerts or security rules were changed.
Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
Separate engine cleanup, investigation reset and terminal disposal in the shared application owner. Keep stable Evidence IDs and full Unknown revision history through engine close and target changes; serialize reset with all record writers and cleanup owners, optionally acknowledging a complete checkpoint before clearing. Bound retained representation without eviction.
Validation: 43 focused files / 533 distinct tests, source/type/lint/schema/documentation checks, nine genuine source cases across eight families, real Ghidra session identity and cleanup, and nine actual local Codex CLI terra/high runs with independent full-result and checkpoint audits. See docs/verification/investigation-lifecycle.md for measurements, partial attempts and verification limits.
Fixes#8347.
BREAKING CHANGE: close_binary retains investigation records. Use reset_investigation to clear them. Cross-process recovery requires an explicitly acknowledged Evidence bundle checkpoint.
Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
The DOS verifier compared a fresh CLI session with an MCP session that
its address-boundary helper had renamed and restored, so the entry's
symbol source differed (imported vs user_defined). Compare the CLI with
the pre-edit MCP window, then assert the restoration changes only the
symbol provenance.
Fixes#8189
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(native): label binary overview counts with their inventory scope
`binary_overview` described its counts as "exhaustive", and its result was
a pair of bare scalars. An agent could read them as a discovery of every
function and string in the file, or as comparable across providers. They
are exact totals of the rows each provider's inventories return.
The shared `projectBinaryOverview` now adds one provider-neutral
`inventory_coverage` record, so Ghidra's direct summary and the
listing fallback carry the same record:
`{ scope: "provider_inventory", discovery_coverage: "unknown" }`.
The record reuses the `discovery_coverage` vocabulary that
`list_procedures({detail:"summary"})` already reports, now owned by one
domain schema. The tool description states the scope and that counts are
not comparable across providers. Counts, segment omission semantics and
limitations are unchanged.
Fixes#8287
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): label overviews in the workflow so older snapshots stay valid
Analysis snapshots replay the projected inventory summary. A snapshot saved
before inventory_coverage existed would then fail the overview output schema.
The record describes what overview counts mean, whoever produced them, so
the binary_overview workflow now adds it on both its provider-summary and
listing paths. Provider values and projectBinaryOverview are unchanged, and
the composed overview's linked atomic record stays as the provider
reported it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(native): refresh composed overviews from before inventory_coverage
Bump the binary_overview workflow revision to 5, so snapshot and CLI replay
recompute a revision-4 composed overview from its atomic record. Replay
would otherwise return the cached overview without its count labels. The
revision-4 case joins the existing refresh scenario. The atomic Evidence
is not rewritten.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(kuna): omit Mach-O data symbols from the procedure inventory
Kuna's `functions --json` registers every Mach-O export as a function
entry, so globals in `__DATA,__data`, constants in `__TEXT,__const` and
`__mh_execute_header` were listed, counted and decompiled as procedures.
The Kuna session now classifies each inventory entry with the target's own
section table, read by the shared `inspect_macho` load-command reader from
the header bytes kept while its private copy is hashed. Entries in sections
that declare no instructions, or in the header and load commands, are data:
listings and searches omit them and report per-region counts, and
procedure operations refuse them by name or address while `address_name`
and `xrefs` still accept them. Instruction sections, symbol stubs and
symbol-pointer import slots are kept; addresses outside every section stay
with an explicit limitation, an unreadable section table keeps the whole
inventory unclassified, and ELF inventories are unchanged. Composed
workflows such as `decompile_all` and `binary_overview` follow the listing.
Fixes#8168
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(kuna): keep an entry's classification caveat on direct answers
Resolution now returns the selected entry's Mach-O placement and the
caveat it leaves open, built by the same constructor as the listings: the
one-entry unplaced wording, or the unreadable section table's reason.
Pseudocode, assembly, analyze_function (including the dossier's own
limitations), procedure_address, call queries by name and address_name
carry it; code and import-slot entries carry none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(mcp): name the close_binary retry for retained cleanup
A close_binary that retired its target but could not confirm cleanup
returned cleanup_incomplete with retryable: false and only the generic
"Review the reported local resources" remediation, although the session
keeps every failed owner and the next close_binary on the same connection
retries them. Agents could abandon the connection that owns the only retry.
The MCP close tool now passes its retry operation to the session. The
session binds that typed AnalysisCleanupRetry to a failed retirement only
while it retains the cleanup for its next close; the close tool binds it to
process-capture failures its own scope retains and to compositions whose
session part was offered. Projection then reports
recovery.kind=retry_cleanup and appends a conditional instruction: keep the
connection, let the condition settle or resolve it, call close_binary
without snapshot_path and check the result. The hint disappears once that
cleanup resolves. retryable keeps describing the failed operation.
One-shot CLI closes, open_binary, analysis tools and failed snapshot saves
(which keep the target open) offer no retry. Cleanup composition copies the
owner's remediation without a retry offer, so the text appears once.
Fixes#8211
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test: derive close retry guidance from its owning formatters
The new close_binary retry assertions froze ProviderCleanupError's
explanatory sentence, so a harmless wording edit would break them. The MCP
test now compares delivered remediation with analysisErrorRemediationAction
applied to the delivered typed error plus cleanupRetryRemediation. The
domain tests use the same formatters, check that unbound and released errors
carry no retry text, and check that a bound composition adds the offer once.
Recovery fields, cleanup state and retry behavior keep separate assertions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* perf(process): reuse a verified Darwin ownership helper across processes
Every REA process on macOS compiled the Swift ownership helper into a fresh
temporary root and then paid macOS's first-launch assessment of that new
executable: 408 ms + 325 ms of a 1.4 s `rea inspect-macho /bin/ls`.
With the default xcrun compiler and module cache, the reader now keeps one
content-addressed copy under ~/Library/Caches/rea/process-helper/<key>/.
The key covers the helper sources, toolchain selection, architecture and
Darwin release. A copy is executed only after the cache root, entry and
files are verified as current-user owned, not group- or world-writable,
not links, and digest-matched to the entry manifest. Malformed entries
owned by the user are removed and rebuilt; linked or foreign entries are
left untouched and the per-process compile runs as before. Any cache
failure falls back to that compile, so the cache never fails an ownership
operation.
A miss compiles exactly as before, then publishes a copy through an atomic
rename, keeping the first concurrent publisher, and runs the shared copy.
That way its first-launch assessment is paid once. Per-run roots keep their
existing cleanup; published entries are never removed by close or exit.
An explicit REA_DARWIN_SWIFTC_PATH or REA_DARWIN_MODULE_CACHE_PATH keeps the
per-process compile and writes nothing to the cache. `rea uninstall
--purge-data` removes the namespace.
Fixes#2483
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): harden the shared Darwin helper cache
Address review findings on the shared ownership-helper cache:
- Verify every real parent of the cache root: each must belong to the user
or root and not be writable by others (sticky root-owned directories
excepted), so no other user can rename a verified entry. Execute through
the real path.
- Open manifest and helper through the owned non-blocking regular-file
reader, so a FIFO cannot block preparation, and retain handles whose close
fails.
- Key entries on the swiftc and SDK that xcrun itself resolves, including
their real paths, sizes and modification times. DEVELOPER_DIR, TOOLCHAINS,
xcode-select and in-place updates therefore select new entries.
- Never remove or replace an invalid or published entry. Removal could race
another process that has verified or is running that path, so such an
entry falls back to the per-process compile.
- Give the cache a close() that retries retained staging removals and
handle closes and reports ProviderCleanupError while any remain. The
reader closes it with its per-run root.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): keep cache lookups owned and fall back from unlaunchable helpers
Address the second round of review on the shared ownership-helper cache:
- Pass the preparation signal to both xcrun toolchain lookups, and wait for
every started lookup before reporting the first failure. Cancellation and
close then stop a stalled lookup instead of waiting out its deadline.
A failed or cancelled key computation is retried by the next lookup.
- When the verified shared copy cannot launch (EACCES, EPERM, ENOEXEC,
ENOENT or an unrequested SIGKILL, for example from a noexec home volume),
the reader stops using the cache. It reuses its own compiled helper or
compiles one, and retries the read once.
- Anchor the cache's returned cleanup errors as cleanupRoot does, so every
error returned while cleanup was pending resolves after a retry succeeds.
- Build the cache path with appendCallerPath, so a home with symlinks and
`..` keeps the caller's meaning instead of being normalized lexically.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): keep concurrent fallbacks and dual cleanup failures
Each helper read now records before awaiting whether it launched the shared
copy, so a concurrent read's fallback cannot hide its own. When the per-run
root and the cache's retained resources both fail one close, the returned
error composes both. It stays incomplete until both owners recover.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(process): refuse shared helpers that an ACL lets others write
macOS ACLs can grant writes that the POSIX mode bits do not show. On
Darwin, lookup and publication list the real ancestors, the entry and its
files with `ls -lde`, and refuse sharing when any ACL entry allows a
write-class right. The default `group:everyone deny delete` entries on
macOS home folders are unaffected. The unlaunchable-helper test now removes
the execute bit from a verified copy, which refuses launch on both Linux
and macOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(guidance): distinguish focused matches from broad parent sections
Guidance search treated a parent section, including every descendant, as one
match. Terms scattered across unrelated subsections made a 49K-character root
the only result for "switch provider annotations", and every ancestor of a
focused hit was listed alongside it.
The section parser now records each section's own text (heading and body
before its first subsection) and its parent. Search keeps the same admission
rule but classifies each match by match_scope: "section" when every term is
in the section's own text, "subsections" when the terms are only spread
across subsections. Ancestors whose terms all sit inside one subsection are
no longer listed, section matches rank first, and subsections matches carry
own_text_terms plus bounded greedy pointers to the subsections holding the
remaining terms. Every match reports content_length so callers can judge a
read before making it. Read still returns the complete section.
Own text is tokenized once per request instead of each expanded subtree.
Fixes#8290
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(guidance): require the scattered-match checks to run
Count the subsections matches the real-bundle test inspects and fail when
none were checked, and document that a parent whose own text holds every
term stays listed beside its matching subsection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
swiftc rejects assembly sources as inputs, so verify:ghidra:swift failed
during fixture preparation before any provider check. Assemble both
assembly fixtures to objects with xcrun clang and link only objects.
Fixes#8188
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(decompile): keep provider no-pseudocode facts on batch items
A batch_decompile item whose provider answered without pseudocode was
reported as unreadable output with doctor guidance, and the provider's
reason, such as a Kuna export failure for that entry, survived only in the
batch-wide limitations. The workflow read only the observation's result.
Read each item's complete observation. A null answer is now a
capability_unavailable item naming the provider, and every item answered
without usable text keeps that observation's own limitations, so a
batch-only Evidence export retains the association. Empty or non-text
pseudocode stays unreadable output; typed provider failures and successful
items are unchanged, and the batch Evidence still aggregates every caveat.
The batch workflow revision changes so earlier snapshots are not replayed
with the previous item semantics.
Fixes#8226
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(decompile): match null-pseudocode guidance to reported caveats
A null answer without caveats told the caller to read item limitations
that the batch omits when the list is empty. The guidance now depends on
whether that observation reported caveats, and says the provider gave no
further explanation when it did not.
The batch tests now select their contracts by name from the enhanced and
session families instead of the full catalog.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
trace_native_values set one truncated flag for two different facts: the
returned page omits retained nodes, and a budget stopped or narrowed the
traversal. A last page of a complete trace and a node- or edge-capped trace
both reported truncated: true with next_offset null and no unknown naming the
cap, so a caller could not tell whether to follow pages, raise a budget, or
treat the examined graph as exhausted.
Results now carry page_truncated for page omission and a traversal object
that is complete, or truncated with the observed reasons in schema order:
max_depth, max_functions, max_call_sites, max_nodes, max_edges,
node_payload_bytes, provider_pcode_truncated and ambiguous_call_candidates.
truncated keeps its previous meaning as the union of both.
A new budget owner admits nodes and edges and records every cause together
with a located unknown, only after a budget rejects work; an exactly filled
budget is no longer truncation. Depth and ambiguous-candidate limits are
resolved after traversal, so a destination analyzed or queued through a
shallower path is no longer reported as depth-limited. Once an edge is
rejected, remaining call sites in that procedure are not resolved.
Fixes#8283
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(ghidra): report unbacked file-offset addresses without schema repair
address_to_file_offset projected a well-formed address in uninitialized
or unmapped Ghidra memory as generic invalid input: the headline said the
arguments were invalid and recovery pointed at the input schema, while the
real reason survived only in a nested issue.
The bridge now gives each outcome its own code (address_unmapped,
address_uninitialized, file_mapping_absent, file_mapping_ambiguous,
file_offset_unrepresentable) instead of folding them into invalid_request.
The Ghidra projection keeps the bridge reason as the headline, points to
inspect_native_load_image, records the caller's exact address and the
file_mapping outcome, and scopes diagnostics to the request.
AnalysisInputError gains conformsToInputContract so a conforming request
omits recovery.kind=inspect_input_contract. Malformed addresses and other
bridge refusals keep their existing projection.
Fixes#8244
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(ghidra): match fixture block ends to bridge conventions
The fake bridge treated .text's exclusive list_segments end (0x1100) as an
inclusive bound, so address_to_file_offset mapped an address outside the
block. Give the fixture's blocks one owner (start and size) and derive each
reply from the production bridge convention: exclusive list_segments ends,
inclusive byte-search coverage, half-open containment for file offsets.
Add an MCP boundary test that reads the block edges from list_segments and
search_bytes, maps the last file-backed byte and refuses the first address
past each block with its outcome code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(ghidra): decide batch names independently of request order
The Ghidra bridge applied set_addresses_names in JSON member order, each
address in its own transaction, so a reordered but equivalent map could give
a shared name, or a name released by another selected address, to a
different address.
The bridge now resolves every destination (namespace and leaf name) against
the names held before the batch. Addresses that request the same destination,
or a name that another selected address holds, are rejected without applying;
the remaining edits are independent and apply as before. Both naming
operations report each failure's canonical address, reason and related
addresses. The adapter projects the bridge reply to the unchanged boolean
results and keeps the reply as raw_result only when an address failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(ghidra): report each contested batch name once
A batch conflict listed every other requester on each failure, so k
addresses competing for one name produced k(k-1) related addresses, and
the adapter retained the whole reply, booleans included, as raw_result.
The bridge now reports each contested destination once in a conflicts
table with its namespace, name, requesting and holding addresses, and
each conflict failure refers to it by index. Conflicts are ordered by
their first requester, so indexes do not depend on member order. Single
and batch naming share one planner and reply layout. The adapter
validates the table in linear time and keeps only failures and conflicts
in raw_result.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Declare logical Evidence sources once and resolve exact retained inputs through
shared application workflows across 20 composition tools. Preserve domain-owned
authentication, source closure, caller choices, coverage and resource admission.
Validation includes public CLI/MCP regressions, local Codex terra/high trials
and fresh-import real-artifact workflows. Measurements, failed attempts and
coverage limits are recorded in docs/verification/evidence-input-composition-2026-10-31.md.
Fixes#8293.
Fixes#8281.
Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
* feat(artifacts): inventory and extract UF2 and DfuSe regions
open_binary refused UF2 and DfuSe firmware containers as an unsupported
format, so inspect_artifact and extract_artifact could not show which
bytes a drag-and-drop or DFU update writes at which addresses.
UF2 is recognized by a complete first block with all three magic
numbers. Valid main-flash payloads are joined into address-contiguous
regions per familyID, named family-0x<id>/0x<start>-0x<end>.bin, and
known IDs are named from a small table copied from Microsoft's
uf2families.json at a pinned commit. Numbering is counted per familyID
and block count: absent, repeated, out-of-order and out-of-range block
numbers are reported, and absent blocks leave coverage unknown.
Not-main-flash blocks are excluded and reported; file-container blocks
are reported and leave coverage unknown; invalid blocks and a partial
final block are excluded with their position. A rewrite of one address
with different bytes fails closed, naming both block spans.
DfuSe is recognized only by its prefix together with a DFU 1.1a suffix.
Each element becomes one region at its file range with its target's
alternate setting and name. Structural faults fail closed with a byte
offset; overlapping elements stay separate and are reported. A suffix
CRC mismatch follows integrity_policy: fail refuses with a dfu-crc32
integrity error, and record-and-continue records a root contradiction
that extraction also carries.
HEX, UF2 and DfuSe share one retained-region reader and one run
merger, so the HEX decoder and reader lose their private copies.
* docs(artifacts): document UF2 and DfuSe images and their esptool lane
Describe recognition, region names, metadata, numbering, failures, the
DfuSe suffix CRC under integrity_policy, and limits, and link the page
from the CLI and firmware guides and the agent skill reference.
npm run verify:uf2 runs esptool merge-bin --format uf2 on generated
inputs at known addresses, with and without MD5, with 256-byte
payloads, and on a concatenation of two chips' outputs. Inspection and
extraction through the built CLI must return every input at its address
under the familyID esptool itself reports.
* fix(artifacts): accept both DfuSe image size conventions
UM0391 calls DFUImageSize the file length, and dfu-util's dfuse-pack.py
writes that value, while ST's DfuFileMgr, MicroPython's dfu.py and pydfu
leave the 16-byte suffix out. The decoder accepted only the second form,
so it refused dfuse-pack.py output as malformed.
Accept either value and keep the declared one in image_size; any other
value still fails with the suffix offset and the file length. The tests
pin both producers' output for one image, CRC included, from their
build() functions transcribed and run under Python 3.
* fix(artifacts): keep reused UF2 block numbers from hiding missing blocks
esptool numbers every merged input from 0, so two inputs with the same
block count share one numbering group. A block lost from one input left
its number present in the other, and the file reported complete
coverage. Each block number now records the target addresses it was
seen at: equal address counts are complete copies, a resend to the same
address is one block, and unequal counts make coverage unknown with a
limitation and ambiguous_numbering_group_count. sequence_count becomes
numbering_group_count, and region block positions become
first_file_block and last_file_block.
Review follow-ups in the same change:
- compare_artifacts compares a DfuSe element's alternate setting and
target name, so bytes moved to another device memory are changed;
- the shared reader keeps no region bytes outside its map after close;
- one domain type replaces four identical decoder failure types;
- the UF2 decoder returns a typed limit instead of throwing on bytes
beyond its admitted size, and caps retained regions, not runs;
- DfuSe caps elements with bytes only, names empty elements and
element-less targets, drops the constant bVersion and bcdDFU fields,
and says whether the CRC matches when DFUImageSize fits neither
convention;
- classification evidence carries a suffix only when the probe saw the
end, and root classification reads the tail only for a DfuSe prefix;
- the UF2 domain region owns family_name, and the root records the
family registry revision; the table's MIT notice is preserved;
- docs pin the producer revisions (dfu-util ee6b393d and f9a537c9,
MicroPython cb99ca98), give the MD5 structure's real meaning, and
describe container contradictions in the MCP contract guide;
- tests and the esptool lane add the equal-count inputs and a block
lost from one of them, accepted boundaries beside each refusal, a
DfuSe root larger than the classification prefix, and CLI refusals.
* feat(artifacts): name the UF2 families that LibreTiny builds for
LibreTiny writes UF2 for Realtek, Beken and Bouffalo Lab modules. The
Microsoft registry revision REA already cites (cf3bd7f9) lists eight of
those families: RTL8710A, RTL8710B, RTL8720C, RTL8720D, BK7231U, BK7231N,
BK7251 and BL602. Their regions now carry those names. LN882H, which
that revision omits, keeps its familyID with no name.
* fix(artifacts): count DfuSe declarations without bytes instead of listing them
Empty DfuSe elements have no entry, so DFUSE_MAX_ELEMENTS does not cap
them, and the target walk kept a description string for each one. At 8
bytes per element, a file under the 128 MiB ceiling can declare about
16.7 million of them. The walk now counts every declaration without bytes
and builds only the first NAMED_ELEMENTS descriptions, which is all that
the limitation shows. The limitation text and empty_element_count do not
change. The guide now says that both DfuSe limitations name the first four.
On this host, decoding 4,000,000 empty elements (a 32 MB file) ran out of
memory under a 128 MB V8 heap before this change, and it peaked at 1.59 GB
RSS with the default heap. It now decodes in 114 ms with a 220 MB peak.
---------
Co-authored-by: morluto <[email protected]>
* feat(artifacts): inspect classic iNES container regions
* fix(artifacts): preserve iNES source disappearance as integrity failure
* fix(artifacts): revalidate iNES source before parsing header
* fix(artifacts): validate admitted roots before classification
* fix(artifacts): normalize source loss during root hashing
* fix(artifacts): bind iNES extraction admission to inventory
* test(artifacts): clarify non-executing inspection wording
* fix: preserve binary plist detection with extended magic probe
* test(browser): wait for popup document loads before capture
* fix(artifacts): honor cancellation across source validation
* fix(artifacts): prefer artifact magic over implicit database suffix
* fix(artifacts): select extraction readers from scanned format
* fix(artifacts): complete prefix reads and share ZIP format admission
* fix(browser): budget scenario cleanup for content drain and shutdown
* test(artifacts): admit valid universal inputs before routing
* test(artifacts): verify iNES workflows on macOS
* test(ci): own iNES macOS coverage and portable fixture paths
* fix: preserve cleanup retries and avoid duplicate inventory parsing
* fix(contracts): share ZIP target formats across MCP lifecycle schemas
* fix(target): preserve typed cancellation during descriptor admission
* refactor(artifacts): use JSON-safe inventory producer output directly
* test(artifacts): share file read setup and satisfy CI checks
* fix(artifacts): classify changed admission as integrity failure
* ci: run Mach-O summary delivery coverage on macOS
* test(javascript): align parameter provenance with main contract
* test(browser): await discoverable fixture pages before runtime verification
* fix(artifacts): pass target metadata to format detection
* fix(artifacts): bind extraction to opened target
* fix: repair the static-check failures on the default branch (#2895)
* fix: repair the static-check failures on the default branch
The default branch fails its own required `Static checks` job three ways, so
every pull request reports that job as failed regardless of its own change.
- `npm run format:check` reports `src/domain/functionComparison.test.ts` and
`src/ida/IdaOperationRunner.ts`.
- `npm run lint` reports a duplicate `import { join } from "node:path"` in
`tests/boundary/mcp/ghidraInventoryMcp.test.ts` (lines 4 and 17).
- `npm run typecheck` reports `format: "devin"` missing from the client format
union in `src/application/SupportedClients.ts`, and an unchecked index in
`tests/boundary/cli/clientProfileLifecycle.test.ts`, whose pair array is
inferred as `string[][]`.
Apply the pinned formatter, drop the duplicate import, list the `"devin"` format
that its own definition already uses, and mark the pair array `as const`.
Validation: `npm run check:fast` and `npm run format:check` pass.
* fix(setup): keep Devin on the JSON configuration dialect
---------
Co-authored-by: 吴佳浩 <[email protected]>
Co-authored-by: morluto <[email protected]>
(cherry picked from commit df3d96da80)
* fix(artifacts): bind session inventories to target identity
* test(browser): bound fixture readiness acknowledgements
---------
Co-authored-by: 64579525 <[email protected]>
Co-authored-by: 吴佳浩Alben <[email protected]>
Co-authored-by: 吴佳浩 <[email protected]>
* feat(ghidra): record the RISC-V float-ABI calling-convention decision
Decision D3 (#7477), provisional: admit ELF32 soft-float (ilp32) RISC-V
images through a lane that keeps the pinned RISCV:LE:32:default language and
corrects the calling convention in the bridge.
This commit is the contract only; admission is unchanged.
- The domain owns the e_flags float-ABI vocabulary and parses
.riscv.attributes and Tag_RISCV_arch into a declared, absent or
unavailable ISA declaration.
- The Ghidra profile owns the lane table (class and float ABI), lane ids
built from fields, the ilp32-prototype-v1 policy and its __rea_ilp32
prototype, and the extensions Ghidra 12.1.4 decodes with p-code.
- The session handshake accepts the bridge's calling-convention report.
- docs/ghidra-riscv.md keeps the decision record and per-ABI policies for
ilp32 and ilp32f; a test keeps it in step with the tables.
- tests/conformance/c/riscv-float-abi.c is the source the policies were
measured on.
* feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention
ESP32-C2, C3, C5, C6, C61, H2 and H21 builds are ELF32 soft-float (ilp32)
images, which the RISC-V lane refused at its double-float check. Ghidra
12.1.4's generic RV32 compiler specification models ilp32d, so its stock
prototype would read float and double arguments from fa0-fa7.
- Target resolution reads the one SHT_RISCV_ATTRIBUTES section through the
same bounded ELF declaration reader as range validation, and records
Tag_RISCV_arch as declared, absent or unavailable.
- Admission selects a lane from the class and float-ABI table. The soft-float
lane also requires an RV32I Tag_RISCV_arch that declares only extensions
Ghidra decodes with p-code; the refusal names any others (Zcb, Zcmp, Zcmt,
Zb*, xesp*).
- Soft-float profiles commit float_abi, arch, riscv_calling_convention
ilp32-prototype-v1 and the lane elf32-exec-le-soft-float-generic-v1.
Double-float profiles are unchanged.
- ReaGhidraRiscvCallingConvention.java runs after analysis. It derives
__rea_ilp32 from the default prototype without floating-point entries and
rules, with fs0-fs11 killed by calls, self-checks its storage and assigns
it to every non-thunk function, external function and function
definition. Its report is written last.
- The provider client requires that report exactly when the profile commits
the policy, before any operation, and keeps it as session limitations and
caller-project history.
ELF32 single-float (ilp32f), quad-float and ELF64 soft- or single-float
images stay refused, now with a reason built from the lane table.
* test(ghidra): verify RISC-V ilp32 against real Ghidra
verify:ghidra:riscv now also builds tests/conformance/c/riscv-float-abi.c
with ESP32-C3's -march=rv32imc_zicsr_zifencei -mabi=ilp32, typed with DWARF
and linked again with --strip-all. Every allocated section of the stripped
image must keep its address and bytes.
- riscv-checks.mjs holds the ilp32 storage of each fixture function together
with the words the compiler's call site writes there. An LLVM objdump
reading of the caller must find every word at its stated location: an
unaligned named double, the a7/stack split, two floats in an integer pair,
a large structure by reference, aligned variadic pairs and an indirect
call.
- Through CLI and stdio MCP, the soft-float builds must report the soft-float
lane and ilp32-prototype-v1, __rea_ilp32 with exactly that storage (one
integer word per location when stripped), caller pseudocode that passes the
source constants and names no floating-point register, the prototype
outcome in retained Evidence, and decoded fence.i and csrr.
- The RV32 double-float image relabelled as soft-float, refused before, is
admitted through the new lane. RV64 soft-float, single-float and unsupported
bits are refused with their reasons.
- riscv-checks.test.ts runs the checks offline against recorded LLVM and
Ghidra output, including Ghidra's stock ilp32d prototypes as a negative
control.
* fix(ghidra): pass RISC-V ilp32 values over 8 bytes by reference
The psABI passes any value wider than 2 x XLEN by reference, scalars
included. __rea_ilp32 converted only structures and unions over 8 bytes to
pointers, so a long double (16 bytes) was joined into a1-a4. Measured on
Ghidra 12.1.4, rea_riscv_abi_quad(long double value, int sel) read value
from a4:4,a3:4,a2:4,a1:4 and sel from a5, where clang passes a pointer in
a1 and sel in a2.
- ReaGhidraRiscvCallingConvention.java now has one derivation driven by a
per-policy row (floating-point entry size, preserved floating-point
registers). Every policy inserts a rule before the input list's final
join that converts any value of 9 bytes or more to a pointer; a return
that size already becomes a caller buffer. The self-check requires a
16-byte value to pass by reference.
- The fixture gains rea_riscv_abi_quad. The call-site oracle now checks the
words the by-reference pointers point at, for the large structure and the
long double, and the recorded data comes from an LLVM 19 build.
- docs/ghidra-riscv.md: the decision record and the ilp32 policy row.
ilp32-prototype-v1 keeps its id: no release carries it yet.
* test(ghidra): check RISC-V lane limitations by their facts
The limitation tests froze a full explanatory sentence, the note count and
array positions, so a harmless rewording failed them: the pattern Greptile
flagged on #7486. Assert the facts the notes must carry instead: the pinned
language, the static-linkage boundary, one note naming the declared ABI,
the ABI the pinned language models and the prototype REA applies, and the
declared ISA.
* test(ghidra): check return storage in stripped RISC-V ilp32 builds
The stripped branch of assertRiscvIlp32Storage compared parameter storage
only, so an inferred return that lost the high word of a register pair,
such as rea_riscv_abi_wide at a0:4 instead of a1:4,a0:4, still passed.
Stripped returns must now use the typed build's storage, or none when the
result goes through caller memory. Recovered type names are still not
compared: a test accepts other type names and refuses the dropped high word.
* docs(ghidra): record decision D3 as decided in #7477
* feat(artifacts): inventory and extract ESP32 firmware images
ESP32-family app and bootloader images (magic 0xE9) had no reader, so
open_binary refused OTA downloads, app partitions and bootloaders.
Add the esp-image container format behind inspect_artifact and
extract_artifact. A pure decoder reads each byte once and keeps only
fixed-size fields. Derived facts cover the chip, from a table cited to
ESP-IDF v6.1; segment classification as ESP-IDF's should_map and
should_load; checksum and appended SHA-256 results; app and bootloader
descriptors; Secure Boot v2 signature blocks, observed and unverified;
and undescribed trailing byte ranges. Truncation and undescribed bytes
make coverage unknown. ESP8266 images are refused with a specific
reason, and ciphertext stays unrecognized.
Add the format to every target, Evidence, snapshot, live-summary and
artifact vocabulary, document it, and add an optional verify:esp-images
lane that compares esptool-built images with esptool image-info and
espsecure signature-info-v2.
Refs #3615
* test(artifacts): run the ESP image lane on esptool v5.6.0
The lane compared descriptor fields only when esptool image-info printed
one, so a descriptor that REA reports and esptool does not print passed
without comparison. Compare the descriptor kind in both directions.
Record that the lane passed for all 13 images with esptool and espsecure
v5.6.0, the version the layout facts cite, as well as with 4.12.0.
* refactor(artifacts): define ESP image layout sizes once
The decoder and the derived facts each defined the segment-header,
appended-digest and flash-sector sizes, the erased-byte scan and the
offset formatter. Define them once with the other fixed-layout structures
and import them in both places. The signature slot count is the one
constant that drives the slot scan.
Correct the observedEnd comment: it is the end of the last chunk read.
* fix(artifacts): report ESP-IDF's segment length limit by chip
ESP-IDF's verify_segment_header rejects a segment of 16 MiB or more
unless the chip maps flash through 32-bit cache addresses
(SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP: ESP32-S3, P4, C5, C61, H4 and
S31 in v6.1). Record that capability in the chip table and add a
limitation for such segments; unknown chips get none.
`loaded` repeats should_load. It does not mean the bootloader accepts the
segment: verify_load_addresses still rejects one outside the chip's RAM,
which REA does not model. Say so in the docs, the skill reference and the
inventory limitation.
* test(artifacts): check ESP lane tools first and verify extraction
The lane started work before checking its tools, so a missing RISC-V
target, ld.lld, esptool or espsecure surfaced as a raw spawn or command
error. Check each one before building and name the variable to set. Warn
when esptool is not v5.6.0, the release the layout facts cite.
Assert each segment's class for the ESP32-C3 app, its variants and the
bootloader, as the Xtensa image already did. Extract the app and the
signed image through the CLI and compare every file with the bytes at
esptool's segment offsets or in the appended signature sector; the
source's SHA-256 must be unchanged and equal the reported root_sha256.
* docs(artifacts): state what the ESP image lane does not establish
The Verification section listed the lane's checks but not its gaps: only
ESP32-C3 and ESP32 images come from a real producer, no real project
build, OTA download or flash dump is inspected, no signature is verified,
and the lane does not run in CI.
Record the timing of the final lane run on esptool v5.6.0.
* fix(artifacts): compare ESP segment declarations
Segment entries hash only their data, so compare_artifacts reported a
segment that moved to another load address with the same bytes as
unchanged; only the image itself changed. Compare each segment's
load_addr and data_len as its declaration, as Node SEA and PyInstaller
members are compared, and count a changed declaration as a metadata
change, also when content is unavailable.
Header offsets are layout and the classification is derived, so neither
is compared.
* test(artifacts): run ESP lane commands as owned process trees
The lane ran its tools through execFile. A timeout killed only the direct
child, so a linker or assembler that Clang or GCC had started could keep
running while the lane removed its workspace. Run every command through
createVerifierCommands, check the tools inside withVerifierCleanup, and
remove the workspace only after commands.close() has stopped every tree;
a failed close keeps the workspace.
Only a nonzero exit now counts as a refusal or an expected esptool
failure; timeouts and spawn failures propagate. Supervising each command
roughly doubles the lane's run time.
* test(artifacts): cancel ESP decoding after its first decoded chunk
The cancellation test requested cancel or close on setImmediate, which
can land while the file is still being opened and checked, so it never
showed that decoding had started. It also passed against a reader that
ignored both requests while decoding. Acknowledge the decoder's first
chunk, then cancel or close, join the operation, and assert that no
later chunk reaches the decoder and that the reader ends closed.
* docs(artifacts): record which ESP metadata comparison ignores
Segments are compared by their declaration. The image's and the
signature blocks' format_metadata are not: every field comes from their
own bytes, which content comparison already covers, and a block's
image_digest_matches follows the image's content.
* feat(artifacts): inventory and extract ESP flash layouts
A whole-flash dump of an ESP32-family chip was read as its bootloader
image, with the partition table, apps and data left as undescribed bytes,
or refused when it began with erased flash. Read it as an esp-flash-image
container instead.
The partition table is decoded as esp_partition_table_verify accepts it
and is found at the caller's partition_table_offset, at the default
0x8000, or by scanning 0x1000-aligned sectors for a table whose MD5
verifies, which the inventory reports as derived. The bootloader is
located at its chip's ROM bootloader offset. Each partition is an entry
with its declaration in flat format_metadata; the bootloader and app
partitions carry ESP image facts from the existing streaming decoder; the
otadata records are reported with a selection derived without rollback or
anti-rollback, and the running partition stays unknown. One unreadable
partition does not hide the others, and extraction writes every partition,
nvs_keys included, as ordinary data.
Flash layouts are detected before single ESP images. The scan runs only
when an ESP image sits at a ROM bootloader offset, and is refused when a
bootloader at another chip's offset shows that the dump does not start at
flash address 0. open_binary gains flash_offset and partition_table_offset,
which require format esp-flash-image and are part of the snapshot target
identity; the CLI takes them with --target-format esp-flash-image.
Comparison treats a partition's label, type, subtype, range and flags as
its declaration.
* test(artifacts): verify ESP flash layouts with gen_esp32part, merge-bin and otatool
Add the optional verify:esp-flash lane. It builds default and relocated
partition tables with ESP-IDF's gen_esp32part.py, assembles dumps with
esptool merge-bin, including one written from flash 0x1000, and writes
OTA records with otatool through flash_file_tool.py, which replaces
parttool's esptool calls with reads and writes of the dump file.
It compares REA's bootloader and app facts with esptool image-info, every
partition with gen_esp32part.py's decoding of the extracted table, and the
OTA records and derived slot with otatool read_otadata after each switch.
It also checks extracted bytes and source integrity, the scan and caller
table offsets, flash_offset on the 0x1000 dump, a corrupted app beside
valid partitions and a truncated dump. ESP32 linker scripts place the
conformance bootloader and app in ESP32 address ranges.
* fix(artifacts): read ESP flash layouts as the bootloader does
Read the partition table as bootloader_utility_load_partition_table
does: the last factory, otadata and same-subtype OTA declaration wins,
and a limitation names repeats. Plan each otadata record within its
partition and the dump. An otadata partition smaller than two sectors
selects none, the bootloader's INVALID_INDEX; a record the dump lacks
leaves the selection unknown while the other record stays reported. The
fallbacks follow the bootloader's indexes: ota_0-fallback and
factory-fallback name no partition when the table declares none, and the
assumptions add GPIO holds and deep-sleep wake.
Refuse a dump whose bootloader contradicts its origin before the default
table offset is tried, whether flash_offset was omitted or given, and keep
the ESP image reader's ESP8266 refusal. A table never lies inside the
located bootloader image, so single app images are no longer scanned. An
explicit esp-flash-image selection is decided before other container
signatures, and the native commands' --target-format no longer takes it.
Say when no bootloader confirms the default origin.
Exempt the primary bootloader and partition_table entries of ESP-IDF 5.3
from placement limits, report every region inside a longer one, and name
partitions by index when labels repeat ignoring case. Keep an undecodable
label's bytes in label_hex and compare them. Word undescribed ranges as
starting at a byte that is not erased, and treat a short erased region as
erased.
Add an MCP compare_artifacts test for partition declarations, reader
identity and close tests, scan edge cases, the 0x2000 ROM offset and a
literal otatool CRC.
* test(artifacts): verify ESP primary layout entries and the otadata fallback
Build a table with the primary bootloader and partition_table entries
that gen_esp32part.py fills from --primary-bootloader-offset, and check
that REA reads it with complete coverage, no overlap or misplacement
limitation, and a table that agrees with gen_esp32part's decoding. Erase
the otadata with otatool after the slot switches and check the records
against read_otadata and the factory fallback.
* fix(artifacts): keep ESP flash coverage complete for a dump cut short
An esptool merge-bin dump ends after the last part it wrote, usually inside
its last partition. Such a partition made the layout's coverage unknown, so
artifact comparison of two merge-bin dumps reported every unchanged
partition as unknown.
The partition table names every partition whatever the file's length, so a
partition cut short or left out by the end of the file hides no entry. As
the ESP image reader does for a cut-short segment, its entry holds the bytes
the file has and an entry limitation gives the declared size; a partition
with no bytes stays in partitions_outside_dump. Coverage stays complete
unless undescribed bytes remain or no bootloader is located. An MCP
compare_artifacts test of two dumps that end inside ota_0 now classifies
every partition, and fails on the old rule.
* fix(artifacts): report written bytes in ESP partitions, erased slots included
An app slot is erased for ESP-IDF's bootloader when its image header bytes
are erased flash, and REA reported only that status. A slot can still hold
data further in: a dump read from a device held a complete older app image
1 MiB into an OTA slot reported as erased, with no limitation.
Each partition entry now records where the bytes the dump holds are
written, relative to the partition: written_offset, the first byte that is
not 0xFF, written_end, just past the last, and written_bytes, how many are
not 0xFF. They are null for a partition outside the dump. The status stays
erased, which is the bootloader's view, and a limitation names an erased
app slot that holds written bytes and says they are not read as an image.
The reader reads every partition byte once more to find them.
* fix(artifacts): derive the ESP boot selection for every partition table
The boot selection lived inside otadata, so a table without an otadata
partition, the common factory-only layout, had none. ESP-IDF's
bootloader_utility_get_selected_boot_partition returns the factory index
when the table declares no otadata partition.
Move it to a top-level boot_selection that every layout has. Its basis
names the bootloader's branch: otadata, factory-fallback and ota_0-fallback
from the records, no-otadata for a table without otadata, none for the
bootloader's INVALID_INDEX, and unknown when the dump lacks a record the
bootloader reads, which replaces the null selection. running_partition
moves with it, and otadata keeps its partition index and records. The
assumptions name what each branch leaves out: no records assumption
without otadata, and none for none or unknown. The real-tool lane checks
no-otadata on a table gen_esp32part built without otadata.
* fix(artifacts): say when an ESP image is a flash dump read from 0x1000
An ESP32 dump read from flash 0x1000, as read-flash 0x1000 or merge-bin
--target-offset 0x1000 writes it, opened automatically as its bootloader
image with a megabyte of undescribed bytes and no next probe. The flash
reader refuses that origin, but nothing told an agent which flash_offset to
pass.
espFlashDumpOrigin owns the inference: an image of a chip whose ROM loads
the bootloader from flash R, at file offset F, is the bootloader of a dump
read from R - F when a partition table sits where that origin puts 0x8000.
The ESP image reader asks it for a non-app image at file offset 0, so the
result does not depend on how the reader was created, and records
flash_dump {flash_offset, table_file_offset}, null otherwise, with a
limitation that says to reopen the file as esp-flash-image with that
flash_offset. inspect_artifact proposes the same open_binary call as a next
probe, and the flash reader's misplaced-origin refusal names the offset.
* perf(artifacts): summarize nested ESP images in the flash layout summary view
The summary view returned an ESP flash layout's root facts whole, so each
app partition and the bootloader carried every segment and signature
block, about half of a dump's summary.
The summary view now gives each nested image a summary instead of its
facts: status, chip, entry point, segment count, load ranges (each mapped,
loaded or unclassified segment's address and declared length), the
checksum and appended SHA-256 statuses, coverage and limitations. Complete
detail keeps the full facts, every segment included, and the retained
Evidence is unchanged. On a 16 MiB device dump the summary's root falls
from 5,141 to 2,657 bytes, and the summary Evidence from 10,038 to 7,554.
* test(artifacts): expect the misplaced image the ESP bootloader search reports
8a748d3cc added misplacedImage to the bootloader search, which the
locator uses to name the origin a misplaced bootloader implies, but one
test still compared the search result without it. Expect the image's
chip and file offset, and no image for an unknown chip.
* fix(artifacts): give every ESP partition from the dump's start an entry
After ecbc1a772 kept coverage complete for a dump cut short, a partition
wholly past the end of the file still had no entry, only a
partitions_outside_dump row. Comparing a merge-bin dump with a full device
read then reported that partition as added or removed, as if one table
did not declare it.
Every partition from the dump's first byte on is now an entry holding the
bytes the file has: none for one past the end, with a limitation saying
the file holds none of its bytes, no image facts and null written fields.
partitions_outside_dump keeps only partitions that begin before the dump's
first byte, and coverage is unknown when one does, since it has no entry.
Two dumps that lack the same partition now compare it as unchanged, and a
dump against a longer one reports the partition as changed; an MCP test
of a merge-bin dump against a full one checks that no path is added or
removed.
Unlike the ESP image reader, which marks a truncated image's coverage
unknown because later segments and signature blocks may be hidden, the
flash reader can list every partition from the table whatever the file's
length; only the entry limitation follows the image reader.
* fix(artifacts): select no ESP app from a partition table the bootloader rejects
boot_selection followed bootloader_utility_get_selected_boot_partition but
not the table check before it. For a table whose MD5 does not match, or
that has a second MD5 entry, esp_partition_table_verify fails, the
bootloader selects no app and resets. REA still derived a slot from the
otadata records, although the table's own limitation said the bootloader
rejects it.
The selection now has basis none, with no assumptions, for a table the
decoder marks invalid. It also follows two more branches of the
bootloader: an otadata partition at offset 0, which the bootloader takes
for none, selects the factory index as no-otadata, and a table without an
OTA app selects its fallback even when the dump lacks a record, since the
bootloader then ignores the records.
The bootloader also rejects a table whose partitions end past the flash
size in its header. REA does not model that check; the format guide says
so.
* fix(artifacts): require the ESP dump origin's table to follow the image
espFlashDumpOrigin named a later flash origin whenever a partition table
decoded where that origin puts 0x8000. Bytes inside the image's own
segments could be read that way, so an ESP32 bootloader whose segment data
held table-shaped bytes at file offset 0x7000 was reported as a dump read
from 0x1000.
The table must now start at or after the image's end, the rule the locator
already applies to the default table. The ESP image reader passes the
image_end it decoded, or the file size for a truncated image; the flash
reader's refusal passes the end of the misplaced image's segments. The
bootloader search drops its misplaced flag, which only restated
misplacedImage, and the next probe formats offsets with offsetText.
Reader tests cover the 0x1000 and 0x2000 ROM offsets, a standalone
bootloader, bytes that begin like a table, an app with a table at 0x7000
and a table inside a segment; without the boundary check, three of them
fail.
* fix(artifacts): give the ESP flash summary its own kind and each image's identity
The summary view's ESP flash root kept kind esp-flash-image, so a consumer
bound to kind could not tell an image's summary from its full facts, and
the advertised schema held two objects with the same kind const. It is now
esp-flash-image-summary.
The per-image summary also dropped the descriptor, which is what tells two
app slots apart, and named each image only by its partition index, which
the summary view, having no entry rows, could not resolve. Each nested
image now carries its entry path from the layout plan, in both views. The
summary adds the descriptor kind with its identifying fields (an app's
project_name, version, idf_ver, secure_version and app_elf_sha256; a
bootloader's version, idf_ver and secure_version), the signature block
count and each load range's classification. Its enums and fields derive
from the schemas that own them.
An MCP test reads a dump with an app in ota_0 and an erased ota_1 that
holds older bytes: it checks both summaries, that the summary's inventory
Evidence is the one the complete view returns with full facts, and the
erased slot's written range.
* test(artifacts): cover cancel and close during the ESP written-bytes scan
The written-bytes pass reads every byte of each partition the dump holds,
checking that the reader is open before each chunk, but only app decoding
had a cancellation and close test. A data partition, which only that pass
reads, now holds 9 MiB; once the scan has examined its first chunk, a
cancel rejects preparation with the caller's reason and a close with
unavailable, and the scan examines no further chunk.
The erased-slot test now asserts the whole set of erased-slot limitations,
so factory, ota_0 and nvs, which the bootloader reads or which are not app
slots, are shown to have none.
* docs(artifacts): re-time the ESP flash lane and state only observed causes
The lane ran again after its assertions changed; both esptool runs passed,
and the timed run's figures replace the earlier ones. The erased-slot
paragraph no longer names a cause for the older bytes, which REA cannot
observe, and the skill reference states that every table has a
boot_selection in its own sentence, with the none basis for a table the
bootloader rejects.
* fix(artifacts): retire ESP flash members as the ESP image reader does
#7498's maintainer commits made the ESP image reader keep a member owned
until its consumer closes it, retire fully buffered bytes when
cancellation arrives after production, and refuse to finish an inventory
paused across close. The ESP flash reader was copied from the earlier
reader. Main's shared stored-member lifecycle cases failed two of eight
against it: a paused inventory completed after close, and buffered member
bytes stayed readable after cancellation.
Give the flash reader the same lifecycle and run the shared cases against
it.
* docs(artifacts): re-time the ESP flash lane after merging main
The lane passed again with esptool v5.6.0 and 4.12.0 on the merge of upstream main. Record the v5.6.0 run's time and peak resident set.
* fix(artifacts): read ESP image Evidence recorded before flash_dump
The flash layout work made `flash_dump` a required field of the
`esp-image` root metadata. Evidence recorded before it has no such
field, so newer REA rejected records that were valid when they were
made: `compare_artifacts` and `inspect_analysis_view` reported
`evidence_integrity_mismatch` for them.
The field is now optional when read. A record without it parses as it
is, so the graph digest it commits to still matches; nothing fills the
field in. The image reader still writes it for every image, and the
inspection probe reads a missing field as no hint.
A regression test recommits an image inventory without the field and
passes it inline to `compare_artifacts` and `inspect_analysis_view`.
Against a server built with the required field, `compare_artifacts`
rejects it.
* fix(artifacts): stop the ESP flash gap scan when the reader closes
The scan for the first written byte in each undescribed gap checked
only the caller's signal. `close()` marks the reader closed and waits
for preparation, so it waited while the rest of a long erased gap was
read.
The gap scan now takes the reader's open check, as the written-bytes
scan and the image decoder do, and checks it before each chunk. Tests
cancel or close the reader after the scan's first chunk of a 9 MiB
erased gap and confirm that no later chunk is examined.
* fix(artifacts): name ESP partitions with Windows device labels by index
The safe-label rule accepted labels such as `NUL`, `con` or `COM1`, so
extraction wrote `partitions/NUL.bin`. Windows reserves those names
before any extension, and ordinary Windows tools cannot use such a
file.
A label that is a Windows device name (`CON`, `PRN`, `AUX`, `NUL`,
`COM0`-`COM9` and `LPT0`-`LPT9`, any case, alone or before a dot) now
takes the existing index fallback. The partition metadata and the
entry's source path keep the label.
* fix(artifacts): read ESP flash partitions' written bytes in one pass
Each partition got its own scan of the bytes the dump holds, so
partitions declared over the same bytes read them again and again.
Overlaps only add limitations, so a crafted table reached this normally:
94 overlapping partitions over a 32 MiB dump would examine about 3 GiB
before the first entry.
The reader now reads the bytes the partitions cover once. Partition
boundaries cut them into segments; one pass counts each segment's
written bytes and bounds, and each partition's extent is the sum of the
segments inside it. Every partition keeps its own declaration, extent
and limitations. The pass checks the reader's open state before each
chunk, as the per-partition scan did.
A test declares 60 partitions over one MiB, 40 over all of it and 20
over its first half, and checks each partition's extent and the
overlap limitations. The reader reads 1,113,256 bytes of the
1,114,112-byte dump; one scan per partition read 52,493,480.
* test(artifacts): fail the ESP lanes on failed commands and refusals
`otatool` turned a nonzero exit into returned text, which its callers
discarded, and `inspect()` returned `{ refused }`, which the flash lane
never checked. A failed command then surfaced later as a slot
assertion, an output-parser error or a TypeError, hiding its cause.
Commands the lanes need to succeed now reject with the output they
exited with: `otatool` and `inspect()` do so through a shared
`failedCommand`. An expected refusal is opt-in: `refusal()` returns
REA's output and fails the lane when REA opens the file. The ESP image
lane's two ESP8266 cases use it.
Checked by calling `otatool` on a file without a table, `inspect()` on a
file REA refuses and `refusal()` on a file REA opens; each rejects with
its cause.
* fix(artifacts): require a partition entry in an ESP partition table
The decoder counted the slot, not the partitions, so a lone MD5 entry
over the empty prefix, followed by the terminator, decoded as a table
with no partitions. Default and caller-selected detection then accepted
that empty layout, although the guide requires one partition.
A table now needs one partition entry before its terminator. An MD5
entry alone is refused as `entry 1 at 0x20 ends the table before any
partition entry`. The cases cover the decoder and both automatic and
caller-selected location.
* fix(artifacts): decode an image shared by ESP app partitions once
Each app partition got its own decode, so app partitions declared over
the same bytes decoded and hashed the same image again and again, the
same pattern the written-bytes scan had. With 60 app partitions over
one 960 KiB image the reader read 64,029,736 bytes of a 1,114,112-byte
dump.
Regions with the same file range now share one decode; each keeps its
own copy of the facts. Partitions with different ranges still decode
separately, because truncation can change the facts: at most one decode
per partition the table holds, each within its partition.
A test declares 60 app partitions over one image, 40 holding all of it
and 20 cutting it short, and expects three decodes: the bootloader, the
whole image and the cut one. The 60-partition dump now reads
2,161,864 bytes, as a dump with one app partition does.
Since 651afa445 target opening refuses every ELF declaration beyond EOF,
including carved copies, and ac6e0e258 moved the carved MIPS boundary tests
to that contract. The real-Ghidra MIPS lane still expected a carved image to
resolve with section-table limitations and be refused later by Ghidra
admission, so verify:ghidra:mips failed at its first carved fixture.
The lane now requires open_binary and the CLI to refuse the carved copy as
target_unavailable (file_format_rejected) with the exact declared
section-table range and captured size. The carve and that expectation live in
the shared MIPS checks, and a boundary regression applies them to the
production resolver and error projection, so a later contract change fails
without real Ghidra. The MIPS and PSP guides describe the current contract.
* fix(ghidra): report a missing existing-project program as a configuration error
When REA_GHIDRA_PROJECT_PATH selects a project that has no root program
with the selected name, Ghidra's headless analyzer aborts with
"Requested project program file(s) not found: <name>". REA reported this
as a generic execution_failure with rea doctor advice, and the reason was
visible only in the captured diagnostics.
Recognize that refusal in the operation-scoped launcher output, matching
only the exact name REA passed to -process, and return a
ConfigurationError for REA_GHIDRA_PROGRAM_NAME. The message says whether
the name was configured or defaulted from the target file name, and the
remediation explains how to select the exact root program. The selected
project is unchanged and there is no fallback to a fresh import. Other
launch failures keep their existing projection.
Fixes#8216
* test(ghidra): assert missing-program guidance through its formatter
Compare the projected setting constraint and remediation with
ghidraMissingProgramGuidance instead of freezing explanatory phrases,
following docs/testing.md. The program name and configured-or-defaulted
origin are still checked independently.
Expose static Electron protocol call facts through the shared application graph and existing CLI/MCP Evidence views. Preserve canonical case-insensitive scheme identity, original spellings, exact source provenance and explicit unknowns; keep registration success and runtime routing unproven.
Integration fixes also enforce module-only privileged API ownership, reuse the shared finding-key owner, and align the owning type documentation with the Evidence semantics. Validation and bounded real Obsidian CLI/MCP measurements are recorded in the PR.
Co-authored-by: UnknownHacker1 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
description:Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version:"55"
version:"60"
---
# REA
@@ -121,6 +121,15 @@ explicit path or endpoint and do not need it.
cleanup effects, and do not require a preliminary static session unless their
and the exact retained Evidence ID to read selected facts without reanalysis.
@@ -336,8 +369,9 @@ the complete tool inventory grouped by current status. Expand each group's
overrides them; availability, reasons, remediation, and modes remain inline.
Provider candidates keep availability and target-support status, code, and
reason. `omitted_fields` names what the summary leaves out; `{"detail":
"complete"}` adds provider diagnostics, candidate capability sets, and tool
surface, `effects`, and`annotations`. Those arrays name true flags from fixed
"complete"}` adds each unavailable candidate's `remediation`, provider
diagnostics, candidate capability sets, and tool surface, `effects`, and
`annotations`. Those arrays name true flags from fixed
vocabularies, so an omitted known flag is false. `tools/list` remains the
canonical source for full tool schemas when target or provider state changes.
@@ -347,6 +381,28 @@ operation's full ordered list. Candidate capabilities, returned with complete
detail, describe possible providers. Top-level session capabilities describe the current route, and
`analysis_provider_binding` identifies the selected provider.
## Continue editable Ghidra analysis across processes
For a task spanning agent sessions, check `binary_session` after selecting
Ghidra: the current provider's `capabilities[].shared_limitations` states whether
Program checkpoints are enabled or the session is ephemeral. Successful native
queries distinguish initial analysis from an actually restored packed Program;
do not infer that restoration happened merely because checkpoint mode is enabled.
In ephemeral mode, choose a private absolute directory and set
`REA_GHIDRA_CHECKPOINT_DIR` in the environment starting REA. Restart the MCP server
after changing that launch configuration; no current tool call or runtime
analysis-setting update enables it. Stock ELF/PE/Mach-O imports on Linux/macOS
are supported; see the [checkpoint setup and limitations](https://github.com/morluto/rea/blob/main/docs/installation.md#automatically-checkpoint-an-owned-ghidra-program).
With the setting enabled, normal opens restore compatible saved Programs
automatically. Initial completed analysis is saved, and `close_binary` saves
later metadata edits; no separate save/restore tool is needed. Repair a reported
save failure and retry close. Cancellation or process failure can lose edits
after the last successful save; unfinished initial analysis cannot resume.
Keep the same checkpoint configuration for later processes. Evidence/Unknown
history and immutable REA query snapshots have separate persistence workflows.
## Reuse caller-selected Ghidra analysis
When the caller supplies an expanded project, configure `REA_GHIDRA_PROJECT_PATH`
@@ -370,7 +426,7 @@ When the error names `ghidra.startup_timeout_ms`, call
`update_analysis_settings` with `action: "set"` and a larger value within the
reported bounds. Resolve retained cleanup before the next startup attempt.
No REA restart is needed; an already accepted startup keeps its original
budget. A new attempt repeats import. Preserve useful Evidence before closing
budget. Follow the error's session-specific restart guidance, and preserve useful Evidence before closing
and reopening the target when remediation requires that lifecycle step.
For a per-function timeout, update `ghidra.decompile_timeout_seconds` and retry
@@ -578,6 +634,16 @@ REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
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
@@ -606,10 +672,27 @@ semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
### Function annotations in Ghidra
Fresh-import sessions admit annotations; existing-project reuse is read-only.
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic and session-scoped; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits until close. CLI `annotate-native-function` returns the updated analysis before discarding the session. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier.
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits. Normal close saves them when owned Program checkpoint mode is enabled; ephemeral mode discards them. CLI `annotate-native-function` follows the same setting when closing after its updated result. Select the [checkpoint workflow](#continue-editable-ghidra-analysis-across-processes) before edits that must survive a later process. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier. Ghidra rejects batch addresses that request the same name or a name another selected address holds; `raw_result.failures` gives each failure's reason.
For `compare_functions`, obtain `analyze_function` Evidence before and after the
edit while the MCP session is open; annotation Evidence is not a comparison
input. Export the needed Evidence before close if comparison will happen later.
Authenticated same-artifact coordinates preserve renamed entries as
`matched / same-entry`, with the canonical `entry_address` inline; name and
exact-text changes remain separate dimensions. This requires compatible
architecture, provider-owned mapping and selected-image commitments. Equal
@@ -28,6 +28,10 @@ Assess brittleness in touched code during implementation, debugging, review, and
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
After confirming a bug, search sibling adapters, consumers and similar workflows
for the same failure. Verify suspected matches, record working comparisons, and
group findings by root cause instead of filing duplicate reports.
When addressing a review thread, inspect the producing boundary and affected consumers for brittleness. Complete directly related refactoring in the same change. Before resolving the thread, explain the root cause, structural fix, validation, and any remaining cleanup.
Parse unknown boundary values and model expected failures with the tagged error algebra and `Result`. Preserve partial facts and meaningful failure reasons. Keep identity and lookup values separate from display formatting; preserve source values and explicit unknowns when normalization loses information. Absolute paths, file URLs, and HTTP paths have different semantics.
@@ -75,6 +79,9 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
When changing installers or setup recovery, follow
[Checking installers and publisher downloads](docs/testing.md#checking-installers-and-publisher-downloads).
Coordinate expensive checks across agents. If a test unexpectedly consumes heavy CPU, stop its owned process family and profile a bounded representative case before retrying. Inspect production work, fixture generation, subprocesses, and repeated setup; preserve regression coverage when optimizing. Follow the [test resource guidance](docs/testing.md#developer-commands).
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
@@ -83,6 +90,12 @@ Coordinate expensive checks across agents. If a test unexpectedly consumes heavy
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
For user- or agent-facing tool problems, prioritize fixing the owning behavior,
contract, and actionable result or recovery at the point of use. Document the
result in product docs when explanation is needed. Add skill guidance only for
workflow judgment or context that the tool cannot reasonably convey; do not use
a skill as the primary repair for a discoverability or contract defect.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
@@ -115,8 +128,8 @@ owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates
the filled declaration; CI and automated review results do not establish that
The readiness check validates the filled declaration or the author exemption
listed in CONTRIBUTING.md; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Join the REA community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [Build RL environments](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
@@ -203,6 +205,7 @@ Additional tools and host support depend on the target:
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](docs/pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](docs/pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](docs/elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](docs/binary-hardening.md) |
| npm dependencies | [Complete inventory](licenses/npm-dependencies.txt) of every entry in `package-lock.json`, including transitive, optional platform, development, and website dependencies. [Runtime notices](licenses/npm-runtime-notices.txt) preserve the copyright, license, and NOTICE documents from integrity-verified release archives, plus the MIT notice for Rolldown's generated runtime helpers. Development dependencies are inventoried but are not all distributed in REA's npm package. |
| pefile ordinal tables | Import-hash ordinal names are derived from [pefile 2024.8.26](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4), copyright 2004–2024 Ero Carrera. Preserve the [MIT license](licenses/pefile-2024.8.26-LICENSE.txt). The Python package is not a runtime dependency. |
| UF2 family names | UF2 family IDs and short names are copied from [microsoft/uf2 `utils/uf2families.json`](https://github.com/microsoft/uf2/blob/cf3bd7f9c274b949528f8cd6a19eef2502a687d2/utils/uf2families.json), copyright Microsoft Corporation. Preserve the [MIT license](licenses/microsoft-uf2-MIT.txt). |
| NativeAOT parser | REA's derived parser uses the MIT-licensed [ghidra-nativeaot](https://github.com/Washi1337/ghidra-nativeaot/tree/effeb734fc570c32650f88b159608979dc7b423e) work of Washi and the NativeAOT layout definitions in [dotnet/runtime](https://github.com/dotnet/runtime/tree/a3b2d40328be0be3f8dd43f0e5cc925b8827b044). Preserve [Washi's MIT notice](licenses/nativeaot-parser-MIT.txt) and the [.NET Foundation MIT notice](licenses/dotnet-runtime-MIT.txt). |
| EVM analysis | `[email protected]`, MIT, copyright 2023 Maxim Andreev. The published npm archive omits its license document; REA retains the [original upstream license](third_party/evmole/LICENSE) and [provenance](third_party/evmole/README.md). It is also included in the runtime notices. |
| Windows native addon | The release addon is compiled with MinGW-w64 and statically links GCC runtime libraries. Preserve the [MinGW-w64 runtime notices](licenses/MinGW-w64-runtime.txt), [GPLv3 text](licenses/GPL-3.0.txt), and [GCC Runtime Library Exception 3.1](licenses/GCC-Runtime-Library-Exception-3.1.txt). The exception governs eligible compiled combinations; it does not relicense the runtime itself under MIT. The Node-API build headers are from Node 22.19.0; its [original license and component notices](licenses/Node-22.19.0-LICENSE.txt) are retained. Node itself is supplied by the user. Snapshot provenance and release-toolchain review requirements are in [licenses/README.md](licenses/README.md). |
@@ -59,7 +60,7 @@ pin inspection sources; they are not included in the npm package.
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.833/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.833/NOTICE) | Bring your own `kuna`; REA verified 1.833 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.990/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.990/NOTICE) | Bring your own `kuna`; REA verified 1.990 and never downloads, installs or bundles it. REA invokes it as a separate process. |
result.add("limitations",GSON.toJsonTree(List.of("Layouts are Ghidra data-type database observations; original debug/source authority and flexible-tail semantics are not inferred. Nested and recursive types retain category-path identities for separate inspection.","At most 20000 fields or enum members are serialized per type to bound provider output. total_fields and truncated report omissions. Enum values preserve Ghidra's signed long representation.")));
result.add("limitations",GSON.toJsonTree(List.of("Layouts are Ghidra data-type database observations; original debug/source authority and flexible-tail semantics are not inferred. Component type paths do not necessarily identify separately registered types; bit-field lookups report whether the manager resolves equivalent types.","At most 20000 fields or enum members are serialized per type to bound provider output. total_fields and truncated report omissions. Enum values preserve Ghidra's signed long representation.")));
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>static WASM/EVM descriptors: named contracts, factories, retryable lifetimes"]
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>typed capability bindings: construction, availability and retryable lifetimes<br/>pure capability declarations feed provider catalogs"]
end
subgraph "Adapters"
@@ -23,6 +23,7 @@ graph TD
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback<br/>ProviderInspectionScope: prerequisite observation lifetime"]
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
LIFETIMES["CapabilityScope<br/>host-owned service close capabilities<br/>released peers removed; failed cleanup retained for retry"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots and session file ownership<br/>record coordination and post-commit notifications"]
an `__RESTRICT` segment, `LC_CODE_SIGNATURE` presence, encryption from the
existing encryption reader, the arm64e subtype, and a stack-protector import.
`hardened_runtime` is `unknown` with `next_probe``inspect_signature`.
CodeDirectory flags stay with that tool.
The report states presence of build-time features. It does not state
exploitability. `inspect_binary_layout` is unchanged.
The default read budget is 64 MiB (`max_file_bytes`, ceiling 512 MiB). A changed
file is refused. A malformed header, a Mach-O segment or encryption command that fails admission, or a program, section,
or segment payload declared outside the file returns no partial report, except that one bad slice in a universal Mach-O leaves the other slices. A symbol table or
symbol name over its budget leaves the import rows `unknown` and keeps the
header rows. A GNU property note over its budget, or a compressed note section
that may hold one, leaves the CET and BTI/PAC rows `unknown` and keeps the header
rows. A dynamic-table budget still fails the ELF report. The image is never
@@ -500,7 +500,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
connection/frame inventories; matching direction/opcode sequences alone do
not prove alignment after drops. 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. 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.
- 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`. Source locations are CDP's zero-based lines and UTF-16 columns. A negative column, which Chrome reports on the first line of some inline scripts, is kept as reported and does not locate a column in the source URL; an invalid line or a non-integer column is `null` (WebMCP `registration_source` is `null`) and 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.
`compare-source-to-bundle`, and `reconcile-javascript-runtime`.
- `import-managed-reconstruction`, `verify-managed-native-boundaries`, and
`project-managed-application-graph`.
- `project-android-application-graph`, `project-apple-application-graph`, and
`project-harmony-application-graph`.
- `compare-wasm-interfaces` and `inspect-analysis-view`.
For `inspect-analysis-view`, the request's source reference selects a parent
from the bundle for every supported view kind. Bundles exported from MCP work
for these commands. Its inline source form cannot also select a bundle.
A missing ID is refused with `details.reason: "missing"`;
supplying a bundle does not make incompatible Evidence valid for a workflow.
`compare-managed-members` keeps its two assembly paths and an optional
repeatable `--type` selection applied to both sides, and
`compare-process-captures` keeps its two capture Evidence JSON paths. The bundle
option above selects an input; producer options that write a new bundle, such
as `inspect-managed-members --evidence-bundle`, retain their existing meaning.
File selectors remain available only on contracts that explicitly support them.
Compare two artifact inventories with `compare-artifacts`. It takes the same
`left` and `right` input as MCP `compare_artifacts`: complete Evidence, or an
@@ -669,7 +817,7 @@ rea inspect-artifact new.zip --json > new.json
rea compare-artifacts '{"left":{"kind":"evidence-file","path":"old.json"},"right":{"kind":"evidence-file","path":"new.json"}}' --json
```
A one-shot command keeps no session Evidence, so it refuses a
A one-shot`compare-artifacts` command keeps no session Evidence, so it refuses a
`retained-evidence` source with `details.reason: "missing"`. Select the record
from a saved bundle instead.
For an artifact summary selected from a bundle, include its complete parent
@@ -690,6 +838,7 @@ Import an older source tree to compare with the current artifact:
```bash
rea import-reference-source /absolute/path/to/source
rea import-reference-source /absolute/path/to/source --detail summary --outline-depth 2 --json
rea import-reference-source /absolute/path/to/source --exclude-paths '["vendor/","*.generated","!keep.generated"]'
```
The default (`--detail full`) returns the complete `HistoricalSourceGraph`.
@@ -836,6 +985,10 @@ Choose the guide for the target and intended interaction:
- [Electron fuses](electron-fuses.md): read packaged fuse bytes before expecting `--inspect` to reach an Electron main process.
- [Node/Electron Inspector](javascript-runtime-observation.md): record script locations and execution contexts.
- [Process capture](process-capture.md): run an executable and compare terminal, exit and filesystem observations.
- [Desktop application capture](desktop-application-capture.md): launch a Linux desktop application and record its windows and home-directory state at checkpoints while it is used.
- [Desktop application state](desktop-application-state.md): read a capture folder into the application's state files, typed views of each version and kinds of change.
- [Desktop application export](desktop-application-export.md): write a capture folder as an agentenv computer app bundle with its class, skill and seed.
- [Desktop application tasks](desktop-application-tasks.md): describe how a capture's files changed, then validate an agent-written task against the capture and export it with its verifier.
Runtime requests name the target and actions. Launched targets run with your
user permissions; consult the chosen guide for host requirements and effects.
@@ -853,7 +1006,13 @@ rea mcp --http --port 8765
`--host` and `--port` default to `MCP_HOST` and `MCP_PORT` from the environment,
then to `127.0.0.1` and an ephemeral port. A bind outside loopback requires
`MCP_ALLOWED_HOSTS` or `--allowed-hosts`, a comma-separated list of client host
names or IP addresses without URLs or ports. For example:
names or IP addresses without URLs or ports.
Repeat `--allowed-hosts` to append names, or put multiple names in one
comma-separated value. All occurrences are validated together and deduplicated
after hostname normalization; the explicit list replaces `MCP_ALLOWED_HOSTS`.
For example:
```sh
rea mcp --http --host 0.0.0.0 --allowed-hosts 127.0.0.1,rea.lan
@@ -1041,6 +1200,10 @@ such as `UNSUPPORTED_OUTPUT_COMBINATION` for `--json` with `--token-limit`. Use
Commands reject surplus positional arguments before execution, including commands
that accept no positionals. Use `rea <command> --help` for the declared argument
shape; a final variadic argument continues to accept multiple values.
`rea mcp add` accepts options only and rejects unknown options before reading or
backing up client settings or starting registration. Its `--agent` and
`--command`/`-c` options accept spaced and equals forms. Values must be nonempty; a following flag does not satisfy a spaced option's
missing value.
`rea mcp doctor --json` checks production MCP startup and reports child logs in
`diagnostics`. It drains stderr while retaining at most its first 64 KiB of raw
@@ -1081,9 +1244,12 @@ treated as malformed.
exact array path with `--filter-output` and provide comma-separated direct
scalar columns with `--columns`; column order is the output column order:
For an artifact inventory request that selects the `occurrences` collection
and includes `logical_path` and `artifact_id` in its view fields:
```bash
rea inspect-analysis-view request.json --format csv \
virtualizes AppData writes. Once it has saved its configuration, it reads
the package-private copy
@@ -135,6 +237,11 @@ in the file the installed app reads:
private copy when it exists. Before the package has saved one, they use the
`%APPDATA%` file it still reads; with neither file, setup creates the
private copy.
- If `%LOCALAPPDATA%\Claude-Data\claude_pzs8sxrjxfjjc` exists, setup, doctor
and uninstall report an unresolved location. Migrated MSIX installations
have been observed reading `claude_desktop_config.json` in this directory.
Quit Claude Desktop and verify its active configuration before registering
manually. See [#8968](https://github.com/morluto/rea/issues/8968).
- If `%LOCALAPPDATA%\Packages` holds a `Claude_<publisher ID>` package from
another publisher, an `AnthropicPBC.Claude_<publisher ID>` package, or cannot
be read, REA cannot establish which file Claude
@@ -273,7 +380,7 @@ This headroom is a practical default, not a guarantee for every analysis
duration. Setup preserves positive timeout values already in the `rea` entry.
Setup also forwards explicitly selected input/output schema profiles,
`REA_MCP_MAX_RESPONSE_BYTES`, `REA_MCP_MAX_INPUT_ELEMENTS`, provider paths and supported Ghidra, JADX and Kuna
`REA_MCP_MAX_RESPONSE_BYTES`, `REA_MCP_MAX_INPUT_ELEMENTS`, the Windows ChangeTime settle settings `REA_WINDOWS_CHANGE_TIME_DRIFT_MS`, `REA_WINDOWS_CHANGE_TIME_SETTLE_POLL_MS`, and `REA_WINDOWS_CHANGE_TIME_SETTLE_MAX_POLLS`, provider paths and supported Ghidra, JADX and Kuna
settings. It also forwards explicit executable and resource selections for ADB,
apktool, packet capture, GDB/Rizin/objdump, Cutter, WABT, Wakaru, firmware and
signal tools, browser inspection, and pwntools/pwndbg. This includes their
@@ -306,6 +413,22 @@ Kimi Code also discovers personal skills in `~/.agents/skills` and project-local
and verifies the matching skill files through the normal shared-skill flow.
Start a new Kimi Code session to pick up MCP and skill changes.
## LM Studio
For [LM Studio](https://lmstudio.ai/docs/app/mcp), setup writes `mcpServers.rea`
into `mcp.json` in the LM Studio home directory. Setup finds that directory as
LM Studio does: the path stored in `~/.lmstudio-home-pointer`, otherwise
`~/.cache/lm-studio` when it exists, otherwise `~/.lmstudio`. Setup only reads
the pointer file. LM Studio parses `mcp.json` as strict JSON, so setup rejects
a file with comments, trailing commas, or a byte order mark without a backup
or a write. The entry carries `command`, `args`, and `env`, and setup keeps
every other server. Local models often have small context windows, so setup
selects `REA_MCP_INPUT_SCHEMA_PROFILE=compact` unless the stored entry or the
setup environment selects a profile explicitly. Doctor reports an entry with no
explicit profile as stale, as for Kimi Code. LM Studio 0.4.26 on macOS loaded
this registration, and a local model called an REA tool through it. Linux and
Windows remain unverified.
## Pi
For [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md),
@@ -586,6 +709,18 @@ The user-level MCP paths follow the editor's VS Code layout. Trae's official
documentation describes the raw JSON editor without specifying those paths;
loading this registration in Trae remains unverified.
## CodeBuddy
For CodeBuddy, setup writes `mcpServers.rea` into the global
`~/.codebuddy/mcp.json` (a standard `mcpServers` JSON file at the user home
root on every platform). Entries carry `command`, `args`, and `env`; stdio is
the default transport. CodeBuddy discovers personal skills from
`~/.codebuddy/skills`, so setup installs its skill copy there.
The global MCP path is a plain JSON file; setup preserves every other server
and top-level key when it upserts `rea`. Loading this registration, the
bundled skill, and real REA tool calls through CodeBuddy are verified.
## WorkBuddy
For [WorkBuddy](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/MCP-Guide), setup writes `mcpServers.rea` into the
@@ -628,21 +763,20 @@ writing a registration ZCode cannot load.
## FreeBuff
For FreeBuff, the proposed integration writes `mcpServers.rea` into the user-scope
`~/.agents/mcp.json` shared-agent file. Entries carry `command`,`args`, and
`env`; stdio is the default transport. The proposed personal skill location is
`~/.agents/skills`, so setup installs its skill copy there. Because the same
For FreeBuff, `rea setup --client freebuff` writes `mcpServers.rea` into the
discovery, and ownership behavior only. Automatic applicability requires the
FreeBuff profile directory even when the shared MCP file exists.
When uninstall cannot establish FreeBuff or another shared-storage reader from
product discovery, a malformed or unreadable shared MCP file is retained
unchanged and does not stop other REA removals, including `--purge-data`. When
@@ -650,8 +784,9 @@ FreeBuff or ZCode product applicability is established or unresolved, the same
malformed shared file stops uninstall before removals. See
[Diagnose, update, and remove](installation.md#diagnose-update-and-remove).
FreeBuff setup support requires `[email protected]` or later. Older versions
reject `rea setup --client freebuff` as an unsupported client. See the
FreeBuff setup support requires `[email protected]` or later (including the
current npm `latest` release). Older versions reject
`rea setup --client freebuff` as an unsupported client. See the
[installation guide](installation.md#supported-agents) for client availability.
## DeepSeek Harness
@@ -704,6 +839,103 @@ Harness integration, with live DeepSeek API compatibility remaining unverified.
See the [Harness MCP guide](https://github.com/deepseek-ai/deepseek-harness/blob/639ed015397290b3745d163aafe02ffee4aa3f84/packages/mcp/mcp-client/README.md)
for its native configuration contract.
<a id="llamacpp"></a>
## llama.cpp
Use REA from llama.cpp's local Web UI through `llama-server`'s native stdio
MCP support. REA supplies the analysis tools; llama.cpp runs your chosen model.
It is not a replacement for a native analysis provider such as Ghidra or Hopper.
Install REA in a persistent location so the configuration below can use a stable
entrypoint. For a project-local installation, run this in your chosen directory:
```bash
npm install --save-exact rea-agents
```
Or install it globally:
```bash
npm install --global rea-agents
```
See [package runners and global installation](installation.md#package-runners-and-global-installation)
for runtime requirements and installation options. Running `npx rea-agents setup`
alone does not create either installation; do not use its temporary package-cache path.
Use a llama.cpp build whose `llama-server --help` lists
`--mcp-servers-config`; older builds without that option cannot use this route.
The configuration below uses an already installed REA package, so tool discovery
does not depend on downloading an npm package during server startup.
and [renderer usage notes](https://github.com/earendil-works/pi/blob/b45e0ebcc5ae2fc42a6f2f23104d10e656e6bedb/packages/codemode/README.md#declarations-for-the-model).
<a id="llamacpp"></a>
## llama.cpp
The [manual stdio connection](client-configuration.md#llamacpp) was checked on
macOS arm64 with Node.js 24.18.0, llama-server b11715 (`9d1878a43`), and the
published `[email protected]` package. `GET /tools` exposed 207 REA tools without
enabling llama.cpp's built-in tools. With the source checkout at `9e372c6`, all
207 forwarded names, descriptions and input schemas matched a direct MCP client.
Calls through `POST /tools` returned session metadata and JavaScript analysis
Evidence for a fixture at a Unicode path. A missing target returned REA's input
error, and a subsequent session call still succeeded. The owned llama-server
process exited successfully after the checks. No native analysis engine or
Windows host was exercised.
A local Qwen3-1.7B Q8_0 run selected the JavaScript analysis tool and received its
Evidence, but its follow-up answer reached the output-token limit. Other small
Qwen3 attempts returned proposed calls as ordinary JSON text rather than tool
calls. These runs do not establish dependable model-driven investigations.
A request with the full catalog and task history exceeded a 16,384-token context;
select tools for the task instead of assuming that discovery implies model fit.
The checks exercised HTTP and stdio, not browser interaction with the Web UI.
## End-to-end, integration and golden evidence
The optional `verify:qwen-client`, `verify:pi-client`, and `verify:hermes-client`
@@ -147,6 +147,14 @@ window-targeted renderer reload/crash actions, and synthetic `open-url` or
WebContents identities, process metrics, and IPC channel and value-shape metadata.
Payload values are not retained.
For the exact Playwright launch sequence, the active hook removes only REA's
bootstrap arguments from the application-visible `process.argv`. The vector keeps
the selected entry point and every caller argument, in order, including repeated
hook paths, `.asar` filenames, and debug-like options. The private launch handoff
is consumed before application code runs. If the observed sequence cannot be
matched exactly, argv stays unchanged and capture coverage reports `hook_conflict`;
REA does not guess an entry point or remove arguments by filename patterns.
`window_index` selects from Playwright's current Page list at the start of each action. REA resolves that Page to its corresponding BrowserWindow once; the action's `target` uses the BrowserWindow's capture-scoped `window_id`, matching the window inventory. Reordering either enumeration after selection does not retarget the action. Handle cleanup failures retain the completed action records and remain owned for provider cleanup retry.
Run the real fixture verifier with an operator-provided Electron runtime:
@@ -193,6 +201,9 @@ neither result means the requested host operation occurred. The existing
`openExternal`, `openPath`, and `trashItem` blocks reject their promises.
The `trashItem` block prevents that call from reaching the OS trash operation;
it does not intercept other ways the application can remove files.
Process rejection and uncaught-exception events are observed at dispatch without
adding exception handlers. The runtime and application retain their warning,
handler, and fatal-exit policies, including for rejected shell-block promises.
CLI SIGINT/SIGTERM requests cancellation and awaits owned-process cleanup.
Window IDs, URLs, and cached titles come from one main-process Electron snapshot,
so a crashed renderer does not require a successful renderer-side title query.
| Integer and pointer values | `a0`–`a7`, then the stack in 4-byte-aligned slots | The default integer and stack entries, unchanged |
| `float` | One integer register, or a stack slot | The `fa0`–`fa7` entries and the `float` rule are removed, so a `float` takes the next integer register |
| `double` and 8-byte values | A register pair with the low word in the lower register. Named arguments need no aligned pair. With one register left, the low word goes in `a7` and the high word on the stack | The default `join` rule with stack spill: Ghidra writes `a2:4,a1:4` for a pair and `Stack[0x0]:4,a7:4` for the split |
| Aggregates up to 8 bytes | One or two integer registers, laid out as in memory, also when every member is floating-point | The homogeneous-float-aggregate rule is removed; the `join` rule applies |
| Aggregates over 8 bytes | By reference: the caller passes the address of a copy | The default rule that converts structures and unions of 9 bytes or more to pointers |
| Return values | As the first argument of the same type: `a0`, or `a0` and `a1`. Larger values go to a caller buffer whose address is an implicit first argument in `a0` | The `fa0`/`fa1` return entries and floating-point rules are removed; Ghidra's hidden-return fallback supplies the pointer in `a0` |
| Variadic arguments | The integer convention. A variadic argument with 8-byte alignment and size takes an aligned (even-numbered) register pair, or the stack; after the first stack argument, the rest go on the stack | The default variadic rule (`join` with alignment), unchanged |
| Preserved registers | `s0`–`s11` and `sp`; `gp` and `tp` are not allocated. No floating-point register is preserved | The default preserved list without `fs0`–`fs11`, which join the killed-by-call list |
| Scalars over 8 bytes | By reference (`long double` is 16 bytes), and returned through a caller buffer | A rule before the final join converts any value of 9 bytes or more to a pointer; a return that size already goes to a caller buffer |
Ghidra recovers the arguments of a variadic call from the registers the caller
writes, not from their types. A padding register that still holds an earlier
value can appear as an argument, and arguments after an unwritten register are
omitted.
### ilp32f
Written policy for `EF_RISCV_FLOAT_ABI_SINGLE` on ELF32. REA does not admit it
| `float` | `fa0`–`fa7` while one is free, then the integer convention |
| `double` and other values wider than 32 bits | The integer convention, as in ilp32 |
| Structure of one or two floats (after flattening) | One floating-point register per member when enough are free, otherwise the integer convention |
| Structure of one float and one integer, in either order | One floating-point and one integer register when both are free, otherwise the integer convention |
| Unions | Never flattened; the integer convention |
| Return values | As the first argument: `fa0`, `fa0` and `fa1`, or `fa0` and `a0` |
| Variadic arguments | The integer convention, as in ilp32 |
| Preserved registers | `s0`–`s11`, `sp`, and `fs0`–`fs11` for values of 32 bits or fewer |
The planned prototype limits the default floating-point entries to 4 bytes and
keeps the shared rule that passes values of 9 bytes or more by reference.
Observed on Ghidra 12.1.4, a `double` then takes an integer pair and a structure
of two floats takes `fa0` and `fa1`. Ghidra's model has no rule for a structure
of one float and one integer: it joins `struct { float; int; }` into `a1:a0`
instead of `fa0` and `a0`. The ilp32f policy needs that rule and its own
fixtures before it is admitted.
### ilp32d and lp64d
The double-float lanes use Ghidra's stock `riscv32-fp.cspec` and
`riscv64-fp.cspec` models unchanged. This page does not verify them. They share
the float-and-integer structure gap above.
## ISA declarations
The target reader finds the one `SHT_RISCV_ATTRIBUTES` section and reads
`Tag_RISCV_arch`, such as `rv32i2p1_m2p0_c2p0_zicsr2p0_zifencei2p0`. It
retains the string, the base and the extension names, or an unavailable reason
when the section is malformed, declares other vendors or scopes, or uses a
non-canonical string. A missing section or tag is recorded as absent.
Calling-convention lanes admit an image only when it declares an RV32I base
and only extensions that Ghidra 12.1.4's generic RV32 language implements with
p-code: `m`, `a`, `f`, `d`, `c`, `zicsr`, `zifencei`, and the subset names
toolchains record, `zmmul`, `zaamo`, `zalrsc`, `zca`, `zcf` and `zcd`. Any
other extension is refused. In particular:
- Ghidra 12.1.4 decodes no Zcb, Zcmp or Zcmt instruction.
- Its bit-manipulation, cryptography and packed-SIMD files follow draft
encodings and mark nearly every instruction unimplemented.
- Vendor extensions, such as Espressif's `xesploop`, `xespv` and `xespdsp`,
decode only as custom-opcode placeholders.
ESP-IDF builds ESP32-C2 and C3 images as `rv32imc_zicsr_zifencei` and ESP32-C5,
C6, C61, H2 and H21 images as `rv32imac_zicsr_zifencei_zaamo_zalrsc`, all ilp32.
Those base strings are admitted. Builds that add `_zba_zbb_zbs`,
`_zcb_zcmp_zcmt` or an `xesp` extension are refused.
## Evidence
The lane identity is `elf32-exec-le-double-float-generic-v1` or
`elf64-exec-le-double-float-generic-v1`, and `riscv_elf` retains `elf_class`,
`byte_order`, `type`, and the raw `flags` alongside the read-only booleans
`float_abi_double`, `rvc_declared`, `rve_declared`, and `tso_declared`. Those
fields report declarations the header makes; none of them is evidence of the
original compiler, of instruction conformance, or of a floating-point calling
convention beyond the ABI the pinned language models.
The lane identity is one of the lane ids above, and `riscv_elf` retains
`elf_class`, `byte_order`, `type`, and the raw `flags` alongside the read-only
booleans `float_abi_double`, `rvc_declared`, `rve_declared`, and
`tso_declared`. Soft-float profiles also retain `float_abi: soft`, the declared
`arch` string, and `riscv_calling_convention: ilp32-prototype-v1`. Those fields
report declarations the header and attributes make, and the policy REA applied;
none of them is evidence of the original compiler or of instruction
conformance. Session Evidence states the prototype and how many functions and
definitions use it.
## Verification and limits
@@ -76,9 +268,9 @@ tree ships no host decompiler binary, so `Decompiler/os/<platform>/decompile` an
[installation](installation.md#ghidra).
Run `npm run verify:ghidra:riscv` with an existing Ghidra 12.1.4 installation,
JDK 21+, and a RISC-V-capable Clang, LLD and LLVM readelf. The verifier acquires
no tools and never executes target code. Select installed tools explicitly when
they are not on `PATH`:
JDK 21+, and a RISC-V-capable Clang, LLD, LLVM readelf and LLVM objdump. The
verifier acquires no tools and never executes target code. Select installed
and [keys members by them](https://github.com/godotengine/godot/blob/77dcf97d82cbfe4e4615475fa52ca03da645dbd8/core/io/file_access_pack.cpp#L50-L52).
Godot 4.4.1
[stores paths without the prefix](https://github.com/godotengine/godot/blob/49a5bc7b616bd04689a2c89e89bda41f50241464/editor/export/editor_export_platform.cpp#L257-L260)
and [removes it before keying](https://github.com/godotengine/godot/blob/49a5bc7b616bd04689a2c89e89bda41f50241464/core/io/file_access_pack.cpp#L48-L50).
Each version finds `res://scenes/main.gd` in its own export, but comparing the
stored `source_path` would mark every member changed between such exports.
`logical_path` names the member Godot's lookup finds, except in two cases.
REA normalizes Unicode to NFC, so a member renamed only between Unicode forms
compares unchanged, although Godot treats it as another path. Godot reads `\`
as `/` and REA keeps it literal, so a member spelled with backslashes compares
as removed and added. Directory order and offsets are layout. The encryption
bit, the only member flag REA admits, is the occurrence's `encrypted` field. A
member whose encryption changes compares `unknown` in `metadata` and
`availability`: the change is known, but REA does not read encrypted bytes,
so whether the script changed is unknown. Godot's exporter
[stores the MD5 of a member's original bytes](https://github.com/godotengine/godot/blob/a13da4feb8d8aefc283c3763d33a2f170a18d541/editor/export/editor_export_platform.cpp#L465-L468),
as the 4.3 and 4.4.1 exporters do, so the declared MD5 does not depend on the
encryption IV. Encrypted members whose declared MD5s differ therefore also
compare `unknown` in `metadata` and `availability`: the MD5s declare
different scripts, but REA cannot verify them, and equal MD5s add nothing for
the same reason. An encrypted member with an MD5 on only one side stays
`unknown`.
## CLI
Start with a compact summary and keep the complete Evidence bundle:
@@ -43,20 +43,27 @@ rea decompile /absolute/path/to/program main --json
If the registration cannot be read or validated, provider diagnostics retain its
selected `registration_path` and the configuration owner's setting/constraint
facts in `configuration_details.settings`, plus `configuration_remediation`.
facts in `configuration_details.settings`. The availability's `remediation`, and
each operation it makes unavailable, carry the configuration owner's repair.
`configured` means a path was selected, not that it is valid or an IDA connection
was established; failed registrations remain unavailable. Correct
`REA_IDA_MCP_CONFIG` in the next CLI invocation's environment or the MCP server's
launch environment. Restart/reconnect that server after changing its environment;
an export in another shell does not update a running server.
An HTTP registration's `url` must use `http` or `https` without URL credentials,
and its host must be `localhost`, `127.0.0.1`, `[::1]`, or the Docker/Podman
host-gateway name `host.docker.internal` or `host.containers.internal`. Use a
gateway name when REA runs in a container and IDA MCP listens on the container
host. REA refuses every other host.
Open the **original input binary** in REA, not an `.idb` or `.i64` database. Its SHA-256 must match IDA's recorded input hash. REA checks the attached identity and image base around each analysis request. A mismatch or target switch fails with recovery guidance. The original input must remain readable by REA; WSL can use its mounted alias while the GUI runs on Windows. Attached profiles, including native mode, refuse universal Mach-O inputs at admission: every slice shares the recorded whole-file hash, so REA cannot verify which slice the GUI loaded. Open a thin file extracted with `lipo -thin` in both IDA and REA, or use headless mode, which analyzes the selected slice.
REA's close operation releases its MCP connection or proxy. It never saves or closes the existing GUI database. External edits remain visible because results are live and are not replayed from snapshots. These checks do not lock the GUI or prove that the database is unpatched.
## Optional native IDA MCP bridge
IDA Pro and IDA Free can use the separate [ida-free-mcp native plugin](https://github.com/0xshlomil/ida-free-mcp) through an explicit attached registration. Install and start that plugin using its upstream instructions, open the original binary in the IDA GUI, and wait for analysis. The native MCP 1.1.2 release binds `0.0.0.0` without a listen-address setting. Restrict inbound access with the host firewall or use an upstream build that binds only to loopback before starting it. REA's loopback URL restriction does not change the plugin's listener. REA does not install IDA, build the plugin, activate a license, or launch the GUI.
IDA Pro and IDA Free can use the separate [ida-free-mcp native plugin](https://github.com/0xshlomil/ida-free-mcp) through an explicit attached registration. Install and start that plugin using its upstream instructions, open the original binary in the IDA GUI, and wait for analysis. The native MCP 1.1.2 release binds `0.0.0.0` without a listen-address setting. Restrict inbound access with the host firewall or use an upstream build that binds only to loopback before starting it. REA's local URL restriction does not change the plugin's listener. REA does not install IDA, build the plugin, activate a license, or launch the GUI.
@@ -64,8 +64,9 @@ All four preserve target-file bytes. `open_jeb_project` selects or creates an en
- When `decompile_jeb_item` omits `unit_path`, the engine implicitly selects the project's first code unit; REA reports `unit_path: null` and a limitation instead of guessing the effective unit.
- `item_address` is a JEB code-item reference, such as a value returned by the engine's `list_code_items`. Preserve that representation and select its code unit explicitly. For the audited CE SQLite unit, the exported method reference was `sqlite3_libversion_number`; using its raw ELF symbol address did not produce a successful decompilation. File offsets, ELF virtual addresses and engine item references have different semantics. REA currently exposes unit listing, while code-item discovery remains an upstream JEB operation.
- Valid engine refusals (`success: false` envelopes) return `execution_failure` with the engine's message when available. Diagnostics preserve the original envelope, including any recovery advice, and the inputs of a refused primary request. If client metadata fails after a primary reply, that reply and the caller's inputs remain in partial Evidence. REA does not infer an unsupported target from a refusal, invoke a recovery tool, or retry the request.
- Successfully parsed client information is reused for the lifetime of the MCP connection, including when the engine omits its version. Explicit client inspection and availability probes refresh it.
- An unreachable endpoint is a provider-availability failure with recovery guidance; REA does not retry or spawn the engine.
- Successfully parsed client information is reused within the current MCP session, including when the engine omits its version. Explicit client inspection and availability probes refresh it; reconnecting invalidates the cached information.
- An unreachable endpoint is a provider-availability failure with recovery guidance; REA does not spawn the engine or automatically replay failed operations.
- After a transport or RPC failure (including a request or catalog timeout), the next explicit call attempts a new MCP connection after cleanup succeeds. Engine version and GUI mode are refreshed for that connection. Caller cancellation and decoded engine refusal envelopes preserve the existing connection.
Real-engine verification lives in the JEB lane described in [testing.md](testing.md); mock transport tests do not establish real-engine compatibility.
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. REA uses a `kuna` you supply. It does not download, install, update or bundle Kuna. REA verified this provider with Kuna 1.833; it accepts other releases and reports them as unverified in `rea doctor` and in each analysis profile.
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.990, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases; `rea doctor` and analysis Evidence report them as unverified. Verification compares the reported version; it does not authenticate the executable. Analysis profiles retain the observed version and executable digest, independently of REA's current verified-release policy. The 1.990 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
Kuna is static: it reads the selected image and never executes it.
This profile change makes earlier Kuna analysis snapshots and resumable directory
exports incompatible, even with the same Kuna executable. Keep the old files:
their recorded Evidence remains historical. To continue analysis, open the
binary without the old snapshot and save a new one, or choose a new export
directory. Existing snapshot Evidence can be extracted from its
`evidence_bundle` JSON member and imported as a separate Evidence bundle; that
does not restore cached workflow entries or resume the old directory export.
The CLI command validates the bundle in a one-shot session. To use the records
in an MCP session, call `import_evidence_bundle` with the extracted file path.
This validates the embedded bundle, not the rest of the snapshot container.
## Configure
```bash
@@ -30,6 +48,10 @@ completes. Cleanup retries keep both the observations and the exact file owner.
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile, and for the inventory run REA makes when the export ends without one. |
| `REA_KUNA_JOBS` | 1 | `decompile-project --jobs`. 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
Pseudocode read from a parallel project export includes a prototype-context
limitation in its Evidence, including in function dossiers and composed
workflows. Early single-function results have their own source limitation.
A single-function decompile, inventory, string, graph or xref run that exceeds its deadline fails as `provider_timeout`, naming the setting that owns the deadline and that raising it takes a restart. A project export that exceeds `REA_KUNA_PROJECT_TIMEOUT_MS` is an export failure instead: requests that need the export report `capability_unavailable` with the export's own retry guidance.
Windows is not supported yet. Supported targets are ELF and thin Mach-O executables for x86-64, AArch64 and ARM; PE and universal Mach-O are rejected with a typed reason.
@@ -38,7 +60,7 @@ Windows is not supported yet. Supported targets are ELF and thin Mach-O executab
| `list_procedures`, `search_procedures`, `procedure_address` | Kuna's callable inventory (`kuna functions --json`), including import slots it does not decompile |
| `list_procedures`, `search_procedures`, `procedure_address` | Kuna's callable inventory (`kuna functions --json`), including import slots but not [Mach-O data](#mach-o-data-entries) |
| `address_name` | Function entries, then the data labels Kuna's export assigns |
| `procedure_pseudo_code`, `batch_decompile` | The project export, or a single-function run while the export builds |
| `procedure_assembly` | The project export's labeled listing |
@@ -47,6 +69,8 @@ Windows is not supported yet. Supported targets are ELF and thin Mach-O executab
| `analyze_function` | Pseudocode and assembly from the export, plus call relationships; typed references, comments, strings and CFG remain unavailable |
Kuna's inventory gives some entries aliases, such as ELF versioned-symbol names and generated `sub_` names. Name selectors accept an alias that names one entry and list every entry a shared alias names. `search_procedures` matches aliases too: each entry appears once under its primary name, with the matching aliases in `matched_aliases`. `list_procedures` shows primary names only.
Typed procedure references, basic blocks, raw-memory tools, annotations and type recovery are not available from this provider. Use another provider for those operations.
## How answers are produced
@@ -59,10 +83,10 @@ The first text or assembly request in a session looks for this target's export i
- Meanwhile, pseudocode comes from one `kuna decompile --addr` run. Name and address lookups, and listings, use the inventory the export takes with `kuna functions --json` before it decompiles. The session runs `kuna functions` itself, under `REA_KUNA_TIMEOUT_MS`, only when no export is building (an inventory-only request does not start one) or the export ends without an inventory.
- Requests for the same function share one run; it stops when its last waiting request is cancelled. Each run analyzes the whole image again in its own process, so one REA process runs at most as many at once as the host has cores beyond `REA_KUNA_JOBS` (at least one). Further requests wait in order, and a waiting request is answered from the export if it finishes first. A run whose stop REA cannot prove keeps its slot until `close_binary` proves it stopped.
- The session keeps each completed single-function text, up to 16 MiB in all with the least recently used dropped first, and answers repeats from it until the export can. After a failed export that lasts for the session.
- Assembly and data names wait for the export; progress notifications report the wait every ten seconds. A session does not rebuild a failed export: requests that need it fail with `retryable: false`, the export's failure, its captured Kuna output and what to change before reopening the target. A setting such as `REA_KUNA_PROJECT_TIMEOUT_MS` or `REA_KUNA_JOBS` is read when REA starts, so changing it also needs a restart. Pseudocode keeps using single-function runs.
- Assembly and data names wait for the export; progress notifications report the wait every ten seconds. A session does not rebuild a failed export: requests that need it fail with `retryable: false`, the export's failure, its captured Kuna output and what to change before reopening the target. REA does not expose per-function records from a failed project export, even if the retained Kuna diagnostic refers to them. Single-function fallback Evidence reports the failed state and this limitation. If cleanup remains incomplete, its error takes precedence and `close_binary` retries the retained resources. A setting such as `REA_KUNA_PROJECT_TIMEOUT_MS` or `REA_KUNA_JOBS` is read when REA starts, so changing it also needs a restart. Pseudocode keeps using single-function runs.
- A single-function answer says so in its limitations. It lacks the export's whole-program prototypes, so a later request can return different text for the same function.
Pseudocode, assembly, `analyze_function` and data-name answers record where their text came from in `raw_result.source`: `single-function`, or `project-export` with the cache directory. `procedure_pseudo_code` and `procedure_assembly` answers from the export also name the file, byte span and the file's SHA-256. Combined `analyze_function` answers retain both facet records in `raw_result.source.files`, each with its file, digest and byte span. Both texts come from one export; losing that project retries the complete report. Listings and procedure lookups carry no `raw_result`. Agents can open or search the export's `.c` and `.asm` there directly when that is cheaper than calling a tool per function. Text read that way is target-derived, like any decompiler output, but it bypasses the untrusted-content marker that REA's tool results carry.
Pseudocode, assembly, `analyze_function` and data-name answers record where their text came from in `raw_result.source`: `single-function`, or `project-export` with the cache directory. `procedure_pseudo_code` and `procedure_assembly` answers from the export also name the file, byte span and the file's SHA-256. Combined `analyze_function` answers retain both facet records in `raw_result.source.files`, each with its file, digest and byte span. Both texts come from one export; losing that project retries the complete report. Listings and procedure lookups carry no `raw_result`; omitted [Mach-O data entries](#mach-o-data-entries) are reported in their limitations. Agents can open or search the export's `.c` and `.asm` there directly when that is cheaper than calling a tool per function. Text read that way is target-derived, like any decompiler output, but it bypasses the untrusted-content marker that REA's tool results carry.
The background export belongs to the session. `close_binary`, or the end of a CLI command, cancels it unless another session still waits for it. A one-shot CLI decompile therefore answers from a single-function run; any request that needs the export, such as `procedure_assembly` or `analyze_function`, keeps it.
@@ -70,7 +94,18 @@ The assembly label index is scanned once per export reader. Cancelling a request
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a matching deadline and a worker old-generation heap. Literal matching remains the default. Cancellation or a matching deadline leaves the session available. The deadline is `REA_REGEX_MATCHING_DEADLINE_MS`, a positive decimal integer from 1 to 2147483647 ms. Unset uses 5000 ms. A larger value is refused because Node schedules a delay past that signed 32-bit bound as 1 ms. Invalid values are configuration errors and do not fall back to the default. The setting is read from the environment that starts REA, so a change needs a restart. A deadline failure names the effective milliseconds, whether they came from the default or the setting, the default, and the maximum. The worker old-generation heap does not raise it. The worker heap request is `REA_REGEX_WORKER_HEAP_REQUEST_MIB`, a decimal integer from 7 to 16384 MiB. Unset uses 128 MiB. A request below 7 MiB aborts the worker during startup, and a larger value is refused. Invalid values are configuration errors and do not fall back to 128. The setting is read from the environment that starts REA, so a change needs a restart. A heap failure names the effective request, whether it came from the default or the setting, the default, the floor, and the maximum. 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 worker heap 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 worker heap 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 worker heap 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` with `detail: "complete"` (the CLI default) returns the complete dossier inline, which can still be large; later retained views do not remove that first transfer. The MCP default retains the dossier and returns its summary, so page the facets you need through `inspect_analysis_view`, or prefer the individual text tools for a focused question.
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger project-export spans remain in the local export and the error identifies the file and range. A single-function run does not by itself retain a complete project export.`analyze_function` with `detail: "complete"` (the CLI default) returns the complete dossier inline, which can still be large; later retained views do not remove that first transfer. The MCP default retains the dossier and returns its summary, so page the facets you need through `inspect_analysis_view`, or prefer the individual text tools for a focused question.
## Mach-O data entries
Kuna's inventory also lists Mach-O exports that are data, such as globals in `__DATA,__data`, constants in `__TEXT,__const` and `__mh_execute_header`. REA classifies each entry with the target's own section table, the same load-command facts `inspect_macho` reports, read from the leading bytes kept while the session's private copy is hashed:
- A section with `S_ATTR_PURE_INSTRUCTIONS` or `S_ATTR_SOME_INSTRUCTIONS`, or of type `S_SYMBOL_STUBS`, holds code; the entry stays.
- A lazy or non-lazy symbol-pointer section (`__got`, `__la_symbol_ptr`) holds import slots; the entry stays.
- Any other section, or the header and load commands before the first section, holds data. Listings and searches omit the entry and say how many they omitted in each region. Pseudocode, assembly, `analyze_function` and `procedure_address` refuse it by name or address, and call queries refuse its name, naming its region; call queries by exact address stay validated by Kuna's graph. `address_name` and `xrefs` still accept it.
- An address outside every section and the header stays, with a limitation saying REA cannot classify it. If the section table cannot be read, every entry stays and listings say why. Answers about one such entry, including pseudocode, assembly, `analyze_function`, `procedure_address`, call queries by name and `address_name`, carry the same limitation; code and import slots carry none.
A section's attributes are the format's declaration, not proof of what runs there, and a kept entry is not proven to be a function. ELF inventories are kept as Kuna reports them. On a Mach-O target, the first listing or lookup establishes the private copy that Kuna commands also use, once per session, even when a warm export answers it.
## Strings
@@ -137,7 +172,7 @@ the manifest's file identity as well as the export files. These refusal and
migration cases are verified with controlled Kuna-format exports; they do not
establish behavior of an independently installed Kuna release.
Builds publish by one rename, so a reader sees a complete export or none. If later staging retirement fails, the typed cleanup error retains the completed export's key, directory and manifest in `published_export`. The session adopts that verified project; later queries reuse its immutable files while `project_export.cleanup_incomplete` reports the retained owner's live state. `close_binary` retries only the remaining cleanup, and a successful retry resolves prior cleanup errors. A failed export followed by failed staging cleanup retains both failures; it does not invent a publication receipt. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. REA retains the file state verified with each digest, checks it before warm-cache reuse, and checks open descriptors before and after reading text. Assembly indexes are checked against the same state; a changed file makes the whole report retry rather than mixing facets from different exports. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven. Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
Builds publish by one rename, so a reader sees a complete export or none. If later staging retirement fails, the typed cleanup error retains the completed export's key, directory and manifest in `published_export`. The session adopts that verified project; later queries reuse its immutable files while `project_export.cleanup_incomplete` reports the retained owner's live state. `close_binary` retries only the remaining cleanup, and a successful retry resolves prior cleanup errors. A failed export followed by failed staging cleanup retains both failures; it does not invent a publication receipt. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. REA retains the file state verified with each digest, checks it before warm-cache reuse, and checks open descriptors before and after reading text. Assembly indexes are checked against the same state; a changed file makes the whole report retry rather than mixing facets from different exports. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven; that MCP error names the same-connection retry as `recovery.kind=retry_cleanup` ([retained cleanup retry](mcp-contracts.md#retained-cleanup-retry)). Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
| `compare_application_versions`, `compare_javascript_export_shapes` | `left`, `right`; version comparison also accepts both native-observation arrays |
| `compare_static_and_observed_endpoints` | `application`, `capture`; summary retains the complete comparison for `endpoint-comparison` pages |
`get_evidence_bundle`, and the adapter's `discover_tools`.
`inspect_analysis_view` is loaded on demand like other analysis tools. When a
follow-up needs retained facts, include it in `discover_tools.names`; if the
producer and follow-up are known together, load both in one batch. Producers
that accept `view` can answer a selected first question without loading the
cross-domain view contract. Complete output schemas and canonical result
validation are preserved when each tool loads.
An explicit tool-family scope can remove any of those domain tools.
`discover_tools` searches canonical names and descriptions using case-insensitive
words, or accepts exact `names`. It loads the matching page and returns names
and abbreviated descriptions with their configured input schemas, so the next
call can use the returned arguments contract. Output schemas remain in
`tools/list`. Search defaults to three results, allows up to
20 per page, and returns `total_matches` and `next_offset`. Exact names preserve
request order and reject unknown or excluded names before loading anything.
Use fewer keywords when no result matches; search requires every word to match.
Both reply representations must fit the configured response budget before any
tools load. An oversized page returns a resource constraint; reduce `limit` or
request fewer exact names and retry. Input schemas are retained in full within
the selected input profile.
Loaded definitions retain their original names, complete configured schemas,
annotations, handlers and client approval rules. Loading does not execute an
analysis, open a target, or initialize the deferred analysis runtime. A batch
emits one list-change notification; repeating it emits none. Loaded tools remain
available until the connection ends. There is no generic execution gateway.
If a loaded tool does not appear in the client, enable client catalog refresh
or reconnect using `REA_MCP_TOOL_CATALOG_PROFILE=full`.
The catalog identity covers the complete in-scope inventory, the discovery
contract and the hot/cold policy; its count includes `discover_tools`. It differs
from full advertisement and remains stable as tools load. `rea mcp doctor`
checks the profile's initial advertisement and identity. The domain inventory in
`binary_session.tool_availability` continues to describe provider and target
availability, independently of whether a definition is loaded. Load a listed
cold tool before calling it. Provider health and target changes do not load,
unload or remove definitions. The input/output schema profiles and explicit
family exclusions continue to apply.
### Tool family advertising scope
Agents pay context for every advertised tool schema, and clients that send
@@ -1573,6 +2013,7 @@ bundle import/export; it does not include those view and settings tools.
| Web investigation | `browser,pcap,session,javascript-runtime` | page, bundle and session inspection, packet captures, JS runtime observation, evidence workflow |
| Android application analysis | `android,adb,apktool,session` | class and method inspection, device bridge, resource decoding; add `frida` for dynamic instrumentation, `jeb` for JEB projects, `flutter` for Flutter builds |
| Native macOS or iOS binary | `native,artifact,session` | Mach-O, signature and property-list inspection plus container extraction |
| Native analysis of a known function | `session,enhanced,application` | open a binary, analyze a known function, page retained pseudocode, and export Evidence; add `direct` for focused procedure search or assembly |
| Managed .NET assembly | `managed,session` | managed member comparison and reconstruction against the evidence workflow |
| Electron or browser-hosted JavaScript application | `browser,electron,application,javascript-runtime,session` | runtime targets, live page inspection, bundle and capture tools, feature tracing, JavaScript semantics and reconstruction obligations |
| Evidence bookkeeping only | `session` | open and close binaries, bundles, comparisons, unknown tracking |
@@ -1598,15 +2039,20 @@ profile and the advertised form that profile produces. `full`, `compact`, and
bounded schemas clients receive; `off` keeps the canonical output contracts in
the digest while advertising nothing.
Some stdio hosts reject a complete `tools/list` frame before they load any
tools. The opt-in `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact` setting bounds every
advertised output schema to 4,096 bytes; `off` omits `outputSchema` entirely
Some clients reject a complete `tools/list` frame or HTTP SSE event before
they load any tools. The opt-in `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact`
setting bounds every advertised output schema to 4,096 bytes; `off` omits `outputSchema` entirely
(the measured pi-web-ui failure and catalog breakdown are in
[#2512](https://github.com/morluto/rea/issues/2512)). The default `full` profile
is unchanged. Both settings keep the same named canonical tool contract for
server-side result validation, and both still return complete
`structuredContent` and text results.
For the 1 MiB FastMCP SSE limit reported by Unsloth in
[#9093](https://github.com/morluto/rea/issues/9093), see the
rea inspect-native-data-type <binary> --address <typed-data-address> --provider ghidra
```
Ghidra bit-field components may have a `type_id` that is not a registered
database type. When `bit_field` is present, `base_type_id` is the provider's
base type and the lookup fields report whether the manager resolves equivalent
types at those exact paths. `bit_size` is the effective width;
`declared_bit_size` can be larger. `bit_offset` is the least-significant-bit
offset within the base type, not a byte-order-independent offset from the start
of the containing structure. Account for the component's storage size and byte
order before applying that offset to a larger word. Missing `bit_field` details
are unreported, not proof that a component is not a bit-field.
Instruction facts include bytes, length, decoder text, ordered register/scalar/
address tokens, typed references and flow destinations. Mid-instruction, data,
outside-memory and unlisted addresses have separate outcomes. For Ghidra,
@@ -185,12 +209,24 @@ observed entry label. An interior address therefore remains visible without
being mistaken for the function entry. Identity failures are reported as
`procedure.status: "unknown"`; label failures leave a null name and `name_error`.
Neither failure discards usable pseudocode, and names are not parsed from it.
A provider answer without pseudocode is an error item that keeps that
observation's own `limitations`, so a reason the provider reported for that
entry, such as a Kuna export failure or a missing body, stays with the item.
Null pseudocode is `capability_unavailable`; empty or non-text pseudocode is
`unreadable_output`. Provider failures keep their typed error. Successful items
do not repeat limitations; the batch Evidence aggregates every observation's
limitations by provider and operation.
Resolved entries also appear in the composed Evidence locations. New batch
Evidence commits the revised workflow profile; retained historical Evidence
remains valid. This tool
has no dedicated CLI command; the existing `decompile` command remains the
single-procedure CLI operation.
`detail: "summary"` retains the complete batch and returns only its counts,
identities and sizes. `inspect_analysis_view` then reads rows or one body from
the retained record, an Evidence file or a portable bundle without repeating
decompilation: see [selected batch results](mcp-contracts.md#selected-batch-decompile-results).
Each batch schedules one provider read at a time, but has no shared input-count
or aggregate retained-output allowance. It retains every requested row and full
pseudocode body through Evidence construction. The MCP response budget checks
@@ -211,8 +247,23 @@ the index retains the complete selected names, addresses, provider facts and
caveats. Failure details retain at most 32 rows within 2,048 JSON-encoded
UTF-8 bytes; names may be omitted to keep complete addresses and reasons. The
index retains every failure and its complete observed name.
The manifest reports the procedure-inventory limitations of the current call in
`inventory_limitations`. `decompile-index.json`'s `latest_run` records that
call's filters, selected and excluded counts, and inventory limitations; older
runs remain represented only by their retained function records, so a later
inventory warning is not presented as the history of every file.
Each function's `decompile-index.json` record, whatever its status, keeps the
complete limitations of the observations it used. The returned Evidence keeps
complete limitations of the observations it used.
When available, `provider_source` records the source of that attempt's
pseudocode observation: a file path, SHA-256 and byte range, or a
single-function decompile. The latter describes the method, not a unique run. A file
descriptor records the source as observed; the file may since have changed or
been removed, so compare its current digest before using it. The descriptor
does not authenticate the journal. `status: "unknown"` distinguishes
an unavailable source from an existing matching output whose original producer
is unknown. Older journal records can omit this field; omission is unknown.
Failed attempts retain their source in the append-only journal when the
provider supplied one, even if a later attempt succeeds. The returned Evidence keeps
at most eight first-observed distinct per-function provider limitations within an 8,000-byte JSON-encoded UTF-8 text budget and counts omitted observations. Repeated omitted text is counted per observation, not as a distinct caveat. This summary does not retain every omitted string in memory. A fresh output directory must not already exist. `--resume`
continues an export for the same target SHA-256, provider, analysis profile,
and layout; it requires the directory to exist, and an export without a known
@@ -285,6 +336,20 @@ relationships but reports their completeness as unknown. In particular, a resolv
tail call observed in one direction does not prove that the reverse inventory is
complete. An empty Hopper inventory cannot establish that no such call exists.
No whole-binary reverse scan is performed.
For large native traces, `trace_feature` accepts `detail: "summary"`. It retains
the complete trace and returns counts and its parent Evidence ID. Use
`inspect_analysis_view` with that ID and `view: {kind: "native-feature-trace",
collection: "references", offset: 0, limit: 64}` to page the original rows;
`matches` and `unknowns` are separate collections. Matches accept an exact
`address`; references accept exact `source_address` and `target_address`
filters. Row indices refer to the retained collection, and page coverage does not establish provider discovery
completeness. `list_unknowns` pages the independently managed question heads.
In the one-shot CLI, `rea trace ... --detail summary --evidence_output PATH`
stores the trace and its linked source Evidence for later
`inspect-analysis-view` calls. This bundle does not include the CLI session's
Unknown registry; its original question texts remain available in the trace's
`unknowns` pages.
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
matching deadline and a worker old-generation heap. Deadline or cancellation
stops matching while leaving the Hopper API available. The deadline is
@@ -373,6 +438,11 @@ sites remain distinct. A static route does not establish runtime reachability.
Swift/closure dispatch requires resolved static references; unavailable ABI
forms remain unknown.
Encoded Objective-C method records retain their own locations in dispatch
Evidence. A handler node uses the implementation address with an unknown file
offset unless that function already has an independently located graph node;
the method-record offset does not locate the handler's code.
Incomplete reference kinds do not discard known typed calls or partial
provider call references. A `provider_call` with `observed` resolution retains
the provider-reported call site and endpoint without claiming generic direct,
@@ -414,13 +484,48 @@ nodes and edges. Node payloads are bounded to 8 MiB and serialized results to
32 MiB. Pagination returns edges whose source is on the node page and preserves
stable IDs for endpoints outside that page.
Procedure, node and edge `evidence_id` values identify the source function
observations used by the trace. In an MCP session, retrieve them from the
Evidence bundle for inspection or export. A replayed source keeps the original
observation's identity; the trace also reports that replay occurred. Older
cached value traces are not replayed under the revised workflow profile, while
their historical Evidence remains available.
The one-shot CLI accepts `--evidence_output <new-path>` to save the trace and
the transitive source Evidence closure named by its links. The destination is
never overwritten. `--detail summary` requires `--evidence_output`; the bundle
still retains the complete trace and source Evidence.
Within one value trace, each exact procedure selector is attempted at most once,
including failed analyses. A failed seed returns the provider's original typed
failure and corrective guidance; it cannot establish a trace or target identity.
Failures while expanding a validated seed remain unknown observations and do not
establish canonical function identity. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. A new trace
can retry failed selectors.
establish canonical function identity. A cancelled expansion read returns the
cancellation. An expansion read whose provider cannot confirm cleanup of
resources it owns stops the trace: the result is that provider's own
`cleanup_incomplete` failure with its resources and any bound cleanup retry, and the work
admitted before the stop is retained as partial Evidence with a located unknown
at the stopping read. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. An unrecorded
facet stays unknown; when the provider explicitly reports unavailability, the trace
retains its reason. REA's Kuna provider explicitly reports that it does not supply
native def-use observations. A new trace can retry failed selectors.
Paging and traversal completeness are separate facts. `page_truncated` means the
page omits retained nodes, and the edges they source, before `offset` or after
`next_offset`. Every page repeats `traversal`: `complete`, or `truncated` with
`reasons` naming each observed cause: the `max_depth`, `max_functions`,
`max_call_sites`, `max_nodes` or `max_edges` budget, the fixed
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="انضم إلى مجتمع REA على Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [إنشاء بيئات التعلم المعزز](#build-rl-environments) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [أدوات GNU objdump وRizin](../reverse-engineering-tools.md) · [التوثيق](#التوثيق)
<code>npx rea-agents setup</code>
@@ -158,6 +160,7 @@ npx rea-agents@latest setup
| شيفرة EVM البايتية | محددات التوجيه، وإزاحات البايتات، والمعاملات المستنتجة، وقابلية تغيير الحالة | مدخل محلي يحمل بايتات خامًا أو تمثيلًا سداسيًا عشريًا على Linux x64؛ [دليل EVM دون تشغيل](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
@@ -158,6 +160,7 @@ REA benötigt Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) oder 26+ (>=26.12.1) so
| EVM-Bytecode | Dispatch-Selektoren, Byte-Offsets, abgeleitete Argumente und Veränderbarkeit des Zustands | Lokale Eingabe mit Rohbytes oder Hexdarstellung unter Linux x64; [Offline-EVM-Leitfaden](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Έλα στην κοινότητα REA στο Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
| PE certificates | Authenticode WIN_CERTIFICATE entries and PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](../pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Únete a la comunidad de REA en Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
@@ -158,6 +160,7 @@ REA requiere Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), adem
| Bytecode EVM | Selectores de despacho, desplazamientos en bytes, argumentos inferidos y mutabilidad | Entrada local con bytes sin procesar o representación hexadecimal, en Linux x64; [guía de EVM sin ejecutar](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="به انجمن REA در دیسکورد بپیوندید" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
**[Site web](https://rea.tools/fr/) · [Guides](https://rea.tools/fr/guides/) · [Études de cas](https://rea.tools/fr/showcase/)**
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Rejoignez la communauté REA sur Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Démarrage rapide](#démarrage-rapide) · [Comment fonctionne REA](#comment-fonctionne-rea) · [Ce que vous pouvez analyser](#ce-que-vous-pouvez-analyser) · [Études de cas](#études-de-cas) · [Créer des environnements RL](#build-rl-environments) · [Questions fréquentes](#questions-fréquentes) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
@@ -158,6 +160,7 @@ REA nécessite Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ou 26+ (>=26.12.1), ai
| Bytecode EVM | Sélecteurs de dispatch, offsets en octets, arguments inférés et mutabilité | Fichier local brut ou hexadécimal sous Linux x64 ; [guide EVM hors ligne](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Bergabunglah dengan komunitas REA di Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Mulai Cepat](#mulai-cepat) · [Cara Kerja REA](#cara-kerja-rea) · [Hal yang Dapat Anda Analisis](#hal-yang-dapat-anda-analisis) · [Contoh Penggunaan](#contoh-penggunaan) · [Membangun lingkungan RL](#build-rl-environments) · [FAQ](#faq) · [Dokumentasi](#dokumentasi)
<code>npx rea-agents setup</code>
@@ -161,6 +163,7 @@ Alat tambahan dan dukungan sistem host bergantung pada target yang dianalisis:
| Bytecode EVM | Selector dispatch, offset byte, argumen yang disimpulkan, dan sifat mutabilitas | Pembawa data raw/hex lokal di Linux x64; [panduan EVM offline](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Unisciti alla community REA su Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
@@ -158,6 +160,7 @@ REA richiede Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), oltre
| Bytecode EVM | Selettori di dispatch, offset dei byte, argomenti inferiti e mutabilità | Carrier raw/hex locale su Linux x64; [guida EVM offline](../evm-bytecode.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
| Binary hardening | PIE, NX, RELRO, CFG and related header flags for ELF, PE and Mach-O | Built-in static header inspection; [binary hardening](../binary-hardening.md) |
Loaded 100 of 2443 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.