Compare commits

...
Author SHA1 Message Date
morluto 45a3d03dbf style(test): format CLI help default annotations (#9280)
(cherry picked from commit ac30fbd658)
2026-11-07 23:16:19 +08:00
morluto 4e518b0f2b chore: sync main test updates 2026-11-07 22:50:57 +08:00
morluto 4983d30edf chore: sync main analysis changes 2026-11-07 22:50:54 +08:00
morlutoandCursor Agent 2e2f981ccf test: assert absolute-path facts without frozen sentences (#9231)
* 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]>
2026-11-07 22:46:17 +08:00
morluto eb94309098 fix(cli): reconcile source closure with main 2026-11-07 22:42:55 +08:00
Heo Hao f8bb792f15 fix(cli): stop restating schema defaults in help descriptions (#9278)
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.
2026-11-07 22:38:03 +08:00
morlutoandCursor Agent 0ecf3a4cbf docs(mcp): document native function analysis scope (#8389)
Co-authored-by: Cursor Agent <[email protected]>
2026-11-07 22:14:55 +08:00
ShuaixinHuang 8842092671 fix(pe): reject misaligned certificate table offsets (#9276)
* fix(pe): reject misaligned certificate table offsets

* test: align ghidra limitation wording with main

* style: format ghidra limitation assertion
2026-11-07 22:14:29 +08:00
Edgar Arakelyan c5301e6145 feat(desktop): capture, draft, export and witness a task inside a build computer (#9045) 2026-11-07 17:54:59 +08:00
gensokyo-dev 8eff651270 fix(ghidra): make checkpoint workflows discoverable (#9246)
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.
2026-11-07 16:56:33 +08:00
morluto 441ba94086 fix: preserve source closure during snapshot replay (#8870)
* fix: preserve source closure during snapshot replay

* fix: revalidate restored snapshot sources lazily

* fix: bound and share source-closure checks
2026-11-07 16:22:21 +08:00
morlutoandCursor Agent b31fc5d5fb fix(cli): advertise domain constraints for selector and path options (#9181)
* 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]>
2026-11-07 16:17:09 +08:00
morluto 67ca695caf Merge branch 'babysit-8870' into babysit-8873
# Conflicts:
#	src/application/DirectAnalysis.ts
#	src/cli/analysisEvidenceDelivery.ts
#	tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
2026-11-07 15:52:27 +08:00
morluto 95dc5c0ad0 Merge remote-tracking branch 'origin/main' into babysit-8870
# Conflicts:
#	src/application/DirectAnalysis.ts
#	src/application/binary/AnalysisSnapshotCache.ts
#	src/application/binary/AnalysisSnapshotReplayBinding.ts
#	src/application/investigation/InvestigationEvidence.ts
#	tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
#	tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts
2026-11-07 15:51:36 +08:00
morlutoandCursor Agent 562779bd71 fix(mcp): advertise absolute paths for tools that reject relative inputs (#9179)
* 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]>
2026-11-07 15:49:50 +08:00
morluto 5df3e273ae fix: supervise function bundle verifier processes 2026-11-07 15:24:00 +08:00
吴佳浩Alben 923e6f77e3 fix(javascript): keep timer cancellation within the Node handle family (#7712)
* 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.
2026-11-07 15:17:47 +08:00
Shivam Dwivedi 36dcd985e1 docs(clients): document llama.cpp MCP setup (#4878)
* docs(clients): document llama.cpp MCP setup

* docs(clients): clarify persistent installs and llama.cpp deadlines

* docs(clients): escape Windows paths in llama.cpp config
2026-11-07 15:16:02 +08:00
gensokyo-dev c07ab4b994 feat(ghidra): checkpoint owned Programs across sessions
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.
2026-11-07 14:52:39 +08:00
morluto 99db80a13e fix(cli): reject non-decimal integer tokens on every numeric option (#9178) 2026-11-07 11:52:39 +08:00
Edgar ArakelyanandClaude Opus 5.5 ac23dc8077 feat(desktop): read XML with a plain DOCTYPE, and check an element's whole text (#9050)
* 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]>
2026-11-07 03:20:12 +00:00
lostviolinist 57fc2d4617 fix(test): wait for valid debugger fixture PIDs (#9208)
* fix(test): wait for valid debugger fixture PIDs

* test(process): cover delayed and invalid PID publication

* test(process): share validated PID file readiness
2026-11-07 11:12:43 +08:00
Saurabh Sungh abfccc0683 fix(javascript): bound symbolic property and member queries (#8876)
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)
2026-11-06 23:13:15 +00:00
Soukyu 48f5f30fd5 fix(cli): reject malformed dry-run literals before changes (#7870)
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)
2026-11-06 22:30:39 +00:00
2SinceJuly e9ea9d6f50 fix(cli): validate MCP registration before side effects (#9057)
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)
2026-11-06 21:56:35 +00:00
Nianze Wu a93eb39698 fix(managed): preserve unknown member ownership across invalid rows
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)
2026-11-06 21:21:56 +00:00
Sahinur fdc4e3908b fix(artifacts): report ZIP Zstd window refusals as resource limits
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)
2026-11-06 20:12:31 +00:00
Sahinur 81ada025c0 fix(native): preserve Objective-C records with unreadable protocol lists
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)
2026-11-06 19:09:18 +00:00
Edgar ArakelyanandClaude Opus 5.5 f8356f3d71 fix(desktop): read the PPA key add-apt-repository writes into Signed-By (#8982)
* 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]>
2026-11-07 01:49:11 +08:00
Beqa 91d537098a fix(setup): spell IDA and JADX in interactive completion (#9149) 2026-11-07 01:30:06 +08:00
RRFZ d1103beb8d fix(cli): reject non-decimal web capture deadlines (#9167) 2026-11-07 01:29:44 +08:00
ShuaixinHuang 10407fb624 fix(dotnet): reject invalid UTF-8 metadata version strings (#9156)
* 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
2026-11-07 01:29:22 +08:00
ShuaixinHuang 2cf9c42df6 fix(desktop): recognise JSON scalar files with mixed-case suffixes (#9152)
* 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
2026-11-07 01:28:59 +08:00
ShuaixinHuang fdd4aefd18 fix(cli): preserve literal Bash completion candidates (#9153)
* 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): preserve literal Bash completion candidates
2026-11-07 01:28:34 +08:00
RRFZ 9cdcdf3dff fix(cli): reject non-decimal guidance search limits (#9165) 2026-11-07 01:28:12 +08:00
ShuaixinHuang 128f7c330f fix(browser): clarify shared source-map output budget (#9154)
* fix(browser): clarify shared source-map output budget

* test(browser): assert source-map budget facts independently

* 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
2026-11-07 01:27:49 +08:00
ShuaixinHuang 0fc35a098a test(javascript): normalize module resolution oracle paths (#9159)
* test(javascript): normalize module resolution oracle paths

* 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
2026-11-07 01:27:27 +08:00
gensokyo-devandgensokyo-dev 39bd0cc625 test(cli): select shutdown budgets for worker lifecycle scenarios (#9170)
Co-authored-by: gensokyo-dev <[email protected]>
2026-11-06 16:58:52 +00:00
Nianze Wu 93ce13e76f fix(desktop): read SQLite state despite metadata name collisions
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)
2026-11-06 16:21:02 +00:00
gensokyo-devandgensokyo-dev 98c589e420 perf(tests): reuse bounded CLI workers for native matching errors (#9163)
Co-authored-by: gensokyo-dev <[email protected]>
2026-11-07 00:08:27 +08:00
gensokyo-dev bacc1e3846 perf(mcp)!: defer retained views in progressive catalogs (#9151)
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.
2026-11-06 22:44:56 +08:00
ShuaixinHuang cfa7ebb020 fix(environment): ignore unsafe paging bounds (#9145) 2026-11-06 22:18:25 +08:00
ShuaixinHuang 772d6098f0 fix(environment): validate JSON pointer array indexes (#9147) 2026-11-06 22:17:31 +08:00
Beqa fbe7532fab fix(managed): surface capacity limits in member comparison
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)
2026-11-06 13:23:13 +00:00
gensokyo-dev 1480a7c72e docs(firmware): qualify component comparison workflows (#9143) 2026-11-06 21:15:58 +08:00
RRFZ 55ee36809d fix(javascript): apply package type scope to application sources (#7883)
* 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.
2026-11-06 20:49:41 +08:00
2SinceJulyand2SinceJuly 6c08320654 feat(process): add a non-executing dry run to capture_process_scenario (#8469)
* 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]>
2026-11-06 20:49:20 +08:00
Entrpi 81f9f71f6d feat(rizin): select the decoder and map address for one-shot commands (#8991)
* 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.
2026-11-06 20:48:59 +08:00
Harsh Asalkar 7e9cf83fca feat(mcp): guide agents from argument-name mistakes in procedure and search tools (#2146) (#8649)
* 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.
2026-11-06 20:48:37 +08:00
ShuaixinHuang ee2f5b965a fix(desktop): bound exported task array-index conversion (#9139) 2026-11-06 20:48:17 +08:00
gensokyo-dev 5303199163 feat(api-contract): inspect local GraphQL SDL and field references (#9140)
* feat(api-contract): inspect local GraphQL SDL and field references

* fix(api-contract): retain caller metadata and bound selection work
2026-11-06 20:16:31 +08:00
Saim Shafique 14d2c51d85 fix(managed): decode only selected types in member inspection (#9044)
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)
2026-11-06 12:05:09 +00:00
Christopher Pruijsen abdbaa544a fix(rizin): report the output cap instead of a provider signal (#9033)
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)
2026-11-06 11:20:43 +00:00
ShuaixinHuang f3d6f45315 fix(desktop): preserve exported SQLite text encoding (#9123)
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)
2026-11-06 10:50:12 +00:00
gensokyo-devandgensokyo-dev fe27f96720 test(ci): restore client registration and doctor checks (#9128)
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]>
2026-11-06 18:15:25 +08:00
xin zhuang aefd903adb fix(desktop): preserve nullable SQLite row identity (#8999)
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)
2026-11-06 09:54:08 +00:00
gensokyo-dev 985b9eae96 feat(browser): import saved Playwright v10 trace evidence (#9127)
* feat(browser): import saved Playwright v10 trace evidence

* fix(browser): preserve original Playwright ZIP member names
2026-11-06 17:52:09 +08:00
Rishikesh Gowda K K 9044b6fb36 fix(kuna): mark project-dependent operations unavailable on export failure (#8748)
* fix(kuna): mark project-dependent operations unavailable on export failure

* fix(providers): preserve sibling operations in capability error copies

* fix(kuna): propagate sibling unavailability on export fallback

* fix(binary): preserve observed sibling recovery instructions across fallback observations

* fix(binary): build sibling unavailability from structured reasons without rewriting display text

* test(kuna): synchronize fallback test on settled export workspace retirement

* test(kuna): release and join export synchronization barrier across exit paths

* test(kuna): synchronize fallback test on observed export process exit

* test(kuna): gate fallback completion on settled export cache build

* test(kuna): preserve build promise chain and restore spy after teardown
2026-11-06 17:22:02 +08:00
996977edf2 feat(cli): expose caller-selected reference-source path exclusions (#2630)
* 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]>
2026-11-06 17:21:28 +08:00
김준호andmorluto f1bfa1cba2 fix(cli): honor the selected format for unknown mcp subcommands (#7802)
* 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]>
2026-11-06 17:20:56 +08:00
gensokyo-devandgensokyo-dev afc9cfe9ff perf(tests): reuse bounded CLI workers in mobile archive checks (#9116)
Co-authored-by: gensokyo-dev <[email protected]>
2026-11-06 17:09:46 +08:00
gensokyo-dev 39827ec717 docs(mcp): document verified FastMCP catalog recovery (#9113)
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)
2026-11-06 08:22:26 +00:00
gensokyo-dev 3e0599a949 fix(javascript): preserve loader identity across terminal origins (#9107)
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)
2026-11-06 07:59:03 +00:00
Hasan ZayniandClaude Sonnet 5.5 1cc979c104 feat(ghidra): admit DOS MZ targets on Windows P0 (#7144)
* 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]>
2026-11-06 15:39:03 +08:00
Xiangfang Chen fb841a9596 feat(setup): support CodeBuddy as a setup client (#2492)
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.
2026-11-06 15:38:47 +08:00
Jay-Alexander Elliot 1902fcb13d fix(cli): load Evidence record pairs from JSON files (#5129)
* fix(cli): read explicit Evidence record pairs from JSON files

* test(cli): isolate record-pair file regression

* style(cli): format record-pair file regression

* test(cli): reuse comparison fixture for record-pair file input

* style(cli): format reused record-pair test invocation

* test(cli): keep paired-input coverage within lint function limit
2026-11-06 15:38:30 +08:00
吴佳浩Alben fe9797b487 feat(python): qualify a recognized 3.14/3.15 pyc header (#8927)
* 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.
2026-11-06 15:36:22 +08:00
吴佳浩Alben 99bcd3e4e0 fix(javascript): report empty Electron coverage instead of silent zeroes (#8332)
* 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.
2026-11-06 15:15:43 +08:00
Canandeco-null 85775b635b test(ghidra): verify installation metadata budget through doctor (#6690)
* fix(ghidra): name installation metadata limit

* test(reference): return byte-preserving entry names

* fix(javascript): coalesce projected source facts

* test: repair isolation and format fixtures

* fix(evidence): retain rebound snapshot proof

Carry immutable member proof across caller and target path rebinding so low-headroom error projection reuses completed Evidence without cloning its payload.

---------

Co-authored-by: eco-null <[email protected]>
2026-11-06 15:15:23 +08:00
Canandeco-null 7ad3d7928b test(search): cover Unicode literal syntax and astral case folding (#6685)
* fix(search): fold Unicode literal matches

* test(ida): require Unicode literal case folding

* test(reference): return byte-preserving entry names

* fix(javascript): coalesce projected source facts

* test: repair isolation and format fixtures

* fix(evidence): retain rebound snapshot proof

Carry immutable member proof across caller and target path rebinding so low-headroom error projection reuses completed Evidence without cloning its payload.

---------

Co-authored-by: eco-null <[email protected]>
2026-11-06 15:15:03 +08:00
Canandeco-null 1d4e9f31c3 test(cli): cover production doctor output option ordering (#6683)
* fix(cli): retain production doctor output routing

* test(reference): return byte-preserving entry names

* fix(javascript): coalesce projected source facts

* test: repair isolation and format fixtures

* fix(evidence): retain rebound snapshot proof

Carry immutable member proof across caller and target path rebinding so low-headroom error projection reuses completed Evidence without cloning its payload.

---------

Co-authored-by: eco-null <[email protected]>
2026-11-06 15:14:43 +08:00
Jay-Alexander Elliot dc9f329764 test(ghidra): verify PSP VFPU prefix and vector decoding (#8614)
* test(psp): add source-owned VFPU prefix and vadd sequence

* test(psp): assert VFPU prefix and consumer decode facts

* test(psp): verify VFPU prefixes and consumers through public Ghidra MCP

* docs(psp): delimit VFPU prefix/consumer conformance coverage

* test(psp): preserve unknown when Ghidra omits high-pcode data

* style(psp): apply pinned oxfmt output to verify-real-ghidra-mips.mjs

* style(psp): apply pinned oxfmt output to psp-fixture.mjs
2026-11-06 15:14:24 +08:00
吴佳浩Alben 172f248884 fix(javascript): emit no fs acquisition for a rejected literal mode (#8624)
* 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.
2026-11-06 15:14:03 +08:00
gensokyo-dev 91d73a113e feat(contracts): inventory offline OpenAPI operation declarations (#9104)
* feat(contracts): inventory offline OpenAPI operation declarations

* docs(contracts): link fixture provenance outside the docs site

* fix(contracts): describe the shared API document byte allowance

* fix(api-contract): preserve safe diagnostics and completed source facts
2026-11-06 15:13:51 +08:00
RRFZ 42aaef8bdb fix(cli): require absolute ADB device paths for file pull and push (#9048)
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.
2026-11-06 15:13:43 +08:00
Luis Araya 3959b68d91 test(rust): assert non-retryable compiler-hash capacity in CLI and MCP (#5504) (#7520) 2026-11-06 15:13:24 +08:00
吴佳浩Alben 1c0a02d4a4 fix(evidence): bound malformed file field diagnostics (#8557)
* 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.
2026-11-06 14:53:25 +08:00
e69f27fba0 fix(web-capture): bound the mitmproxy headline facet index (#2876)
* 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]>
2026-11-06 14:53:14 +08:00
gongfppandgongfpp 08bc52104a fix(cli): preserve Unicode edge whitespace in Markdown tables (#8915)
* fix(cli): preserve Unicode edge whitespace in Markdown tables

* fix(cli): bound Markdown whitespace encoding to linear time

---------

Co-authored-by: gongfpp <[email protected]>
2026-11-06 14:53:03 +08:00
RRFZ 6430f395a8 fix(cli): reject non-decimal pyc and BEAM limit options (#9060)
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.
2026-11-06 14:52:53 +08:00
Christopher Pruijsen c9797c5dd5 fix(native): keep inspect_macho usable when dyld_info refuses the slice (#9034)
* 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
2026-11-06 14:52:42 +08:00
hhangandhhang d77442228d fix(desktop): align exported XML formatting whitespace (#9020)
* fix(desktop): align exported XML formatting whitespace

* test(process): align symlink comparison snapshots

* test(boundary): align inherited CLI and MCP fixtures

---------

Co-authored-by: hhang <[email protected]>
2026-11-06 14:52:32 +08:00
Hasan ZayniandClaude Sonnet 5.5 6571be631a fix(decompile): keep the output failure streak across external functions (#7924)
* 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]>
2026-11-06 14:51:47 +08:00
ShuaixinHuang f624c3b8e3 test(desktop): make Python output fixtures portable (#9066)
* 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
2026-11-06 14:41:44 +08:00
ShuaixinHuang d3a4fc87cd fix(cli): stop completion after the option terminator (#9064)
* 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
2026-11-06 14:37:29 +08:00
ShuaixinHuang 6993e4ac73 fix(managed): explain single-type member capacity exhaustion (#9065)
* test(process): align symlink fixture with filesystem checkpoints

* fix(managed): explain single-type member capacity exhaustion

* 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(managed): count selected types in capacity guidance
2026-11-06 14:37:16 +08:00
RRFZ a74ce4512c fix(cli): reject non-decimal desktop application limits (#9061)
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.
2026-11-06 14:34:07 +08:00
RRFZ 415ddb74c0 fix(cli): publish the GPU inventory digest format in the CLI schema (#9062)
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.
2026-11-06 14:33:56 +08:00
ShuaixinHuang 34b954ec8a test(cutter): synchronize peer descriptor close before cancellation (#9082)
* 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
2026-11-06 14:33:21 +08:00
ShuaixinHuang edd32e5af1 test(paths): isolate POSIX cwd fixture from the host platform (#9092) 2026-11-06 14:33:03 +08:00
ShuaixinHuang c92cbcaf28 fix(desktop): preserve significant keyfile value whitespace (#9077)
* fix(desktop): preserve significant keyfile value whitespace

* fix(desktop): preserve keyfile whitespace in exported verifier

* 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
2026-11-06 14:27:41 +08:00
Ace c7b0e0dec6 feat(harmony): disclose the unread pkgContextInfo.json declaration (#9096)
* 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.
2026-11-06 14:26:41 +08:00
hhangandhhang 8766bcef59 fix(desktop): retain UTF-16 seed content after the signature (#9098)
Co-authored-by: hhang <[email protected]>
2026-11-06 14:26:28 +08:00
highmachs 711b374d1c fix(native): retain defined global and weak ELF symbols with GNU version index 0 (#9100) 2026-11-06 14:26:00 +08:00
highmachs 15fad0efa3 fix(desktop): normalize XML attribute whitespace and line endings during seed scrubbing (#9099) 2026-11-06 14:25:26 +08:00
morlutoandCursor Agent 8580f1430c fix(desktop): launch spaced REA archives and Electron apps (#9085)
* 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]>
2026-11-06 14:07:01 +08:00
gensokyo-dev c467cf8685 fix(mcp): clarify discovery bounds and isolate catalog evals (#9101)
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.
2026-11-06 05:48:06 +00:00
gensokyo-dev 3708538e85 feat(javascript): compare static endpoints with HAR captures (#9090)
* 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
2026-11-06 13:34:26 +08:00
Edgar ArakelyanandClaude Opus 5.5 fc4c381dd7 feat(desktop): keep an agentenv computer up and drive it through REA's MCP tools (#9041)
* 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]>
2026-11-06 11:01:11 +08:00
RRFZ de8b2f3592 fix(pe): reject malformed and repeated certificate extensions (#8928)
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.
2026-11-06 10:15:05 +08:00
RRFZ b2672d8727 fix(pe): reject a SignerInfo version that does not match its sid (#8924) 2026-11-06 10:14:35 +08:00
RRFZ 4b9392e41c fix(pe): reject certificate versions and fields RFC 5280 does not allow (#8921) 2026-11-06 10:14:15 +08:00
RRFZ f1212b9e4e fix(cli): reject non-decimal native UI and command deadline options (#8920) 2026-11-06 10:13:45 +08:00
Afloat 3eff8065e7 fix(process): preserve UTF-8 at diagnostic truncation boundaries (#8907)
* fix(process): preserve UTF-8 at diagnostic truncation boundaries

* fix(process): preserve malformed UTF-8 excerpt evidence
2026-11-06 10:13:14 +08:00
Afloat a3621e5e1f fix(process): resume buffered NDJSON frames on partial chunks (#8909) 2026-11-06 10:12:43 +08:00
Afloat b153ba8ddd fix(process): honor cancellation at bounded file read completion (#8908) 2026-11-06 10:11:56 +08:00
Dragolone b28b1e0ae2 fix(adb): list devices adb reports with bare no-permissions text (#8905)
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.
2026-11-06 10:11:24 +08:00
Edgar ArakelyanandClaude Opus 5.5 c363f3e27b feat(desktop): pick seed and end by checkpoint, and keep the capture host out of bundles (#9037)
* 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]>
2026-11-06 10:01:25 +08:00
a7574d4274 fix(filesystem): settle a Windows ChangeTime before recording a baseline (#8979)
* 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]>
2026-11-06 07:57:14 +08:00
gensokyo-dev 0beedcd712 perf(mcp): add opt-in hot/cold tool discovery (#9046)
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.
2026-11-05 20:27:31 +00:00
APZNandgensokyo-dev ad518cff65 feat(artifacts): select extraction entries and output destination (#4031)
* 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]>
2026-11-06 03:28:05 +08:00
gensokyo-devandgensokyo-dev 8084916859 perf(eval): measure MCP catalog usage through the real Codex harness (#9035)
* perf(eval): measure MCP catalog usage through real Codex sessions

* fix(eval): control the active Codex code mode host

* fix(eval): reject unavailable host controls without hiding client errors

* test(eval): cover the supported default and actionable client errors

* docs(eval): record pinned Codex catalog and real package measurements

* fix(eval): pin matrix identity and retain failed trial observations

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-11-06 02:25:34 +08:00
+27 fb453f2930 fix(mcp): support retained pseudocode summaries and paging (#5046)
* 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 #7384
Fixes #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]>
2026-11-06 02:06:39 +08:00
Saim ShafiqueandSaim Shafique e5f638310c fix(cli): reject project-scoped Amp registration (#7979)
* 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.
2026-11-06 02:01:30 +08:00
Edgar ArakelyanandClaude Opus 5.5 0ac6be1a3d ci(environment): pin the real agentenv lane to agentenv-framework 0.9.1431 (#8865)
* 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]>
2026-11-06 01:53:53 +08:00
dd31958743 fix(discovery): advertise attached IDA navigation effects (#7017)
* 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]>
2026-11-06 01:47:56 +08:00
f76be69428 fix(cli): reject non-decimal byte-entropy window options (#8919)
* fix(cli): reject non-decimal byte-entropy window options

* fix(cli): admit artifact numeric options as decimals

Keyed-archive offset and limit, asset-catalog paging, and interface-builder budgets use the same decimal token check as byte-entropy windows. Defaulted fields stay numbers.

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-11-06 01:46:53 +08:00
c1ee8289c9 fix(signals): name the limiter as a possible cause of a failed sigrok version probe (#6395)
* 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]>
2026-11-06 01:46:40 +08:00
560674e2de 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]>
2026-11-06 01:07:00 +08:00
morluto ec3410998b fix(ci): align tests with current evidence contracts 2026-11-06 00:31:09 +08:00
Kaoru0822andClaude Opus 5.5 fe677f6cf9 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]>
2026-11-05 18:38:12 +08:00
Mohamed Ayoub Eleuch 845eb54d2b 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
2026-11-05 18:37:20 +08:00
morlutoandCursor Agent 56be61191e 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]>
2026-11-05 18:35:22 +08:00
Beqa 3395c9e6b3 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
2026-11-05 18:34:28 +08:00
Jiabin Yin da79ea0baa fix(il2cpp): reject module image references outside retained inventory (#8956) 2026-11-05 18:33:36 +08:00
Beqa 4362a0848e 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
2026-11-05 18:32:43 +08:00
highmachs 7b6fc6f13d 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
2026-11-05 18:31:52 +08:00
highmachs 281dbc8514 fix(apple): preserve exact integer precision with post-table plist objects (#8910) 2026-11-05 18:30:59 +08:00
027c768f07 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]>
2026-11-05 18:30:07 +08:00
amyanger a9febababc 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
2026-11-05 18:29:14 +08:00
akushonkamenandakushonkamen 0714eba505 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]>
2026-11-05 18:28:22 +08:00
NoThingandSarthak Pandey fbfb1b768c 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]>
2026-11-05 18:27:29 +08:00
Beqa 08a923f722 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.
2026-11-05 17:52:25 +08:00
xin zhuangandKaoru0822 c7fce66035 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]>
2026-11-05 17:38:14 +08:00
morluto 0aaa582977 test(ghidra): check retained historical snapshot evidence (#8381)
* test(ghidra): check retained historical snapshot evidence

* test(ghidra): select historical observations by address
2026-11-05 17:37:29 +08:00
morluto 15107aa81b test(ghidra): check structured snapshot profile mismatch (#8536) 2026-11-05 17:36:46 +08:00
morluto 2be2d006b8 test(ghidra): check regex recovery at its owning boundary (#8578) 2026-11-05 17:36:02 +08:00
morluto ab9f7235a3 fix(evidence): clarify unsupported view recovery (#8412)
* fix(evidence): clarify unsupported view recovery

* fix(evidence): explain sensitive capture view refusal
2026-11-05 17:35:19 +08:00
morluto b8963c1808 fix(mcp): clarify retained dossier handoff in crash prompt (#8437) 2026-11-05 17:34:33 +08:00
morluto 9eff4f7265 fix(verification): use inspected Ghidra JDK (#8440) 2026-11-05 17:33:48 +08:00
morluto 6f3f06a891 fix(evidence): traverse large source link sets safely (#8457) 2026-11-05 17:33:04 +08:00
Rudy Mizrahi Celekli 7504f2ddfc fix(native): reject nonzero reserved ELF dynamic symbols (#7528)
fix(native): validate the reserved ELF dynamic symbol

Signed-off-by: Rudy Celekli <[email protected]>
2026-11-05 17:32:20 +08:00
Rudy Mizrahi Celekli 21ad5b1f67 fix(pe): classify valid DOS targets as unsupported (#7530)
fix(pe): classify confirmed DOS targets as unsupported

Signed-off-by: Rudy Celekli <[email protected]>
2026-11-05 17:31:35 +08:00
ShuaixinHuang 6fceda83d5 fix(devicetree): reject duplicate node paths in Evidence (#8970) 2026-11-05 17:30:50 +08:00
ShuaixinHuang 1dc83b160d 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
2026-11-05 17:30:06 +08:00
morluto 65986d0a0f docs(tool-design): audit agent workflow guidance (#8439)
* docs(tool-design): audit agent workflow guidance

* docs(tool-design): check deployed guidance versions
2026-11-05 17:28:48 +08:00
morluto 146064c2e9 fix(enhanced): restore batch input record limit 2026-11-05 17:20:05 +08:00
ShuaixinHuang 3484432fb1 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
2026-11-05 17:14:19 +08:00
Martin Klein d854fb155a 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
2026-11-05 17:05:29 +08:00
Wade 9317c7d422 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.
2026-11-05 17:05:12 +08:00
2SinceJuly fc20aebb94 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.
2026-11-05 17:04:55 +08:00
RRFZ 1f14d648fa 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
2026-11-05 17:04:37 +08:00
Lucien 585fe56712 fix(enhanced): bound batch decompile input size (#5937) 2026-11-05 16:57:52 +08:00
Salman AlmutairiandClaude Opus 5.5 fc3ff7b833 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]>
2026-11-05 16:57:34 +08:00
Salman AlmutairiandClaude Opus 5.5 25923d372c 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]>
2026-11-05 16:57:16 +08:00
Salman AlmutairiandClaude Opus 5.5 e1094313f5 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 #7384
Fixes #7383

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-05 16:56:58 +08:00
Hasan ZayniandClaude Opus 5.5 c1df51c7cb 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]>
2026-11-05 16:56:12 +08:00
ShuaixinHuang a2134b3a74 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
2026-11-05 16:52:19 +08:00
ShuaixinHuang ce07d358cd test(gdb): expect configurable stream truncation diagnostics (#8965)
* test(gdb): expect configurable stream truncation diagnostics

* build(gdb): include prerequisite duplicate-import repair from #8884
2026-11-05 16:49:48 +08:00
김준호andt 92f332652d 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>
2026-11-05 15:40:32 +08:00
Adolanium 0686ea60f6 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
2026-11-05 15:39:46 +08:00
Paritosh LahreandClaude Opus 5.5 5fd325066d 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]>
2026-11-05 15:39:03 +08:00
hhangandhhang 8e3d794834 fix(gpu): distinguish ELF string scan limits from corruption (#8955)
Co-authored-by: hhang <[email protected]>
2026-11-05 15:38:19 +08:00
hhangandhhang 0dcb456bbe 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]>
2026-11-05 15:37:35 +08:00
highmachs 66d55c3bbb fix(native): declare temporary filesystem writes for inspect_macho (#8959) 2026-11-05 15:36:51 +08:00
ShuaixinHuang 347dc9a32c docs(mcp): explain PowerShell launch environment scope (#8961)
* docs(mcp): explain PowerShell launch environment scope

* docs(mcp): qualify saved setup selections and OpenCode environment
2026-11-05 15:36:07 +08:00
ShuaixinHuang 455d7953ec docs(testing): explain npm launcher selection in PowerShell (#8962) 2026-11-05 15:35:20 +08:00
ShuaixinHuang f6f565093f 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
2026-11-05 15:34:34 +08:00
ShuaixinHuang 4bd58d577c fix(contracts): align managed example MethodDef scope (#8966) 2026-11-05 15:33:49 +08:00
ShuaixinHuang 97fe4535f6 fix(electron): reject non-directory scenario roots before launch (#8967) 2026-11-05 15:33:04 +08:00
Edgar ArakelyanandClaude Opus 5.5 970969cb53 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]>
2026-11-05 15:14:04 +08:00
morlutoandCursor Agent 71f680d7c1 feat(mcp): page retained instruction references (#8594)
* feat(mcp): page retained instruction references

* test(mcp): verify oversized instruction reference recovery

* test(mcp): follow instruction reference continuations

* test(mcp): validate continuation source kind

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:30:52 +08:00
morlutoandCursor Agent 1951cdc980 fix(mcp): expose bounded parameter type view (#8782)
* fix(mcp): expose bounded parameter type view

* fix(mcp): qualify value-flow view availability

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:26:19 +08:00
morlutoandCursor Agent 9ef70696dd fix(ghidra): expose bit-field base type identity (#8602)
* fix(ghidra): expose bit-field base type identity

* test(ghidra): verify bit-field type handoff

* test(ghidra): verify big-endian bit-field handoff

* fix(ghidra): bind bit-field facts to snapshot profile

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:03:43 +08:00
morlutoandCursor Agent f1819f5cb2 fix(test): load real Frida verifier address dependency (#8596)
Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:02:09 +08:00
morlutoandCursor Agent 2b740cab36 fix(native): preserve function byte source qualifications (#8606)
* fix(native): preserve function byte source qualifications

* fix(native): recover oversized inline matching sources

* test(native): verify matching byte source recovery

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:01:16 +08:00
morlutoandCursor Agent 4ec2726b1b fix(kuna): clarify failed export record availability (#8789)
* fix(kuna): clarify failed export record availability

* test(kuna): cover failed export cleanup retry

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 04:00:22 +08:00
morlutoandCursor Agent 982e750a70 fix(ghidra): bind existing MIPS projects to target metadata (#8605)
Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 03:35:21 +08:00
morlutoandCursor Agent 4afb6b79ae fix(mcp): guide retained evidence input recovery (#8761)
* 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]>
2026-11-05 03:34:26 +08:00
morlutoandCursor Agent 93295c3df3 fix(mcp): report selection after failed target opens (#8726)
* 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]>
2026-11-05 03:34:03 +08:00
morlutoandCursor Agent f1622fc8b4 feat(native): page retained feature traces (#8585)
* 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]>
2026-11-05 03:26:31 +08:00
morlutoandCursor Agent 1ff5a655bc fix(native)!: preserve value-flow absence semantics (#8785)
* 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]>
2026-11-05 02:32:15 +08:00
morlutoandCursor Agent dd4b83c220 fix(ghidra)!: search adjacent initialized memory blocks (#8840)
* fix(ghidra)!: search adjacent initialized memory blocks

BREAKING CHANGE: Ghidra analysis snapshots from earlier byte-search policies are refused; reopen the target and create a new snapshot. Historical Evidence remains importable.

* test(ghidra): supervise cross-block fixture process

* docs(ghidra): clarify contiguous search range

* test(ghidra): verify search metadata and cleanup

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 02:31:32 +08:00
morluto d896ab7bd7 fix(mcp)!: distinguish unavailable batch pseudocode (#8737)
* fix(mcp)!: distinguish unavailable batch pseudocode

* test(mcp): preserve historical batch error details

* test(mcp): retain unavailable items in batch recovery

* test: update Ghidra batch unavailable expectation
2026-11-05 01:51:38 +08:00
morluto 30228c9a5f fix!: guide snapshot inputs to the restore workflow (#8867)
BREAKING CHANGE: Snapshot-shaped bundle import failures use invalid_request rather than evidence_integrity_mismatch.
2026-11-05 01:50:53 +08:00
morluto 6df2ac1d7e fix(evidence): report partial workflow question publication (#8848)
* fix(evidence): report partial workflow question publication

* fix: project nested recovery for caller lifetime
2026-11-05 01:49:36 +08:00
morluto b97023809f feat(native): retain decompile export source facts (#8805)
* feat(native): retain decompile export source facts

* test(native): verify historical export source facts

* test(native): retain source after export conflict
2026-11-05 01:48:53 +08:00
morluto 5a1f75461a fix(mcp): guide address context recovery (#8766)
* fix(mcp): guide function comment recovery from address context

* fix(mcp): retain specific comment refusal recovery

* docs(mcp): advertise paged function comments

* fix(mcp): preserve address facet recovery

* test(mcp): verify session-specific facet recovery

* test(mcp): extract address recovery fixture

* fix(mcp): direct provider recovery to complete status

* fix(mcp): qualify address recovery across providers

* fix(mcp): make coordinate recheck actionable

* test(mcp): correct address recovery fixtures

* test(ghidra): verify address comment recovery
2026-11-05 01:48:08 +08:00
morluto 5b82a253dc fix(evidence): link snapshot replays to original observations (#8734)
* fix(evidence): link snapshot replays to original observations

* style(tests): format snapshot replay assertions

* test(evidence): cover replay parent metadata refresh

* test(evidence): surface snapshot replay failures

* test(mcp): check replay source after cleanup recovery
2026-11-05 01:47:24 +08:00
morluto 1a9aa1296b fix(kuna): recover overflow when project can answer (#8747)
* fix(kuna): recover overflow when project can answer

* fix(kuna): provide actionable CLI overflow recovery

* test(kuna): cover shared overflow cleanup

* test(kuna): cover cancellation during overflow cleanup

* fix(kuna): make overflow recovery usable after handoff
2026-11-05 01:46:30 +08:00
morluto 9196c30f6e fix(ghidra): guide packed program import recovery (#8820)
* fix(ghidra): guide packed program import recovery

* test(ghidra): verify packed import recovery facts
2026-11-05 01:45:22 +08:00
morluto fdfc210879 fix(native): guide unreadable journal recovery (#8809) 2026-11-05 01:44:34 +08:00
morluto eac28b2047 fix(xrefs): guide symbol lookup recovery (#8753)
* fix(xrefs): guide ambiguous name selection

* fix(xrefs): guide missing symbol lookup

* fix(xrefs): name available symbol recovery paths

* fix(xrefs): qualify exported address coordinates

* fix(xrefs): point to procedure address lookup

* fix(xrefs): keep CLI recovery focused

* test(xrefs): cover CLI ambiguity recovery
2026-11-05 01:43:45 +08:00
morluto 9a79ee8e9e fix(kuna): clarify single-function fallback evidence (#8742)
* fix(kuna): describe fallback evidence without guessing project state

* fix(kuna): qualify export capacity guidance by source
2026-11-05 01:43:00 +08:00
morluto 51e9027d58 fix(session): preserve availability recovery guidance (#8764) 2026-11-05 01:42:16 +08:00
morluto 78d7bf3ea0 docs(native): clarify memory and file byte sources (#8829)
* docs(native): clarify memory and file byte sources

* docs(native): qualify Ghidra fixup coverage
2026-11-05 01:41:39 +08:00
morluto 9031adf0b0 fix(ci): own source-map captures and honor browser startup budget 2026-11-05 01:38:33 +08:00
RRFZ a1660bc016 fix(pe): reject certificates whose signature algorithms disagree (#8922)
* fix(pe): reject certificates whose signature algorithms disagree

* test(pe): keep the signature algorithm case apart from the version case
2026-11-05 01:18:40 +08:00
Entrpiandmorluto 0a99c2a580 fix(artifacts): give Godot PCK and Node SEA readers the ESP member lifecycle (#8395)
* 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]>
2026-11-05 00:54:53 +08:00
5990ff0324 feat(artifacts): read WeChat mini-program wxapkg containers (#8148)
* 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]>
2026-11-05 00:52:20 +08:00
morlutoandCursor Agent 63b66fa498 fix(search): clarify regex diagnostics and recovery (#8776)
* fix(ghidra): identify invalid regex patterns

* docs(native): clarify regex dialects in search contracts

* fix(ghidra): guide regex syntax recovery

* fix(search): guide regex worker recovery

* fix(search): preserve regex parser diagnostics

* test(search): update regex boundary expectations

* refactor(search): remove unused regex error hook

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-11-05 00:20:01 +08:00
morlutoandCursor Agent 52bca6de41 fix(native): retain value trace source evidence (#8384)
* 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]>
2026-11-05 00:19:50 +08:00
ShuaixinHuang 25b62db838 test(firmware): cover S-record high address workflows (#8897) 2026-11-05 00:07:29 +08:00
김준호 30a11af923 fix(browser)!: keep the Chromium sandbox for REA-launched browsers (#8738)
* 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
2026-11-04 23:54:29 +08:00
星闌 b1b6cb64c2 fix(analysis-view)!: distinguish unavailable native collections (#8291) 2026-11-04 23:53:45 +08:00
Entrpi c13dd36d95 fix(artifacts): decide how compare_artifacts compares every format_metadata kind (#8231)
* 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.
2026-11-04 23:53:01 +08:00
morlutoandCursor Agent 89317a39fa fix(javascript): remove duplicate childNodes import (#8886)
* 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]>
2026-11-04 20:43:04 +08:00
morluto 2d16a40a49 fix(ghidra): report selected project identity failures (#8379)
* fix(ghidra): report selected project identity failures

* fix(ghidra): retain refusal until client shutdown
2026-11-04 19:58:50 +08:00
Dhruvilkumar Ashokbhai Chodvadiyaanddhruvac29 85be0f70a6 fix(artifacts): guide retries for symlinked files (#8725)
* fix(go): suggest resolved path for rejected symlinks

* test(go): cover unresolved symlink recovery paths

* fix(go): omit schema recovery for resolved symlinks

* fix(artifacts): share resolved-path recovery across inspectors

* fix(sqlite): retain resolved database path through snapshot reads

* fix(captures): retain symlink retry guidance

* fix(artifacts): preserve retry hints through redaction and selected reads

---------

Co-authored-by: dhruvac29 <[email protected]>
2026-11-04 19:58:06 +08:00
Ha1baraA1 bd7002b441 fix(cli): export source Evidence for native value traces (#8297)
* 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
2026-11-04 19:56:50 +08:00
064bb3f713 fix(firmware): reject staged files cut off at the per-file limit (#4430)
* 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]>
2026-11-04 19:56:05 +08:00
ShuaixinHuang 33e0bf101a fix(adb): admit completed path probes after daemon startup (#8858)
* fix(adb): admit completed path probes after daemon startup

* test(adb): preserve startup probe and digest evidence
2026-11-04 19:55:13 +08:00
Wenyu Chiou e416ce1983 fix(cli): preserve Markdown validation error cells (#7978)
fix(cli): preserve markdown validation error cells
2026-11-04 19:54:29 +08:00
김준호 61bb02d3df fix(artifacts): keep inventorying an ASAR whose member declares an invalid size (#8719)
* 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
2026-11-04 19:53:45 +08:00
JANG BYUNGKUNandgensokyo-dev abc647bf6c feat(desktop): suggest managed members for Unity script assemblies (#5511)
* 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]>
2026-11-04 19:53:00 +08:00
Christopher Pruijsen 398eb0d1f1 feat(setup): register LM Studio during setup (#8611)
* 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
2026-11-04 19:52:16 +08:00
morluto 103684bce1 fix(ghidra): guide recovery from unresolved procedure selectors (#8470)
* fix(ghidra): guide recovery from missing procedures

* fix(ghidra): preserve mapped offset lookup recovery

* fix(ghidra): distinguish unmapped procedure offsets

* fix(ghidra): guide ambiguous selector recovery

* fix(ghidra): clarify fresh import excludes project edits
2026-11-04 19:51:27 +08:00
hhangandhhang ebea0f009f fix(snapshot): retain one-shot Ghidra pseudocode results (#8089)
* fix(snapshot): retain one-shot Ghidra pseudocode results

* test(snapshot): cover evidence-only pseudocode migration

---------

Co-authored-by: hhang <[email protected]>
2026-11-04 19:50:39 +08:00
Akshad Jogi 4d2aff2ec6 fix(mcp): report session tool target requirements accurately (#7340)
* 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.
2026-11-04 19:49:40 +08:00
Mohamed Ayoub Eleuch 09c889b3b5 test(javascript): cover Request endpoint identity and provenance (#6365)
fix(javascript): resolve inline Request URLs in fetch calls
2026-11-04 19:48:56 +08:00
Abel YagubyanandAbelo9996 1dc9e8774d fix(cli): keep marker-only Markdown paragraph facts literal (#7813)
Fixes #7634

Co-authored-by: Abelo9996 <[email protected]>
2026-11-04 19:48:12 +08:00
Beqa 7570aabd9d docs(skill): route SPA passive capture through allowed_origins (#7252)
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.
2026-11-04 19:47:28 +08:00
Lewis WangandClaude Opus 5.5 b9138a1261 fix(native): select versioned arm64e slices for codesign by family name (#8713)
* 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]>
2026-11-04 19:45:46 +08:00
吴佳浩Alben 54d819f382 fix(javascript): emit no timer for a literal non-function callback (#8044)
* 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.
2026-11-04 19:45:02 +08:00
Ace 848f165417 feat(harmony): disclose unclassified inventory files (#4968)
* 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.
2026-11-04 19:44:18 +08:00
吴佳浩Alben b668cea2af fix(javascript): emit no fs acquisition for a literal non-flag string (#8125)
* 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.
2026-11-04 19:43:33 +08:00
ShuaixinHuang 65311d4992 fix(process): preserve literal temporary-root absence text (#8859) 2026-11-04 19:42:49 +08:00
김준호 066a23475c fix(artifacts): classify bundle executables by their own name (#7365)
* 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.
2026-11-04 19:42:04 +08:00
Beqa 2f5c0155dc fix(web): omit importer integrity for the trusted resolver stub (#8693)
* 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
2026-11-04 19:41:11 +08:00
Beqa 54146627f0 fix(cli): emit compact JSON validation errors for demangle-swift (#7622)
* 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
2026-11-04 19:40:27 +08:00
吴佳浩Alben f285fd4f82 fix(javascript): emit no child process for a literal non-object options slot (#8019)
* 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.
2026-11-04 19:39:43 +08:00
Wenyu Chiou 9cc34178c0 test(native): bind lipo refusal fixture to a real source (#7977) 2026-11-04 19:38:59 +08:00
STDquantum 2ae7878fc7 fix(native): retain all breakpoint locations within budget (#7975)
* fix(native): retain all breakpoint locations within budget

* fix(native): describe incomplete locations accurately

* test(native): cover incomplete location warning

* test(native): bind lipo failure to a real fixture

* test(native): use valid Mach-O fixture for lipo errors

* test(native): drop fixture change already in main

* test(native): share breakpoint limitation wording with formatter

* test(native): assert limitation delivery via owner constants

* style(native): format breakpoint limitation projection
2026-11-04 19:38:15 +08:00
STDquantum 094ec5eb3f fix(cli): reject missing mcp add option values (#7996)
* fix(cli): reject mcp add options missing their values (#7609)

* fix(cli): reject mcp add values swallowed by the next flag (#7609)

* fix(cli): reject empty mcp add -c value (#7609)

* test(cli): verify rejected mcp add preserves settings

* chore: keep main formatting out of merge diff
2026-11-04 19:37:31 +08:00
吴佳浩Alben 3640f01512 fix(javascript): emit no promise for a literal non-callable executor (#8264)
* 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.
2026-11-04 19:36:45 +08:00
Wenyu Chiou 6aeaa04e54 fix(javascript): reuse shared AST child traversal (#7967) 2026-11-04 19:36:00 +08:00
吴佳浩Alben d5ba7cec60 fix(javascript): link a response body reader only to a settled fetch (#8028)
* 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
2026-11-04 19:35:16 +08:00
RRFZ a97f827469 fix(pe): reject malformed certificate validity times (#8832) 2026-11-04 19:34:31 +08:00
Machen John 39172ca2e1 fix(ghidra): keep a changed existing project's refusal typed through launch (#8273)
* 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.
2026-11-04 19:33:29 +08:00
2SinceJuly 1dd70f6c2b fix(cli): seal in-process Evidence before publishing (#8468)
* 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.
2026-11-04 19:32:44 +08:00
Rohit Ghumare f042aaaf90 fix(gdb): keep sessions usable after an oversized stream record (#8029)
* 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
2026-11-04 19:32:00 +08:00
Dragolone 1f8084c7db fix(node-sea): bound ELF note scans by the notes a carrier holds (#8031)
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.
2026-11-04 19:31:14 +08:00
Adolanium 1861059f8b fix(wasm): name the compare_wasm_interfaces side of rejected Evidence (#7302)
* 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
2026-11-04 19:30:31 +08:00
highmachs d0d31fcf21 fix(browser): accept 3-digit HTTP response status codes up to 999 (#8075) 2026-11-04 19:29:44 +08:00
Dragolone d857b61bc5 fix(android): ignore action-less filters in activity export defaults (#8030)
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.
2026-11-04 19:28:59 +08:00
morluto 810cebcba0 fix(ghidra): explain read-only project edit refusals (#8413)
* fix(ghidra): explain read-only project edit refusals

* test: follow provider guidance ownership
2026-11-04 19:28:15 +08:00
Tawhidul IslamandTawhidul Islam 703c11c90f fix(javascript): recover configuration file reads from fs/promises (#8446)
* fix(javascript): recover configuration file reads from fs/promises

* fix(javascript): disallow nonexistent readFileSync on fs/promises

---------

Co-authored-by: Tawhidul Islam <[email protected]>
2026-11-04 19:23:52 +08:00
Beqa 4910e149ad docs(setup): describe FreeBuff as published setup support (#7246)
Setup against npm latest already accepts --client freebuff. Replace the
stale proposed-integration wording while keeping the unverified native
client loading caveat.
2026-11-04 19:22:36 +08:00
Christopher Pruijsen 5b953e13c4 fix(mcp): keep detail guidance in compact input schemas (#8034)
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.
2026-11-04 19:14:52 +08:00
Beqa a56cc93cab test(desktop): cover ENOTDIR intermediate path refusal (#8692)
* test(desktop): cover ENOTDIR intermediate path refusal

Lock the desktop consumer projection for selections whose intermediate
component is a regular file, matching inspect-artifact's unavailable-target
refusal after directory source spelling validation.

Fixes #7424

* style: oxfmt ArtifactSourceResolution test
2026-11-04 19:13:53 +08:00
吴佳浩Alben 007e181f68 fix(javascript): require exactly one fork IPC channel (#8017)
* 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
2026-11-04 19:11:35 +08:00
hhangandhhang f8ec48095a fix(cli): guide delimited analysis view exports (#8730)
Co-authored-by: hhang <[email protected]>
2026-11-04 19:10:35 +08:00
Entrpiandrobotaro 1df7ac9320 fix(ghidra): update the host no-return probe for the current bridge (#8462)
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]>
2026-11-04 19:09:33 +08:00
RRFZ 52ff3123cb docs(mcp): list every tool that omits the untrusted-content marker (#8837) 2026-11-04 19:04:57 +08:00
김준호 67552d56cc docs(limits): say setup forwards the selected MCP response budget (#8342)
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
2026-11-04 19:03:56 +08:00
Vinícius Andrade 6ba69cdcc1 docs(cli): document standard input for JSON request arguments (#7139)
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
2026-11-04 19:02:54 +08:00
SathishM 578e4bb71a docs(installation): list every engine accepted by doctor --provider (#4767)
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.
2026-11-04 19:02:32 +08:00
Abduallah Jamal 47cedcc93a fix(cli): reject empty artifact command targets before resolution (#8834) 2026-11-04 16:47:46 +08:00
RRFZ 6c3c5ebc69 fix(rust): state inflate limits on the consumed zlib stream (#8835) 2026-11-04 16:47:31 +08:00
RRFZ 772c1bc401 fix(pe): reject empty and non-minimal certificate serial INTEGERs (#8836) 2026-11-04 16:45:48 +08:00
ShuaixinHuang 1998e65705 fix(ghidra): discover installed versioned Homebrew JDKs (#8853)
* fix(ghidra): discover installed versioned Homebrew JDKs

* test(ghidra): protect Homebrew Java candidate precedence

* fix(ghidra): continue automatic JDK discovery after a failed keg
2026-11-04 16:10:46 +08:00
ShuaixinHuang 650a459346 fix(cli): share boolean timing argument handling (#8857)
* fix(cli): share boolean timing argument handling

* fix(cli): preserve executable launcher mode
2026-11-04 16:10:30 +08:00
morluto 196f0afbbc fix(cli): retain sources across incomplete finalization 2026-11-04 14:37:44 +08:00
morluto 3368a036ae test(ghidra): verify saved project evidence bundles 2026-11-04 14:34:19 +08:00
morluto 2e1e99283e fix(cli): preserve partially captured source closure 2026-11-04 14:26:17 +08:00
morluto 2e8d463400 fix(cli): retain function evidence sources in bundles 2026-11-04 14:24:09 +08:00
morluto e62484931c fix: bound and share source-closure checks 2026-11-04 14:17:15 +08:00
morluto 89d5bb7056 fix: revalidate restored snapshot sources lazily 2026-11-04 14:06:59 +08:00
morluto 75c7187290 fix: preserve source closure during snapshot replay 2026-11-04 13:55:45 +08:00
Pierre d7df9df339 fix(ghidra): mark empty xrefs results as unresolved, not unreferenced (#8831)
* 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.
2026-11-04 11:58:44 +08:00
morluto 5a11bf09a5 fix(mcp): surface byte search for encoded literals (#8817) 2026-11-04 11:53:38 +08:00
morluto f8f9095d2d feat(kuna): verify release 1.990 (#8813) 2026-11-04 11:49:36 +08:00
Rishikesh Gowda K K e75d1c741c feat(elf): decode Linux core dump process and memory mapping notes (#8830)
* 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
2026-11-04 11:46:11 +08:00
morluto c3647ade1e fix(objdump)!: preserve unknown executable identity (#8824)
* fix(objdump)!: preserve unknown executable identity

* fix(objdump): avoid claiming rejected architecture output

* fix(objdump): align launch errors with command identity

* test(objdump): avoid coupling identity checks to wording

* test(objdump): align launch failure expectations
2026-11-04 02:01:44 +08:00
morluto 6be6596aac test(mcp): bound schema compilation for native and web workflows 2026-11-04 00:14:47 +08:00
morluto f3e5b33538 fix(artifacts): settle transient DMG verification within its deadline 2026-11-04 00:14:46 +08:00
morluto 26b033d51f fix(process): classify withheld Darwin environments before parser failures 2026-11-03 23:48:22 +08:00
ShuaixinHuang 9a68ce57a0 test(elf): cover big-endian ABI notes through CLI and MCP (#8803) 2026-11-03 23:25:44 +08:00
highmachs 276d99e787 fix(plist): decode OpenStep quoted string unicode and octal escape sequences (#8794) 2026-11-03 23:12:06 +08:00
ShuaixinHuang 19082acddb test(electron): consolidate handler presence coverage in public workflows 2026-11-03 23:11:45 +08:00
ShuaixinHuang e8cb71740b test(electron): remove duplicate graph protocol fixture 2026-11-03 23:11:45 +08:00
ShuaixinHuang c28d47bf13 fix(electron): retain wrapped protocol handler registrations 2026-11-03 23:11:45 +08:00
Cursor Agent 8c857ce56c docs(readme): move Discord button below site links
Place the Join us on Discord button under each hero's website, guides,
and showcases line. The invite and alt text stay the same.
2026-11-03 16:56:44 +08:00
morluto 28bffe92c8 docs(readme): move Discord button below site links (#8755) 2026-11-03 16:51:45 +08:00
morluto 4f7c4cc218 docs(readme): add Join us on Discord button (#8754) 2026-11-03 16:45:28 +08:00
Liu Jie 4b961e3715 fix(cli): protect Amp settings during default registration (#8076) 2026-11-03 14:50:33 +08:00
Paritosh LahreandClaude Opus 5.5 8db538a7ca fix(browser): keep negative script columns Chrome reports (#8684)
* 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]>
2026-11-03 14:50:00 +08:00
Wenyu Chiou e01fcfb4b2 fix(reference): Exclude Git pointer files from source identities (#7667)
* 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
2026-11-03 14:49:28 +08:00
Wenyu Chiou f5cfe09c07 fix(environment): disclose truncated comparison differences (#7673)
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
2026-11-03 14:48:54 +08:00
Beqaandmorluto f59d884f35 fix(flutter): read unlabeled Dart Version::String() from engines (#7232)
* 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]>
2026-11-03 14:48:21 +08:00
ShuaixinHuang 4f4e2552cc fix(verification): compare retained Ghidra function evidence (#8672)
* fix(verification): compare retained Ghidra function evidence

* test(verification): require the exact missing Evidence refusal
2026-11-03 14:47:49 +08:00
Ha1baraA1 b3e22620a5 fix(mcp): keep scoped doctor output parseable (#7648) 2026-11-03 14:47:16 +08:00
Hasan Zayni 53fca98e3a fix(setup): omit manual connectors from automatic selection (#8009)
Retain detected manual connectors and explicit selection while excluding unsupported formats from automatic setup readiness. Fixes #7522.
2026-11-03 14:46:43 +08:00
Jeremy Levartovsky 5ed283d168 fix(har): accept Chrome DevTools bracketed IPv6 server addresses (#7225)
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.
2026-11-03 14:46:10 +08:00
Ha1baraA1 f251009e0a fix(artifacts): report required bytes for ASAR header limits (#7452) 2026-11-03 14:45:36 +08:00
Beqa d892e7861a fix(docs): drop duplicate native-inspection guide row (#7480)
Remove the incomplete Choose a guide duplicate that omitted kernel-modules.md so the docs index keeps one complete native-inspection entry.

Fixes #7405
2026-11-03 14:45:03 +08:00
morluto 9ebbe67e05 fix(ci): await website worker tests and format Wrangler config 2026-11-03 12:04:33 +08:00
Lewis WangandClaude Fable 5.1 b912ec184c fix(javascript): accept retained empty endpoint seeds (#6462)
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]>
2026-11-03 10:10:33 +08:00
morluto 193e01abb1 fix(kuna): keep timeout recovery from changing workers (#8687)
* fix(kuna): keep timeout recovery from changing workers

* test(kuna): verify timeout recovery with real provider
2026-11-03 10:10:11 +08:00
morluto d496d803a4 fix(kuna): give actionable lookup recovery (#8656)
* fix(kuna): give actionable lookup recovery

* fix(kuna): keep lookup advice operation neutral

* style(kuna): apply repository formatter
2026-11-03 09:50:10 +08:00
morluto 6259ac41a4 fix(mcp): guide target recovery for scoped tools (#8659)
* fix(mcp): guide target recovery for scoped tools

* test(mcp): cover unscoped target recovery

* fix(mcp): warn before scoped session restart

* refactor(mcp): own scoped target recovery guidance

* fix(mcp): include unknown history in restart guidance
2026-11-03 09:49:49 +08:00
morluto 79766ca96a fix(website): identify all public deployment verification requests 2026-11-03 09:41:49 +08:00
ShuaixinHuang 22c493ad5c fix(native): preserve implementation file locations (#8667)
fix(native): keep UI handler offsets distinct from method records
2026-11-03 09:38:12 +08:00
ShuaixinHuang b7911b2d30 fix(evm): explain limiter configuration and access failures (#8669) 2026-11-03 09:38:01 +08:00
JANG BYUNGKUN 6442ea02cb feat(binary-layout): admit ELF64 AArch64 and name the refused host (#8711)
* 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.
2026-11-03 09:37:41 +08:00
morluto eef8f44003 feat(website): publish agent discovery and Markdown responses 2026-11-03 09:33:41 +08:00
ShuaixinHuang 510e83eadc fix(firmware): recommend the operation-specific engine (#8668) 2026-11-03 09:17:33 +08:00
2d11fd92d2 fix(javascript): reconcile Node targets with entry assets (#8670)
* fix(javascript): reconcile Node targets with entry assets

* test(inspector): read reconciled evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(inspector): read the workflow evidence payload

* Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* test(javascript): restore Evidence workflow result access

---------

Co-authored-by: morluto <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-11-03 08:54:39 +08:00
morluto 9bea563e30 fix(kuna): qualify parallel project pseudocode (#8694)
* fix(kuna): qualify parallel project pseudocode

* test(kuna): cover parallel caveats through MCP
2026-11-03 03:30:19 +08:00
morluto e749160ab0 fix(mcp): qualify string byte-read guidance (#8704)
* fix(mcp): qualify string byte-read guidance

* fix(mcp): avoid implying complete string references
2026-11-03 03:29:59 +08:00
morluto 920d634e83 ci: exempt designated authors from readiness declaration 2026-11-03 02:18:55 +08:00
morluto 55078b9f57 feat(mcp): page retained incoming reference addresses (#8591) 2026-11-03 02:13:21 +08:00
morluto 75c3cab540 fix(kuna)!: separate verification policy from analysis identity (#8699)
* feat(kuna): verify release 1.989

* fix(kuna)!: separate verification policy from analysis identity

* docs(kuna): show historical evidence recovery

* test(kuna): cover verification warnings through MCP
2026-11-03 01:34:40 +08:00
morluto 3c05b4d3fe fix(ci): remove undeclared dependency and handle PTY chunks 2026-11-03 01:10:56 +08:00
morluto 2cb29693c3 fix(ci): preserve release proposal overflow notes 2026-11-03 00:21:16 +08:00
Kaoru0822andClaude Opus 5.5 26c3e9e413 fix(native): select off-screen windows so the UI lane can complete (#8654)
* 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]>
2026-11-02 23:57:32 +08:00
Kaoru0822andClaude Opus 5.5 144843ccc2 fix(debugger): let REA exit when a debugger could not be stopped (#8652)
* 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]>
2026-11-02 05:16:34 -08:00
Kaoru0822andClaude Opus 5.5 4a01211f11 fix(javascript): keep static facts after semantic results use the parent budget (#8648)
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]>
2026-11-02 05:16:16 -08:00
Kaoru0822andClaude Opus 5.5 6a0ccf7eeb fix(doctor): report provider settings a client registration does not record (#8645)
* 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]>
2026-11-02 05:14:50 -08:00
Kaoru0822andClaude Opus 5.5 710fad8acf feat(evidence): discover missing Evidence links without transferring records (#8633)
* 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]>
2026-11-02 05:14:34 -08:00
Kaoru0822andClaude Opus 5.5 93b8b82db7 feat(native): deliver batch decompile as a retained summary (#8631)
* 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]>
2026-11-02 00:50:42 -08:00
Kaoru0822andClaude Opus 5.5 3f4aff2f22 fix(cli): keep captured source Evidence when one-shot delivery never commits (#8632)
* 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]>
2026-11-02 00:50:19 -08:00
highmachs 8b89eee767 fix(native): retain ELF dynamic symbol visibility in export evidence and views (#8617)
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
2026-11-02 16:47:37 +08:00
吴佳浩Alben 59f5502a40 fix(javascript): emit no event operation for a literal non-function listener (#8621)
* 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.
2026-11-02 16:47:22 +08:00
Kaoru0822andClaude Opus 5.5 2ac6d3efe2 fix(process): re-read unreadable tokens before signaling and name the process (#8553)
* 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]>
2026-11-02 00:21:23 -08:00
Kaoru0822andClaude Opus 5.5 365c3245e8 fix(setup): refuse Hopper on macOS below the release's minimum (#8548)
* 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]>
2026-11-02 00:05:30 -08:00
Kaoru0822andClaude Opus 5.5 aba56df689 fix(ghidra): reject uncommitted handshake reports before the first query (#8630)
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]>
2026-11-02 00:00:24 -08:00
Kaoru0822andClaude Opus 5.5 dec8d2d588 feat(native): select retained batch decompile results through analysis views (#8629)
* 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]>
2026-11-01 23:58:54 -08:00
hhangandhhang 0e6943c9da fix(session): guide retained cleanup before reopening targets (#8628)
* fix(session): guide retained cleanup before reopening targets

* test(cli): isolate worker completion deadline from startup

---------

Co-authored-by: hhang <[email protected]>
2026-11-02 15:56:09 +08:00
hhangandhhang 20a282a181 fix(javascript): report SVG script references and skipped bodies (#8627)
Co-authored-by: hhang <[email protected]>
2026-11-02 15:55:56 +08:00
Kaoru0822andClaude Opus 5.5 f95bdbce76 fix(native): stop value traces at unconfirmed child cleanup (#8576)
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]>
2026-11-01 22:56:02 -08:00
gensokyo-dev 8250bf2320 perf(test): bind deferred runtime calls to advertised schemas (#8622) 2026-11-02 11:55:40 +08:00
morluto 00c4f44a1c fix(ci): repair verifier fixtures and formatting failures 2026-11-02 10:59:40 +08:00
gensokyo-dev d3ab0faf52 refactor(ci): parse readiness fences with a static expression (#8586) 2026-11-02 03:14:29 +08:00
gensokyo-dev 6d60db1951 fix(test): restrict storage fixture to loopback (#8582) 2026-11-02 02:52:20 +08:00
morluto 0354145a2a docs: clarify test audits and organize verification lanes 2026-11-02 02:21:40 +08:00
morluto 7bcefead69 test: strengthen and prune audited test coverage (#8573)
* test: strengthen and prune audited test coverage

* test: require delivered DeepSeek skill instructions

* test: keep schema fixture types private

* test: verify Gemini receives skill instructions

* test: bind Qwen skill evidence to its tool result

* test: report NativeAOT verifier checks precisely

* fix: verify Ghidra cleanup paths reliably

* fix: verify Command Code skill activation

* test: tighten cleanup and fixture checks

* test: verify packet capture evidence is recorded

* fix: report HTTP3 capture provenance accurately

* test: preserve resource coverage through MCP

* test: prune repeated assertions and retain cancellation identity

* fix: validate client verifier fixture paths

* test: skip Copilot verifier cases on Windows

* test: keep verifier setup target relative
2026-11-02 02:13:32 +08:00
gensokyo-dev ce9095d727 fix(test): contain browser fixture HTTP request targets (#8566) 2026-11-02 01:48:46 +08:00
ShuaixinHuang 46c13b9981 fix(electron): preserve runtime rejection and exception policies (#8542)
* fix(electron): preserve runtime rejection and exception policies

* fix(electron): retain rejection handlers for revoked proxies
2026-11-02 01:15:08 +08:00
Kaoru0822andClaude Opus 5.5 85013da0ce fix(cli): describe one-shot timeouts for an invocation whose session has ended (#8535)
* 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]>
2026-11-01 08:57:32 -08:00
Kaoru0822andClaude Opus 5.5 cbc69537cd fix(discovery): carry provider repair instructions into capability discovery (#8532)
* 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]>
2026-11-01 08:57:11 -08:00
Kaoru0822andClaude Opus 5.5 eb39a07966 feat(evidence): read selected complete records without the whole bundle (#8528)
* 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]>
2026-11-01 08:56:58 -08:00
Kaoru0822andClaude Opus 5.5 4b840aa1fb fix(setup): report the failed Hopper request URL and HTTP status (#8526)
* 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]>
2026-11-01 08:56:38 -08:00
Kaoru0822andClaude Opus 5.5 423059a4de feat(javascript): page retained feature traces through analysis views (#8522)
* 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]>
2026-11-01 08:56:05 -08:00
Kaoru0822andClaude Opus 5.5 44b6384009 fix(native): find procedures by the secondary names exact lookup accepts (#8520)
* 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]>
2026-11-01 08:53:29 -08:00
Kaoru0822andClaude Opus 5.5 3f61303939 fix(session): clear provider-inferred unavailability after recovery (#8518)
* 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]>
2026-11-01 08:53:11 -08:00
Kaoru0822andClaude Opus 5.5 35ae7232bf fix(enhanced): stop call graph traversals at unconfirmed provider cleanup (#8516)
* 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]>
2026-11-01 08:52:53 -08:00
Kaoru0822andClaude Opus 5.5 76cd23d2e8 fix(process): remove unused Darwin helper cache type export (#8511)
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]>
2026-11-01 08:52:42 -08:00
morluto 3e6a5cb20a fix!: preserve address context input errors (#8419)
* 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
2026-11-02 00:52:27 +08:00
morluto 1d343a88e5 feat(kuna): verify release 1.988 (#8509) 2026-11-02 00:25:06 +08:00
morluto df25bc8068 fix(ghidra): retain relocation symbol observations (#8531)
* fix(ghidra): retain relocation symbol observations

* test(ghidra): verify named relocation symbols through CLI

* fix(ghidra): clarify verification and supervise test commands

* test(ghidra): share load verification guidance
2026-11-01 23:53:56 +08:00
ShuaixinHuang aa48d494e9 fix(pe): enforce certificate name string alphabets (#8494)
* fix(pe): enforce certificate name string alphabets

* test(mcp): assert structured absolute-path diagnostics

* test(mcp): assert typed selector and stdio diagnostics

* fix(pe): bound IA5 name scans before validation
2026-11-01 21:24:01 +08:00
ShuaixinHuang 32c253ba31 fix(electron): preserve application argv during active capture (#8492) 2026-11-01 21:23:46 +08:00
morluto bafe3784cd fix(native): report dispatch target applicability (#8400)
* fix(native): report dispatch target applicability

* fix(native): clarify unavailable mode recovery

* test(kuna): avoid overlapping verifier imports

* docs(native): explain mode-specific discovery

* fix(native): preserve unbound provider recovery
2026-11-01 21:17:55 +08:00
morluto d684125eed docs(providers): verify prerequisite recovery and sibling failures 2026-11-01 21:14:27 +08:00
morluto 53363166f0 fix(evidence)!: validate native view parent predicates (#8451)
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.
2026-11-01 20:27:34 +08:00
morluto 3842809c37 docs(setup): clarify Hopper failures and installer verification 2026-11-01 20:27:15 +08:00
gensokyo-dev e5e0dc5542 fix(ci): include hidden VitePress configuration in CodeQL (#8486) 2026-11-01 19:05:37 +08:00
2SinceJuly 1aa6beb5dc fix(decompile): name the differing resume identity (#8465)
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
2026-11-01 18:42:27 +08:00
gensokyo-dev 71746351d3 fix(ci): preserve the package verifier HTTP boundary (#8483) 2026-11-01 18:36:21 +08:00
morluto 293db94dd0 docs: prioritize product contracts over skill guidance 2026-11-01 18:29:34 +08:00
morluto 60c0cf977a docs: clarify recovery for valid lookup failures 2026-11-01 17:53:37 +08:00
morluto 7c755f2147 fix: retain address context cleanup failures (#8417) 2026-11-01 17:24:01 +08:00
morluto 1c1c8f6286 fix(snapshot): report binding mismatches (#8432)
* fix(snapshot): report binding mismatches

* test(snapshot): assert structured mismatch recovery

* docs(snapshot): explain profile comparison recovery

* fix(snapshot): include profile in recovery advice

* docs(snapshot): explain fresh-session profile discovery

* refactor(snapshot): derive admission from binding comparison
2026-11-01 17:23:11 +08:00
gensokyo-dev a6caa79bf4 ci(codeql): separate production scans from test fixtures (#8450) 2026-11-01 15:24:29 +08:00
gensokyo-dev e18594b247 refactor(composition): unify capability bindings and cleanup ownership (#8441)
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)
2026-11-01 13:30:21 +08:00
morluto 6e9ee97a69 fix!: preserve feature traces when owner lookup is unavailable (#8368)
* fix!: preserve feature traces when owner lookup is unavailable

* test: strengthen feature trace cancellation assertions

* fix: preserve feature trace provider readiness errors

* test: verify all feature trace reference sources

* test: verify IDA guidance at its owning boundary

* style: format feature trace regression
2026-11-01 05:38:40 +08:00
gensokyo-dev f72638f6b1 feat(investigation)!: retain records across engine cleanup (#8361)
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)
2026-10-31 21:34:45 +00:00
Kaoru0822andClaude Opus 5.5 d54a4661da fix(ghidra): compare the DOS verifier's CLI window before its own rename (#8339)
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]>
2026-10-31 11:13:04 -07:00
Kaoru0822andClaude Opus 5.5 d2518a19f8 feat(native): label binary overview counts with their inventory scope (#8338)
* 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]>
2026-10-31 11:12:52 -07:00
Kaoru0822andClaude Opus 5.5 a36c9dd9c8 fix(kuna): omit Mach-O data symbols from the procedure inventory (#8335)
* 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]>
2026-10-31 11:12:41 -07:00
Kaoru0822andClaude Opus 5.5 8a6ddec83a fix(mcp): name the close_binary retry for retained cleanup (#8329)
* 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]>
2026-10-31 11:12:30 -07:00
Kaoru0822andClaude Opus 5.5 c534e249d1 perf(process): reuse a verified Darwin ownership helper across processes (#8318)
* 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]>
2026-10-31 11:12:02 -07:00
Kaoru0822andClaude Opus 5.5 41e7333cb5 feat(guidance): distinguish focused matches from broad parent sections (#8305)
* 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]>
2026-10-31 11:11:05 -07:00
morluto 60149282fc test: strengthen integration oracles and verification (#8295)
* test: strengthen integration oracles and verification

* test: bound lifecycle gates and strengthen workflow checks

* test: verify complete paged and extracted results

* test: preserve bytecode and cancellation evidence

* test: strengthen retained evidence and fixture cleanup

* test: require cross-projection bridge identities

* test: retain fixture owners through cleanup failures

* test: verify graph examples and project selectors

* test: tighten provider and lifecycle regressions

* test: consolidate hex coverage and bound cutter cleanup

* test: bound subprocess output waits and strengthen graph checks

* test: check mcp recovery and preserve cleanup owners

* test: verify structured mcp argument failures

* test: preserve artifact cleanup and gdb limit evidence

* test: preserve evidence identities and bound provider gates

* test: retain browser and comparison cleanup owners

* test: bound startup and strengthen fixture oracles

* test: bound lifecycle gates and verify recovered evidence

* test: verify public outcomes and lifecycle ownership

* test: assert precise evidence and release lifecycle gates

* test: verify selected facts and bound cleanup waits

* test: check retained evidence and decoded records

* test: verify final reports and retain cleanup ownership

* test: bound reviewed startup gates and retain cleanup

* test: verify PE errors and preserve MCP cleanup

* test: preserve MCP fixture cleanup ownership

* test: release provider gates before late startup
2026-11-01 00:15:28 +08:00
Kaoru0822andClaude Opus 5.5 2d5dec26c3 fix(ghidra): assemble the Swift verifier's unresolved-jump fixture before linking (#8325)
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]>
2026-10-31 09:01:06 -07:00
Kaoru0822andClaude Opus 5.5 da0e9669a8 fix(decompile): keep provider no-pseudocode facts on batch items (#8323)
* 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]>
2026-10-31 09:00:48 -07:00
Kaoru0822andClaude Opus 5.5 73ff6ca0c8 fix(native): separate value-trace page omission from traversal truncation (#8309)
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]>
2026-10-31 08:59:58 -07:00
Kaoru0822andClaude Opus 5.5 c287a73276 fix(ghidra): report unbacked file-offset addresses without schema repair (#8308)
* 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]>
2026-10-31 08:59:00 -07:00
Kaoru0822andClaude Opus 5.5 7c9ed94259 fix(ghidra): decide batch names independently of request order (#8306)
* 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]>
2026-10-31 08:53:11 -07:00
gensokyo-dev a989c0e458 refactor(evidence): unify retained inputs across domains (#8299)
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)
2026-10-31 14:45:21 +00:00
morluto 23de10bb28 fix: streamline tests and preserve Darwin process ownership (#8275)
* test: strengthen suite guards and regression oracles

* test: share JSON client registration contract

* test: reuse fixtures and reduce repeated setup

* fix(process): preserve ambiguous Darwin token ownership

* test(process): bound child readiness waits
2026-10-31 16:43:04 +08:00
Entrpiandmorluto b43c329df7 feat(artifacts): inventory and extract UF2 and DfuSe firmware containers (#8230)
* 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]>
2026-10-31 16:27:00 +08:00
22e5aa97af feat(artifacts): inspect classic iNES container regions (#2816)
* 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]>
2026-10-31 15:49:16 +08:00
Entrpi e669365c3b feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention (#8229)
* 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
2026-10-31 15:48:46 +08:00
Entrpi e6d0618979 feat(artifacts): inventory and extract ESP flash layouts (#8228)
* 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.
2026-10-31 15:24:32 +08:00
Entrpi 091892dfed test(ghidra): expect carved MIPS copies to be refused at target opening (#8227)
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.
2026-10-31 15:24:17 +08:00
Machen John 1dc97d8cec fix(ghidra): report a missing existing-project program as a configuration error (#8222)
* 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.
2026-10-31 15:24:00 +08:00
Jonnalagadda Sai Amarthya 7ba3f558d4 fix(windows): keep polling owned process output after a failed poll (#8163)
* fix(windows): keep polling owned process output after a failed poll

* test(windows): pin the failed-poll retry delay
2026-10-31 15:23:13 +08:00
morluto 245a581818 feat(kuna): verify release 1.986 and guide version selection (#8187)
* feat(kuna): verify release 1.986 and guide version selection

* test(kuna): cover verified release doctor guidance
2026-10-31 14:58:56 +08:00
morluto ec107022fa docs: guide audits of existing investigation tools (#8272) 2026-10-31 14:38:13 +08:00
gensokyo-dev b349e3c708 fix(evidence): give missing retained closures actionable recovery (#8270)
* fix(evidence): give missing retained closures actionable recovery

* refactor(evidence): assign retained lookup context at resolution
2026-10-31 14:30:30 +08:00
gensokyo-dev d9e01e5af2 feat(evidence): aggregate exact retained comparison references (#8267) 2026-10-31 13:59:13 +08:00
gensokyo-dev 6e2404b869 fix(comparison)!: preserve same-entry correspondence across renames (#8263)
* fix(comparison): preserve same-entry correspondence across renames

* docs(comparison): align rename guidance and focused contract checks
2026-10-31 12:53:42 +08:00
morluto 356cc50475 docs: guide design and verification of provider workflows (#8261)
* docs: guide provider workflow design and annotation comparisons

* docs: connect provider tool design to workflow verification
2026-10-31 12:15:15 +08:00
ShuaixinHuang d48e472fa6 fix(mcp): preserve repeated HTTP host allowlists (#8164) 2026-10-31 03:13:07 +08:00
Abdelrahman Teimaandgensokyo-dev 47fa6ab379 feat(electron): report custom URL protocol registrations
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]>
2026-10-31 02:23:47 +08:00
Harsh Bhardwaj 69bf61f7c7 fix(browser): preserve event coverage for failed requests (#8158) 2026-10-31 01:36:41 +08:00
Machen John ca7d1c3750 fix(ghidra): match startup-timeout guidance to the project mode (#8114)
In fresh-import mode, the startup-timeout remediation now points to
REA_GHIDRA_PROJECT_PATH for reusing a completed analysis, next to the
existing REA_GHIDRA_STARTUP_TIMEOUT_MS and REA_GHIDRA_DISABLED_ANALYZERS
advice.

In existing-project mode, the message no longer says Ghidra was
importing and analyzing the target, and the remediation no longer
suggests REA_GHIDRA_DISABLED_ANALYZERS, which configuration validation
rejects together with REA_GHIDRA_PROJECT_PATH.

Refs #3751
2026-10-31 01:32:06 +08:00
吴佳浩Alben e5c46dff03 fix(javascript): emit no socket for a literal out-of-range port (#8137)
* fix(javascript): emit no socket for a literal out-of-range port

`net` and `tls` validate a numeric port before creating a socket, so a literal
port Node rejects throws synchronously and no socket exists — but the analysis
reported an `acquire` operation.

Measured on Node 24.22.1:

    net.connect(0) / 80 / 65535        -> Socket created
    net.connect(65536)                 -> THREW ERR_SOCKET_BAD_PORT
    net.connect(-1) / 1.5              -> THREW ERR_SOCKET_BAD_PORT
    net.connect(NaN) / Infinity        -> THREW ERR_SOCKET_BAD_PORT
    net.connect(1e400)                 -> THREW ERR_SOCKET_BAD_PORT
    net.connect(0n) / true / false     -> THREW ERR_INVALID_ARG_TYPE
    net.connect(null)                  -> THREW ERR_INVALID_ARG_TYPE
    net.connect("")                    -> THREW ERR_SOCKET_BAD_PORT
    net.connect("/tmp/x.sock")         -> Socket created (Unix socket path)
    net.connect({ port: 80 })          -> Socket created
    net.connect(undefined) / {} / []   -> THREW ERR_MISSING_ARGS (undecided)
    --- tls mirrors net ---
    tls.connect(80)                    -> Socket created
    tls.connect(65536) / -1 / NaN      -> THREW ERR_SOCKET_BAD_PORT

A string is a Unix socket path and an object may carry a path, so only an
exactly readable non-port literal is decided. `undefined` and an empty object
throw `ERR_MISSING_ARGS`, which is a missing option rather than a bad port, so
they stay undecided. `NaN` and `Infinity` are identifiers rather than literals
and are decided only through `isUnmodifiedGlobalReference`.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): place the port check away from the fs flag check

`#8125` adds an fs flag guard at the top of `collectAcquisitions` and this branch
added a socket port guard in the same spot, so the two conflicted on every
rebase. The port check now filters the completed candidate list from
`collectJavaScriptSemanticResources` and its helpers sit at the end of the file,
which cannot collide with the fs flag guard.

* fix(javascript): drop the redundant in-loop port guard

The caller now filters the completed candidate list, so the loop guard became
dead code. Removing it also keeps this branch from editing the same lines as the
fs flag guard in #8125.
2026-10-31 01:23:50 +08:00
morluto 96606f5114 test(process): strengthen macOS token parser boundaries 2026-10-31 01:18:12 +08:00
morluto ebfef27b46 fix(ci): consolidate shared timer limit export 2026-10-31 01:16:19 +08:00
gensokyo-devandhhang 0ffed7a48c feat(android): trace callers of external DEX methods (#8154)
Resolve exact external method targets from directly encoded DEX invokes.
Preserve complete caller identity, bounded position evidence and follow-up
selectors. Report aggregate position coverage independently of the caller
inventory, and reuse bounded prototype facts within the exact session.

Absorbs #8096. Fixes #7811.

Co-authored-by: hhang <[email protected]>
2026-10-31 01:07:18 +08:00
morlutoandCopilot Autofix powered by AI 675d4076de test: strengthen regression oracles and cleanup ownership (#8152)
* test: strengthen suite oracles and cleanup ownership

* test: reuse setup across independent scenarios (#8153)

* Potential fix for pull request finding 'CodeQL / Improper code sanitization'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-10-31 00:22:16 +08:00
+1 1ddee1089b feat(dotnet): expose field constant defaults (#8015)
* feat(dotnet): expose field constant defaults

* test(dotnet): guard field default comparison warning

Compare actual ordinary and constant-bearing observations independently of the limitation producer. Preserve warning delivery and unchanged matching assertions while allowing harmless wording changes.

Refs #5150

* test(mcp): retain structured bundle path diagnostics

Keep absolute-path refusal and all rejected parameter facts independent of legacy flat or current structured SDK diagnostic rendering. Retain relative CLI, absolute MCP and no-Evidence controls.

Refs #5150

* Revert "fix(ci): integrate main and repair inherited static checks"

This reverts commit afd4d571f6, reversing
changes made to 317bf3fe23.

* fix(ci): replay reviewed field-default integration

* fix(analysis-view): keep inferred confidence on views of inferred Evidence (#8099)

createAnalysisViewEvidence chose view confidence from the view result kind
only, so every summary, page or item view of an inferred parent
(compare_source_to_bundle, inspect_evm_interface, partially resolved
inspect_il2cpp_metadata) was sealed as derived while keeping the parent's
authority. A projection never makes its facts more certain: an inferred
parent now yields inferred views through CLI and MCP alike. Observed and
derived parents keep their existing view confidence and identities.

Fixes #8098

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(apple): read flattened framework Info.plist from Resources (#8101)

A macOS framework without Versions/ keeps its binary, _CodeSignature/ and
Resources/Info.plist directly in the .framework, as Electron and Gecko apps
ship it. Bundle anatomy looked for a shallow bundle's plist only at its root
and reported info_plist_path: null under complete coverage.

Resolve a shallow bundle's root Info.plist first, then Resources/Info.plist,
the same layout a framework version directory uses. Keep the iOS platform
rule tied to a root-level Info.plist so a Resources/-only application root
is not classified as iOS.

Fixes #8100

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(desktop): charge retained rows at their measured representation (#8105)

* fix(desktop): charge retained rows at their measured representation

The desktop scan reserved 1024 + 24 bytes per path character for every
row, omission and the root against its 32 MiB representation budget. Real
rows cost 800-940 heap and 490-640 JSON bytes, so the charge overstated
them 6-13 times and large macOS bundles stopped at 3,700-7,200 entries,
far below max_entries.

Charge each path at the larger of its UTF-16 heap size and its escaped
JSON size, plus the existing fixed row and omission overheads, in one
helper. The bound still covers the retained heap and the serialized
report, including control-character paths that expand six times in JSON.

Fixes #8104

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(desktop): charge every retained copy of a path

The tighter row charge counted each path once, but rows repeat it in
convention-fact values and the next probes those facts offer, and host
failure issues name the absolute path. Control-character paths under a
convention such as Content/Paks or resources/app.asar therefore serialized
47-80 MiB of reports within the 32 MiB budget.

Reserve each copy through the same text helper where it is stored: path
convention facts with their row, the probes any fact offers (at most two,
pinned by a projection test) with that fact, retained read ranges before
their read, and probe failure issues before they are recorded.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(source-maps): resolve empty sourceRoot sources against the map URL (#8107)

webpack 5 and tsc write "sourceRoot": "" for map-relative sources. Since
5f702dd6c the shared join added "/" for any non-null root, so these
sources resolved from the URL root: rxjs's ../../../src/internal/Observable.ts
under /npm/[email protected]/dist/cjs/internal/ became /src/internal/Observable.ts,
and xterm's webpack://xterm/./src/... became https://host/webpack://xterm/....

Treat an empty root like a null root in sourceMapReference, the single join
used by trace-web-source-location, analyze-javascript-application and the
web source-map fetcher. Chrome, Firefox and trace-mapping skip an empty root,
and tc39/ecma426#183 wrote the current steps to match them. Non-empty roots,
including "/" and repeated slashes (#6726), keep their existing behavior.

Fixes #8106

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(javascript): stop reporting self-defaulted receivers as module loads (#8109)

The CommonJS loader search treated any repeated state on its own ancestor
chain as an unknown loader. A self-default such as
`session.messages = session.messages || []` forms that cycle, because a
member read inside the assigned value was attributed to the later call's
read site and selected the same write again. Each `push` on the value was
then reported as an unresolved `require` load, and graph coverage became
partial.

Record a member read inside a flowed value as a depth-1 captured selection
at its own position, as member mutations already do for flowed origins.
Treat a revisited state as adding no source; the cycle's other entries are
still expanded once each. Remove the superseded ancestor tracking.

Fixes #8102

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(captures): admit browser-serialized HAR request URLs (#8112)

* fix(captures): admit browser-serialized HAR request URLs

[email protected] checks request.url with ajv-formats' strict RFC 3986
`uri` format. Browsers record and send URLs as WHATWG serializes them,
which keeps characters such as `|` and `{` unescaped in the query, so one
Google Fonts or GraphQL GET entry made inspect-web-network-capture and
environment-build reject the whole HAR.

HAR 1.2 defines request.url only as the absolute request URL. Own that
correction in the HAR validator: every URL the RFC 3986 check accepts
stays accepted, and a URL that WHATWG URL serialization reproduces
unchanged is accepted too. Spellings WHATWG would rewrite (a backslash
authority, a tab, a space) and relative references still fail.

Fixes #8110

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(captures): state that RFC 3986 request URLs stay admitted

The HAR request URL rule is "passes RFC 3986, or WHATWG serialization
reproduces it unchanged". A spelling WHATWG would rewrite therefore fails
only when it also fails the RFC 3986 check; an uppercase host still
passes. Say so in the docs and the validator comment, and pin the
accepted uppercase-host case in the CLI test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(apple): charge the XML plist decoder's DOM before decoding (#8115)

* fix(apple): charge the XML plist decoder's DOM before decoding

The XML preflight charged 4 bytes per character and 256 bytes per start
or end tag. The decoder builds an @xmldom/xmldom DOM that measured
1,000-1,160 bytes per element and up to 240 bytes per text, comment or
CDATA node, which were not charged at all. A 1.75 MB plist of 230,000
<true/> lines was admitted under the 64 MiB decode budget and peaked at
249 MiB of heap; shipped Apple plists peaked at 80-104 MiB.

Charge each element and each text, comment, CDATA or DOCTYPE node at a
measured upper bound, keeping the character and number-projection terms.
The binary estimator already bounds its decoder and is unchanged.

Fixes #8113

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(apple): charge every XML element, attribute and reference

The XML preflight recognized only element names starting with an ASCII
letter, so names such as é, _, a CJK or astral character escaped the
element charge: 100,000 <é/> elements (0.5 MB) built an 84 MiB DOM before
the plist decoder rejected the tag. Attributes and entity or character
references were also uncharged; 20 attributes per element measured about
210 bytes each, and each reference about 26 bytes beyond its characters.

Classify markup by the character after "<" instead of a name pattern, so
anything other than an end tag, comment, CDATA, PI or declaration counts as
an element (saxes rejects malformed markup before the DOM is built). Charge
each unquoted "=" in a start tag as an attribute node and every "&" as a
reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(apple): charge XML references only where the parser expands them

The previous commit charged every "&" in the document as a reference,
including literal ampersands inside CDATA sections and comments. A valid
plist with one CDATA string of 1,000,000 ampersands was refused although
it is a single node the decoder holds as one string.

Count "&" during the existing token walk, only in text runs and in the
attribute values of start tags, so comments, CDATA, PIs and declarations
add no reference charge and the scan stays a single pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(fit): preserve leading U+FEFF in property strings (#8083)

* fix(environment): retain conflicting scenario MIME uncertainty (#8082)

* fix(environment): retain conflicting scenario MIME uncertainty

* test(mcp): assert structured absolute-path diagnostics

* test(mcp): assert typed selector and stdio diagnostics

* fix(electron): keep literal IPC sends ambiguous beside unresolved handlers (#8120)

* fix(electron): keep literal IPC sends ambiguous beside unresolved handlers

A main handler on an unresolved channel can receive any literal renderer
send of a compatible mode, but pairing ignored it unless it had a scoped
receiver. The send was then reported unpaired, or paired with another
handler. Treat an unresolved-channel handler as a competitor that never
pairs.

The wrapper pass seeds only calls with a literal first argument, so it now
records which invocations of a dynamic site it traced. The syntax finding
gains `invocation_channels: "literal"` only when every invocation is a
traced direct call of an unexported local function supplying a literal
channel. Such a site is already represented by its per-invocation findings
and does not compete.

Fixes #8118

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(electron): keep guarded wrapper registrations competing in IPC pairing

A guarded wrapper registration keeps its predicate on a call path and has
no IPC finding. It still counted toward `invocation_channels: "literal"`,
so pairing lost it: a send on that channel was reported unpaired, or
paired uniquely with a direct handler. The enumerated site now carries
these channels as `guarded_invocation_channels`, and each one competes for
its own channel without pairing.

Index each binding's direct call sites by appending in place. Spreading
the earlier calls on every reference was quadratic in a file's call count.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(ci): remove unused exports that fail deep static checks (#8095)

* fix(ci): remove unused exports that fail deep static checks

Knip on main reports three analysis-view hub re-exports, two type
re-exports, an unused artifact-format guard and an alias constant. Drop
the dead re-exports and guard, and give the debugger control-reply budget
its own literal: it is a separate budget from the caller command-output
default, as its comment already states.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(config): read the Rizin control budget from its own constant

The Rizin control-reply admission limit still took its value from the
caller command-output default, while GDB used DEBUGGER_CONTROL_REPLY_BYTES.
Read the dedicated constant in both so the control budget has one owner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* perf(javascript): keep one member-effect record per stored fact (#8103)

Member-mutation indexing appended a binding write or escape record for
every traversal state that reached it. States that differ only in
coverage fields the record never stores (origin, owner initializer,
getter widening) produced identical records, and each later receiver
query rescanned all of them. Record each stored fact once, alongside the
existing required-module effect deduplication. Propagation is unchanged.

Effect ordering also re-walked the same top-level statement ancestry for
every candidate record. Cache it with the other initializer positions,
and test the selected path before the costlier receiver comparison.

On the #7774 reproducer the semantic IR and the complete
analyze-javascript-application Evidence are byte-identical, while the
CLI run drops from 146 s to 40 s.

Refs #7774

Co-authored-by: Claude Opus 5.5 <[email protected]>

* feat(native): open extension, framework, XPC and plug-in bundle directories (#8117)

* feat(native): open extension, framework, XPC and plug-in bundle directories

open_binary and the CLI inspection commands admitted only .app
directories and pointed every other Apple bundle at the JavaScript
analyzer. Resolve .appex, .framework, .xpc, .systemextension, .bundle
and .plugin directories through their Info.plist CFBundleExecutable, in
the Contents or flat root layout, plus the versioned framework layout
under Versions/Current. Exactly one layout may declare the bundle and
every resolved path must stay inside it.

Unresolvable bundles fail closed with typed constraints, probed paths
and the Info.plist that was read. Other Apple bundle types are refused as
unsupported instead of being offered the JavaScript route. Resource-root
discovery shares the same layout rule, so inspect_plist reads a
versioned framework's Info.plist. The recognized bundle vocabulary now
has one owner in the domain.

Refs #4310

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): refuse kernel extensions and state bundle error fields

Name the Apple bundle types binary routes recognize but do not open
(.dext, .kext, .qlgenerator, .mdimporter) explicitly, so a kernel
extension is refused as unsupported instead of receiving the JavaScript
route. Document which bundle refusals carry probed_paths and info_plist,
and make success tests throw the typed admission error before asserting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(native): open flattened frameworks through Resources/Info.plist

Electron and Gecko apps ship frameworks without Versions/: the program
file sits at the framework root beside Resources/Info.plist, the layout
#8101 taught bundle anatomy. Read that layout for a framework without
Versions/, and keep the versioned layout for one with it, so a versioned
framework's root Resources link is never read as a second declaration.
The resource root reports it as flattened-framework.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* chore(security): configure dependency and ownership reviews

* perf(javascript): index member-effect paths for projected reads (#8108)

* perf(javascript): index member-effect paths for projected reads

Each projected read scanned every member-write record of its binding and
tested the record path against the selection. On the #7774 reproducer
only 3.7% of scanned records pass that test. Index each binding's
append-only records by path in a lazily built trie that extends itself
with newly appended records, and visit only records whose exact keys a
selected choice names or whose keys are wildcards. Candidates come back
in record order and still pass the exact predicate, now owned next to
the index as semanticEffectPathMatches.

The semantic IR and the complete analyze-javascript-application Evidence
are byte-identical. On the full reproducer each worker phase drops from
about 80 s to 29 s, and to 8 s together with #8103.

Refs #7774

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* perf(javascript): stop escape checks at the first indexed match

The escape check only asks whether any matching escape is unsuperseded,
so it does not need ordered candidates. Share one trie traversal between
the ordered write candidates and an early-exit visitor for this check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(hopper): distinguish an absent demo dialog from publisher rejection (#8074)

* fix(hopper): distinguish an absent demo dialog from publisher rejection

The Linux demo watcher collapsed an admitted launcher with no first-run dialog into unsupported_demo_dialog. Report that post-authentication timeout as demo_dialog_absent, keep the unsupported dialog code for an observed but unrecognized Hopper window, and allow REA_HOPPER_DEMO_DIALOG_TIMEOUT_MS to widen the fixed wait. Closes #8008.

* fix(hopper): tolerate callers without the dialog timeout argument

* fix(hopper): report an invalid demo dialog timeout as its own failure

An invalid REA_HOPPER_DEMO_DIALOG_TIMEOUT_MS exited 73 and was shown as
invalid_launch_command. Exit 82 is invalid_dialog_timeout. The public error
names the setting, shows the rejected value and source, lists the allowed
range, and says to restart REA.

Drive the demo watcher with controlled windows and time. No window exits 81,
a Hopper window without a supported dialog exits 76, and a dialog that arrives
after the old 10 second limit is still accepted.

* fix(hopper): keep startup stderr capture on AdapterFailure

DemoWatchStop was inserted above with_stderr, so launch failures called a
method they no longer had and became unexpected_failure. Restore with_stderr
on AdapterFailure and reject a watcher fixture that cannot capture stderr.

---------

Co-authored-by: Cursor Agent <[email protected]>

* test(ci): require provider checks and reduce test startup (#8145)

* test(ci): require provider coverage and reuse test sessions

* fix(ci): prepare the runtime before Python producer tests

The fast Python job ran the suite on a fresh checkout, so analysis-efficiency
checks failed without compiled process modules. Install locked dependencies
and prepare the runtime before the suite.

* fix(ci): keep provider test reports inside the checkout

The provider-report reader accepted an arbitrary path from argv. Resolve the
path and reject anything outside the checkout before reading it. The packet
and signal workflows write those reports under .cache.

* fix(ci): require a test module in each named Python bridge family

Discovery still runs every bridge test. The mitmproxy, native, and pwndbg
families must each keep a test module, so deleting one cannot pass through
the others.

---------

Co-authored-by: Cursor Agent <[email protected]>

* fix(javascript): emit no http request for a target that is not a URL (#8147)

* fix(javascript): emit no http request for a target that is not a URL

`node:http` parses a string target with `new URL(...)` before a request object
exists, so a literal that is not an absolute URL throws `ERR_INVALID_URL`
synchronously. The analysis reported `endpoint` at `resolution: "complete"` for
those calls, and the graph then projected `constructs-request` as `resolved`.

Measured on Node 24.22.1:

    http.get("nope")                     -> THREW ERR_INVALID_URL
    http.get("example.test/api")         -> THREW ERR_INVALID_URL
    http.get("/api/items")               -> THREW ERR_INVALID_URL
    http.get("/api/items", { host })     -> THREW ERR_INVALID_URL
    http.get("")                         -> THREW ERR_INVALID_URL
    http.get("//example.test/x")         -> THREW ERR_INVALID_URL
    http.get("http://")                  -> THREW ERR_INVALID_URL
    http.request("nope")                 -> THREW ERR_INVALID_URL
    https.get("/api/items")              -> THREW ERR_INVALID_URL
    --- accepted ---
    http.get("http://example.test/x")    -> OK ClientRequest
    http.get("HTTP://example.test/x")    -> OK ClientRequest
    http.get("http:/x")                  -> OK ClientRequest
    http.get("http:///x")                -> OK ClientRequest
    http.get("http:x")                   -> OK ClientRequest

`URL.canParse` is the exact predictor of that branch, so a relative spelling such
as `"http:x"` keeps its operation. It already has precedent in this codebase
(`javascriptPackageImportAliases.ts`).

Two boundaries are deliberate. `fetch` and `new WebSocket` are host-supplied
globals: a browser resolves a relative target against the document URL, so their
spellings are not judged by Node's absolute-URL rule. A protocol mismatch such as
`http.get("ftp://...")` is also left alone because an options object can rescue
it, so it is not a provable throw.

The guard is scoped to `method === "get" | "request"`, which only
`requestMethod`'s `http`/`https` builtin branch can produce.

* chore: retrigger title and readiness checks after the title was corrected

* refactor(ci): unify lane ownership and guard Python tests (#8150)

* refactor(ci): unify lane ownership and guard Python tests

* fix(ci): reject entirely skipped bridge subtests

* docs: correct the raw ARM example and document a Cortex-M language override (#7483)

* docs(raw-binary): replace the STM32-style ARM example

The headline raw example opened ecu.bin at 0x8000000, an STM32 flash
address, while the arm lane decodes in ARM state. Map a 32-bit ARM-state
image at 0x8000 instead, and state beside the example that Thumb-only
images, including Cortex-M firmware, are not supported.

* docs(ghidra): document an interim Cortex-M language override

Ghidra imports little-endian ARM ELF files as ARM:LE:32:v8, which
defaults to ARM state and marks Thumb code only from $t mapping symbols
and odd function-symbol addresses. Unpinned arm ELF already admits
REA_GHIDRA_LANGUAGE_ID, so document ARM:LE:32:Cortex (ARMv6-M, ARMv7-M,
ARMv7E-M) and ARM:LE:32:v8-m (ARMv8-M) as an unverified interim.

State the override's full effect while it is set: raw-binary, DOS, DOL,
PSP and language-pinned lanes such as RISC-V refuse it, and REA asks
Ghidra to import every other target in the ARM language, because it does
not check the override's processor against the target.

* fix(ida): admit architecture families by allowlist (#7485)

IDA refused MIPS, RISC-V, PowerPC, Hexagon and arm64_32 by denylist, so
any family nobody excluded was reported supported, and the label ternary
printed "Hexagon" for any other metadata family. Admit exactly the
families IDA admits today (x86, x86_64, arm, arm64) and name every other
family from its own architecture id. Families that Ghidra admits keep
the pointer to a Ghidra profile.

Refusal text now uses architecture ids, for example "no verified mips
lane" instead of "no verified MIPS lane".

* ci(ghidra): probe M-profile ARM and Xtensa decoding (#7482)

* ci(ghidra): probe M-profile ARM and Xtensa decoding

The embedded plan relies on ARM:LE:32:Cortex, ARM:LE:32:v8-m and
Xtensa:LE:32:default, but CI only exercised ARM:LE:32:v7. Import
source-owned bytes for each language at 0x1000 with -noanalysis and
check how they decode from the language's default context: Cortex and
v8-m must start in Thumb state, v8-m must decode the ARMv8-M MSPLIM
register, and Xtensa must decode entry and retw.n.

Add the probe scripts the job runs, including the existing ARM context
probe, to the ghidra_bridge change scope.

* ci(ghidra): match decode-probe operands as complete operands

The operand expectation searched the instruction text for a substring,
so mrs/4/msp also accepted mrs r0,msplim. Compare it with each operand
Ghidra reports for the instruction, so only a complete operand matches.
The Cortex (msp, lr), v8-M (msplim, lr) and Xtensa (a1) expectations are
unchanged.

* test: guard target format vocabularies and DOL snapshot saves (#7481)

* test(mcp): save and reopen a GameCube DOL snapshot

#7290 reported that DOL Evidence could not be bundled or saved; 50822e1ef
fixed the bundle manifest and added bundle export/import coverage, but no
test saved a DOL session through close_binary. Save a DOL snapshot with
recorded Evidence, check its target and artifact manifest, then reopen it
and replay the read without running the provider again.

* test(binary): require every target format in each target vocabulary

A new target format must be added by hand to the Evidence subject,
bundle manifest, snapshot target and live summary vocabularies; missing
one caused #7290. Give every BinaryTarget format a fixture through a
mapped type, so a new format fails compilation until it has one, and
push each fixture through the producers that persist or project targets.
Caller-selected format hints must name executable target formats.

This is a guard until target formats have a single owner.

* test(mcp): assert the replayed DOL result and the owned replay limitation

The DOL snapshot replay test checked the subject and the provider-call
count but not the answer, so a replay returning null or another value
would pass. Compare the replayed normalized result with the original.
Name the replay limitation as SNAPSHOT_REPLAY_LIMITATION in the snapshot
cache that adds it, and assert that value instead of repeating the
sentence.

* test: list Intel HEX and S-record targets in the vocabulary guard

Upstream added the intel-hex and srec formats (#7136). The guard requires
a fixture for every target format, so after merging main it no longer
typechecked; both formats now have archive fixtures and pass the same
persistence and projection checks.

* test: list UnityFS among the target format fixtures

Upstream #7699 added the unityfs target format, so the vocabulary test's
per-format fixtures must name it; without one the test no longer typechecks
against main.

* test: list uImage among the target format fixtures

Upstream #7234 added the uimage target format, so the vocabulary test's
per-format fixtures must name it; without one the test no longer typechecks
against main.

* test: list the ESP image among the target format fixtures

Upstream #7498 added the esp-image target format, so the vocabulary test's
per-format fixtures must name it; without one the test no longer typechecks
against main.

* fix: correct stale provider refusal and diagnostic text (#7484)

* fix(ghidra): name every admitted family in the architecture refusal

The refusal listed x86, x86_64, arm, arm64, MIPS, RISC-V, raw 6502 and
raw m68k, but the admission set also holds powerpc and hexagon. Build
the text from SUPPORTED_ARCHITECTURES, marking raw-only families, so a
newly admitted family cannot be left out again, and name the refused
family.

* fix(hopper): stop calling the Ghidra Hexagon lane verified

Hopper declined Hexagon targets with "Select a verified Ghidra Hexagon
profile instead", but REA has no verified Hexagon lane. Record whether
each declined family's Ghidra lane is verified, and say that Ghidra
admits Hexagon through an unverified lane.

Name declined families by architecture id, as the Ghidra and IDA
refusals do.

* fix(ghidra): name the pinned language in profile readback refusals

The session readback said "MIPS" whenever a provider-pinned profile
conflicted with the target or with REA_GHIDRA_LANGUAGE_ID, although the
RISC-V, PowerPC and DOL lanes are pinned too. The readback helper was
MIPS-named and re-parsed the language slot that
ghidraProfileLanguageOverride already owns. Move it beside that owner as
a family-neutral readback and name the pinned language id instead.

* fix(ghidra): list DOL among independently verified load images

GameCube DOL load images are independently verified, but the declined
format reason, the inspect_native_load_image capability note and the
Windows P0 guide listed only DOS MZ, DOS COM and raw-binary.

* fix(ghidra): count RV32E's sixteen integer registers

The EF_RISCV_RVE refusal described a 15-register ABI. RV32E has 16
integer registers, x0-x15, as docs/ghidra-riscv.md already says.

* fix(elf): name the refused e_machine

"unsupported ELF architecture" did not say which machine was refused.
Include the numeric e_machine, for example EM_XTENSA 94.

* test(hopper): assert declined-lane facts and owned refusal reasons

The MIPS case banned the phrase "not verified" instead of checking
facts, so wrong advice would still pass. Expose each declined family's
Ghidra lane status, assert those statuses, check that the advice depends
on the status, and compare Hopper's refusals with
hopperDeclinedArchitectureReason. The RISC-V refusal table now compares
the provider's reason with ghidraRiscvUnsupportedReason and expects the
EF_RISCV_RVE declaration name rather than fixed wording.

* test(ghidra): assert verified load-image formats as owned facts

The declined-format test required display phrases such as "DOS MZ" and
"GameCube DOL", so harmless rewording failed it while wrong guidance
containing those names passed. The format list was also spelled three
times: in the attestation's membership check, its reason and the
inspect_native_load_image capability note. Give the verified formats one
owner with the formatters for both texts, assert the list as a fact, and
compare the delivered reason and capability note with those formatters.

* fix(hopper): stop restating which Ghidra lanes REA has verified

Hopper's refusal for families it declines carried its own table of
which Ghidra lanes REA has verified (mips, riscv and powerpc verified,
hexagon not). That restated the Ghidra provider's facts in Hopper and
goes stale whenever they change, the drift that made the Hexagon refusal
wrong in the first place. Reading Ghidra's facts from Hopper would
couple the providers instead; no module in src/hopper imports from
src/ghidra.

The table and hopperDeclinedGhidraLane are removed.
hopperDeclinedArchitectureReason now states only what Hopper owns: it
does not admit the family, and the Ghidra provider's lane reports
whether REA has verified it. The arm64_32 sentence is unchanged.

The test checks every declined family against the formatter: each
reason names the family and Ghidra, and apart from the name all reasons
are the same text, so none states a per-family Ghidra status.

* fix(hopper): route declined families to Ghidra without promising its lane facts

The refusal said the Ghidra lane reports whether REA has verified it, but
Ghidra admits Hexagon with no such note, so a caller could not find the
promised status. Hopper now only states that it does not admit the family
and routes the caller to the Ghidra provider.

---------

Co-authored-by: hhang <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: ShuaixinHuang <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: Henglie <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: 吴佳浩Alben <[email protected]>
Co-authored-by: Entrpi <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-31 00:10:36 +08:00
Entrpi 250dc81cd6 fix: correct stale provider refusal and diagnostic text (#7484)
* fix(ghidra): name every admitted family in the architecture refusal

The refusal listed x86, x86_64, arm, arm64, MIPS, RISC-V, raw 6502 and
raw m68k, but the admission set also holds powerpc and hexagon. Build
the text from SUPPORTED_ARCHITECTURES, marking raw-only families, so a
newly admitted family cannot be left out again, and name the refused
family.

* fix(hopper): stop calling the Ghidra Hexagon lane verified

Hopper declined Hexagon targets with "Select a verified Ghidra Hexagon
profile instead", but REA has no verified Hexagon lane. Record whether
each declined family's Ghidra lane is verified, and say that Ghidra
admits Hexagon through an unverified lane.

Name declined families by architecture id, as the Ghidra and IDA
refusals do.

* fix(ghidra): name the pinned language in profile readback refusals

The session readback said "MIPS" whenever a provider-pinned profile
conflicted with the target or with REA_GHIDRA_LANGUAGE_ID, although the
RISC-V, PowerPC and DOL lanes are pinned too. The readback helper was
MIPS-named and re-parsed the language slot that
ghidraProfileLanguageOverride already owns. Move it beside that owner as
a family-neutral readback and name the pinned language id instead.

* fix(ghidra): list DOL among independently verified load images

GameCube DOL load images are independently verified, but the declined
format reason, the inspect_native_load_image capability note and the
Windows P0 guide listed only DOS MZ, DOS COM and raw-binary.

* fix(ghidra): count RV32E's sixteen integer registers

The EF_RISCV_RVE refusal described a 15-register ABI. RV32E has 16
integer registers, x0-x15, as docs/ghidra-riscv.md already says.

* fix(elf): name the refused e_machine

"unsupported ELF architecture" did not say which machine was refused.
Include the numeric e_machine, for example EM_XTENSA 94.

* test(hopper): assert declined-lane facts and owned refusal reasons

The MIPS case banned the phrase "not verified" instead of checking
facts, so wrong advice would still pass. Expose each declined family's
Ghidra lane status, assert those statuses, check that the advice depends
on the status, and compare Hopper's refusals with
hopperDeclinedArchitectureReason. The RISC-V refusal table now compares
the provider's reason with ghidraRiscvUnsupportedReason and expects the
EF_RISCV_RVE declaration name rather than fixed wording.

* test(ghidra): assert verified load-image formats as owned facts

The declined-format test required display phrases such as "DOS MZ" and
"GameCube DOL", so harmless rewording failed it while wrong guidance
containing those names passed. The format list was also spelled three
times: in the attestation's membership check, its reason and the
inspect_native_load_image capability note. Give the verified formats one
owner with the formatters for both texts, assert the list as a fact, and
compare the delivered reason and capability note with those formatters.

* fix(hopper): stop restating which Ghidra lanes REA has verified

Hopper's refusal for families it declines carried its own table of
which Ghidra lanes REA has verified (mips, riscv and powerpc verified,
hexagon not). That restated the Ghidra provider's facts in Hopper and
goes stale whenever they change, the drift that made the Hexagon refusal
wrong in the first place. Reading Ghidra's facts from Hopper would
couple the providers instead; no module in src/hopper imports from
src/ghidra.

The table and hopperDeclinedGhidraLane are removed.
hopperDeclinedArchitectureReason now states only what Hopper owns: it
does not admit the family, and the Ghidra provider's lane reports
whether REA has verified it. The arm64_32 sentence is unchanged.

The test checks every declined family against the formatter: each
reason names the family and Ghidra, and apart from the name all reasons
are the same text, so none states a per-family Ghidra status.

* fix(hopper): route declined families to Ghidra without promising its lane facts

The refusal said the Ghidra lane reports whether REA has verified it, but
Ghidra admits Hexagon with no such note, so a caller could not find the
promised status. Hopper now only states that it does not admit the family
and routes the caller to the Ghidra provider.
2026-10-30 22:39:46 +08:00