Compare commits

...
Author SHA1 Message Date
morluto 1a4db31eea chore: combine trace and function evidence prototypes 2026-11-04 14:29:02 +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 1fc86c45aa docs(mcp): guide trace reference classification 2026-11-04 10:15:18 +08:00
morluto de9d468211 docs(mcp): clarify imported residual questions 2026-11-04 10:02:56 +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
morluto ec05b9de27 merge: integrate main into native trace views 2026-11-03 19:31:43 +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
morluto 2a0415cd5b test(snapshot): verify linked foreign evidence stays scoped 2026-11-02 02:56:23 +08:00
morluto 673d971381 fix(snapshot): retain linked source evidence for replay 2026-11-02 02:54:41 +08:00
gensokyo-dev 6d60db1951 fix(test): restrict storage fixture to loopback (#8582) 2026-11-02 02:52:20 +08:00
morluto 4809afeef5 feat(native): page retained feature traces 2026-11-02 02:43:25 +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
Entrpi 54756ff24e 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.
2026-10-30 22:35:53 +08:00
Entrpi 19ab239278 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.
2026-10-30 22:32:41 +08:00
Entrpi 1926fc2dac 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".
2026-10-30 22:30:23 +08:00
Entrpi b33f4a5a1b 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.
2026-10-30 22:28:06 +08:00
morluto cd878ad57d 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
2026-10-30 21:23:33 +08:00
吴佳浩Alben 8f920bb65b 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
2026-10-30 19:42:11 +08:00
morlutoandCursor Agent 77b09314ff 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]>
2026-10-30 19:34:19 +08:00
HenglieandCursor Agent b1ff7cdacc 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]>
2026-10-30 18:29:18 +08:00
Kaoru0822andClaude Opus 5.5 767f6afc18 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]>
2026-10-30 17:52:46 +08:00
morluto 6bb831df60 chore(security): configure dependency and ownership reviews 2026-10-30 17:35:34 +08:00
Kaoru0822andClaude Opus 5.5 37342ecde0 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]>
2026-10-30 02:27:52 -07:00
Kaoru0822andClaude Opus 5.5 a47dcca010 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]>
2026-10-30 02:26:01 -07:00
Kaoru0822andClaude Opus 5.5 6b68fb642a 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]>
2026-10-30 02:25:43 -07:00
Kaoru0822andClaude Opus 5.5 81378efb71 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]>
2026-10-30 02:21:23 -07:00
ShuaixinHuang 72039ecca9 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
2026-10-30 17:19:46 +08:00
ShuaixinHuang d77104daab fix(fit): preserve leading U+FEFF in property strings (#8083) 2026-10-30 17:19:38 +08:00
Kaoru0822andClaude Opus 5.5 cce33142ed 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]>
2026-10-30 01:29:16 -07:00
Kaoru0822andClaude Opus 5.5 c5f50e80d8 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]>
2026-10-30 01:03:35 -07:00
Kaoru0822andClaude Opus 5.5 9d68aac76d 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]>
2026-10-30 01:01:21 -07:00
Kaoru0822andClaude Opus 5.5 24398a89e4 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]>
2026-10-30 01:00:41 -07:00
Kaoru0822andClaude Opus 5.5 0ce76e70f4 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]>
2026-10-30 01:00:28 -07:00
Kaoru0822andClaude Opus 5.5 6d19649c0b 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]>
2026-10-30 01:00:06 -07:00
Kaoru0822andClaude Opus 5.5 bfa082f777 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]>
2026-10-30 00:59:53 -07:00
morlutoandCursor Agent 317bf3fe23 fix: report provider admission failures as typed codes (#8066)
* fix: report provider admission failures as typed codes

* fix(evm): keep limiter path on admission diagnostics

* fix: assert WABT and CBOR admission codes

* fix: keep admission details on returned failures

* fix(evm): detect repeated CBOR keys in the decoder

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 06:48:01 +08:00
morlutoandCursor Agent 3b647132c6 fix(kuna): report document failures as typed codes (#8064)
* fix(kuna): report document failures as typed codes

Kuna JSON, graphs, xrefs, spans, and command checks threw Error and
published the sentence. Those refusals are now KunaDocumentError codes
with the field or path that failed. Output errors keep the code as
reason and the same facts in diagnostics.

* fix(kuna): keep incoming-reference address fields

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 06:07:53 +08:00
morlutoandCursor Agent 249b90c5e3 refactor(javascript): break the semantic analysis import cycle (#8063)
* refactor(javascript): break the semantic analysis import cycle

Split shared semantic facts out of the analysis hub so projectors of
effects, evaluation, and identity no longer import one another in a cycle.

* refactor(javascript): share the import specifier reader

Read static imports in one module used by the boundary checker and both
cycle guards, so those rules cannot drift apart.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 06:07:49 +08:00
940d71a45a refactor(analysis-view): break the analysis view hub cycle (#8061)
* refactor(analysis-view): break the analysis view hub cycle

Move request fragments, the request union, and the result union out of
analysisView.ts so projectors no longer import the module that imports them.
The dispatcher re-exports the public schemas and seals projector results.

Co-authored-by: morluto <[email protected]>

* fix(analysis-view): check inferred projector results

Require each projector result to match the unsigned result union before
sealing it, and parse the cycle guard's imports with Babel so comments
and side-effect imports are not misread.

* fix(analysis-view): close IL2CPP and Android symbol view inputs

Admit those projectors through AdmittedView and check test selections
against their request schemas, so misspelled fields fail at the call site.

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-30 05:41:58 +08:00
morlutoandCursor Agent 4a8d8f72b3 feat(debugger): share GDB and Rizin session caps as REA_ settings (#8056)
* feat(debugger): share GDB and Rizin session caps as REA_ settings

Move the duplicated deadline guidance into the owned debugger helper and
replace the shared session, queue, output, and 120s command caps with
launch settings that name their value, source, and range.

* test(gdb): assert the command-output setting on truncated exit evidence

The retained-output limitation now names REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES
instead of the old fixed-cap sentence.

* test(gdb): assert command-output limit fields on truncated exit

A truncated GDB exit now carries the command-output setting, value, and
source on the cleanup error. The coverage test reads those fields.

* fix(debugger): keep control replies and evidence hashes inside their budgets

Point the session-cap mutation target at DEFAULT_DEBUGGER_MAX_SESSIONS.
Give startup and cleanup replies a fixed 16 MiB budget so a small caller
output cap cannot truncate them. Lower the command-output maximum so
escaped Evidence JSON stays under Node's string ceiling, and keep GDB
startup timeout diagnostics when the error is rebuilt.

* test(debugger): throw typed Result errors before success checks

Success cases no longer assert ok and return. They throw the typed
error first, then check session, deadline, and close fields.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 05:41:54 +08:00
morlutoandCursor Agent 395b598383 refactor(admission): own inspector, web, and CLI state per host (#8058)
* refactor(admission): own inspector, web, and CLI state per host

Replace the process-global inspector, web-bundle, guidance, and JavaScript
counters with objects a host passes in, so independent instances do not
share admission or cleanup.

* fix(admission): keep worker and file cleanup on the host

Web-bundle and JavaScript workers retry only their host registry, and
each Inspector admission authorizes and closes its own file scope.
Browser providers can share one web-bundle admission.

* test(inspector): gate one admission's file cleanup

The isolation test delayed the first ArtifactResourceScope.retryCleanup
call, so a second call on a shared ElectronFileScope still finished.
Hold first.files.retryCleanup until the other admission's close
finishes, then release and join before restoring the method.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 05:23:33 +08:00
morlutoandCursor Agent b663c79552 fix(domain): replace bare parser errors with typed codes (#8057)
* fix(domain): replace bare parser errors with typed codes

OpenStep syntax, canonical JSON digests, managed-graph source checks,
frame fingerprints, and reference-flow invariants threw Error and
published the sentence. Each refusal now carries a code. Tests assert
that code, including OpenStep position, instead of the previous prose.

* test(domain): pin OpenStep refusal codes and positions

The syntax cases accepted any code and any offset. Each container
refusal now expects its code and source position, including the deep
suffix that changes (x from unterminated_array to expected_separator.
Scalar failures stay identical to the pinned decoder.

* test(filesystem): assert OpenStep keyed-archive codes

The deep-suffix keyed archive cases compared ArtifactReaderFailure
message text with the pinned decoder sentence. They now require
reason format and the OpenStep code and source position on the cause.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 05:23:10 +08:00
a4c183b750 refactor(adb): move device and client operations out of the provider switch (#8062)
* refactor(adb): move device and client operations out of the provider switch

Keep AdbProvider as the session owner. Device listing, device inspection,
and client identity now run in AdbDeviceOperations, and shared listing
execution construction lives next to the client identity result.

Co-authored-by: morluto <[email protected]>

* refactor(adb): use the shared client and provider identity types

Drop the ClientIdentity and ProviderIdentity aliases so method signatures
name AdbClientResult and AdbProviderIdentity directly.

Co-authored-by: morluto <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-30 05:02:41 +08:00
morlutoandCursor Agent fa1aa64ce4 fix(errors): store resource-constraint recovery as one shape (#8055)
* fix(errors): store resource-constraint recovery as one shape

AnalysisResourceConstraintError kept remediationAction, fixedAllowance,
and retryable as independent optionals, so a refusal could claim both a
fixed allowance and a separate remediation. The constructor now stores
the existing ResourceConstraintRecovery union, and the three accessors
read that object.

* test(errors): cover competing resource-constraint recovery fields

A runtime options object can still carry both a fixed allowance and a
remediation action. The constructor keeps the allowance and drops the
other field; the regression now supplies both.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 04:50:02 +08:00
morlutoandCursor Agent b894be6361 fix(native)!: report NativeAOT failures as reason codes (#8053)
* fix(native)!: report NativeAOT failures as reason codes

PeImage admission failures and parser recoveries used to publish
Error.message as NativeAotReport.reason. The report is now a union on
status, and reason is a typed code with the fields that code needs.
Complete reports keep reason null and a located header. Partial and
not_applicable reports carry a code. Caller budget mistakes throw
AnalysisInputError or AnalysisResourceConstraintError with a diagnostics
code instead of a bare Error.

* fix(native): keep out-of-domain hydration pointers partial

An inline pointer outside the unsigned 64-bit domain made
writeBigUInt64LE throw RangeError, and the hydration catch rethrew it
instead of recording a partial report. Those targets are now an
admission detail. The load-image summary schema still accepts the
optional Ghidra extension's string reason.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 04:49:57 +08:00
morlutoandCursor Agent 5f9187cd27 refactor(artifacts): describe formats in one registry (#8054)
* refactor(artifacts): describe formats in one registry

Reader construction, classification retention, container role, and
extraction now read one descriptor per domain format id. unknown has
no reader, and zip package families share one capability row.

* fix(artifacts): keep reader presence in format descriptor types

Descriptor helpers no longer widen every format to an optional reader,
so ArtifactReaderFormat stays the union of formats that construct one.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 04:27:17 +08:00
morlutoandCursor Agent 1c14f66356 refactor(contracts): register CLI and MCP tools by family id (#8051)
* refactor(contracts): register CLI and MCP tools by family id

Walk the published tool-family list once for each adapter so a new
family id must supply both a CLI registrar and an MCP registrar.

* fix(cli): keep family registration off the contract tables

Walk family ids from the startup-safe list, and register the web script
commands with the application family that owns their MCP contracts.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 04:25:48 +08:00
HawkForgeandClaude Opus 5.5 1da304b282 fix(adb): verify pushes to long device paths (#8047)
* fix(adb): verify pushes to long device paths

sha256sum echoes the device path, so a correct digest line for a path
near 1 KiB exceeded the fixed 1 KiB capture budget. The resulting
resource failure was then reported as device_digest_source "unavailable",
the same outcome as a device without sha256sum. Size the budget to the
path, and return a capture-budget failure as a resource constraint
instead of an unverified success.

Fixes #3369

* Keep adbDeviceAnalysis.ts as on main

* test(adb): keep only the long-path digest test

Main already covers an oversized checksum answer as a resource
constraint with the completed push retained.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-30 02:48:51 +08:00
ShuaixinHuang da5ec6b62c fix(macho): preserve literal segment name BOMs (#8040) 2026-10-30 02:47:33 +08:00
morlutoandCursor Agent c0c3902207 fix(native): preserve Mach-O target selection in macOS tools (#7831)
* fix(native): preserve Mach-O target selection in macOS tools

* fix(native): retain exact Mach-O slice signature coverage

* fix(native): admit x86_64 LIB64 signature selectors

* test(native): match signature checks to actual slices

* fix(native): recognize vtool refusals on private Mach-O aliases

Match vtool's container refusal to the operand the command received so a
parenthesized universal path keeps completed facts and the refusal
limitation. Expect codesign -a only when the target format is universal.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 00:03:23 +08:00
morlutoandCursor Agent 845080aef6 fix(session): align resource-only status and snapshot recovery (#7896)
* fix(session): align resource-only status and snapshot recovery

* fix(session): report independent workflows during resource sessions

* test(mcp): honor SQLite runtime availability

* test: compare resource snapshot guidance with its formatter

Assert projected error codes and the owning remediation action for
resource-session snapshot refusals, and type MCP error text as a closed
content item.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-30 00:03:19 +08:00
morluto bf115be2cd fix(evidence)!: report storage publication limits (#7887)
* fix(evidence): report storage publication limits

* test(evidence): cover staged storage failure cleanup
2026-10-29 23:03:09 +08:00
morluto 539a494d25 docs(setup): restore target and Hopper launch settings (#7902) 2026-10-29 23:03:02 +08:00
morlutoandCursor Agent ec4a0a01c4 fix(hopper): reconcile observed caller relationships (#7953)
* fix(hopper): reconcile observed caller relationships

* fix(hopper): retain completed caller scans on failure

Attach the native reply and collected forward facts when reconciliation
fails or is cancelled, and check scan stop codes separately from their
wording. Live observations keep REA evidence ids rather than provider
supplied ones, and the real Hopper verifier uses advertised MCP schemas.

* fix(hopper): answer caller-scan methods in the package fixture

The packaged Hopper stand-in echoed unknown bridge methods, so
analyze_function rejected rea_caller_scan_target as an invalid scan
target and the native package baseline returned unreadable_output.

* fix(hopper): share caller identities in failed scan observations

Failure partials repeated each caller identity under every destination.
The page budget counted compact replies, then schema parsing copied the
expanded form. Identities are stored once and destinations cite addresses.

* refactor(config): own the Hopper caller-scan budget default

The environment schema hard-coded the 2000 ms scan default. The domain
limit owner now names that millisecond budget and the schema reuses it.

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* test(flutter): assert ABI selector refusals as structured issues

inspect_dart_aot reports the ABI label as an issue on path abi. The
message is the selector contract, without an abi: prefix.

* fix(session): retain caller observations that cannot be replayed

A missing document selector blocks snapshot replay. Backward call-graph
and trace reads still keep the native result and forward observations,
and the workflow Evidence links those records.

* fix(session): keep decompile bodies out of the in-memory ledger

Caller and callee walks still stay in Evidence without a document.
procedure_pseudo_code does not, so decompile_all leaves each body in the
file-backed export instead of retaining the whole export in memory.

* fix(hopper): own caller-scan recovery and the page byte allowance

Scan facts stay in the domain. Environment recovery, including the time
budget bounds and the fixed allowances with no runtime override, is
appended from configuration. Setup forwards the budget, and the bridge
uses one byte allowance for instructions, native callers, and pages.

* fix(hopper): retain compact caller references within the scan budget

Recovered call sites stay in the address form the scan already counted.
Expanded identity copies are measured before allocation and refused when
they exceed the retention budget. Instruction lists are not part of the
retained reply.

* fix(session): omit instruction payloads from retained caller evidence

Documentless caller and callee reads stay in the ledger for the session.
Their evidence keeps call sites and drops instruction lists the scan
already consumed.

* fix(hopper): keep the owning caller address on each compact call site

A page reference marked source_procedure "caller" belongs to that page
item's caller. Saved sites record that address, and an owner missing from
the callee inventory is stored once instead of copied onto every site.

* fix(session): leave Hopper reply shaping to the Hopper adapter

Documentless caller evidence stores the adapter's raw result unchanged.
The session no longer reads native_result.instructions.

* test(hopper): throw typed scan failures before reading results

Successful caller-scan results now throw the typed error instead of
returning after an ok check. Expected failures assert structured tags,
stop codes, and reported limits.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 22:54:58 +08:00
8188497f13 feat(artifacts): inspect and extract Bun standalone module graphs (#6998)
* feat(artifacts): inventory and extract Bun standalone executable module graphs

Adds a Bun standalone artifact reader for Mach-O, PE, ELF and appended
trailers, supporting the 36-byte and 52-byte module record layouts. It
preserves module names, entry-point details and raw metadata, and
exposes contents, sourcemaps, bytecode and module-info as separate
entries for safe extraction without executing the binary. Parsing
rejects malformed or ambiguous graphs and enforces module-count,
retained-byte and expansion limits. Adds synthetic parser tests, CLI and
MCP coverage, agent skill guidance, and a verification script for
Bun-produced executables.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(artifacts): bound Bun section probes and reject truncated sections

Split Bun recognition into a bounded probe that reads only section
headers, length prefixes, and the Offsets/trailer suffix, and a single
payload load used by the reader. Inventory classification and extraction
admission no longer allocate the whole graph.

Match ELF `.bun` section names by comparing only the name bytes and
terminator, so many headers sharing one long name cost constant work each.
Reject sections whose declared extent ends past EOF instead of reading a
silently shortened window.

Launch Bun, Docker, and REA in the standalone verifier through the shared
owned verifier commands, and release the runtime root only after those
command families have settled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* style(artifacts): format Bun reader integration

* fix(contracts): describe static Bun inventory facts

* fix(artifacts): bound Bun metadata reads and combined path growth

---------

Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-29 22:31:50 +08:00
gensokyo-dev cac6e0613e fix(ci): enforce architecture guards before dependency setup (#8027)
* fix(ci): enforce architecture guards before dependency setup

* test(ci): bind cache policy to preceding Node setup
2026-10-29 21:29:15 +08:00
a108a1b817 fix(cli): report incomplete debugger cleanup as typed outcomes (#8021)
* fix(cli): report incomplete debugger cleanup as typed outcomes

debug-with-gdb and debug-with-rizin hand-rolled final cleanup in
try/finally blocks: a failed or interrupted final close only logged
and assigned the process exit status, so callers still received
complete successful Evidence with no typed error or partial
observation.

Both commands now route through the shared close owner used by the
other one-shot CLI commands (deliverCliAnalysisEvidence /
finishAnalysisCommand) via a small closeDebuggerCommandSession
helper. A rejected closeAll projects as ProviderCleanupError, which
the owner turns into cleanup_incomplete retaining the completed
Evidence as partial observation; a signal observed after finalization
projects as cancelled with the same retained observation. The
per-session close failure still only logs, preserving the rule that
the retained owner's closeAll decides completion.

Fixes #7378. Fixes #7429.

* test(cli): reject unmodeled GDB requests in cleanup regressions

* test(cli): match modeled GDB requests exactly

* Update tests/boundary/cli/gdbStartupCleanupCli.test.ts

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

---------

Co-authored-by: morluto <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-29 20:52:03 +08:00
gensokyo-dev b3d31ed106 fix: preserve nullable source-map facts and streamline model tests (#8018)
* test: repair MCP validation assertions and reuse model sessions

* test(ci): audit constrained guidance bundle fingerprints

* fix(browser): distinguish empty and absent source-map declarations

* refactor(browser): encode script metadata groups once

* test: assert structured path errors without freezing prose
2026-10-29 20:35:25 +08:00
morlutoandCursor Agent 34577eb1cc fix(filesystem): preserve undecodable symlink targets (#7935)
* fix(firmware): preserve symlink target bytes

* fix(apple): preserve undecodable dylib link targets

* style(tests): fix CI formatting

* test(firmware): select the named contract directly

* test(firmware): record raw bytes for a decodable symlink target

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* test(firmware): stat raw links without following them

access follows a symlink, so a published dangling raw-link still looked
absent. lstat and readlink inspect the link itself.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 20:07:30 +08:00
morlutoandCursor Agent e124bdfc54 fix(cli): preserve literal Markdown rendering (#7938)
* fix(cli): preserve literal Markdown rendering

* fix(cli): retain literal Markdown attributes

* fix(cli): preserve literal punctuation in markdown

* test(cli): update escaped empty object expectations

* style(cli): format the literal empty-object Markdown expectation

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* test(flutter): assert ABI selector refusals as structured issues

inspect_dart_aot reports the ABI label as an issue on path abi. The
message is the selector contract, without an abi: prefix.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 20:07:17 +08:00
记得吃中饭andgensokyo-dev 22cab8215a feat(artifacts): inspect and extract .NET single-file bundles (#3787)
* feat(artifacts): inventory and extract .NET single-file bundles

* fix(artifacts): retain bundle source I/O failure reasons

* docs(artifacts): record Linux bundle regression verification

* fix(artifacts): preserve bundle source failures and cancellation

* docs(artifacts): record synchronized bundle verification

* fix(artifacts): bind bundle extraction to inventoried host

* docs(artifacts): record bundle verification outcome

* fix(artifacts): bound bundle paths and suggest extraction

* fix(artifacts): preserve nested executable container boundaries

* test(artifacts): verify projected .NET members and simplify extraction admission

* fix(artifacts): explain fixed bundle limits at their producing boundary

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-29 20:06:58 +08:00
morlutoandCursor Agent 5c05eb88e5 fix(javascript)!: preserve operator influence in semantic traces (#7939)
* fix(javascript)!: preserve operator influence in semantic traces

* test(javascript): align semantic influence regressions

* style(javascript): format semantic IR

* test(javascript): follow literal argument seed influence

* fix(javascript): keep one literal node per operator operand

Reuse an existing literal for operator influence, and treat TypeScript
assertion wrappers as transparent when deciding whether a call result is used.

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* fix(javascript): index operator literals by range and initializer

Repeated string operands rescanned every retained literal and every
initializer flow. Lookup now uses those indexes, and a 1200-declaration
file still projects one literal per operand.

* fix(javascript): keep literal and call facts on their own occurrences

Operator literals match their own source range, so a nested function does
not inherit the enclosing declaration's literal. A direct argument still
reaches the call, while the assigned result follows modeled returns.
for-loop initializers and updates are discarded call results.

* test(javascript): expect conditional operand literals beside binding values

A conditional binding keeps one candidate literal per union value and one
operator literal per operand token. Those are different facts, so the
aggregate payload check counts four nodes and compares literal seeds in
query order.

* perf(javascript): index call return flows by call site

projectCalls filtered every call return for each assigned result. Group
those flows once while projecting returns-to-call edges.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 20:06:55 +08:00
morlutoandCursor Agent 6fa48f201f docs(apk): clarify rotated signer agreement (#7936)
* docs(apk): clarify rotated signer agreement

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* fix(javascript): ignore worker frames for a different source job

A failure frame for another job was dequeued as an output error, so a
later caller abort lost the race and was not reported as cancellation.

* test(flutter): assert ABI selector refusals as structured issues

inspect_dart_aot reports the ABI label as an issue on path abi. The
message is the selector contract, without an abi: prefix.

* fix(javascript): check each worker frame condition once

A ready frame is rejected on its own. A non-ready frame for another job
keeps waiting. The following id comparison repeated that skip, so it
never ran.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 20:00:02 +08:00
morlutoandCursor Agent 1ef5918179 fix(browser): preserve source-map declaration uncertainty (#7945)
* fix(browser): preserve source-map declaration uncertainty

* style(tests): fix CI formatting

* fix(browser): retain known source map differences

* fix(web): bind map comparisons to script metadata

* fix(browser): match source maps with partial script metadata

Keep each script's observed hash and declaration constraints instead of
dropping a metadata field for the whole URL group.

* test(flutter): assert ABI selector refusals as structured issues

MCP input validation reports the ABI path and the schema message separately.
The old concatenated "abi: ..." sentence is no longer the delivered error.

* fix(web): prove large source-map changes without factorial search

Groups larger than eight scripts were reported as unexplained, so nine
x.map declarations plus one omission compared as unknown against ten
y.map declarations. Declaration counts reject impossible placements, and
augmenting paths assign the rest without retrying identical scripts.

* test(mcp): assert bundle path issues by field

compare_bundles reports each relative path as an invalid_format issue
instead of one joined diagnostic sentence.

* fix(web): match repeated source-map scripts by count

Identical declarations share one group, and a shared omission pool is
counted once. A 2000-script self-compare no longer builds a dense
assignment graph.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 18:50:26 +08:00
51ad84516e feat(environment): serve sorted creates, listed references and toggles (#7600)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

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

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

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

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

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

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

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

* docs(environment): record verification for blank members and sorted writes

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

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

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

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

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

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

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

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

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

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

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

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

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

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

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

* docs(environment): record the census with main's unknown-clock rule merged

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

* feat(environment): serve query numbers, counted values and made objects

- A query parameter accepts a number or boolean, which serving sends as its
  JSON text, as agents send page sizes and flags.
- A sequence may start at the value its first create took above every value
  its type held (Vikunja's per-project task index), and a later create's
  composed, template or started-sequence rule is adopted when it also spells
  the first create's value.
- A made-object rule explains an object the application makes with each
  created entity: members the request stamped, members holding the entity's
  own id, and counts one past the held objects' values.
- A create's answer names the id of the one object a one-element array member
  holds, so bulk creates link to their type.
- The skill reads each listed object by id before writing.

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

* feat(environment): let a linked create take path values it stores

A create linked to its type's own collection, whose path has no parameters,
accepts any path values when its plan stores each one in a field, as a Vikunja
task stores its project; replay and serving apply it alike.

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

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

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

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

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

* docs(environment): verify query values, counted values and made objects

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

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

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

* docs(environment): record the call-values checks at #7274 93821024

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

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

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

* docs(environment): record the call-values checks at #7274 45832406

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

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

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

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

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

* fix(environment): check made objects as recorded and keep their ids and clocks honest

- A made object's clock counts as a write clock, so a capture with no clock
  anchor withholds the create instead of stamping 1970.
- Members holding the entity's identity follow the id written, generated or
  sent, through the type's id field.
- A later create's made object is checked as recorded, before its own members
  are rewritten, so one naming another entity withholds the plan.
- A later create's template is adopted only when it spells that create's value
  too, not just when the first create's text holds its source value.

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

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

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

* docs(environment): count six withheld plans that explain more, not seven

The bulk task create counted among them is served on this branch.

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

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

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

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

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

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

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

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

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

* chore(environment): bump the skill to 52 above #7260's 51

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

* chore(environment): put back the call-values hunks main's merge took its side for, and bump the skill to 53

The merge of main kept main's side where it overlapped this branch, so it
could commit without a conflict. This restores this branch's side next to
main's: made-object agreement beside the & slug spellings, sequence starts
in the earlier evidence (now built by earlierOf), the combined value
imports, and recording step 2 beside main's UI-first step 3. Main is at
skill 50 and #7274 at 52, so this branch takes 53.

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

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

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

* feat(environment): serve writes whose calls send different members

Recordings now vary what a type's two creates and updates send, and REA
withheld writes where a later call left out what the first sent. An update
plan's required members narrow to those every call sent; a member one create
sent and another left for the application to number is a sequence that
takes the value a call sends; a PUT's left-out member the application
cleared becomes that member's default; and an update's clock is held as
the read after it spells it. The skill spaces creates (skill 54).

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

* fix(environment): keep a later create's made object only with clocks its request stamped

followsMadeObjects only checked that a made object's clocks parsed, so a
later create whose object showed an older clock still passed, while serving
stamps the write's clock. Its clocks must now be in the rule's format and
within the stamp window of that create's request.

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

* fix(environment): accept sent values either way, check defaults against every call, and store update clocks as reads spell them

A sequence that takes sent values now accepts one a later create sent
after the first left the member to be numbered. A default an update learned
must agree with every update that left its member out, else the plan is
withheld. An update plan whose read-back spells a stamped clock at a coarser
precision keeps that read as observation.read, and serving stores the
update's clocks in its spelling while answers keep the response's.

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

* fix(environment): check recorded member and sequence values

* fix(environment): compare soft-deleted read fields

* fix(environment): reject contradictory learned sequences

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

* fix(environment): replay lists that include deleted records

* feat(environment): serve sorted creates, listed references and toggles

A create a whole listing placed last is placed again by the sort a later
listing shows. A field holding the entities a sent list of ids names is a
reference with `many`, and a create that sent the member empty agrees with
one that named entities. A scenario request whose body Playwright did not
expose and that names no Content-Type sent no body. An item action calls
show flipping a boolean both ways learns a `toggle` rule. Skill 55 asks
recordings to run an action twice on one object.

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

* fix(environment): count a sequence number a create sends, as replay does

Serving now records a number a call sends for a `sequence` with `sent` in the
same count history it generates from, so after that entity is deleted the next
generated number still follows it, as replay's takeSequences does.

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

* docs(environment): record the final gate run in the verification

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

* fix(environment): make no count of a made-object member earlier creates held alike

A count only a later create shows starts at the exemplar's value, which is
right only while the exemplar is the one create accepted: creates accepted
after it showed the member unchanged, which a count would have moved. The
merge now refuses such a count, as the top-level sequence check does.

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

* docs(environment): restate the verification after merging #7539

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

* fix(environment): check PUT defaults against reads, and respell update clocks by serving's rule

A default a PUT learns must also agree with what the read after an earlier or
the current call showed for the member its answer left out, so a call whose
read showed the member kept withholds a later clearing default.

Replay and serving now choose an update clock's stored spelling by one rule,
readSpellsCoarser: the read after it at a coarser precision in the same zone
and separator. A read in another zone or separator is no longer adopted by
replay, which serving could not store, and a create's plain clocks stay as
answered, as serving makes them.

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

* fix(environment): keep sorts earlier listings ruled out, and empty added references

A sort a whole listing that placed a create did not follow stays ruled out for
that create, though the entity that showed it is later deleted. A create that
sends a reference's member empty after a later create added the reference
agrees with it, as with a reference the first create showed.

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

* docs(environment): restate the verification after the review round

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

* fix(environment): leave out a reference the observed create left out

A create the plan observes that left out a reference's member stored no such
field, so a served call leaving the member out stores none either, though a
later recorded create sent it empty; a call sending it empty still stores that.

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

* docs(environment): restate the verification after #7539 merged main

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

* fix(environment): keep the largest sequence value in replay, and store update clocks by the kept read

Replay now keeps the largest value a sequence took, as serving's commitWrite
does, so a lower sent value no longer lowers the next number.

Serving stores every update's clocks in the spelling of the read the plan
keeps as observation.read; replay now does the same through
readSpelledClocks, instead of adopting each update's own read, so a later
update whose read spells its clock otherwise contradicts the plan. The plain
clock branch of restampedFields, which no caller used any more, is gone.

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

* docs(environment): restate the verification after merging #7539 3b800a10

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

* fix(environment): keep learned sorts, references, and toggles faithful

Do not adopt a sort an earlier update already ruled out. Learn a list
reference only when every element shows the same nonempty fields. Promote
a toggle only when every call started from a boolean.

* test(environment): compare list-reference fields with matching values

The mixed-field case uses the held tag's color, so rejection comes from
the field set rather than a value the recording never stored.

* test(mcp): assert bundle path issues by field

The compare_bundles rejection reports each relative path as an
invalid_format issue instead of one joined diagnostic sentence.

* test(mcp): assert field issues for bundle paths and Dart ABI

Relative bundle paths and rejected ABI selectors are reported as
input issues on those fields, not as one joined diagnostic sentence.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 18:46:18 +08:00
01e00dae72 fix(process): corroborate exiting Linux ownership candidates (#7958)
* fix(process): corroborate Linux exits after procfs environ read races

A token read failing with ENOENT/ESRCH may race with process exit while
native identity observation still sees a live PID. Recheck native lifetime
and accept absence only when corroborated; keep unverifiable or reused
PIDs fail closed.

Refs #7790

* fix(process): recheck Linux exit after final token retry

A process can exit after the final retry of a failed procfs environment
read. Re-observe native identity before producing final cleanup failures,
and keep PID replacement and uncertain identity fail-closed.

Add five integration-facing sweep regression cases for absence, live,
unavailable, replaced and zombie-replaced candidates.

Refs #7790

* fix(process): bound native exit settlement for token-open races

After a terminal ENOENT/ESRCH run-token read, allow a tightly bounded
native-only settlement window to corroborate pending Linux process exit.
Retain an explicit failure for live/unavailable or changed PID identities,
without inferring exit from the token error itself.

Cover delayed absence, delayed replacement and bounded live retries.

Refs #7790

* fix(process): share native token exit settlement

* fix(process): signal only groups with validated members

* fix(process): retain lineage through exit settlement

* fix(process): type mixed identity observations in exit checks

Map inference collapsed a readable/absent union onto the absent
overload after main tightened ProcessIdentityObservation.

* fix(process): refresh groups whose members exit during revalidation

A same-group child forked after the revalidation snapshot is not an
empty group, so cleanup reads the table again before skipping the signal.

* test(process): arm group-refresh exit from the revalidation snapshot

The regression follows the table read after planning observed the
launcher token, and requires a later refresh to see the forked child.

* test(mcp): assert bundle path issues by field

The compare_bundles rejection reports each relative path as an
invalid_format issue instead of one joined diagnostic sentence.

* test(mcp): assert field issues for bundle paths and Dart ABI

Relative bundle paths and rejected ABI selectors are reported as
input issues on those fields, not as one joined diagnostic sentence.

---------

Co-authored-by: Harshit Sharma <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 18:40:12 +08:00
860fdd8fbe feat(macho): report per-slice encryption status in inspect_macho (#5359)
* refactor(macho): extract non-throwing container and slice load-command readers

Split readMachoImage into readMachoContainer, which classifies a file as
thin, FAT or not Mach-O and keeps FAT range issues per slice, and
readSliceLoadCommands, which reads one slice header and frames its load
commands, returning every command framed before a failure.

readMachoImage keeps its strict policy on top of them: commands framed
before a framing defect still decode first, an unsupported FAT slice
still yields to a later malformed one, and the first FAT range issue is
still reported. A characterization test pins that precedence for inputs
with several defects; it passes against both the previous and the new
implementation.

Part of #4306.

* feat(macho): report per-slice encryption status in inspect_macho

inspect_macho now reports the LC_ENCRYPTION_INFO(_64) state of every
slice in a new `encryption` collection, read in process from load
commands without reading code bytes.

- Each slice is encrypted, not-encrypted, absent, malformed or unknown.
  Absent requires a completed load-command walk; malformed and unknown
  carry a reason and keep commands read before the failure.
- The named range is end-exclusive in slice and input-file coordinates
  and maps to file-backed segment fragments, VM addresses and the
  overlapping nonempty sections.
- A ciphertext range adds a limitation naming its input-file bytes.
- The in-process read and the native tools are bracketed by one regular
  file descriptor and stat identity checks; a change reports
  artifact_changed. A missing or inaccessible target keeps the tool
  facts and reports encryption as non-exhaustive, never absent.
- Retained Evidence recorded before this field is parsed by the owning
  schema and reports encryption as unknown in the macho view and in
  managed/native verification.

Fixes #4306

* fix(macho): keep slice facts and failures under large tables and close errors

- Frame load commands through a visitor instead of retaining every
  command, restoring the pre-refactor memory profile (2M eight-byte
  commands: peak heap 370 MiB -> 0.9 MiB).
- Confirm a large FAT container from a valid slice's Mach-O header so a
  sibling's range fault stays per slice instead of hiding healthy slices.
- Name only sections whose bytes the range and their segment both cover.
- Throw inspection failures inside the owned descriptor callback so a
  close failure keeps them as its primary error.

* fix(macho): map encrypted ranges only within segment VM extents

mapRange gave every file-backed segment fragment VM addresses, even for a
segment with no VM size, whose file bytes dyld never maps, and past vmsize
when a segment declared more file bytes than memory.

Read vmsize with the segment. A segment with no VM size keeps its fragment
with null VM addresses. A file size beyond the VM size, or a VM range past
the address space, makes the mapping unknown with a reason while the slice
keeps its encryption status.

The docs also claimed the encryption read never reads code bytes. Like
every Mach-O container read, a 0xcafebabe input of at most 16 MiB is read
whole to rule out a JVM class file; say so.

* fix(macho): read encryption through the admitted descriptor facts

Main's withArtifactRegularFile now hands callers the admitted handle and
its initial stats. Use those facts instead of re-stating the handle, and
pass the legacy Evidence payload straight to createEvidence.

* fix(macho): retain a finished report when the target close fails

The file owner keeps the completed callback value when the descriptor close
fails or cancellation arrives during close. inspect_macho now validates that
value and retains the report as the error's partial observation, as
inspect_signature does, instead of returning only the cleanup or
cancellation error.

* test(native): supply readEncryption in merged dyld refusal cases

main's new dyld refusal cases build NativeMachoInspectionContext without
the encryption reader this branch requires.

* test(views): bind the Mach-O encryption page to its facet

main's facet-ownership case builds inspect_macho Evidence without the
encryption coverage this branch requires; the fixture now carries one
slice row and the loop checks the encryption page like the other facets.

* fix(macho): bound encryption commands and range mapping across slices

A slice retains at most two encryption load commands, since a second one
already makes it malformed; the reason keeps the full count. Range mapping
in every slice shares one budget of segment and section records, charged
from each segment command's capacity before it is decoded. A slice past
the budget keeps its encryption status and reports an unknown mapping.

* style(macho): format encryption reader and binding test

* fix(macho): stop retaining segments past the mapping budget

Segment commands are collected during framing only while their mapping
records still fit in the budget shared by all slices, and the budget is
charged only when the slice maps its range.

* docs(macho): clarify retained mapping allowance

* fix(ci): own the Mach-O encryption test in Apple scope

---------

Co-authored-by: t <t@t>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-29 18:19:39 +08:00
morlutoandCursor Agent d3ecf68000 docs(website): clarify platform setup requirements (#7925)
* fix(website): clarify platform setup requirements

* style(website): format platform guidance

* docs(website): clarify Ghidra Linux host support

* style(website): format the Linux platform guidance paragraph

* docs(website): refresh gettext locations after guidance formatting

The wrapped get-started paragraph shifted source lines. Message ids and translations are unchanged.

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 16:55:17 +08:00
c88e1b0b43 feat(guidance): search installed workflow sections (#6996)
* feat(guidance): search bundled REA workflow guidance

* fix(guidance): address review findings

* test(guidance): fix boundary and dispatcher coverage

* test(setup): use supported doctor fixture runtime

* fix(guidance): resolve CI conformance, coverage, and review findings

* fix(guidance): own complete requests through shutdown

* test(guidance): bound lifecycle gates and join failed work

---------

Co-authored-by: Deepak-Kambala <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-29 16:47:10 +08:00
131fbe0754 feat(environment): serve writes whose calls send different members (#7539)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

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

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

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

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

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

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

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

* docs(environment): record verification for blank members and sorted writes

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

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

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

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

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

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

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

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

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

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

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

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

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

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

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

* docs(environment): record the census with main's unknown-clock rule merged

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

* feat(environment): serve query numbers, counted values and made objects

- A query parameter accepts a number or boolean, which serving sends as its
  JSON text, as agents send page sizes and flags.
- A sequence may start at the value its first create took above every value
  its type held (Vikunja's per-project task index), and a later create's
  composed, template or started-sequence rule is adopted when it also spells
  the first create's value.
- A made-object rule explains an object the application makes with each
  created entity: members the request stamped, members holding the entity's
  own id, and counts one past the held objects' values.
- A create's answer names the id of the one object a one-element array member
  holds, so bulk creates link to their type.
- The skill reads each listed object by id before writing.

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

* feat(environment): let a linked create take path values it stores

A create linked to its type's own collection, whose path has no parameters,
accepts any path values when its plan stores each one in a field, as a Vikunja
task stores its project; replay and serving apply it alike.

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

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

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

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

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

* docs(environment): verify query values, counted values and made objects

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

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

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

* docs(environment): record the call-values checks at #7274 93821024

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

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

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

* docs(environment): record the call-values checks at #7274 45832406

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

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

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

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

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

* fix(environment): check made objects as recorded and keep their ids and clocks honest

- A made object's clock counts as a write clock, so a capture with no clock
  anchor withholds the create instead of stamping 1970.
- Members holding the entity's identity follow the id written, generated or
  sent, through the type's id field.
- A later create's made object is checked as recorded, before its own members
  are rewritten, so one naming another entity withholds the plan.
- A later create's template is adopted only when it spells that create's value
  too, not just when the first create's text holds its source value.

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

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

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

* docs(environment): count six withheld plans that explain more, not seven

The bulk task create counted among them is served on this branch.

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

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

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

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

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

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

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

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

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

* chore(environment): bump the skill to 52 above #7260's 51

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

* chore(environment): put back the call-values hunks main's merge took its side for, and bump the skill to 53

The merge of main kept main's side where it overlapped this branch, so it
could commit without a conflict. This restores this branch's side next to
main's: made-object agreement beside the & slug spellings, sequence starts
in the earlier evidence (now built by earlierOf), the combined value
imports, and recording step 2 beside main's UI-first step 3. Main is at
skill 50 and #7274 at 52, so this branch takes 53.

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

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

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

* feat(environment): serve writes whose calls send different members

Recordings now vary what a type's two creates and updates send, and REA
withheld writes where a later call left out what the first sent. An update
plan's required members narrow to those every call sent; a member one create
sent and another left for the application to number is a sequence that
takes the value a call sends; a PUT's left-out member the application
cleared becomes that member's default; and an update's clock is held as
the read after it spells it. The skill spaces creates (skill 54).

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

* fix(environment): keep a later create's made object only with clocks its request stamped

followsMadeObjects only checked that a made object's clocks parsed, so a
later create whose object showed an older clock still passed, while serving
stamps the write's clock. Its clocks must now be in the rule's format and
within the stamp window of that create's request.

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

* fix(environment): accept sent values either way, check defaults against every call, and store update clocks as reads spell them

A sequence that takes sent values now accepts one a later create sent
after the first left the member to be numbered. A default an update learned
must agree with every update that left its member out, else the plan is
withheld. An update plan whose read-back spells a stamped clock at a coarser
precision keeps that read as observation.read, and serving stores the
update's clocks in its spelling while answers keep the response's.

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

* fix(environment): check recorded member and sequence values

* fix(environment): compare soft-deleted read fields

* fix(environment): reject contradictory learned sequences

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

* fix(environment): replay lists that include deleted records

* fix(environment): count a sequence number a create sends, as replay does

Serving now records a number a call sends for a `sequence` with `sent` in the
same count history it generates from, so after that entity is deleted the next
generated number still follows it, as replay's takeSequences does.

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

* fix(environment): make no count of a made-object member earlier creates held alike

A count only a later create shows starts at the exemplar's value, which is
right only while the exemplar is the one create accepted: creates accepted
after it showed the member unchanged, which a count would have moved. The
merge now refuses such a count, as the top-level sequence check does.

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

* fix(environment): check PUT defaults against reads, and respell update clocks by serving's rule

A default a PUT learns must also agree with what the read after an earlier or
the current call showed for the member its answer left out, so a call whose
read showed the member kept withholds a later clearing default.

Replay and serving now choose an update clock's stored spelling by one rule,
readSpellsCoarser: the read after it at a coarser precision in the same zone
and separator. A read in another zone or separator is no longer adopted by
replay, which serving could not store, and a create's plain clocks stay as
answered, as serving makes them.

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

* fix(environment): keep the largest sequence value in replay, and store update clocks by the kept read

Replay now keeps the largest value a sequence took, as serving's commitWrite
does, so a lower sent value no longer lowers the next number.

Serving stores every update's clocks in the spelling of the read the plan
keeps as observation.read; replay now does the same through
readSpelledClocks, instead of adopting each update's own read, so a later
update whose read spells its clock otherwise contradicts the plan. The plain
clock branch of restampedFields, which no caller used any more, is gone.

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

* fix(environment): keep a left-out default from numbering, a read's sub-millisecond digits, and references named by member

A member a call left out and the application stored a fixed value for is no
longer turned into a numbered sequence by a later call that left it out.
Serving shortens an update's clock to the read's precision from the answer's
own digits, keeping microseconds. A PUT that names a reference field's entity
by its member supplies that field, for the default check and in serving.

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

* fix(environment): count a reference a later PUT names toward the default an earlier one cleared

The default check now reads the reference rules the current call shows as
well as the plan's, so a PUT naming the owner by ownerId after one that
cleared it is served, as in the other order.

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

* fix(environment): number a left-out member past every sent value, and never fill it with a sent blank

A later create the application numbered at or below a value an earlier create
sent no longer turns the echo into a numbered sequence, since serving counts
past every sent value. A member a numbered sequence covers is not filled with
an empty value another call sent when a call leaves it out.

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

* fix(environment): count sent values in the sequence's scope before numbering a left-out member

The largest value earlier creates sent is kept by type and by collection, and
a collection-scoped sequence adopted later checks only its own collection, so
a number sent in another collection no longer withholds a create numbered
within its own.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-29 16:14:05 +08:00
morlutoandCursor Agent 3503578f91 fix(session): preserve cancellation during mutation retirement (#7959)
* fix(session): preserve cancellation during mutation retirement

* fix(session): retain exact cleanup owners through retry

---------

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 16:05:43 +08:00
morlutoandCursor Agent bd3792566d docs(skill): clarify scenario pause deadlines (#7961)
Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 16:05:03 +08:00
f7cd4e5256 fix(adb): validate screen capture PNG headers (#7962)
* fix(adb): validate screen capture PNG headers

* fix(adb): state screen header validation limits

* style(adb): format screen capture assertion

Match the current formatter after integrating main.

Co-authored-by: morluto <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-29 16:04:49 +08:00
morlutoandCursor Agent 2b85a4a2ba fix(native): preserve bookmark address uncertainty (#7966)
fix(analysis): preserve bookmark address uncertainty

Co-authored-by: Cursor Agent <[email protected]>
2026-10-29 15:54:33 +08:00
gensokyo-dev e3d8d20236 fix(native): preserve dispatch metadata format refusals (#8013)
* fix(native): preserve dispatch metadata format refusals

* test(native): normalize CLI format refusal envelopes

* test(native): assert admitted metadata target paths
2026-10-29 15:48:42 +08:00
17ceb77ca5 fix(mcp): preserve input validation issue attribution (#7981)
* fix(mcp): preserve input validation issue attribution

* fix(mcp): charge nested issue projection before allocation

* style(mcp): format server declarations

* fix(mcp): avoid duplicate fallback diagnostics

* test(mcp): assert structured validation issues

* test(mcp): align browser input issue assertions

* fix(mcp): retain SDK diagnostic parts once

* style(mcp): format diagnostic projection

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-29 15:39:10 +08:00
STDquantum f25d1e3e9f fix(cli): preserve Amp MCP registration settings (#7995)
* fix(cli): preserve Amp registration environment and report failures (#7538, #7536)

* fix(cli): normalize equals-form mcp add agents before Incur parses them (#7538)

* fix(cli): remove the Amp settings backup after a verified registration (#7538)

* fix(cli): secure Amp registration backups

* test(cli): cover Amp registration backup permissions

* fix(cli): retain Amp registration recovery backups

* fix(cli): create private Amp recovery copies

* test(cli): fix Amp backup assertion typing

* fix(cli): preserve typed Amp backup failures

* fix(cli): handle Amp backup cleanup failures
2026-10-29 15:23:42 +08:00
Dragolone 124e97eac5 fix(native): explain categories whose class reference names no class (#8006)
An Objective-C category whose class field is null, or is bound to a
symbol other than `_OBJC_CLASS_$_`, was recorded as partial with
`category_class_unresolved`, but no issue was raised, so the
`objc_properties_categories` coverage facet reported `partial` with a
null reason. swiftc emits such categories for @objc members of an
extension on another module's Swift class and binds the class field to
the class metadata symbol (`_$s…CN`); Keynote and Notability carry them.

Report the reference as a `category_class_unresolved` issue naming the
bound symbol, or stating that the reference is null, matching the issue
already raised for an unreadable local class reference.
2026-10-29 15:14:10 +08:00
Dragolone 95637a3d70 fix(artifacts): name every U-Boot uImage type in the pinned image.h (#8007)
TYPE_NAMES stopped at flat_dt (8), while the U-Boot include/image.h
commit that the tables are pinned to defines image types 0-47. A header
declaring a later type, such as kernel_noload (14) or tfa-bl31 (45),
was reported with type_name "unknown".

Add types 9-47 with their mkimage -T short names from boot/image.c at
the same commit. IH_TYPE_LOADABLE (22) has no short name there and is
named "loadable", as other tables already name identifiers U-Boot does
not list. Identifiers past the enum stay "unknown".
2026-10-29 15:13:37 +08:00
ShuaixinHuang ca666ec130 fix(pe): reject invalid certificate bit-string padding (#8011) 2026-10-29 15:11:28 +08:00
gensokyo-dev 45cc935964 fix(native): accept walkable Mach-O command tables (#8002)
* fix(native): accept walkable Mach-O command tables

* test(native): verify large command tables through macOS CLI and MCP
2026-10-29 14:39:20 +08:00
morluto d82ba11ce2 test(ci): allow more time for Windows CLI subprocesses 2026-10-29 14:35:12 +08:00
LN Bodicherlaandgensokyo-dev c9bf6a9df7 feat(managed)!: show readable type names in member signatures (#5309)
feat(managed): show readable type names in member signatures

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-29 14:34:42 +08:00
morluto afc18131f4 fix(ci): reuse shared JavaScript AST child traversal 2026-10-29 14:33:14 +08:00
morluto 54f5cf7e14 fix(javascript): retain static facts under projection pressure (#7986)
* fix(javascript): retain static facts under projection pressure

* fix(javascript): scope IPC ambiguity to omitted projections

* fix(javascript): preserve IPC uncertainty for omitted sources

* fix(javascript): qualify IPC pairing by wrapper coverage

* fix(javascript): retain provisional IPC wrapper matches
2026-10-29 14:02:35 +08:00
gensokyo-dev f60e42a9ae fix(mcp)!: bound validation work before tool dispatch (#7905)
Reuse the pinned SDK's combined array-element and object-member admission
before canonical validation, deferred runtime loading and tool execution.
Share a configurable finite allowance across eager/deferred HTTP and stdio,
and project refusals to bounded, actionable transport resource errors.

Preserve canonical CLI/application contracts, caller-selected input values,
ordinary validation semantics and complete Evidence access. Exercise actual
transport boundaries, real artifact/TShark workflows, packaged installation,
continued service and downstream identity/capacity regressions.

Fixes #7882

BREAKING CHANGE: MCP tool arguments now default to 1,024 combined array
elements and object members per call. Select a larger positive finite
REA_MCP_MAX_INPUT_ELEMENTS allowance and restart for larger inline inputs,
or use retained/file selectors where supported. Inputs are never truncated.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-29 05:56:38 +00:00
morluto ddefba83ef test(artifacts): handle blocked open-parent renames (#7904)
* test(artifacts): handle blocked open-parent renames

* test(artifacts): exercise blocked parent rename cleanup
2026-10-29 13:45:12 +08:00
gensokyo-dev b966ebeb22 docs(readme): align Linux x64 analysis prerequisites (#7997) 2026-10-29 13:44:07 +08:00
RRFZ fca331804a fix(javascript): select JSON and addon loaders by exact suffix (#7821)
Node's CommonJS loader matches registered extensions case-sensitively, so
require("./data.JSON") and require("./addon.NODE") run as JavaScript.
Derive target_json_status and the native loads relation from the exact
loader suffix instead of the inventory's case-folded file kind.

Fixes #7623
2026-10-29 13:43:55 +08:00
RRFZ 4fbce5c122 fix(cli): reject empty IPS and PE import targets before resolution (#7963)
inspect-apple-ips and inspect-pe-imports were the two commands in the
binary diagnostics group whose positional target was still a plain
string. An empty argument passed CLI validation, was resolved to the
working directory, and failed later as a non-file input, unlike their
siblings (inspect-elf-notes, inspect-pe-certificates and others) that
already reject it at validation.

Declare both targets with the shared nonempty, NUL-free local path
schema the rest of the file uses. Explicit "." is unchanged.

Fixes #7421
2026-10-29 13:43:25 +08:00
ShuaixinHuang 35ea02d27e fix(xml): reject ambiguous namespace attribute identities (#7991) 2026-10-29 13:38:37 +08:00
吴佳浩Alben 2bfd84b2c8 fix(javascript): emit no fork when a literal stdio array omits the IPC channel (#7992)
* fix(javascript): emit no fork when a literal stdio array omits the IPC channel

Node's fork() requires an explicit stdio array to carry the IPC channel:

    if (!ArrayPrototypeIncludes(options.stdio, 'ipc'))
      throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio')

The throw is synchronous, before any child process exists, but the analysis
reported a resolved fork with stdioMode "array" — byte-identical to the valid
`[..., "ipc"]` form — which projects to a child-process node, a resolved spawns
relation and resolved connects-stdio.

Measured on Node 24.22.1:

    fork({stdio: ["inherit","inherit","inherit"]}) -> THROWS ERR_CHILD_PROCESS_IPC_REQUIRED
    fork({stdio: ["pipe","pipe","pipe"]})          -> THROWS ERR_CHILD_PROCESS_IPC_REQUIRED
    fork({stdio: ["ipc"]})                         -> SPAWNED connected=true
    fork({stdio: ["inherit","inherit","inherit","ipc"]}) -> SPAWNED connected=true
    fork({stdio: "pipe"})                          -> SPAWNED connected=true
    spawn({stdio: ["inherit","inherit","inherit"]})-> SPAWNED connected=false

A string stdio is normalised with "ipc" appended, so only the array form can omit
it. The same array is valid for spawn, which has no IPC requirement.

Drop a fork candidate only when its stdio array is fully literal and provably
lacks "ipc"; a spread or a non-literal element may still supply the channel and
stays a candidate.

* chore: retrigger title and readiness checks after the title was corrected
2026-10-29 13:38:29 +08:00
d39b3000ec feat(artifacts): inventory and extract U-Boot FIT images (#7771)
* feat(artifacts): inventory and extract U-Boot FIT images

open_binary refused U-Boot Flattened Image Tree files as an unsupported
format, so inspect_artifact and extract_artifact could not list or
recover a FIT's kernel, devicetree, ramdisk and loadable images.

Recognize an FDT v16/v17 with a top-level /images node and parse it with
the existing parseDevicetree. Each /images child becomes an inventory
entry named images/<unit-name>.bin that extracts its stored bytes from
embedded data, data-offset (past the 4-aligned devicetree end) or
data-position (from the file start). Image format_metadata records the
declared type, arch, os, compression, load and entry; root metadata
records the description, timestamp and configurations.

crc32, md5, sha1, sha256, sha384 and sha512 hash nodes are verified in
one streamed pass per image; other algorithms are reported unverified.
Signatures are reported, not verified, and nothing is decompressed,
decrypted or executed. Data outside the file, overlapping or duplicate
image data, a missing or repeated data source and hash mismatches fail
closed. The devicetree is bounded by the fixed 16 MiB allowance;
external image data is streamed from disk.

Refs #7129

* fix(artifacts): bound FIT configuration references and suggest analysis only for plain images

* fix(artifacts): retain buffered FIT stream cancellation

* test(artifacts): gate FIT faults on the observed read phase

* fix(artifacts): verify FIT links and summary identities

* fix(artifacts): bound FIT diagnostics and retain unknown signatures

* fix(artifacts): compare FIT declarations and resolved data links

* fix(artifacts): admit FIT raw probes through the shared loader contract

* fix(artifacts): preserve FIT field availability independently

* fix(artifacts): isolate nested entry metadata from reader facts

---------

Co-authored-by: gensokyo-dev <[email protected]>
Co-authored-by: gensokyo-dev <342580529+users.noreply.github.com>
2026-10-29 13:33:26 +08:00
gensokyo-dev d157560d40 fix(cli): reject empty Evidence file arguments (#7988) 2026-10-29 12:34:46 +08:00
morluto 429355ca4d fix(ci): repair native fixture and unused export 2026-10-29 11:29:10 +08:00
gensokyo-dev 50b2ab137f fix(browser): preserve DOM length units in captures and comparisons (#7971)
* fix(browser): declare DOM node value length units

* fix(browser): preserve unknown DOM length comparisons
2026-10-29 11:27:01 +08:00
gensokyo-dev cf9f010dcd feat(managed): filter and project retained member views (#7968)
* feat(managed): filter and project retained member views

* test(managed): guard relationship endpoint filter semantics
2026-10-29 10:39:34 +08:00
morluto c19f13ca03 fix(setup): preserve YAML annotations during refresh (#7944)
* fix(setup): preserve YAML annotations during refresh

* fix(setup): preserve yaml argument comment ownership
2026-10-29 10:12:09 +08:00
morluto 62ce357f16 fix(artifacts): preserve known metadata without content hashes (#7932)
* fix(artifacts): preserve known metadata without content hashes

* fix(artifacts): keep missing relation projections unknown
2026-10-29 10:10:41 +08:00
morluto 953a7689df fix(uninstall): preserve layered OpenCode registrations (#7969)
* fix(uninstall): preflight layered OpenCode fallback

* fix(uninstall): preserve inherited foreign registration facts

* fix(uninstall): preflight legacy OpenCode layer removal
2026-10-29 10:07:27 +08:00
morluto 3dcb40dc26 fix(uninstall): protect ZCode fallback registrations (#7970)
fix(uninstall): preserve inactive ZCode fallback servers
2026-10-29 10:05:35 +08:00
morluto 7b9bcf724f style: fix native documentation and test formatting 2026-10-29 10:02:22 +08:00
gensokyo-dev 7ca216a266 fix(docs): restore translated offline provider ownership notes (#7964) 2026-10-29 09:38:51 +08:00
morluto 419a211304 fix(cli): preserve caller paths in format inspections (#7855)
* fix(cli): preserve caller paths in format inspections

* fix(cli): retain caller path on partial evidence

* test(cli): preserve caller path in adapter parity checks

* test(rust): account for retained caller path

* fix(wasm): compare shared cli and mcp evidence
2026-10-29 09:26:22 +08:00
morluto 63f05d1cad fix(javascript): preserve uncertainty for unexamined sources (#7911)
* fix(javascript): preserve uncertainty for unexamined sources

* fix(javascript): compare observed package parsing status
2026-10-29 09:25:25 +08:00
morluto 95f0625781 fix(javascript): preserve uncertain with-scope member writes (#7914)
* fix(javascript): preserve uncertain with-scope member writes

* fix: retain nested initializer candidates

* fix(javascript): admit initializer values in worker transport

* fix(tests): remove duplicate MCP test imports
2026-10-29 09:23:34 +08:00
morluto 611c30cb20 fix(browser): deduplicate scenario coverage guidance (#7933)
* fix(browser): deduplicate scenario coverage guidance

* style(tests): fix CI formatting
2026-10-29 09:23:22 +08:00
morluto 78a1e1956e fix(pcap): validate selected resource limiter before capture (#7947)
* fix(pcap): validate selected resource limiter before capture

* fix(pcap): verify limiter capabilities and preserve probe output
2026-10-29 09:23:07 +08:00
morluto 59a895a193 fix(electron): retain unknown external module ownership (#7950) 2026-10-29 09:20:53 +08:00
morluto 3536ea2f49 fix(discovery): report debugger start host restrictions (#7965) 2026-10-29 09:20:33 +08:00
morluto 70a8325c38 fix(setup): preserve existing configuration mode (#7942) 2026-10-29 08:59:56 +08:00
morluto 9e1a1e700a fix(cli): prevent mcp discovery from registering clients (#7919)
* fix(cli): prevent mcp discovery from registering clients

* fix(cli): use shared output parsing for discovery errors
2026-10-29 08:57:38 +08:00
morluto 33d6975749 fix(certificates)!: disclose undecoded unsigned attributes (#7920)
* fix(certificates): disclose undecoded unsigned attributes

* test(certificates): use parser-owned limitation text
2026-10-29 08:56:07 +08:00
morluto 0386efd1e1 fix(pcap): retain producer contradiction diagnostics (#7937)
* fix(pcap): retain producer contradiction diagnostics

* test(pcap): align DNS fixture with empty captures
2026-10-29 08:43:14 +08:00
morluto c92322b473 fix(process): await Swift frontend lifetime pipe (#7948) 2026-10-29 08:43:04 +08:00
morluto 0448f318e6 fix(outputs): use neutral destination diagnostics (#7949) 2026-10-29 08:42:56 +08:00
morluto 640e19d79d fix(native)!: own Mach-O load commands in binary reader (#7952)
* fix(native): retain selected Mach-O slice identity

* fix(native)!: own Mach-O load commands in binary reader

* fix(native): preserve malformed Mach-O and image base limits

* fix(native): preserve leading BOM in Mach-O names

* test(native): update Mach-O provenance verification

* fix(native): retain completed Mach-O inspection on cleanup failure

* fix: bound Mach-O projected section output

* fix(native): own retained Mach-O command bytes

* fix(native): retain Mach-O outcomes on final cancellation

* fix(cli): preserve caller path in partial evidence

* fix: reserve Mach-O projection envelope budget
2026-10-29 08:37:36 +08:00
RRFZ 6db024dc96 fix(cli): reject empty native and managed targets before resolution (#7956)
Local file commands in the core binary, native, managed and native-tool
CLI groups declared their positional target as a plain string. An empty
argument therefore reached path admission, where it resolved to the
working directory, as docs/cli.md says it never should.

Declare those targets with the shared nonempty, NUL-free local path
schema already used by analyze, so the CLI rejects them as a
VALIDATION_ERROR naming the field before any analysis runner is called.
Explicit "." is unchanged.

Fixes #7436
Fixes #7404
2026-10-29 08:31:12 +08:00
吴佳浩Alben 4671947c97 test(firmware): pin the staging ceiling at the request boundary (#7775)
* test(firmware): exercise the staging ceiling through the emitted refusal

The ceiling recovery added in #6971 was covered through the wording helper only, so a wiring change that passed the schema ceiling where the selected value belongs, or that dropped the remediation, would have stayed green.

Adds boundary coverage that calls inventoryFirmwareOutput itself: a sparse file reaches the real byte ceiling without storing the bytes and asserts the projected refusal carries the ceiling recovery, and a one-byte tree below the ceiling asserts the raise advice. Verified red on both regressions — passing the schema ceiling for the selected value fails the below-ceiling case, and restoring the single-form remediation fails both.

* test(firmware): compare the emitted recovery with its owner

The wiring test matched three phrases of the emitted remediation, so an ad-hoc or truncated action that kept those phrases would still pass while the emitted recovery had diverged from firmwareStagingBudgetRemediation.

Assert exact equality with the owner's output for the selected value in both cases instead. Verified red: replacing the emitted action with a divergent sentence that keeps the ceiling phrases now fails both cases, where the phrase matching passed.

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

* test(firmware): drive the staging ceiling through the request boundary

greptile and codex both flagged that the ceiling tests called
inventoryFirmwareOutput directly, so they would still pass if request parsing or
the provider wiring dropped or replaced max_output_bytes.

Add a request-level test that records the parsed request a provider receives:
the caller's max_output_bytes arrives unchanged at the schema ceiling and below
it, the documented default applies when omitted, and a budget above the schema
maximum is rejected before the provider runs. That is the wiring the previous
tests assumed rather than asserted.
2026-10-29 08:24:10 +08:00
吴佳浩Alben b7442c1958 fix(javascript): match resource release methods to the acquired handle (#7773)
* fix(javascript): match resource release methods to the acquired handle

Every acquisition family admitted every release method, so `h.close()`,
`h.destroy()` and `h.end()` each resolved as a complete release of whatever the
binding held. Node exposes a different subset per acquisition, and the rejected
pairs throw at the call site instead.

Measured on Node 24.22.1:

| acquisition | value | release members |
| --- | --- | --- |
| `fs.openSync` | number | none |
| `fs.open` (callback) | undefined | none |
| `fs.createReadStream` | ReadStream | `close`, `destroy` (`end` is the byte-offset field) |
| `fs.createWriteStream` | WriteStream | `close`, `destroy`, `end` |
| `fs/promises.open` | FileHandle | `close` |
| `net.connect` | Socket | `destroy`, `end` |
| `tls.connect` | TLSSocket | `destroy`, `end` |

So `fs.openSync(...)` + `fd.close()`, `createReadStream(...)` + `.end()`,
`net.connect(...)` + `.close()` and the FileHandle cases emitted a `releases` /
`resolved` fact for a call that throws.

Carry the specifier with each acquisition candidate, list the release methods
each pair actually has, and emit no release when the pair is not one of them. A
release stays `complete` only when the binding held exactly one acquisition and
that acquisition admitted the method.

`tls.createConnection` is not a Node export at all: `'createConnection' in
require('node:tls')` is false and the ESM import does not link. Drop it from the
table so a `tls.createConnection` call yields no acquisition.

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

* fix(javascript): cover a mixed-family binding before completing a release

greptile asked for the mixed-family case: a binding that can hold either a
FileHandle or a socket, then calls close(). The FileHandle is the only linked
acquisition and the release stays partial.

That case also showed the `candidates.length === 1` guard was dead: an
acquisition of another family is not in `linked`, so `holdsOnlyAcquisitions`
already returns false for it and the release already stayed partial without the
guard. Removing the guard and keeping the test means the behaviour is now pinned
by a test rather than by two overlapping conditions.
2026-10-29 08:23:53 +08:00
Ha1baraA1 a965cbab06 fix(ghidra): omit duplicate load-image result (#7777)
* fix(ghidra): omit duplicate load-image result

* docs(mcp): describe load-image evidence projection
2026-10-29 08:23:38 +08:00
64d282b0a6 fix(process): normalize descendants of slash-spelled directory roots (#7803)
* fix(process): normalize descendants of slash-spelled directory roots

A working directory or observation path ending in / or /. kept its
trailing separator as the alias, so descendant paths never met the
delimiter lookahead and stayed literal. Strip those spellings before
building aliases and version the path normalization contract as v3.

Fixes #7584

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

* fix(process): normalize filesystem root descendants

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-29 08:23:19 +08:00
RRFZ 30936d7596 fix(managed): report member signature issues at artifact offsets (#7879)
Field, MethodDef and MemberRef signature issues reported the invalid operand's offset inside its blob, while local signature issues in the same coverage array used artifact file offsets. Translate both through one helper that adds the blob's artifact base.

Fixes #7385
2026-10-29 08:23:04 +08:00
ShuaixinHuangandmorluto 3a46047aa7 fix(pe): reject surrogate code units in BMPString names (#7817)
* fix(pe): reject surrogate code units in BMPString names

* chore: preserve base browser imports

---------

Co-authored-by: morluto <[email protected]>
2026-10-29 08:20:32 +08:00
morluto 7c5700542e test(reference): pin excluded package main resolution (#7941) 2026-10-29 08:18:13 +08:00
morluto ef9ab91e42 fix(il2cpp): validate retained declaring type relationships (#7946) 2026-10-29 08:16:57 +08:00
morluto 5147b6e306 fix(environment)!: attribute source errors to caller fields (#7900)
* fix(environment): attribute source file errors to caller fields

* style(environment): format source admission tests
2026-10-29 08:12:09 +08:00
morluto c7722c8eeb fix(cli): honor token controls for builtin documents (#7897) 2026-10-29 08:11:19 +08:00
morluto b0b25ad93e fix(ida): report registration timeout bounds (#7893) 2026-10-29 08:11:07 +08:00
morluto 3a512303d9 fix(cli)!: refuse lossy text output (#7852)
* fix(cli)!: refuse lossy text output

* fix(cli): retain failure identity on text refusal

* fix(cli): check failed TOON token windows

* test(cli): bound keyed archive case duration
2026-10-29 08:09:49 +08:00
morluto 794907fa3d fix(javascript): trace boundary results to assigned bindings (#7912) 2026-10-29 07:58:57 +08:00
morluto 2129c17925 fix(native): retain selected Mach-O slice identity (#7940) 2026-10-29 07:57:19 +08:00
morluto 0179d94420 fix(browser): disclose opaque redirect coverage gaps (#7943) 2026-10-29 07:57:00 +08:00
Hasan Zayni bbd4989654 fix(config): treat a blank analyzer override as absent beside an existing Ghidra project (#7923)
REA_GHIDRA_DISABLED_ANALYZERS parses a blank or comma-only value to no override, but the existing-project conflict check tested the raw value. MCP startup exited and doctor reported config_drift for an input that selects nothing. The check now uses the parsed value.

Fixes #7422
2026-10-29 07:56:50 +08:00
Hasan Zayni 32b3722d2f fix(ghidra): report unusable existing-project selections as REA_GHIDRA_PROJECT_PATH configuration errors (#7922)
A selected .gpr marker that does not exist and a target that existing-project mode cannot serve both surfaced as execution_failure with no setting and a doctor action that cannot help. Both now return configuration_invalid naming REA_GHIDRA_PROJECT_PATH. The ENOENT diagnostic of the missing marker is kept.

Fixes #7417
Fixes #7416
2026-10-29 07:56:38 +08:00
morluto 844bb46351 fix(javascript): retain Electron switch conditions (#7910)
* fix(javascript): retain Electron switch conditions

* fix(javascript): bound switch path traversal
2026-10-29 07:56:15 +08:00
morluto 086abf3996 fix(javascript): distinguish bundled script locations (#7909) 2026-10-29 07:56:06 +08:00
morluto 9b4546276c fix(browser): preserve frame tree root identity (#7915) 2026-10-29 07:55:57 +08:00
morluto f256492ff3 fix(apple): recognize AppKit cell ownership (#7916) 2026-10-29 07:55:05 +08:00
morluto 63491645fd fix(ledger): withhold readiness for unresolved evidence (#7928) 2026-10-29 07:54:41 +08:00
Hasan Zayni a4bd924772 fix(call-path): reject incompatible coordinate mappings (#7930)
* fix(call-path): reject incompatible coordinate mappings

Use the existing provider-owned coordinate context comparison before composing dossiers. Preserve same-mapping profiles and missing-dossier uncertainty. Fixes #7345.

* test(call-path): assert the structured rejection instead of its wording

docs/testing.md asks for structured error checks. Assert the issue path and reason, and keep the MCP category and code.
2026-10-29 07:54:09 +08:00
morluto 66f5b650d9 fix(apk): explain unknown signer comparison (#7927) 2026-10-29 07:53:38 +08:00
morlutoandgreptile-apps[bot] 1631dcb09a docs: include Kuna in native provider guidance (#7929)
* docs: include Kuna in native provider guidance

* docs(i18n): include Kuna in native provider guidance

* docs: align provider guide links

* Update README.md

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

* docs: align provider links and translated formatting

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-29 07:53:28 +08:00
morluto 42d5425417 fix(browser): enforce initial navigation default deadline (#7934) 2026-10-29 07:53:18 +08:00
hhangandhhang 07dfd12613 fix(pe): reject non-MZ inputs before image inspection (#7709)
Co-authored-by: hhang <[email protected]>
2026-10-29 07:45:32 +08:00
吴佳浩Alben f1da7f7670 fix(javascript): read the spawn env option as Node reads it (#7713)
* fix(javascript): read spawn env as Node reads it

Node's normalizeSpawnArguments starts with `const env = options.env ||
{ ...process.env }`, so a falsy env is replaced by a copy of the parent
environment. A child started with env: null, undefined, 0, "" or false
therefore inherits everything, exactly as if no env were passed.

REA tested the key's presence alone, so every one of those reported
environment_supplied: true and the graph emitted a resolved supplies-env
relation. The presence test also missed the opposite direction: an env
supplied through an inline object spread reported false although Node uses
it.

Read the declared value the way stdioMode already reads stdio: an explicit
property whose statically known value is falsy does not supply an
environment, and an inline spread that carries env does.

Verified on Node v24.22.1 with a child that prints a parent marker variable:
no options, env: null, undefined, void 0, 0, "" and false all print the
marker, while env: {} prints nothing and env: { A: "1" } prints only that.

* fix(javascript): read falsy numeric literals as Node does

greptile flagged that environmentSupplied still reported a supplied environment for
`env: -0` and `env: 0n`: staticJavaScriptTruthiness recognized neither form, so it
returned undefined and `!== false` turned that into true. Node's
normalizeSpawnArguments uses `options.env || { ...process.env }`, so both inherit.

Extend the shared truthiness reader to cover a BigInt literal and a negated numeric
literal, and add regression cases for `-0`, `0n` and `0.0`.

The reader is shared with the route scan and the state tracker; both treat
`undefined` as "unknown", so a more precise `false` only strengthens them.
2026-10-29 07:45:22 +08:00
Wenyu Chiou 3142f7b0dd fix(adb): align CLI selector schemas with canonical inputs (#7722)
* fix(adb): align CLI selector schemas with canonical inputs

Reuse canonical selector constraints for ADB CLI admission and cover schema, parser, and inert provider boundaries. Independent source review approved tree bc722f8d1b1f8d014be29533e6845c6f897d5918; full contribution checks remain pending.

* test(adb): expect CLI serial validation refusal

Independent review approved staged diff 35418e6994339af838b490be51278fd41b9f8e3e. Focused serial refusal test, lint, and formatting passed with existing dependencies.

* test(adb): assert CLI selector validation stage

Independent review approved staged diff a1164a8dcf1df1f0d60323d7a53a69ee2bfdffeb. Focused dispatch tests, targeted lint and formatting passed using existing dependencies.

* fix(adb): retain safe selector validation constraints

Independent review: review_ci_delta APPROVE for diff 18748a3b4b112dc96b95f7c1099bd479e1d1587b. Focused tests, typecheck and targeted lint passed; the aggregate check remains limited by a matching baseline Knip finding.
2026-10-29 07:44:43 +08:00
ShuaixinHuangandmorluto fc24b08851 feat(artifacts): read Zstandard ZIP members (#7780)
* feat(artifacts): read Zstandard-compressed ZIP members

* fix(artifacts): classify ZIP dictionary failures

---------

Co-authored-by: morluto <[email protected]>
2026-10-29 07:44:10 +08:00
ShuaixinHuang edc908af58 fix(xml): reject ambiguous namespace attribute identities (#7785) 2026-10-29 07:38:46 +08:00
PARK HYEONMIN (박현민) b4923f85c5 docs(ghidra): count 27 read-only operations in current guides (#7794)
The inventory registry gained read_binary_inventory_summary, so the
read-only inventory is fifteen inventory and twelve function operations.
Update the adapter summary and the Windows lane descriptions to match the
registries the Windows verifier derives its coverage from. The historical
Windows verification record keeps its observed 26-operation count.

Fixes #7611
2026-10-29 07:38:33 +08:00
ShuaixinHuang 9ac9f91ab5 fix(firmware): validate Intel HEX start-record offsets (#7806) 2026-10-29 07:37:39 +08:00
ShuaixinHuang c2cc7e6eb0 test(cli): cover literal Markdown heading output (#7866) 2026-10-29 07:37:20 +08:00
morluto a001231256 fix(adb)!: report local filesystem admission failures (#7885)
* fix(adb)!: report local filesystem admission failures

* test(adb): assert filesystem error provenance
2026-10-29 07:37:08 +08:00
RRFZandmorluto 8c83886891 fix(pe): report exhausted certificate decoder allowances as resource limits (#7881)
* fix(pe): report exhausted certificate decoder allowances as resource limits

A PKCS#7 payload that exhausted the fixed 64 KiB decoded-text or 16,384-value ASN.1 allowance was refused as malformed input with advice to correct request fields. Keep the allowance and limit on the decoder failure and report it as a nonretryable resource_constraint with fixed-allowance recovery.

Fixes #7531

* test(pe): cover certificate allowances through MCP

---------

Co-authored-by: morluto <[email protected]>
2026-10-29 07:36:56 +08:00
morluto fd297b1b17 fix(apple): preserve bounded archive diagnostics (#7890)
* fix(apple): preserve bounded archive diagnostics

* test(apple): assert diagnostic facts without freezing prose
2026-10-29 07:36:29 +08:00
morluto 16cfe30dab fix(setup): identify invalid launch environment overrides (#7899)
* fix(setup): identify invalid launch environment overrides

* test(setup): cover merged environment recovery
2026-10-29 07:34:43 +08:00
morluto 473cfa36b8 fix(evidence)!: distinguish inline records from bundles (#7895)
* fix(evidence): distinguish inline records from bundles

* fix(evidence): preserve bundle integrity failure reasons
2026-10-29 07:34:31 +08:00
morluto 5082b50f07 fix(decompile): retain limitations across failed resume (#7913) 2026-10-29 07:30:44 +08:00
morluto 31c028f496 fix(mcp): align retained view handoff guidance (#7926) 2026-10-29 07:29:44 +08:00
Hasan Zayni e0719cffa4 fix(ledger): honor every selected fixture proof (#7931)
Prevent a passing fixture entry from hiding applicable failures in another selected entry. Fixes #7412.
2026-10-29 07:27:57 +08:00
morluto 21b192706d docs(website): correct client and IDA support guidance (#7918)
* docs(website): correct client and IDA support guidance

* style(website): format setup support guidance
2026-10-29 05:37:06 +08:00
morluto 286629b570 fix(test): remove duplicate unknown session imports (#7917) 2026-10-29 05:30:05 +08:00
morluto 50d731d456 fix(tests): remove duplicate MCP test imports 2026-10-29 05:19:41 +08:00
morluto f9acfdcf49 fix(cli): retain committed evidence receipts on output failure (#7906) 2026-10-29 05:11:02 +08:00
morluto 9b880e0e3e fix(setup): report concurrent skill destination changes (#7898)
* fix(setup): report concurrent skill destination changes

* fix(setup): avoid rollback after exclusive link refusal
2026-10-29 05:10:45 +08:00
morluto 76c7698ac9 fix(cli)!: preserve native value input diagnostics (#7889) 2026-10-29 05:10:28 +08:00
morluto 83cd1e04c9 fix(javascript)!: report ambiguous artifact paths (#7864)
* fix(javascript): report ambiguous artifact paths

* fix(javascript): retain unavailable colliding sources
2026-10-29 05:10:18 +08:00
morluto 29945d435d fix(process): preserve availability when macOS ownership preparation fails (#7856)
* fix(process): preserve availability when macOS ownership preparation fails

* test(process): verify affected macOS provider availability
2026-10-29 05:10:04 +08:00
morluto 6c205d8a79 fix(evidence): align bundle receipts and conflict recovery (#7849) 2026-10-29 05:09:54 +08:00
morluto 4e663beeb8 fix(javascript): retain guards after labelled breaks (#7841)
* fix(javascript): retain guards after labelled breaks

* fix(javascript): bound labelled exit inspection

* fix(javascript): preserve local break completion

* fix(javascript): avoid exits through unknown control flow

* fix(javascript): retain guards after bounded jump scan
2026-10-29 05:09:45 +08:00
morluto 1e088c19ad fix(environment)!: report recorded reads and target uncertainty (#7834)
* fix(environment)!: report recorded reads and target uncertainty

BREAKING CHANGE: Environment state and coverage can report method-replay for non-GET safe reads; exhaustive reason consumers must handle it.

* docs(environment): include safe-method replay reason
2026-10-29 05:09:31 +08:00
morluto cc5a4062ff fix(browser): preserve unknown WebMCP comparison facts (#7833) 2026-10-29 05:09:22 +08:00
morluto 6c76a7ffb0 fix(wasm): retain core facts with invalid custom names (#7822)
* fix(wasm): retain core facts with invalid custom names

* fix(wasm): retain objdump output on parse failure
2026-10-29 05:08:41 +08:00
morluto 114731eb1e fix(process): retain uncertain cleanup ownership (#7845)
* fix(process): retain uncertain cleanup ownership

* fix(process): retain moved members through cleanup retry

* test(process): reconstruct ownership ledger in cleanup fixture
2026-10-29 05:08:29 +08:00
morluto 583a316e11 fix(macho): preserve POSIX app executable names (#7888) 2026-10-29 05:08:17 +08:00
morluto f6380282fb fix(web)!: retain invalid browser selection (#7903)
fix(web): retain invalid browser selection
2026-10-29 05:08:02 +08:00
morluto 2aa0d45e13 fix(mcp): guide oversized unknown recovery (#7876) 2026-10-29 05:07:35 +08:00
morluto 5f146b3b19 fix(ida): retain selected endpoint on connection failures (#7894)
* fix(ida): retain selected endpoint on connection failures

* fix(ida): redact encoded endpoint credentials and preserve causes
2026-10-29 05:07:02 +08:00
morluto 553f19590f fix(harmony): exclude symlinks from unavailable files (#7886) 2026-10-29 05:05:35 +08:00
morluto e03ff93913 fix(frida): retain selected discovery endpoint on failure (#7892) 2026-10-29 05:04:53 +08:00
morluto 78a350e0b6 fix(skill): clarify packet capture tool selection (#7901) 2026-10-29 05:04:35 +08:00
morluto 52617bd50e fix(model): recognize safetensors ZIP signature collisions (#7816) 2026-10-29 04:59:25 +08:00
morluto 675c73fb4d fix(apple): preserve post-table plist objects (#7820) 2026-10-29 04:59:14 +08:00
morluto bd687bbc40 fix(ghidra)!: distinguish unlisted instruction addresses (#7823)
* fix(ghidra)!: distinguish unlisted instruction addresses

BREAKING CHANGE: Ghidra now returns not-in-listing for in-memory addresses without listed instructions or defined data. Consumers matching the former undecodable value must handle the new status.

* fix(ghidra): invalidate snapshots with old instruction status

* test(ghidra): update raw profile commitment goldens
2026-10-29 04:59:04 +08:00
morluto b0880d660f fix(qlog): exclude hidden custom events from incomplete facts (#7825) 2026-10-29 04:58:53 +08:00
morluto 2dd9a3092e fix(artifacts): extract readable inventoried entries (#7827)
* fix(artifacts): extract readable inventoried entries

* test(artifacts): align directory omission coverage
2026-10-29 04:58:43 +08:00
morluto 5c60720ad7 fix(pe): distinguish bounded imphash name reads (#7830) 2026-10-29 04:58:33 +08:00
morluto 9b7c2bb1ac fix(javascript): retain anchored route constants under reserved prefixes (#7838) 2026-10-29 04:58:22 +08:00
morluto 0759fb9ad5 fix(build): explain unsupported source loader runtimes (#7839) 2026-10-29 04:58:11 +08:00
morluto 46c457f74c docs(ghidra): fix DOL guide PowerPC link (#7857) 2026-10-29 04:57:59 +08:00
morluto 1b636c890f fix(macho): retain inspection when vtool rejects universal container (#7865)
* fix(macho): retain inspection when vtool rejects universal container

* test(macho): assert facts retained after vtool refusal
2026-10-29 04:57:22 +08:00
morluto fe52ea12c3 fix(evidence): explain fixed analysis view page recovery (#7860)
* fix(evidence): explain fixed analysis view page recovery

* fix(evidence): explain unpublished view recovery
2026-10-29 04:55:53 +08:00
morluto 00bd92c56e fix(ida): align workspace discovery with readiness (#7863) 2026-10-29 04:55:44 +08:00
gensokyo-devandEntrpi 37827af591 feat(artifacts): compare inventories from Evidence sources (#7869)
* feat(artifacts): compare artifacts by Evidence reference and from the CLI

compare_artifacts made agents send back the complete inventory Evidence
that REA already retains, and the CLI had no artifact comparison.

Each side of compare_artifacts now takes exactly one of: complete inline
Evidence (unchanged), {kind: "retained-evidence", evidence_id}, or
{kind: "evidence-file", path, evidence_id?}. The two references are the
sources inspect_analysis_view accepts; both tools now compose one
definition in evidenceReference.ts. A summary still resolves to its exact
retained parent, and every source form gives the same comparison Evidence.
An unresolvable retained reference is refused with a typed missing
reference error.

The application layer owns source resolution and the comparison Evidence,
so MCP and the new `rea compare-artifacts` command share one workflow.
readEvidenceFileSource reports bundle-selection refusals at the caller's
own field path.

* refactor(artifacts): resolve compare_artifacts sides like compare_functions

compare_functions, compare_wasm_interfaces and the JavaScript application
comparisons name each side with evidenceInputSchema and resolve it with
resolvePairedEvidenceRequest. compare_artifacts reached the same result
for inline and retained sides through its own dispatch.

evidenceSourceInputSchema now extends evidenceInputSchema with the
Evidence-file reference, and resolvePairedEvidenceSources sends inline and
retained sides to the same resolveEvidenceInput and file sides to the
shared Evidence file reader. compare_artifacts uses both, so the forms it
shares with compare_functions are parsed and resolved by one owner. The
advertised input schema is unchanged.

* docs(cli): point rea compare to compare-artifacts for inventory contents

rea compare (compare_bundles) compares records by Evidence identity and
explicit pairs and never reads artifact contents, so two build
inventories stay unresolved there. Its help and the CLI guide now name
compare-artifacts for that comparison.

* fix(artifacts): resolve summary parents within Evidence source bundles

* perf(evidence): reuse file-owned immutable authentication proofs

* test(evidence): inject failures at both authentication boundaries

---------

Co-authored-by: Entrpi <[email protected]>
2026-10-29 03:12:03 +08:00
gensokyo-dev 2f3c71f39e docs(errors): align byte entropy recovery catalog (#7884) 2026-10-29 03:03:04 +08:00
RRFZ b75419b61e test(firmware): keep producer fixtures within supported Node limits (#7048)
* test(firmware): bound fixture hashing and avoid Node startup warnings

* test(firmware): apply the Wasm flag only on Node 22
2026-10-29 02:42:21 +08:00
gensokyo-dev 8f9af920ef fix(pcap): redact HTTP credentials in failed capture diagnostics (#7871)
Project bounded JSON diagnostics using shared URI roles and source spans.
Keep complete parser input unchanged and compute safe diagnostics lazily.
Preserve byte facts, error classification, path data and cleanup ownership.
Reuse JSON escape-coordinate mapping with IDA diagnostics.

Fixes #7812

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 18:14:22 +00:00
Rudy Mizrahi Celekli e23cb3b240 fix(browser): preserve script exports with filesystem-unrepresentable paths (#7526)
fix(browser): isolate unrepresentable script export paths

Signed-off-by: Rudy Celekli <[email protected]>
2026-10-29 02:13:54 +08:00
gensokyo-dev 4b0ff68e2e fix(browser): retain prior-hop response facts in session timelines (#7807)
* fix(browser): retain prior-hop response facts in session timelines

* fix(browser): share response URL classification

* fix(browser): preserve malformed response status failures

* fix(browser): account for filtered redirect response coverage

* test(browser): supply prior response URLs in redirect fixtures

* fix(browser): retain malformed orphan response status coverage
2026-10-29 02:04:15 +08:00
Jiakai Xu 19032204aa fix(javascript): resolve relative ESM backslash paths (#4323)
Normalize raw URL separators after admitting an original relative ESM
request, before artifact path validation and root confinement. Preserve
the authored declaration and the existing encoded-separator checks.

Retain Node-backed static and dynamic loader controls and compare the
application graph with the historical importer already present on main.
Narrow PR #4323 to this remaining case after the upstream #4269 fix.

Signed-off-by: Jiakai Xu <[email protected]>
2026-10-29 02:03:00 +08:00
Dragolone f19436c156 fix(artifacts): split U-Boot script uImages on their size table (#7805)
U-Boot's mkimage writes the multi-file size table for script images
(`mkimage -T script`, the boot.scr format) exactly as it does for multi
images, `mkimage -l` lists the script as "Image 0", and the `source`
command skips the table before running the script ("scripts are just
multi-image files with one component", boot/image-board.c).

The uImage reader parsed the table only for IH_TYPE_MULTI, so a script
image reported sub_image_count 0 and exposed only payload.bin, whose
first bytes are the binary size table rather than the script text.

Treat IH_TYPE_SCRIPT like IH_TYPE_MULTI: parse the same bounded table,
expose image-<n>.bin components and report the size-table limitation.
payload.bin is unchanged. A script payload without a valid table now
fails closed, as a multi image already does.
2026-10-29 01:57:09 +08:00
morluto 44331d04d2 fix(browser): retain launch failure diagnostics (#7826) 2026-10-29 01:56:39 +08:00
morluto 0a272d5cf5 fix(capture): bound preview headlines by source format (#7832)
* fix(capture): bound preview headlines by source format

* test(capture): cover scalar HAR preview extensions
2026-10-29 01:56:07 +08:00
morluto 0747e46401 fix(cli): recognize integrity failures with partial observations (#7836) 2026-10-29 01:55:57 +08:00
morluto 183e2e2d1e fix(il2cpp): validate structural record extents (#7861) 2026-10-29 01:55:47 +08:00
morluto 5119d08daf fix(web): preserve portable captured script paths (#7862) 2026-10-29 01:55:38 +08:00
morluto 1e421242b9 fix(javascript): preserve uncertainty for incomplete runtime captures (#7854) 2026-10-29 01:53:03 +08:00
morluto 5bef2b48b6 fix(input)!: classify unresolvable source paths (#7847) 2026-10-29 01:52:34 +08:00
morluto e79ad6ca8e fix(jeb): preserve selected endpoint in failures (#7835)
* fix(jeb): preserve selected endpoint in failures

* fix(jeb): retain endpoint on typed failures
2026-10-29 01:30:24 +08:00
morluto 128c2cf632 fix(sqlite): report SQL statement resource allowance (#7829) 2026-10-29 01:30:08 +08:00
morluto aaf4d21068 fix(source-map): clarify policy and evidence provenance (#7828) 2026-10-29 01:29:59 +08:00
morluto aba158506b fix(javascript): preserve repeated native resolver calls (#7824) 2026-10-29 01:29:48 +08:00
morluto 477f63ae21 fix(session)!: classify unavailable snapshot prerequisites (#7837)
fix(session): classify unavailable snapshot prerequisites
2026-10-29 01:28:57 +08:00
morluto 60ab4da329 fix(cli): correct completion options and setup guidance (#7851)
* fix(cli): align MCP doctor completion with its options

* fix(cli): explain zsh completion initialization

* fix(cli): include launcher timing in doctor completion

* style(cli): format doctor completion schema
2026-10-29 01:28:04 +08:00
morluto f9e7571a3d feat(javascript): discover semantic call literals in feature traces (#7840)
* feat(javascript): discover semantic call literals in feature traces

* fix(javascript): bound retained call literals by transfer capacity

* test(javascript): align feature no-match diagnostic
2026-10-29 01:25:23 +08:00
morluto 685f6db960 fix(browser): preserve unknown CDP source coordinates (#7842) 2026-10-29 01:23:56 +08:00
morluto b21a975faf fix(update): distinguish launcher failures from registry errors (#7843) 2026-10-29 01:23:29 +08:00
morluto d49a1504ca fix(erlang): respect legacy atom table layout (#7844) 2026-10-29 01:23:17 +08:00
morluto 4109271bcb fix(process)!: reject unsupported one-column terminals (#7848)
* fix(process)!: reject unsupported one-column terminals

* test(process): exercise actual two-column resize
2026-10-29 01:23:02 +08:00
morluto a6295e433b fix(python): preserve code objects after STORE_SLICE (#7815) 2026-10-29 01:11:27 +08:00
morluto af1ccb6506 fix(android): preserve manifest component spelling (#7818) 2026-10-29 01:10:45 +08:00
morluto 7dbebb4e7d fix(browser): preserve prefixed XHTML link hints (#7819) 2026-10-29 01:10:36 +08:00
morluto dfa0689890 fix(process): preserve comparison uncertainty and attribution (#7853)
* fix(process): preserve comparison uncertainty and attribution

* fix(process): retain known file differences with unknown effects

* fix(process): preserve proven filesystem path differences

* fix(process): preserve trace and symlink differences
2026-10-29 01:01:16 +08:00
morluto 251e27ef3b fix(website): align Electron guide with downloadable source (#7858)
* fix(website): align Electron guide with downloadable source

* test(website): detect renderer example drift
2026-10-29 00:56:14 +08:00
Entrpiandgensokyo-dev 64196b08ca feat(artifacts): inventory and extract ESP32 firmware images (#7498)
* 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.

* docs(artifacts): state that ESP mmu_page_size is a base-2 logarithm

The app descriptor's mmu_page_size keeps ESP-IDF's name and value, the
MMU page size in log base 2 as esp_app_desc_t declares it, so 16 means
64 KiB. Say so in docs/esp-image.md and the schema comment; a reader
could otherwise take 16 as 16 KiB.

* fix(esp-image): retain member consumption and share lifecycle regressions

* test(artifacts): scope MCP catalogs and budget full-schema validation

* test(artifacts): verify compact output profiles and document bounded catalogs

* test(artifacts): close readers when cleanup-test admission fails

* test(artifacts): compare ESP image limitations with their owners

The decoder and reader tests froze complete limitation sentences, so a
harmless rewording failed them. The wording now has two owners:
ESP_IMAGE_LIMITATIONS in espImageFacts.ts, which the decoder, the inventory
and the reader use, and ESP_DESCRIPTOR_LIMITATIONS beside the descriptor
decoder. Tests compare delivered limitations with the owner, called with the
facts each case establishes (segment index, declared and available bytes,
offsets, chip, page range), next to the structured assertions they already
make. Delivered text is unchanged.

* test(esp-image): assert structured facts and owned guidance

* fix(esp-image): keep spi_flash_mode and descriptor fields before a truncated tail

ESP-IDF v6.1's esp_app_desc_t declares spi_flash_mode at byte 181, after
mmu_page_size, as the committed reference layout shows; the descriptor now
keeps it as stored. The decoder also required all 256 descriptor bytes,
though it reads none past byte 181, so a file ending in the reserved tail
lost the version, project, digest and revision facts it does hold. The app
descriptor now needs only its fields; the segment's truncation is reported
as before.

* test(esp-image): pin the P-192 public key digest to espsecure's layout

espsecure packs ECDSA signature blocks as "<BBBx32sB64s64s1031x" and
zero-pads a 48-byte P-192 point to 64 bytes, so the signature starts at
byte 101 for every SHA-256 curve. signature_info_v2 hashes sig_block[36:101]
and _digest_sbv2_public_key the curve ID with the padded point. No test built
a P-192 block; this one checks public_key_sha256 against that definition.

* fix(esp-image): keep each app descriptor field the file holds in full

A file can end anywhere inside the 256-byte esp_app_desc_t. The decoder
needed every field byte, so a cut at byte 176 dropped the version, project
and app_elf_sha256 it does hold. Once the 4-byte magic identifies an app
descriptor, each field is now kept when the file holds all of its bytes and
is null otherwise; observed_length records where the descriptor ends, so a
cut field is told apart from text that is not UTF-8. The fields after the
magic become nullable in the metadata schema.

* fix(esp-image): report a signature block whose image digest does not match

A CRC-valid Secure Boot v2 block can carry another image's digest. The
block recorded image_digest_matches false, but no limitation said so, unlike
a checksum or appended-SHA mismatch, so CLI and MCP summaries showed only
the generic unverified note. The mismatch is now a limitation; coverage
stays complete and signature_status stays unverified. The three other
signature-sector limitations move into ESP_IMAGE_LIMITATIONS with their
text unchanged, so all ESP image wording has one owner.

* refactor(esp-image): remove redundant descriptor byte guards

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 23:18:22 +08:00
gensokyo-dev 6c895eeb8e fix(process)!: redact sensitive terminal echoes before retaining Evidence (#7804)
Seal sensitive terminal chunks before deferred rendering, apply one bounded
capture-local literal matcher, and retain exact redacted byte accounting.
Keep private input facts in explicit fields and separate comparison policies.
Retain distinct terminal failures once and verify structured refusal facts.

Fixes #7782

BREAKING CHANGE: sensitive interaction data is empty with sensitive and input_bytes fields instead of a byte-count placeholder. Sensitive raw_data is also redacted. Read input_bytes and recapture both comparison sides when migrating older private-input Evidence.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 14:39:34 +00:00
ShuaixinHuang 8f97a45483 fix(plist): end OpenStep comments at native line separators (#7793) 2026-10-28 22:23:16 +08:00
Dragolone 6fd5d347c7 fix(browser): ignore repeated CSP directives within one policy (#7798)
CSP3 parsing keeps the first directive of each name in a policy and ignores
later repeats; Chrome reports "Ignoring duplicate Content-Security-Policy
directive". parseCsp flattened every policy's directives into one stream, so
a header such as `script-src 'self'; script-src 'unsafe-inline'` reported the
ignored `'unsafe-inline'` directive as declared and counted nonces and hashes
from it. Track directive names per policy so only the effective directive is
retained, while the same name in separate comma-separated or repeated-field
policies is still listed once per policy.
2026-10-28 22:23:00 +08:00
ShuaixinHuang b14b94f0f5 fix(asn1): reject non-minimal OID subidentifiers (#7800) 2026-10-28 22:22:43 +08:00
Ysopwnableandgensokyo-dev 7b6d872a25 docs: celebrate 400,000 GitHub stars 🎉 (#7801)
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 22:12:43 +08:00
gensokyo-dev b9a8f8e9f2 docs(update): align error catalog with current recovery (#7795) 2026-10-28 21:04:12 +08:00
gensokyo-dev 23e7c4e8e2 fix(setup): preserve specific skill path recovery (#7791) 2026-10-28 20:49:05 +08:00
gensokyo-dev ac545adae2 fix(apk): classify fixed inspection allowances as nonretryable (#7784) 2026-10-28 20:13:58 +08:00
ShuaixinHuang 9593bc536b fix(protobuf): reject malformed descriptor field declarations (#7730) 2026-10-28 19:52:11 +08:00
JG Tromp 7c389cbefc fix(javascript): keep both-direction feature traces from crossing shared containers (#7743)
* fix(javascript): keep both-direction feature traces from crossing shared containers

trace_application_feature with direction "both" walked the application
graph as if it were undirected. From a seed, one outgoing or incoming step
into a shared container (an asset or the artifact root) followed by steps
in the other direction reached every sibling of that container, so a trace
seeded on one storage key returned the whole connected graph.

"both" now returns the union of the outgoing and incoming closures. Each
walk keeps its own predecessor chains, so a terminal path never changes
direction midway. "outgoing" and "incoming" are unchanged.

* test(javascript): check both-direction trace paths end to end

The existing tests checked predecessor maps but no returned path. Add a
trace whose seed reaches one storage terminal outgoing and another
incoming, and assert each path's complete node_ids and edge_ids. A
pathTo that reads only the first walk's map returns a one-node path for
the incoming terminal and fails this test.

* fix(javascript): confine only ascended containers in both-direction traces

Making "both" the union of separate outgoing and incoming walks dropped
modules that a feature reaches by crossing directions. In the Notes
known-answer evaluation, the notes:export channel is pointed at by both
the preload asset (invokes) and the handler (handles); its preload,
renderer and csv modules are reached only through relationships that
change direction, so the trace kept main.js alone and the feature_modules
claim failed in tests/boundary/mcp/agentKnownAnswerMcp.test.ts.

Walk both directions in one traversal again, and confine only the
containment case the change was meant to stop: a node reached by stepping
up a contains edge from one of its children follows only further contains
steps up. A shared asset or artifact root then no longer leads into its
other children, while relationships across the feature are kept. The
Notes trace returns csv.js, main.js, preload.js and renderer.js again
(30 nodes; the previous main returned the whole 32-node graph).
outgoing and incoming traces are unchanged, and paths come from one
predecessor map again, as before this branch.
2026-10-28 19:51:59 +08:00
JG Tromp 8b74f30bf9 fix(javascript): stop double-expanding application projection estimates (#7744)
* fix(javascript): stop double-expanding application projection estimates

estimateJavaScriptStaticApplicationProjection multiplied the per-record
metadata allowance (2 KB plus the path) by the 8x expansion that it also
applies to checkpoint bytes. A projected record serializes to about 0.46 KB,
so the allowance alone already covers retention; expanded again, the
charge was about 25x the serialized application relationships.

The charge accumulates in the parent's shared result budget. On a
synthetic 2.8 MB Vite-style build (88 files, one 456 KB chunk), the first
10 files used 2.0 GB of a 2.08 GB budget while the parent heap held about
300 MiB. The other 78 files lost their application relationships and were
offered a 0-byte semantic budget. On a 201-file build the charges exceeded
the budget at file 93 and the analysis failed with resource_constraint,
leaving the remaining sources without static facts.

Keep the 8x expansion for checkpoint and repeated bytes and count the
per-record allowance once. The same builds now refuse 14 and 102
projections instead of 78 and a failed run; peak RSS stays at 1.2 GB and
2.0 GB under a 4.2 GB parent heap limit.

workerFailureComposition simulated 64 KiB of parent result budget, which
the corrected charge for its one-finding checkpoint fits. Lower that
headroom to 32 KiB so the case still exercises a refused projection.

* test(javascript): cover application projections near the shared parent budget

The estimate test compares one file's charge with its serialized graph,
but no test put several sources against one parent result budget. Replay
one recorded static checkpoint for five sources against an exact budget:
sources 1-3 fit, source 4 misses by half a source and keeps its static
facts with the reported required and budget bytes, and the last source
fits again once no checkpoint reserve is held back. A worker that charges
the projection twice, or not at all, passes the existing tests and fails
this one.

* test(javascript): cover a malformed source before a valid one under the shared budget

The testing guide asks representation-expansion and capacity changes for
a large malformed input followed by a valid input, and for escaped or
expanded representations.

Under an exact parent budget, a 3 MiB checkpoint cut off inside a string
comes first and is rejected as unreadable output with no static facts.
The valid source after it keeps its application relationships; it fits
only if the rejected bytes took no charge. A worker that charges the
static checkpoint on receipt, before parsing, passes the existing tests
and fails this one.

The estimate ratio test also runs with a nested source path and storage
keys holding quotes, backslashes, a tab and multibyte text, which expand
in serialized JSON; the charge stays within 3x to 8x of the serialized
application graph (5.3x).
2026-10-28 19:51:38 +08:00
JG Tromp 7aa943c14b fix(javascript): resolve Vite build-output preload dependencies (#7745)
* fix(javascript): resolve Vite build-output preload dependencies

With an absolute base, Vite writes __vite__mapDeps entries relative to the
build output directory (for example assets/chunk.js), and its preload helper
prefixes the configured base at runtime. REA emitted these entries as
dynamic-import module specifiers, so a scheme-less path without a leading
dot resolved as a bare package and every entry was reported as an
unresolved "external" module, even when the build output directory was
selected.

The static analyzer now marks such entries with a vite-output: specifier,
as Webpack chunk loads are marked with chunk:. Entries starting with ./,
../ or /, and scheme-qualified entries, keep their previous handling. The
graph resolves a marked entry as a URL path under each ancestor directory
of the importing file; exactly one ancestor holding the file establishes
the build output directory. Otherwise the entry is not-found (or
unavailable when several ancestors match) with a limitation that names
the build output directory.

On a synthetic 7-file Vite build, the 12 preload entries that were
"external" now resolve when the build output directory is selected, and
are not-found with that guidance when only assets/ is selected.

* fix(javascript): keep unanalyzed Vite output candidates

resolveViteOutputDependency considered only resolved ancestors. A file
that exists under an ancestor but was not analyzed (for example
dist/assets/lazy.css for the entry assets/lazy.css) resolves there as
unavailable, and that result was replaced by the root's not-found with a
limitation saying that no ancestor holds the file.

Keep resolved and unavailable ancestor candidates together. One candidate
is returned with its own path and limitations; several are reported as
unavailable with every candidate path and limitation. Only when no
ancestor can hold the file does the root's not-found result stand.
2026-10-28 19:46:45 +08:00
Dragolone 33698e6b31 fix(apktool): read Apktool 2.9+ metadata in its line-oriented dialect (#7747)
Apktool 2.9.0 replaced SnakeYAML with the YamlWriter that 3.x still uses:
apktool.yml starts with an untagged `version: 2.x.y` line and strings are
written unquoted with Java-style escapes. Only a `version: 3.x.y` header
selected the producer dialect, so 2.9.0-2.12.x output went through YAML
core parsing: a versionName of `1.0 #beta` became `1.0`, escaped quotes kept
their backslashes, an empty name became null, and `1.0: beta` made the whole
decode fail as invalid metadata.

Select the line-oriented reader for 2.9.0 and later as well. Older tagged
SnakeYAML documents keep YAML scalar semantics.
2026-10-28 19:46:11 +08:00
Dragolone fea4f2e397 fix(javascript): test literals for Next page paths in linear time (#7748)
Every source string literal without an established Next root was tested
against /(?:^|\/)app\/(?:[\s\S]*\/)?page(?:\.[cm]?[jt]sx?|-[^/?#]+\.js|)(?:[?#].*)?$/u.
The unanchored search restarted at every app/ segment and rescanned to the
end of the literal, and each page? or page# candidate scanned to the next
line terminator before $ failed. A long literal therefore took quadratic
time on the analysis thread: about 6 s for a 256 KB literal of repeated
app/ segments, and 3.5 s for 224 KB of x/page? followed by a newline.

The first app/ segment admits every later page segment, and . cannot cross
a line terminator, so a query or fragment after the page filename starts on
the last line. Test the tail after the first app/ segment one page segment
at a time instead; results are unchanged.
2026-10-28 19:45:59 +08:00
ShuaixinHuang 09225b08e1 feat(artifacts): read Godot 3 PCK version 1 archives (#7781) 2026-10-28 19:43:43 +08:00
gensokyo-dev c77735fa83 fix(setup)!: bind managed registrations to selected installation
Use one command owner for setup, client writers, Gemini policy and CLI defaults.
Migrate recognized runner registrations with backups and remove runner prewarm.
Retain unknown identities and exercise real offline npm, CLI and MCP workflows.

Fixes #7757

BREAKING CHANGE: managed MCP registrations use the selected absolute runtime
and REA entry script, including package-runner cache paths. Keep that installation
available or restore it before setup; unknown registrations require confirmed
manual repair. Setup no longer returns prewarm or emits its progress events.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 11:34:28 +00:00
Cipherandgensokyo-dev 18a8e71c9f feat(artifacts): add U-Boot legacy uImage reader (part of #7129) (#7234)
* feat(artifacts): add U-Boot legacy uImage reader (part of #7129)

Recognize legacy uImages by their 0x27051956 magic and inventory them as containers: 64-byte header facts (arch/os/type/compression raw and named, load/entry addresses, name, timestamp) with header and data CRC32 verified, stored payload extractable as payload.bin, and multi-file images split from the stored size table into image-<n>.bin. Compressed payloads stay as stored; nothing is executed or decompressed. Wires the format through classification, the artifact reader dispatch, extraction, and the evidence/snapshot format vocabularies. Related to #7129 (uImage part only); HEX/SREC and FIT are out of scope.

* fix(uimage): preserve aligned component ranges and exact header facts

* fix(uimage): retain buffered members through consumption and cancellation

* fix(uimage): preserve active read and validation failure precedence

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 19:11:41 +08:00
gensokyo-dev 06d1247836 fix(evidence): preserve residual unknown constraint diagnostics (#7770) 2026-10-28 18:50:33 +08:00
ShuaixinHuang f483eab543 fix(captures): exclude normalized native numeric values (#7764) 2026-10-28 18:46:30 +08:00
gensokyo-dev df7119aeed fix(mcp): distinguish validation from connection loss (#7761)
* fix(mcp): distinguish validation from connection loss

* fix(mcp): reuse parsed HTTP disconnect facts
2026-10-28 18:17:45 +08:00
gensokyo-dev 60bad9ed06 fix(hopper)!: admit macOS arguments before application handoff (#7732)
Admit the final macOS Hopper launcher arguments before application handoff.
Keep prepared-image source identity, return precise typed refusal locations,
and preserve bootstrap/lease cleanup. Update native CLI/MCP recovery
coverage and the installation guide.

Fixes #7727

BREAKING CHANGE: macOS Hopper launches refuse double quotes, backslashes
and line breaks in final helper paths or arguments before application
handoff. Select literal-safe launcher/runtime/database paths and loader
arguments, or a compatible provider. Original executable names can remain
unchanged when their prepared image path is literal-safe.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 09:12:30 +00:00
gensokyo-dev 3b242491a4 fix(cli): attribute keyed archive errors to archive argument (#7731)
* fix(cli): attribute keyed archive errors to archive argument

* test(cli): assert input-error projection reference reuse
2026-10-28 16:48:20 +08:00
blankymandgensokyo-dev e78ac1a55b feat(artifacts): read UnityFS AssetBundle containers (#7699)
* feat(artifacts): read UnityFS AssetBundle containers

inspect-artifact and extract-artifact now recognize the UnityFS signature
and treat format versions 6 through 9 as containers. Header facts (format
version, player/engine strings, declared size, dataflags, blocks-info
bounds and the declared 16-byte data hash) are retained alongside a
directory of CAB-* and .resS members. Member bytes stream on demand:
only the blocks covering the requested member are read and decompressed
with bounded expansion and cancellation.

Bundles declaring asset-bundle encryption keep header facts and report
unknown directory coverage. LZMA blocks-info reports unknown coverage;
LZMA member reads fail as unavailable while the directory stays
readable. Malformed counts, out-of-stream members and declared sizes
beyond the file fail as format errors. UnityFS classifies by bytes in
root detection, inventory graph construction and desktop inspection,
which emits a unityfs-header fact.

Validation: tsc --noEmit, oxlint (no new findings on touched files),
17 reader tests covering LZ4HC/uncompressed tables, blocks-info at end,
format 6 without padding alignment, multi-block members, malformed
input, unsupported compression, cancellation and abandoned streams;
full artifacts/domain/conformance suites pass. Verified against one
real YooAsset bundle (Unity 2021.3.32f1, format 8, LZ4HC): the
streamed SerializedFile contained its managed assembly byte-identical
to a UnityPy extraction. Other producer variants unverified.

* fix(unityfs): own bounded member reads and preserve partial evidence

* refactor(unityfs): clarify producer revision flag decisions

* fix(artifacts): preserve coverage authority and bound UnityFS block work

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 16:47:39 +08:00
gensokyo-dev 6a226bd092 fix(apple): validate keyed archive selectors consistently (#7728) 2026-10-28 16:01:17 +08:00
gensokyo-dev 2b8fb387b6 fix(windows): resolve default tools before process launch (#7725)
Resolve Windows ADB, Apktool and update commands against the selected
PATH/PATHEXT before launch. Keep explicit paths and relative PATH choices,
report lookup failures, and reuse absolute batch invocation and ownership.

Add native launch/argv regressions, real npm metadata probes and focused
CI ownership so these boundaries retain Windows verification on the fast tier.

Fixes #7724

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 07:27:48 +00:00
gensokyo-dev 0e4b806db1 fix(cli): preserve missing-selection Markdown token windows (#7726) 2026-10-28 15:23:00 +08:00
gensokyo-dev a0a1962e06 fix(cli): validate byte queries before opening targets (#7723) 2026-10-28 15:00:01 +08:00
gensokyo-dev b0fb51cb4c fix(environment): share inline state byte admission (#7721)
Reuse shared padded-base64 facts and derive encoded admission from the existing decoded-byte budget. Refuse overflow before decoded allocation and retain padded syntax. Cover capacity, compact HTTP replies and live MCP state readback.

Fixes #7719

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-28 06:48:26 +00:00
5ae77656e7 feat(workflows): expose guided prompts through CLI and MCP (#6995)
* feat(workflows): expose guided prompts through CLI and MCP

* fix(workflows): address guided workflow review findings

* fix(workflows): preserve scoped contracts and actionable validation

* test(workflows): allow bounded CLI startup on shared hosts

* test(workflows): audit guidance effects and content provenance

* perf(workflows): defer rendering and separate catalog scope

---------

Co-authored-by: Deepak-Kambala <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 14:12:39 +08:00
gensokyo-dev b594fbc3e7 fix(apple): scope application roots to the inventory format (#7714) 2026-10-28 13:28:03 +08:00
gensokyo-dev 8397a0c9eb fix(plugins): align listing metadata with REA branding (#7711)
Use one packaged icon for Codex and Claude, and align listing descriptions with the repository About.
2026-10-28 05:16:41 +00:00
gensokyo-dev fbb0e79354 fix(cli): reject empty observation duration selections (#7710) 2026-10-28 12:57:14 +08:00
Aadityaandgensokyo-dev c36971953c feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions (#7136)
* feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions

open_binary refused Intel HEX and S-record programming files as an
unsupported format, so inspect_artifact and extract_artifact could not
answer which bytes load at which addresses.

Recognize both encodings by a fully valid first record and decode them
statically into address-contiguous regions, each an inventory entry named
0x<start>-0x<end>.bin and extractable as raw bytes. Root format_metadata
records the encoding and variant, record counts, unique and duplicate
bytes, address bounds, termination, declared start address, S0 header and
validated S5/S6 count. Gaps are never filled; start addresses are declared
facts, not proven entry points.

Checksum, record-type, byte-count, line-length, overlap-conflict, address
wrap and post-termination errors fail closed with the line number; a
missing termination record leaves coverage unknown. Decoding is bounded by
a 128 MiB file ceiling (decoded bytes are at most half the file and are
reserved after the size check) and 65,536 address-discontiguous runs.

Refs #7129

* fix(artifacts): refuse Intel HEX extended address records with a non-zero address

* fix(artifacts): bound HEX region copies and retain stream cancellation

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 12:35:55 +08:00
gensokyo-dev ad7d4c1f4d docs(errors): align MCP validation status and verification columns (#7708) 2026-10-28 12:19:25 +08:00
gensokyo-dev b69d8c5467 docs(changelog): repair historical release heading links (#7707) 2026-10-28 12:12:42 +08:00
gensokyo-dev c01ed783ac fix(elf): share section-table presence validation with notes (#7706) 2026-10-28 12:08:25 +08:00
d3b31a0e29 feat(cli): report opt-in command timings (#7042)
* feat(cli): report opt-in command timings

* fix(cli): time standalone MCP doctor

* fix(cli): retain timing on doctor errors

* fix(cli): preserve formatted version output with command timings

---------

Co-authored-by: dhruvac29 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 11:53:29 +08:00
gensokyo-dev 3d5c73f35c fix(cli): retain JSONL parser diagnostics under token counting (#7705) 2026-10-28 11:48:31 +08:00
gensokyo-dev e556134302 fix(deps): patch fast-uri, qs and HTTP/3 certifi (#7704)
Refresh compatible security fixes and verified dependency notices. Keep
historical HTTP/3 guest verification separate from the refreshed trust store.

Fixes #7700

Assisted-by: Aya (gensokyo-skills:aya-source-investigation)
2026-10-28 03:34:36 +00:00
aa6f6436c9 feat(cli): add CSV and TSV analysis views (#7254)
* feat(cli): add CSV and TSV analysis views

Co-authored-by: Copilot <[email protected]>

* Update tests/boundary/cli/filteredArrayOutput.test.ts

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

* Update src/cli.ts

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

* fix(cli): address delimited export review feedback

* fix(cli): format delimited output block in cliResultOutput

* fix(cli): retain delimited view uncertainty and align completions

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 11:27:40 +08:00
40b9c03f99 feat(environment): serve query numbers, counted values and made objects (#7332)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

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

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

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

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

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

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

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

* docs(environment): record verification for blank members and sorted writes

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

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

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

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

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

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

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

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

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

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

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

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

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

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

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

* docs(environment): record the census with main's unknown-clock rule merged

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

* feat(environment): serve query numbers, counted values and made objects

- A query parameter accepts a number or boolean, which serving sends as its
  JSON text, as agents send page sizes and flags.
- A sequence may start at the value its first create took above every value
  its type held (Vikunja's per-project task index), and a later create's
  composed, template or started-sequence rule is adopted when it also spells
  the first create's value.
- A made-object rule explains an object the application makes with each
  created entity: members the request stamped, members holding the entity's
  own id, and counts one past the held objects' values.
- A create's answer names the id of the one object a one-element array member
  holds, so bulk creates link to their type.
- The skill reads each listed object by id before writing.

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

* feat(environment): let a linked create take path values it stores

A create linked to its type's own collection, whose path has no parameters,
accepts any path values when its plan stores each one in a field, as a Vikunja
task stores its project; replay and serving apply it alike.

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

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

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

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

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

* docs(environment): verify query values, counted values and made objects

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

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

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

* docs(environment): record the call-values checks at #7274 93821024

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

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

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

* docs(environment): record the call-values checks at #7274 45832406

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

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

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

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

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

* fix(environment): check made objects as recorded and keep their ids and clocks honest

- A made object's clock counts as a write clock, so a capture with no clock
  anchor withholds the create instead of stamping 1970.
- Members holding the entity's identity follow the id written, generated or
  sent, through the type's id field.
- A later create's made object is checked as recorded, before its own members
  are rewritten, so one naming another entity withholds the plan.
- A later create's template is adopted only when it spells that create's value
  too, not just when the first create's text holds its source value.

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

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

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

* docs(environment): count six withheld plans that explain more, not seven

The bulk task create counted among them is served on this branch.

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

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

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

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

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

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

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

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

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

* chore(environment): bump the skill to 52 above #7260's 51

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

* chore(environment): put back the call-values hunks main's merge took its side for, and bump the skill to 53

The merge of main kept main's side where it overlapped this branch, so it
could commit without a conflict. This restores this branch's side next to
main's: made-object agreement beside the & slug spellings, sequence starts
in the earlier evidence (now built by earlierOf), the combined value
imports, and recording step 2 beside main's UI-first step 3. Main is at
skill 50 and #7274 at 52, so this branch takes 53.

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

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

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

* fix(environment): keep a later create's made object only with clocks its request stamped

followsMadeObjects only checked that a made object's clocks parsed, so a
later create whose object showed an older clock still passed, while serving
stamps the write's clock. Its clocks must now be in the rule's format and
within the stamp window of that create's request.

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

* fix(environment): compare soft-deleted read fields

* fix(environment): reject contradictory learned sequences

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

* fix(environment): replay lists that include deleted records

* fix(environment): make no count of a made-object member earlier creates held alike

A count only a later create shows starts at the exemplar's value, which is
right only while the exemplar is the one create accepted: creates accepted
after it showed the member unchanged, which a count would have moved. The
merge now refuses such a count, as the top-level sequence check does.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 07:51:03 +08:00
PM baec375c34 fix(cli): validate outline-depth before numeric coercion (#7505)
Fixes morluto/rea#7425

Signed-off-by: pqminh27 <[email protected]>
2026-10-28 05:16:49 +08:00
PM 423d190026 fix(cli): preserve Markdown table edge spaces (#7506)
Fixes morluto/rea#7432

Signed-off-by: pqminh27 <[email protected]>
2026-10-28 05:16:28 +08:00
Beqa 6efb384261 docs(uninstall): clarify malformed shared config retention (#7507)
Uninstall retains unverifiable shared registration storage without blocking
REA-owned purge; document that exception beside the stop-before-removal rule.

Fixes #7320
2026-10-28 05:16:13 +08:00
5fcf0d2dcc fix(browser): save from gzip, deflate and cached responses within a bound (#7361)
A saved response's decoded size is bounded by its received size, or the
declared length of one served from cache, times 1032 for each gzip or deflate
coding, the most DEFLATE expands. Grafana answers folder creates gzipped, so a
UI step saving a new folder's uid was refused. Other codings and unknown or
zero sizes stay refused before the body is read.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:08:27 +08:00
7bc6c5bf61 feat(environment): serve the writes callers make without recorded form fields (#7260)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

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

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

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

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

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

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

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

* docs(environment): record verification for blank members and sorted writes

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

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

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

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

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

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

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

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

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

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

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

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

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

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

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

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

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

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

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

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

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

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:07:28 +08:00
aa210b7ccc feat(environment): serve the writes that later reads explain (#7274)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

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

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

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

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

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

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

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

* docs(environment): record verification for blank members and sorted writes

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

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

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

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

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

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

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

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

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

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

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

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

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

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

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

* docs(environment): record the census with main's unknown-clock rule merged

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

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

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

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

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

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

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

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

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

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

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

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

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

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

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

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

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

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

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

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

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

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

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

* chore(environment): bump the skill to 52 above #7260's 51

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

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

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

* fix(environment): compare soft-deleted read fields

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:05:21 +08:00
Wenyu Chiou ef11f77836 fix(evidence): Reject non-file destinations before overwrite advice (#7495)
Check destination kind before overwrite policy and cover directory recovery,
regular-file controls, symlink modes and preservation.

Fixes #7444.

Independent review: APPROVE for patch SHA-256
3d65eb23d01da9b6df786ad0db337dcff79d85923f23678a5168babb88c16080,
relayed by the coordinating thread (reviewer identity not supplied in the
delegated packet). Triggers: file I/O, error handling, three shipping files.

Validation: baseline/candidate runtime builds and actual synthetic CLI probes;
initial focused run 70 passed, 2 Windows EPERM fixture failures, 3 skipped.
Final changed filesystem file 42 passed, 5 skipped; symlink semantics and
full aggregate checks remain unverified. Targeted format/lint/discovery passed.
2026-10-28 03:41:26 +08:00
0cd7ef456b feat(plugins): make Claude Code and Codex installs self-contained (#7514)
Ship Claude Code and Codex from one release-pinned npm artifact with bundled runtime dependencies, extraction-only MCP qualification, plugin-aware recovery, and synchronized release metadata.

Integrates #2815 and the original proposal #1977. The original commits remain available on the PR branch; retain their author and assistant credit in this squash.

Validation: required CI passed, real Codex/Claude loading verified, and reviewed packaging/recovery findings addressed.

Co-authored-by: GamerDubz <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: OpenAI Codex <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-27 17:13:17 +00:00
gensokyo-dev 269630f4e1 docs: archive historical verification and release inventories (#7519)
Retire redundant historical JSON copies from the active documentation tree after verifying identical published release snapshots and an immutable route-report archive. Preserve current release data, original evidence, release publication logic and historical access.

Validation: 48 release tests; documentation conformance and rendered archive link/digest; Git blob/SHA-256 checks; selected current-head CI passed. Greptile: 5/5 with no actionable findings. Codex GitHub review unavailable due quota.

Closes #7517.
2026-10-28 00:54:54 +08:00
gensokyo-dev 65f1c44734 refactor: retire superseded test-only runtime entrypoints (#7515)
Migrate MCP session tests to the named lifecycle output schema and preserve raw reply envelopes. Move R8 type/alias checks to the production field resolver and recovery guidance checks to their scoped factory; remove superseded test-only exports and an unused import.

Validation: 99 focused tests, an exact-old-schema negative control, full selected CI including all 8 shards and source tests, and Greptile source review passed. Fresh-head real stdio MCP analyzed [email protected] and retained a complete authenticated Evidence bundle after close; compact/full catalogs are unchanged. Full local typed lint hit the 3 GiB memory guard; scoped typed lint passed. Codex review quota exhausted.

Fixes #7513.
2026-10-28 00:22:43 +08:00
gensokyo-dev 6eb37a7749 fix(ci): restore qualification and reduce validation overhead
Use one typed-checker worker by default without changing semantic scope or caller overrides. Reuse the bounded CLI worker for pure argument validation while retaining fresh-process coverage and all upstream cases.

Keep registration tests independent of real package-runner downloads while asserting the actual prewarm command and startup context. Pass the advertised binary_session definition by name to package verification, retaining default SDK validation and complete catalog/contract checks while avoiding compilation of unrelated tool outputs. Preserve all four installed-tarball scenarios and existing deadlines.

Strengthen Hermes preview operation/backup assertions and clarify physical-path routing documentation.

Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
2026-10-28 00:20:07 +08:00
S1MonandS1Mon bcc6775dc1 fix(cli): reject non-decimal Android overload index tokens (#7493)
Android overload selection declared a plain number, so the CLI argument
parser coerced the raw token with Number before the schema ran. An empty
token therefore arrived as 0, and hexadecimal, exponent, signed, and
float spellings arrived as their numeric value, leaving both commands to
proceed to artifact admission on a selector the caller never wrote as a
decimal integer.

Reuse the existing decimal token parser that the other integer CLI
options already use. Omitted and explicit decimal zero keep their current
meanings, and the published schema for overloadIndex is unchanged.

Validation: npm run compile, the cli-boundary vitest project for
tests/boundary/cli/cliIntegerArguments.test.ts (56 passed), npm run
typecheck, npm run lint, npm run lint:typed, npm run format:check, npm
run metadata:check, npm run verify:architecture-guards, and npm run
knip:production. The new cases fail against the base source and pass with
the change.

Fixes #7430.

Co-authored-by: S1Mon <[email protected]>
2026-10-27 23:25:32 +08:00
Pirro Zani 313af7119c fix(website): keep phone navigation labels from splitting mid-word (#7497)
The phone header gave each link an equal minmax(0, 1fr) column and
allowed overflow-wrap: anywhere, so single-word labels such as
"Showcases", "Anleitungen", "Руководства" and "Посібники" broke between
letters at 320-430px. Links now share the row as flex items that never
shrink below their longest word. Labels wrap only at spaces, and a word
wider than the whole row can still break, preserving the enlarged-text
fit from #3598.

Refs #3597
2026-10-27 23:25:04 +08:00
gensokyo-dev a95b83c2b0 perf(javascript): cache callable result syntax by AST identity (#7508)
Cache direct return/yield source expressions by immutable AST identity for all existing static and semantic consumers. Weak keys bound retention to AST lifetime; mutable analysis facts and Evidence remain unchanged. Refs #6311; full CastleIO analysis still times out.

Validation: 225 focused tests; baseline cache-regression controls; build, serial check:fast and test discovery passed. CastleIO direct-syntax facts equal with 78,272 warm lookups reduced from 2.72-2.81s to 13.0-13.4ms. Complete sourcemap-codec CLI analyses retained byte-identical Evidence; no end-to-end speedup or token reduction claimed.

CI: source 20,888 passed / 15 skipped; seven built-dependent shards, static, docs, package builds, Linux native baseline and real JS recovery CLI/MCP passed. Shard 6 Hermes path-preview failures both reproduce on exact main b9d1a61dd; Portable tests fails downstream. Greptile 5/5, no findings; Codex quota exhausted. Authorized admin bypass for unrelated baseline CI and unchecked human readiness; no human review attestation made.
2026-10-27 22:53:42 +08:00
morluto 7f90549916 chore(android): remove unused type exports 2026-10-27 22:52:33 +08:00
morluto b3fd0aa3ee test(setup): align Hermes symlink preview expectations 2026-10-27 22:52:33 +08:00
Beqa b9d1a61dd5 docs(ghidra): clarify Objective-C Message Analyzer libobjc log (#7249)
Clarify the Objective-C analyzer message, coverage limitation, structured failure diagnostics, and existing opt-out/startup resource controls.

Verification: exact documentation change applied on main 79d2fc054; docs:check passed. Installed Ghidra 12.2 analyzer/scheduler and REA error/launch owners inspected. No real macOS target reproduction; original failure remains unknown. PR CI and Greptile passed; Codex review unavailable.
2026-10-27 22:28:29 +08:00
Adolanium 79d2fc0540 fix(doctor): classify an unreadable skill identity as unknown (#7090)
rea doctor reported a skill copy with an unreadable or unresolved path as identity state unknown but classified its skill:identity check as config_drift, which claims the copy has drifted. docs/installation.md keeps an unreadable skill identity unknown, and the sibling registration checks already classify unknown state as unknown. The skill check and the DoctorScope fallback now do the same.
2026-10-27 21:34:05 +08:00
Saiyam Jain 097cd2c988 fix(browser): report non-absolute export paths at their input field (#7097) 2026-10-27 21:33:49 +08:00
84f62f76e5 fix(hermes): preserve external YAML anchor consumers (#5152)
* fix(hermes): preserve consumers of anchored registrations

Keep original alias values and comments before setup or uninstall replaces an anchored REA node. Preserve original nested bindings and retained sharing through the common YAML writer.

Fixes #3325

* test(hermes): assert retained alias object identity

* fix(hermes): preserve removed keys and surviving alias owners

* test(cli): restore retained-view cancellation phase coverage

* fix(setup): retain copied Hermes anchor comments

* chore(hermes): reverse incidental merge hook changes

Reverse the merge relative to its included-main parent to restore exact upstream bytes. Reapply only the independently reviewed shared-owner and regression patch in a normal follow-up commit. Keep all ordinary history and normal hooks; no remote push of the rejected hook drift.

Refs #3325

* fix(hermes): retain reviewed shared-owner integration

* chore(hermes): reverse incidental merge hook changes

Reverse the merge relative to its included-main parent to restore exact upstream bytes. Reapply only the independently reviewed shared-owner and regression patch in a normal follow-up commit. Keep all ordinary history and normal hooks; no remote push of the rejected hook drift.

Refs #3325

* fix(hermes): retain exact reviewed shared-owner regression

* test(native): align plist command fixtures with main

Adopt the two maintained-main fixture files as of
dd198d037f. Distinguish exact XML lint
validation from conversion commands and retain truthful provenance,
source-value, format, limitations and unknown-scalar assertions.

The original seven provenance failures reproduce before this repair.
The affected three-file group passes all 86 checks afterward. Product
code and installed-package bytes remain unchanged.

Co-authored-by: morluto <[email protected]>

* fix(native): retain bound-target dispatch failure contracts

Adopt morluto's canonical Apple dispatch metadata owner and bounded MCP
and cleanup controls from 41e10b240c.
Keep fresh admission separate from already-bound target changes and
retain access-denied classification, exact physical causes and cleanup
ownership. Update the two stale unit expectations exposed by Source CI.

The original unit pair reproduces before the correction. Current focused
regressions, package verification and installed feature controls pass.
Only the canonical Apple owner and related controls are ported; this does
not integrate unrelated later-main changes or create another contribution.

Co-authored-by: morluto <[email protected]>

* test: align plist provenance and package listing fixtures

Keep the exact XML lint invocation in CLI provenance and include the
required CLI environment owner in the synthetic package member list.
Preserve binary provenance and all LF, CRLF, literal-CR and bytecode checks.

* revert(hermes): restore canonical main before reviewed replay

This reverts commit 575a10d194, reversing
changes made to bb17e90f27.

* test(cli): retain full cancellation oracle with prepared projection

---------

Co-authored-by: hhang <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-27 21:33:17 +08:00
Afloat 095a51cf38 fix(browser): reject invalid UTF-8 in HTTP CDP discovery (#4524) 2026-10-27 21:33:01 +08:00
Jeff d47efe1a54 fix(native): reject mixed dyld refusal diagnostics (#5778)
* fix(native): classify confirmed dSYM dyld refusals

* style(native): format target refusal handling

* fix(native): confirm precise Mach-O layout refusals before export fallback

* test(cli): observe cancellation at stable source resolution boundary

* fix(native): reject contradictory dyld layout refusals

* fix(native): preserve mixed dyld diagnostic failures

* fix(native): retain the evidence behind contradictory layout refusals

When the imports and exports refusals disagree, or a refusal disagrees with
the parsed Mach-O header, inspect_macho returned a generic output error that
kept only one dyld_info capture. It dropped the header file type and the other
refusal, so the failure could not show why the admitted diagnostic was
rejected.

The contradiction now has its own error. Its diagnostics keep the parsed file
type and every admitted refusal (mode, admitted layout, exit code and output),
and the captured output is still the conflicting command. The test checks the
reason and the retained diagnostics for each contradiction case.

* test(native): allow time for real Mach-O CLI and MCP controls
2026-10-27 21:32:13 +08:00
Ahmed cbeeeda97b fix(javascript): resolve preceding exported variable initializers and computed method keys (#5387)
fix(javascript): resolve variable initializers before computed method keys
2026-10-27 21:31:59 +08:00
Chip Li b4118b9683 docs(hermes): explain nested-home routing and preview limits (#4145)
* docs(hermes): clarify sticky profiles for nested homes

* docs(hermes): qualify nested homes beneath default symlinks

* docs(hermes): cover canonicalized ancestor paths

* docs(hermes): clarify both missing-path containment errors
2026-10-27 21:31:40 +08:00
morluto ec3f84b626 test(ci): avoid pinning skip warning wording 2026-10-27 21:26:38 +08:00
1201c25542 fix(ci): align static checks and regression fixtures with current contracts (#7349)
* fix(ci): retire unused internal analysis declarations

* test: use an admitted app bundle for provider routing

(cherry picked from commit 00aa17f12a)

* test: keep bundle routing coverage outside oversized group

(cherry picked from commit 8658353b0d)

* fix(ci): align regression fixtures with current admission contracts

* test: bind extraction close fixtures to their ZIP digest

(cherry picked from commit 626d0250db)

* test(cli): match typed analysis outcomes in function detail fixture

* test(ghidra): preserve empty Java property separator framing

* test(electron): retain unknown IPC ownership for native module gaps

* fix(electron): share source-scoped call path identity

* test(captures): assert safe source identity on SAZ refusal

* test(apple): match root occurrence kind to inventory manifest

* fix(examples): align directory root occurrence identity

* test(electron): align equivalent fixture assertions with upstream

* test(process): assert retained capture root identity

* test(windows): declare the PE fixture optional header

* test(ci): refresh owned registration and verifier fixtures

* test(setup): use owned registrations for refresh fixtures

* test(process): control unavailable ownership in CLI cleanup fixture

* test(process): guard controlled compiler output arguments

* test(setup): align equivalent entrypoint guards with upstream

* test(process): release the cleanup target through an owned gate

* test(ci): align semantic identities and owned fixture facts

* test(pcap): retain the upstream DNS wire fixture branch

* test(pcap): use the canonical wire ownership fixture

* test(pcap): assert exact DNS wire query labels

* test(ci): preserve admission and ownership fixture prerequisites

* test(ci): align authenticated fixture and cleanup boundaries

* test(windows): align native artifact fixtures with upstream

* test(android): request complete evidence for inline MCP contracts

* test: avoid pinning diagnostic wording

* test: verify cleanup diagnostics at their source

* test: strengthen evidence and cleanup checks

* test: avoid pinning registration guidance

---------

Co-authored-by: Plastic-time <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-27 21:24:30 +08:00
gensokyo-dev 570abe95f2 feat(android): link native declarations to static JNI candidates (#7472)
Link original DEX native declarations to per-ABI function exports and relocated static JNI table candidates through shared CLI/MCP workflows. Preserve global attribution ambiguity, exact provenance, partial coverage and retained Evidence; default MCP delivery to bounded summaries with filtered/batch pages.

Use one immutable ELF relocation decoder with streaming IL2CPP materialization, cooperative bounded export/version parsing and bounded JNI state. Reuse validated session inventories without loading target libraries or executing JNI code.

Validation: relevant type/lint/build and parser/domain/public CLI/MCP regressions; genuine four-ABI NDK fixtures and independent library/slot oracles; actual JDK/JADX Conscrypt workflows with retained-source removal. Full measurements and limitations are recorded in docs/verification/android-jni-bindings-2026-10-27.md.

Closes #6403.
2026-10-27 18:53:22 +08:00
gensokyo-dev 740c0f5615 fix(windows): restore native verification fixture coverage (#7479)
Restore independent PE fixtures and dependency-complete standalone artifact verifier tests. Execute existing real Windows native behavior and inspection recovery proofs before unrelated curated tests.

Validation: 101 focused tests; full typecheck; scoped lint, module/discovery/format/docs checks; valid/corrupt real-image verifier controls; real Windows artifact, behavior, and inspection recovery proof steps passed. Greptile found no actionable issues. Codex review quota exhausted.

CI remains red from exact-base failures: Source 9, Windows 7 (base 15; loader 8 repaired), and built shard 4's 7 matching failures; 7 other shards cancelled by fail-fast. Later Windows package/Node 22/24 checks skipped. User explicitly authorized bypass of established main failures. No claim of complete CI or unavailable coverage.

Fixes #7474.
2026-10-27 18:50:40 +08:00
gensokyo-dev d6f88becd5 fix(mcp): summarize copied browser observations in input declarations (#7469)
Declare copied complete browser observations as abbreviated object operands, keeping canonical validation and normalization controls unchanged. Preserve all other tool declarations and Evidence identities.

Refs #4973 and #4081.
2026-10-27 17:09:22 +08:00
morluto b8cdbdf0ef test(sqlite): gate projection checks on native limits 2026-10-27 15:47:54 +08:00
gensokyo-devand555aaditya 03f17cf41d feat(android): apply caller-selected R8 mapping evidence (#7456)
Apply caller-selected R8/ProGuard mappings to shared Android inspections, preserving raw identities, exact verification, ambiguity and provenance. Add retained summaries and bounded/cached parsing, with measured real R8/Conscrypt workflows and review fixes.

Fixes #6329.

Co-authored-by: 555aaditya <[email protected]>
2026-10-27 15:47:08 +08:00
Rudy Mizrahi Celekliandgensokyo-dev 98422997c0 fix(setup): report Kuna cache aliases consistently (#7207)
fix(setup): report Kuna cache aliases consistently (#7207)

Unify lexical default-cache reports without changing removal authority. Plan selections before deleting managed directories; resolve absolute POSIX paths independently of ambient cwd. Preserve parent traversal, literal backslashes, caller data, and result/removal order.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 15:13:47 +08:00
gensokyo-dev 2817d30ab2 fix(docs): preserve verification reports after experiment archival (#7462)
Move the retained allocation report and unchanged receipt JSON into verification documentation. Keep one report producer, repair current and archived links, document test-support ownership, and remove retired lint inputs.

Fixes #7458.

Validated focused/Python checks, report consistency, docs rendering, and real Bash CLI/MCP facts against an independent oracle. Greptile reviewed all seven files without comments. CI source/Windows/shard-8 failures match clean-main or existing-main controls; the requester authorized merging with those established failures. Codex review quota was unavailable.
2026-10-27 15:10:48 +08:00
吴佳浩Alben 0676ad254c docs(clients): note Droid's FACTORY_HOME_OVERRIDE (#7382)
768b58c6d (fix(setup): honor Droid home override) made factoryDirectory
resolve from FACTORY_HOME_OVERRIDE, so setup now writes the registration
and the personal skill copy under that home instead of ~/.factory. The
Factory Droid section still described only ~/.factory, and the page names
every other client's home override (KIMI_CODE_HOME, QODER_CONFIG_DIR,
QWEN_HOME, HERMES_HOME, CODEX_HOME, GEMINI_CLI_HOME, PI_CONFIG_DIR, DSH_HOME),
so Droid was the only one missing.
2026-10-27 14:30:18 +08:00
morluto dfe6aaf6da fix(gpu): preserve omitted metadata in projected views 2026-10-27 14:23:55 +08:00
morluto e99b6c2c3b fix(dotnet): retain malformed method body diagnostics 2026-10-27 14:15:20 +08:00
morluto 365fb03e73 chore: archive analysis efficiency experiments 2026-10-27 14:10:07 +08:00
morluto 5b14823353 fix(gdb): retain confirmed detach during close 2026-10-27 14:09:06 +08:00
morluto c602fe8828 refactor(schemas): select native and GPU variants by name 2026-10-27 14:04:53 +08:00
morluto d14f199736 fix(debugger): retain verified detach across cleanup retries 2026-10-27 14:04:49 +08:00
morluto 1fe92bff8e fix(pe): bound forwarders to export directory 2026-10-27 14:04:44 +08:00
morluto 50822e1eff fix(evidence): include every subject format in bundles 2026-10-27 13:59:32 +08:00
morluto 59dc48db20 fix(pcap): attribute DNS answers using wire identities 2026-10-27 13:54:20 +08:00
morluto a2e2c29b59 refactor(analysis): move client cleanup to application 2026-10-27 13:49:44 +08:00
morluto abfc42cfb1 fix(wasm): distinguish source and internal failures 2026-10-27 13:49:43 +08:00
morluto 6941d5ab92 fix(native): classify legacy MZ signatures before COFF 2026-10-27 13:49:41 +08:00
morluto df0e2f7b3e fix(desktop): read bounded MZ signature evidence 2026-10-27 13:46:36 +08:00
morluto cce244f037 fix(providers): reject invalid parsed MCP JSON 2026-10-27 13:46:34 +08:00
morluto 4726b19f98 fix(reference): retain ambiguous source identities 2026-10-27 13:46:32 +08:00
morluto 4055cc4df1 fix(verification): retain browser profile until shutdown 2026-10-27 13:42:36 +08:00
morluto d3af70fa27 fix(ida): preserve startup failure ownership 2026-10-27 13:42:35 +08:00
morluto 2733979b4d fix(analysis): validate native UI seed addresses 2026-10-27 13:42:33 +08:00
morluto c9df40f810 refactor(verification): own browser fixture lifecycle 2026-10-27 13:36:57 +08:00
Rudy Mizrahi Celekliandgensokyo-dev 7d6bccb86d fix(harmony): classify exact manifest names once (#7206)
Use one exact-case manifest path classifier for Harmony model hints and components. Preserve unavailable content identity, resource exclusions, Stage precedence and Evidence provenance.

33 boundary tests, docs checks, serial static checks and real pinned-HAP CLI/MCP before/after task passed. Canonical complete projection Evidence stays identical; uppercase derivative now reports unknown/no manifests. Shipped verifier source-ID assertion reproduces on unchanged main. CI7pass4fail19skip7cancel; exactmain reproduces17source and20shard8 failures. Greptile4/5soleupstreamfile-readercleanup scoped/resolvednotfixed. Requester authorized readiness and unrelated-main bypass; human review is not attested.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 13:30:24 +08:00
morluto f59fc19d21 fix(managed): unify native module identity matching 2026-10-27 13:24:11 +08:00
morluto 4e67bc3999 refactor(analysis): consolidate selection ownership 2026-10-27 13:21:35 +08:00
morluto 6d14e25345 refactor(ida): centralize capability admission 2026-10-27 13:21:33 +08:00
morluto a37dbb953c refactor(artifacts): select extraction reader from inventory 2026-10-27 13:21:32 +08:00
morluto 6ed97a22ed fix(export): retain verification cleanup failures 2026-10-27 13:19:25 +08:00
morluto 1ea6903595 fix(ida): preserve path identity across symlink traversal 2026-10-27 13:19:25 +08:00
morluto c29a93097a fix(pcap): preserve metadata on duration overflow 2026-10-27 13:17:00 +08:00
morluto 5b292b630e fix(export): report publication cleanup failures to callers 2026-10-27 13:14:50 +08:00
morluto 01a725ecfa fix(setup): preserve configuration directory requirements 2026-10-27 13:14:50 +08:00
morluto 235e8bc056 refactor(evidence): centralize provider identity comparison 2026-10-27 13:13:06 +08:00
morluto 21483a1a47 refactor(artifacts): reuse descriptor range reader 2026-10-27 13:13:05 +08:00
morluto 98e47e37a2 refactor(pcap): centralize DNS packet facts 2026-10-27 13:10:24 +08:00
morluto 5aa23c3d17 refactor(providers): move inspection scope to application 2026-10-27 13:06:57 +08:00
morluto 82f50f56db fix(macho): validate slice overlap at shared reader 2026-10-27 13:06:56 +08:00
morluto 47f8669c1e fix(export): retain publication and cleanup failures 2026-10-27 13:06:29 +08:00
morluto ddb5337674 fix(setup): resolve Hermes home ancestors before profile selection 2026-10-27 13:06:29 +08:00
morluto b61fe25068 refactor(electron): reuse source range identities 2026-10-27 13:04:25 +08:00
morluto 65aeb80222 refactor(providers): share artifact cleanup binding 2026-10-27 13:04:23 +08:00
morluto c7afedc3d2 refactor(macho): consolidate universal image selection 2026-10-27 13:04:22 +08:00
morluto df8c90df8f fix(browser): retain cancellation after failed worker stop 2026-10-27 13:01:18 +08:00
morluto 8a8331b082 fix(setup): preserve Hermes home traversal 2026-10-27 13:01:18 +08:00
morluto 8d747c2acd fix(macho): unify segment mapping admission 2026-10-27 13:00:33 +08:00
morluto 0a9d92a54b fix(browser): share admitted accessibility identities 2026-10-27 12:57:32 +08:00
morluto 4b6fd03146 refactor(evidence): consolidate import staging 2026-10-27 12:57:31 +08:00
morluto c66729eca9 fix(workers): retain cancellation through final cleanup 2026-10-27 12:56:10 +08:00
morluto 4572f31589 fix(pcap): preserve DNS class when attributing answers 2026-10-27 12:56:10 +08:00
morluto 8691e48e67 refactor(evidence): return import counts from ledger owner 2026-10-27 12:54:37 +08:00
morluto 1dc33c0543 fix(native): reuse PE header layout evidence 2026-10-27 12:54:35 +08:00
morluto 21b6ea7fbd refactor(ida): consolidate pagination progress checks 2026-10-27 12:49:07 +08:00
morluto 8f894546c8 fix(browser): retain evidence across cleanup errors 2026-10-27 12:49:05 +08:00
morluto 178ab26290 fix(pcap): account for SRV owner fields 2026-10-27 12:46:46 +08:00
morluto 7968ceb450 fix(process): normalize capture environment overrides 2026-10-27 12:45:57 +08:00
morluto 160648cc60 fix(filesystem): unify stable file identity checks 2026-10-27 12:45:57 +08:00
morluto 3e590e5b2f fix(evidence): retain malformed native observations 2026-10-27 12:45:57 +08:00
Rudy Mizrahi Celekliandgensokyo-dev 921225d4b2 fix(apk): match signer serials by signed integer identity (#7205)
Match v1 APK certificate and signer serials by signed integer value. Normalize each comparison identity once and preserve raw certificate bytes and Evidence.

37 source/integration tests, 23 real Android SDK verifier cases, SDK-valid padded APK CLI/MCP oracle and serial static checks passed. Hosted real-apk-signature passed. CI27pass7fail2skip7cancel; exact-main controls reproduce17source and14targeted existing-main assertions. NativeWindows/Mac/Node22 baseline gaps and canceled shards are documented. Greptile sole finding is unchanged upstream browser privacy code, scoped and resolved without a fix claim. Requester authorized readiness and unrelated-main failure bypass; no human review is attested.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 12:45:43 +08:00
morluto 25375924f7 fix(pcap): attribute DNS addresses by record owner 2026-10-27 12:42:51 +08:00
morluto e152349c5d fix(process): distinguish omitted byte filenames 2026-10-27 12:42:51 +08:00
morluto 9e4cf28cef refactor(cli): remove obsolete JSON read error mode 2026-10-27 12:42:25 +08:00
morluto a49a3553f4 refactor(managed): consolidate member comparison keys 2026-10-27 12:42:24 +08:00
morluto 397a5599e3 refactor(asar): retain admitted archive extents 2026-10-27 12:42:21 +08:00
morluto 39c7f7e359 fix(evidence): retain malformed function observations 2026-10-27 12:42:18 +08:00
morluto 44310bba32 fix(il2cpp): validate selected FAT slice identity 2026-10-27 12:37:39 +08:00
morluto 279fc04095 fix(snapshot): retain publication receipt on cleanup failure 2026-10-27 12:37:38 +08:00
gensokyo-devandHarsh 7e8f7f6101 feat(android): expose selected manifest component declarations (#7379)
Extend static manifest declarations and retain precise source locations and historical unknowns. Share bounded selected component views across CLI and MCP with complete parent Evidence.

Verified real ApiDemos, an AAPT2-built fixture, legacy Evidence, default-package metadata and relevant CLI/MCP contracts. Recorded whole-task token estimates, timing/resource limits and independent main-failure controls in the PR.

Co-authored-by: Harsh <[email protected]>
2026-10-27 12:37:10 +08:00
morluto 3d311d9eca fix(cli): report failed browser cleanup retries 2026-10-27 12:31:48 +08:00
morluto b9e5a9f463 fix(process): retain unrepresentable filename evidence 2026-10-27 12:31:48 +08:00
morluto dd2cbbf1a2 fix(il2cpp): require FAT container identity 2026-10-27 12:31:35 +08:00
morluto 7cbf375d01 fix(evidence): retain authenticated parents on cancellation 2026-10-27 12:31:33 +08:00
morluto dcfb61826d fix(native): share Swift symbol recognition 2026-10-27 12:31:31 +08:00
morluto dd9f5cdc4d refactor(providers): share client close options 2026-10-27 12:27:34 +08:00
morluto 21f12b896e fix(native): order Objective-C category records 2026-10-27 12:27:32 +08:00
morluto f5edef3c96 fix(hopper): share cancellable probe output waits 2026-10-27 12:25:03 +08:00
morluto ad1a57b65d fix(native): own Objective-C category identity 2026-10-27 12:25:01 +08:00
morluto c4242cb85e fix(evidence): own standalone command finalization 2026-10-27 12:24:59 +08:00
morluto 2e7a206b20 fix(process): preserve symlink target bytes 2026-10-27 12:23:56 +08:00
morluto ff6b3fb9a1 fix(evidence): admit rebound APK and WASM views 2026-10-27 12:23:56 +08:00
morluto 14dd07e740 fix(exports): preserve ELF name byte identity 2026-10-27 12:17:29 +08:00
Rudy Mizrahi Celekliandgensokyo-dev 1cd82597da fix(browser): normalize discovery wire facts at the shared boundary (#7204)
Normalize transport-specific discovery facts at their shared owner; preserve upstream duplicate-ID rejection and correct discovery-time attachment uncertainty in all consumers.

53 focused regressions, serial static checks and real Chrome CLI/MCP fault injection/inspection pass. Greptile final head 5/5. Exact-main controls reproduce all 17 source and 21 shard-8 failures; seven shards cancelled and remain unverified. Requester authorized readiness and unrelated-main CI bypass. Human review is not attested.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 12:16:50 +08:00
morluto e334a13c79 fix(cli): merge mixed array filter selections 2026-10-27 12:14:36 +08:00
morluto effb796027 fix(evidence): validate retained views by semantic identity 2026-10-27 12:14:36 +08:00
morluto 5f9f13d77b fix(evidence): preserve view identity and cancellation outcomes 2026-10-27 12:12:34 +08:00
morluto 6de60993b8 fix(adb): retain output on command failures 2026-10-27 12:06:29 +08:00
morluto 47a8000cce fix(cli): project nested array slices 2026-10-27 12:06:29 +08:00
morluto 2f27d3ac92 fix(evidence): separate source and presentation identity 2026-10-27 12:06:29 +08:00
morluto 3e8923c6eb fix(evidence): share queued cancellation handling 2026-10-27 12:06:15 +08:00
morluto 0793b6c3ca fix(rizin): retain rejected debugger shutdowns 2026-10-27 12:06:14 +08:00
morluto f89b03e738 fix(gdb): retain rejected shutdowns for retry 2026-10-27 12:01:12 +08:00
morluto 7557e39dcd fix(providers): preserve cleanup retry diagnostics 2026-10-27 11:59:44 +08:00
morluto 0eff039d08 fix(mcp): preserve Android cleanup failures 2026-10-27 11:56:01 +08:00
morluto 5d30f82417 fix(artifacts): validate directory source spelling 2026-10-27 11:54:58 +08:00
morluto 7cc33fcf7e fix(server): preserve cleanup failure diagnostics 2026-10-27 11:52:46 +08:00
morluto 8aa6df2d86 fix(native): share otool build tool parsing 2026-10-27 11:52:21 +08:00
morluto a6a69ec63e fix(ghidra): require Windows cleanup ownership 2026-10-27 11:49:23 +08:00
morluto cd7a0eefd8 fix(apple): validate JVM interface identity 2026-10-27 11:49:22 +08:00
morluto c79bd41b5d test(hopper): verify bootstrap cleanup ownership 2026-10-27 11:45:25 +08:00
morluto 4e33bd3e08 fix(browser): preserve discovery identity ambiguity 2026-10-27 11:45:25 +08:00
morluto a486cfe518 fix(windows): validate declared PE header identity 2026-10-27 11:45:25 +08:00
morluto a116f2a7ae fix(process): close lease peers during release 2026-10-27 11:45:02 +08:00
morluto d2da713859 fix(setup): recognize inherited owned launcher 2026-10-27 11:45:01 +08:00
morluto f2277454b8 fix(setup): preserve unowned client registrations 2026-10-27 11:39:32 +08:00
morluto 4de432d868 refactor(pyinstaller): preserve parsed compression flags 2026-10-27 11:35:47 +08:00
morluto f812dc4135 fix(hopper): retain lease after startup failure 2026-10-27 11:35:46 +08:00
morluto 319fee4691 fix(electron): select nested handlers by source coordinates 2026-10-27 11:30:37 +08:00
morluto 5d5fef7391 fix(process): retain lease recovery cleanup owners 2026-10-27 11:30:35 +08:00
morluto 86d4a21215 docs(search): clarify provider case folding 2026-10-27 11:24:10 +08:00
morluto 7b25049dee refactor(browser): centralize passive source deadlines 2026-10-27 11:24:09 +08:00
morluto fe95b7611f fix(process): retain lease directory cleanup ownership 2026-10-27 11:24:07 +08:00
morluto 76eecf7fa1 fix(mcp): advertise standard route path schemas 2026-10-27 11:23:39 +08:00
morluto 081f2e2559 refactor(process): share stable file identity checks 2026-10-27 11:16:44 +08:00
morluto a18297f7d1 refactor(electron): use required native module facts 2026-10-27 11:16:43 +08:00
morluto 76441ed31b fix(browser): preserve comparison evidence identity 2026-10-27 11:15:38 +08:00
morluto 52b3c63495 fix(ida): complete private input writes 2026-10-27 11:15:38 +08:00
morluto 0d1d6e858d fix(ghidra): unify Unicode literal search semantics 2026-10-27 11:10:10 +08:00
morluto 492860ed07 test(captures): assert retained snapshot evidence 2026-10-27 11:10:10 +08:00
morluto 1635e559d2 refactor(go): simplify build metadata decoding 2026-10-27 11:10:10 +08:00
morluto 92fb319b27 refactor(gdb): reuse inferior identity reader 2026-10-27 11:10:10 +08:00
morluto 17da88f117 fix(process): report retained capture root 2026-10-27 11:08:55 +08:00
morluto 768b58c6db fix(setup): honor Droid home override 2026-10-27 11:08:54 +08:00
morluto 6207843814 fix(ida): preserve Unicode literal search matches 2026-10-27 10:59:38 +08:00
morluto e58feb48c3 refactor(mcp): reuse reverse engineering admission 2026-10-27 10:59:36 +08:00
morluto 3c0e021cb3 refactor(captures): retain SAZ member classifications 2026-10-27 10:59:35 +08:00
morluto f8f7ac96de refactor(mcp): share entropy summary delivery 2026-10-27 10:55:00 +08:00
morluto 1fdd3df7b4 fix(hopper): preserve regex inventory facts 2026-10-27 10:54:59 +08:00
morluto 82d3ab7aa8 fix(browser): respect response metadata grammars 2026-10-27 10:51:50 +08:00
morluto e4cdf65e2c refactor(mcp): share diagnostic evidence handling 2026-10-27 10:51:50 +08:00
morluto 37d6e8ca9a refactor(macho): reuse admitted header identity 2026-10-27 10:51:50 +08:00
morluto bae84d1b93 fix(native): validate plist target identity 2026-10-27 10:48:42 +08:00
morluto 0e17aab451 fix(providers): deduplicate nested cleanup notifications 2026-10-27 10:45:54 +08:00
morluto b989e3c3a3 fix(browser): parse HTTP Link whitespace correctly 2026-10-27 10:45:01 +08:00
morluto 6fa8e2ee3d refactor(node-sea): reuse Mach-O segment facts 2026-10-27 10:44:51 +08:00
morluto 79b8219f29 fix(certificates): unify OID decoding and validate reports 2026-10-27 10:40:48 +08:00
morluto f061989ad2 fix(artifacts): validate bundle resource target identity 2026-10-27 10:39:56 +08:00
morluto b7be0d90a9 fix(firmware): retain nested dependency diagnostics 2026-10-27 10:35:50 +08:00
morluto 92213a9155 test(artifacts): verify evidence refresh after reopen 2026-10-27 10:32:58 +08:00
morluto ec40c10749 refactor(process): share comparison dimension projections 2026-10-27 10:30:59 +08:00
morluto 5093d51c05 refactor(browser): share CDP script source capture 2026-10-27 10:30:57 +08:00
morluto 413f24b532 fix(artifacts): unify selected path failure classification 2026-10-27 10:30:56 +08:00
morluto 3655dfd7d5 fix(elf): validate completed note coverage 2026-10-27 10:30:54 +08:00
morluto 30216468cc fix(browser): share media type interpretation 2026-10-27 10:26:32 +08:00
morluto 4f2fc7ca28 refactor(pe): rely on path input contracts 2026-10-27 10:26:31 +08:00
morluto b0c48e8b65 fix(native): preserve mapping and literal coverage 2026-10-27 10:26:29 +08:00
morluto ded6542d93 refactor(plist): unify number source association 2026-10-27 10:22:33 +08:00
morluto b7cae1ad8c fix(artifacts): validate partial inventory edges 2026-10-27 10:19:00 +08:00
Rudy Mizrahi Celekliandgensokyo-dev 6749bd090d fix(browser): keep opaque cookie partitions unaligned (#7203)
Keep cookies with opaque partitions distinct from unpartitioned identities while retaining value hashes and incomplete comparison coverage. Preserve known partition hashes and use the existing shared CLI/MCP fingerprint owner.

Validated with 63 source and 57 storage boundary tests, builds/static checks, and real Chrome156 CLI/MCP controls. Live opaque-cookie emission was not reproduced; its protocol regression is explicit. Hosted CI's unrelated failures were reproduced against exact main a905a71e3; seven fail-fast-cancelled shards remain unverified; Greptile's sole existing-main finding was scoped and documented. Human readiness remains unasserted; bypass authorized by the requester.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 10:16:15 +08:00
morluto f53c256886 fix(macho): unify fixed-width name decoding 2026-10-27 10:15:49 +08:00
morluto 25fc08189e fix(wasm): parse interface group heads consistently 2026-10-27 10:12:38 +08:00
morluto d1c88fab1a fix(artifacts): validate partial inventory root identity 2026-10-27 10:11:49 +08:00
morluto bfdde7ee4c refactor(web): centralize isolated script paths 2026-10-27 10:05:33 +08:00
morluto 8dc4421325 fix(artifacts): reject inventory counts beyond manifest 2026-10-27 10:04:29 +08:00
morluto 9aee546aba refactor(process): unify trace outcome comparison 2026-10-27 10:03:18 +08:00
morluto c81b7ecebc fix(electron): refuse linked ASAR parent declarations 2026-10-27 10:03:17 +08:00
morluto 650873e772 fix(artifacts): preserve mounted DMG traversal coverage 2026-10-27 10:03:15 +08:00
morluto 2146caeb3d fix(electron): share ASAR node validation 2026-10-27 09:59:58 +08:00
morluto ed62f14be4 refactor(ci): consolidate lint task ownership 2026-10-27 09:55:24 +08:00
morluto 8e0b2a9765 fix(wasm): share WAT comment framing 2026-10-27 09:55:22 +08:00
morluto c1e0f531e6 fix(firmware): admit caller-selected source directly 2026-10-27 09:55:01 +08:00
morluto a905a71e31 fix(plist): preserve binary UID and dictionary identity 2026-10-27 09:49:28 +08:00
morluto f878266503 fix(artifacts): admit selected app bundle traversal 2026-10-27 09:48:52 +08:00
morluto 15a8f6847c fix(browser): reject inexact storage values 2026-10-27 09:45:11 +08:00
morluto f3a301eca5 fix(artifacts): validate selected path traversal 2026-10-27 09:45:10 +08:00
morluto 27ac1b6781 refactor(contracts): consolidate tool effect definitions 2026-10-27 09:43:10 +08:00
morluto 443d0778be fix(browser): resolve JSON pointers through array elements 2026-10-27 09:40:11 +08:00
morluto 01d6a95fd7 fix(ghidra): preserve configured seed path traversal 2026-10-27 09:39:43 +08:00
morluto 7ba1d0161e fix(browser): retain ambiguous scenario request identity 2026-10-27 09:35:59 +08:00
morluto 6979c589b0 fix(ghidra): preserve project access failures 2026-10-27 09:35:15 +08:00
morluto 6734dee6a8 fix(managed): retain comparison target cleanup ownership 2026-10-27 09:35:15 +08:00
morluto 83bbad2b2f fix(browser): advance storage pages by examined rows 2026-10-27 09:32:32 +08:00
morluto 242a0f2880 fix(evidence): retain comparison cleanup ownership 2026-10-27 09:31:17 +08:00
morluto 9be640a8b4 fix(providers): preserve selected filesystem identities 2026-10-27 09:31:17 +08:00
morluto 9eb8ba2b29 refactor(cli): use builtin argument owner directly 2026-10-27 09:29:20 +08:00
morluto 55b4efcb88 fix(providers): retain completed cleanup observations 2026-10-27 09:26:42 +08:00
morluto fef83241b1 fix(setup): validate Hopper download channel URLs 2026-10-27 09:26:40 +08:00
morluto 25b69bb803 fix(javascript): qualify network receiver evidence 2026-10-27 09:24:22 +08:00
morluto a293738c66 fix(electron): preserve symlink directory requirements 2026-10-27 09:24:22 +08:00
morluto f68b261db4 refactor(process): remove unused worker PID state 2026-10-27 09:23:20 +08:00
morluto 212479aa02 refactor(environment): reuse recorded query parser 2026-10-27 09:23:19 +08:00
morluto e2da92f5d2 fix(javascript): unify runtime module declarations 2026-10-27 09:23:15 +08:00
morluto 01484bc95c fix(setup): unify macOS version eligibility 2026-10-27 09:23:13 +08:00
morluto a52914649a fix(native): use owned address grammar for coordinates 2026-10-27 09:19:56 +08:00
morluto 4266d32751 fix(cli): retain resources from both cleanup failures 2026-10-27 09:19:54 +08:00
morluto 329325049c refactor(cli): retain typed analysis outcomes through delivery 2026-10-27 09:16:02 +08:00
morluto 0e1500dc8d fix(javascript): qualify renderer endpoint observations 2026-10-27 09:15:24 +08:00
morluto 9cad06db10 fix(managed): retain qualified native candidates 2026-10-27 09:15:24 +08:00
morluto 9c9c64edfa refactor(views): centralize paging contracts 2026-10-27 09:07:50 +08:00
morluto 5cf7133f82 refactor(javascript)!: consolidate renderer and route ownership
Remove the derived package renderer property from new application graphs. Use renderer_declarations for source fields and package loads edges for the selected candidate; previously retained Evidence remains readable.
2026-10-27 09:05:24 +08:00
morluto 39179db68d refactor(artifacts): unify inventory integrity policy 2026-10-27 09:05:18 +08:00
morluto 6d7e627783 fix(cli): preserve literal evidence in Markdown output 2026-10-27 09:04:11 +08:00
morluto bf7721080f refactor(evidence): centralize value flow metadata schema 2026-10-27 09:02:32 +08:00
morluto 5cd61aac8e fix(artifacts): preserve cancellation during inventory cleanup 2026-10-27 08:59:57 +08:00
morluto 72cc2d30a3 fix(browser): preserve request and cleanup failures 2026-10-27 08:58:27 +08:00
morluto 339b82e0c8 fix(cutter): preserve bridge directory traversal 2026-10-27 08:58:27 +08:00
morluto b2df588b4a fix(gpu): retain AMD target declaration evidence 2026-10-27 08:55:43 +08:00
morluto aa253bd13f fix(electron): scope IPC pairing to application ownership 2026-10-27 08:55:34 +08:00
morluto 781eb1b28a fix(browser): validate basic credential usernames 2026-10-27 08:52:22 +08:00
morluto 4932e5615b fix(update): preserve Bun global directory identity 2026-10-27 08:52:19 +08:00
gensokyo-devandAce d109a39105 feat(cli): validate browser scenarios offline (#7343)
Add validate-browser-scenario for offline schema and semantic checks of inline JSON, local files and stdin. Return compact validity/action/secret counts without reading secret values or discovering, launching, attaching to or navigating a browser.

Execution and validation share CLI input admission and the canonical domain schema. Preserve existing capture lifecycle, diagnostics, cancellation and caller-relative executable paths.

Integrates #7149 with attribution. Fixes #5488. Validation includes 24 focused tests, static/build/docs checks, and the preserved real PocketBase scenario with its backend stopped and secret variables unset. Verification and resource/token measurements are recorded in docs/verification/browser-scenario-validation-2026-10-27.md.

Co-authored-by: Ace <[email protected]>
2026-10-27 08:47:36 +08:00
morluto 06642a48fb fix(hopper): preserve selected home and cache paths 2026-10-27 08:47:16 +08:00
morluto e3044645c7 fix(managed): retain local signature comparison coverage 2026-10-27 08:46:31 +08:00
morluto ad92e0f9c8 refactor(ghidra): consolidate function containment lookup 2026-10-27 08:46:27 +08:00
morluto 838f5f44d2 fix(reference): preserve incomplete resolution uncertainty 2026-10-27 08:40:36 +08:00
morluto 54fbfacfb1 fix(macho): preserve leading spaces in export identities 2026-10-27 08:34:42 +08:00
morluto 0781c3e99c fix(reference): validate Git metadata path traversal 2026-10-27 08:34:42 +08:00
morluto 87d023dcc8 fix(flutter): honor cancellation after cleanup retry 2026-10-27 08:34:42 +08:00
morluto f347713e24 refactor(session): share provider replay attribution 2026-10-27 08:32:13 +08:00
morluto 6b3a081f36 fix(flutter): retain snapshot cleanup ownership 2026-10-27 08:29:29 +08:00
morluto abc1351506 fix(artifacts): distinguish incomplete PyInstaller probes 2026-10-27 08:27:08 +08:00
morluto 0ddde829b2 fix(session): preserve failed target restoration 2026-10-27 08:21:41 +08:00
morluto 3431541073 fix(views): honor cancellation before admission 2026-10-27 08:18:40 +08:00
morluto e7f29f8b8f fix(zip): validate admitted source throughout reads 2026-10-27 08:18:40 +08:00
morluto cb0c73246d refactor(artifacts): reuse admitted sources for extraction 2026-10-27 08:18:04 +08:00
gensokyo-devandEdgar Arakelyan 3fcbabe593 feat(browser): reuse saved response values across scenario steps (#7267)
Save typed response fields and reuse generated IDs or authentication values across browser scenario requests, UI observations and navigation through the shared CLI/MCP workflow.

Preserve exact numbers, retained Evidence, explicit source/coverage limits and prospective page-value semantics. Bound response decoding, retained spellings and request serialization before allocation, and protect marked response values across source records, follow-up text and planned screenshots.

Integrates Edgar Arakelyan's #7162, including follow-ups through 9f5191ab0. Final local validation: 560 feature-branch tests and 561 current-main integration tests, static/build/docs checks and real PocketBase CLI/MCP privacy workflows. Whole-task comparison and verification limits are recorded in docs/verification/browser-saved-values-2026-10-26.md.

Co-authored-by: Edgar Arakelyan <[email protected]>
2026-10-27 08:15:52 +08:00
morluto 60822e0702 fix(doctor): preserve Homebrew application paths 2026-10-27 08:11:18 +08:00
morluto ee654e98a6 refactor(artifacts): retain PyInstaller carrier dialect 2026-10-27 08:10:55 +08:00
morluto 5b8e415483 refactor(snapshot): centralize resource cache eligibility 2026-10-27 08:10:48 +08:00
morluto 5d25aa4f52 refactor(artifacts): retain admitted Godot pack location 2026-10-27 08:06:23 +08:00
morluto 69b328eec8 test(browser): verify out-of-order response correlation 2026-10-27 08:04:42 +08:00
morluto 8c8a2752c0 refactor(artifacts): retain admitted PyInstaller cookie facts 2026-10-27 08:02:51 +08:00
morluto 9222526fe2 fix(electron): retain selected scenario paths in evidence 2026-10-27 08:02:45 +08:00
morluto 68533bc0a3 refactor(evidence): share subject architecture declarations 2026-10-27 07:57:37 +08:00
morluto 2ad599dace fix(snapshot): export only target-bound observations 2026-10-27 07:55:16 +08:00
morluto 7fa54e2d62 fix(process): preserve command completion and input ownership 2026-10-27 07:55:16 +08:00
morluto 8a47cbbcce fix(artifacts): deduplicate keyed archive selector choices 2026-10-27 07:54:09 +08:00
morluto b8f58fd7ca refactor(mcp): bind summary delivery to tool contracts 2026-10-27 07:54:01 +08:00
morluto eb24da4481 fix(session): retain unknown cleanup across mixed failures 2026-10-27 07:53:54 +08:00
morluto 25a08ed639 refactor(ida): reuse bounded registration reader 2026-10-27 07:45:45 +08:00
morluto 11b025ef97 fix(session): retain cleanup ownership during failed opens 2026-10-27 07:45:39 +08:00
morluto d1202c539d fix(web): preserve Next route filename patterns 2026-10-27 07:44:04 +08:00
morluto 5261340397 fix(evidence): retain analysis view environment 2026-10-27 07:44:04 +08:00
morluto ecd22c42ac fix(firmware): retain completed command failures 2026-10-27 07:44:04 +08:00
morluto 84a54b6183 fix(asar): preserve header filename identity 2026-10-27 07:44:04 +08:00
morluto 046c4ce353 fix(javascript): preserve native export facts in comparisons 2026-10-27 07:40:55 +08:00
morluto c74059d89f fix(javascript): preserve unknown Next package ownership 2026-10-27 07:40:45 +08:00
morluto 6a9911ea77 fix(session): track retained bundle cleanup dependencies 2026-10-27 07:40:40 +08:00
morluto e80fb638be fix(web): retain source-map declaration provenance 2026-10-27 07:35:13 +08:00
morluto 59a70adefd fix(artifacts): preserve source and coverage facts 2026-10-27 07:35:13 +08:00
morluto 95d9d51f72 fix(process): retain failures through finalization 2026-10-27 07:35:13 +08:00
morluto 381004b664 fix(decompile): preserve provider cleanup through export finalization 2026-10-27 07:34:22 +08:00
morluto af9bc3ab50 fix(ci): validate static evidence through receipt owner 2026-10-27 07:29:57 +08:00
morluto 87e2cc26d8 fix(environment): align comparison collection identity 2026-10-27 07:28:49 +08:00
morluto 29ebd67405 fix(native): retain cleanup failures across capability fallback 2026-10-27 07:25:23 +08:00
morluto 0e578d43c8 refactor(native): retire legacy provider output wrappers 2026-10-27 07:25:08 +08:00
morluto 8706068b57 fix(contracts): validate provider boundary facts consistently
Count only canonical inventory rows in binary overview, reuse the owned Objective-C coverage schemas, and share firmware input issue projection.
2026-10-27 07:19:46 +08:00
morluto 57d0ed45f7 fix(process): retain inherited pipes on output overflow 2026-10-27 07:17:10 +08:00
morluto 0dea08fb07 fix(web): interpret encoded Next route paths 2026-10-27 07:17:10 +08:00
morluto 087acf26e8 fix(evidence): own cached snapshot data 2026-10-27 07:17:10 +08:00
morluto 08f99dbaf2 fix(setup): preserve and validate selected JEB endpoints 2026-10-27 07:17:10 +08:00
morluto dbe2c97af3 refactor(ownership): consolidate snapshot and comparison facts
Retain authenticated immutable snapshot payloads while keeping mutable exports independent. Derive artifact metadata comparisons once and reuse the owning native trace limits.
2026-10-27 07:13:32 +08:00
morluto 3052a067a3 fix(apple): derive NIB hierarchy from observed relationships
Replace overlapping traversals with one bounded relationship owner. Preserve superview direction, treat conflicting or unsupported registry placement as unknown, and retain source facts and coverage.
2026-10-27 07:09:25 +08:00
morluto db0908a732 refactor(artifacts): consolidate projection and PCK interpretation
Keep historical inventory summary variants with their projection owner and share embedded PCK trailer observations while preserving each reader’s admission policy.
2026-10-27 07:09:05 +08:00
morluto 077de92594 fix(setup): preserve selected Hopper loader arguments 2026-10-27 07:03:11 +08:00
morluto 2dd2ffcd45 fix(artifacts): bind operations to admitted sources 2026-10-27 07:03:11 +08:00
morluto ace4dbf36e fix(contracts): align live target classification with admitted facts
Share exact live target variants across open and session responses, preserving historical Evidence and snapshot admission. Consolidate equivalent ELF metadata aliases at their owner.
2026-10-27 06:53:47 +08:00
morluto fc7f042c8c refactor(lifecycle): require cleanup for offline providers
Require resource-owning provider ports to expose cleanup and migrate test adapters to delegate or explicitly close their own resources.
2026-10-27 06:49:07 +08:00
morluto b529bd84d7 refactor(contracts): preserve named output schema types
Keep exact readonly schema maps and constrain contract factories to their owned keys. Verify named catalog lookups retain each schema type.
2026-10-27 06:48:55 +08:00
morluto 248133d784 fix(analysis): interpret provider and ELF inputs once
Pass parsed MIPS program declarations to shared-load analysis and validate IDA string address selectors before enumeration, including empty inventories.
2026-10-27 06:48:48 +08:00
morluto 030fa886f9 fix(setup): verify published configuration content during uninstall
Require publication readback in the owning check so in-place edits are detected and preserved with the original backup.
2026-10-27 06:43:53 +08:00
morluto 82fd223638 refactor(native): consolidate provider-neutral analysis contracts
Move shared function, dossier, inventory, reference and output semantics from the legacy Hopper module to native domain owners. Migrate provider, adapter, test and verifier consumers directly.
2026-10-27 06:43:53 +08:00
morluto a6488a96a9 fix(javascript): preserve renderer path anchors 2026-10-27 06:43:07 +08:00
morluto d0e695b42a fix(hopper): bind relative launcher paths once 2026-10-27 06:43:07 +08:00
morluto 90475ec9e5 fix(artifacts): retain selected keyed archive paths 2026-10-27 06:43:07 +08:00
morluto d60b2cab65 docs(ida): document migration from legacy attached registrations
Describe the native plugin setup, identity and capability differences, verification, and rollback. Link the migration from the packaged skill source.
2026-10-27 06:38:51 +08:00
morluto ed6668b5bc fix(evidence): validate process captures before bundle export
Reuse the read-side capture validation before publication so exported bundles can be imported. Preserve historical and foreign record identity and existing destination files on failure.
2026-10-27 06:36:31 +08:00
morluto 50717c39b6 refactor(contracts): consolidate provider and search ownership
Resolve IDA compatibility facts in one place and migrate capability, session, and verifier consumers. Move native search fields to the domain and reuse residual unknown fields for filters.
2026-10-27 06:36:24 +08:00
morluto 567ea4b1e8 refactor(ownership)!: consolidate legacy aliases
Migrate schema, installer, ASN.1, and tooling consumers to their owning definitions. Remove the redundant CI task entry and the deprecated Cutter command-local JSON flag.

BREAKING CHANGE: Replace Cutter --json=true or --json=false with --json-output=true or --json-output=false. Bare --json still controls CLI result formatting.
2026-10-27 06:31:23 +08:00
morluto 7c8eb63fcc fix(javascript): retain package entrypoint provenance 2026-10-27 06:28:15 +08:00
morluto c27560e434 fix(artifacts): classify selected package names 2026-10-27 06:28:15 +08:00
morluto 3515b26221 refactor(ownership)!: remove redundant aliases
Use canonical owners for literal matches, tool families, MCP schema presentation and IDA protocol selection; remove unused web-bundle reexports. Remove the duplicate CLI command name and keep the canonical compare workflow.

BREAKING CHANGE: rea compare-bundles has been removed. Use rea compare instead. The MCP compare_bundles tool is unchanged.
2026-10-27 06:27:10 +08:00
morluto c4cdb7658b fix(jeb)!: preserve engine refusal diagnostics
Parse JEB failure envelopes at the provider boundary and retain the original response, recovery facts, and selected inputs. Do not infer an unsupported target from undocumented error codes; preserve completed primary observations when metadata requests fail.

BREAKING CHANGE: JEB engine refusals now report execution_failure rather than unsupported_target. Consumers should inspect the retained upstream diagnostics for the engine's reason and recovery facts.
2026-10-27 06:22:10 +08:00
morluto 7f65617064 fix(artifacts): resolve historical retained summaries
Authenticate exact retained parents against current and documented historical summary projections, including caller-selected display paths. Keep source facts, Evidence commitments and unsupported-projection rejection intact.
2026-10-27 06:21:50 +08:00
akushonkamenandgensokyo-dev c6893d1ac6 fix(browser): preserve external defaults during CDP attachment (#7188)
Attach to external browsers without implicit default-context overrides, and own temporary focus only on the selected page. Preserve unrelated page media and exact page inventory through CLI/MCP. Remove bootstrap-page retries; retain late/failed cleanup ownership and verified SDK notices.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 06:16:14 +08:00
morluto 779d548c4e fix(artifacts): preserve selected ASAR companion paths 2026-10-27 06:12:41 +08:00
morluto e891926534 fix(artifacts): compare Node SEA member declarations
Compare each retained member's exact runtime key and role without treating archive order as semantic identity. Keep one-sided legacy declaration gaps unknown, preserve proven changes with unavailable content, and retain original Evidence.
2026-10-27 06:06:27 +08:00
morluto 60834e391c fix(artifacts)!: compare ASAR storage declarations
Record packed and unpacked ASAR file declarations at the reader boundary and compare known storage changes without treating historical omissions as packed. Preserve known differences when bytes are unavailable and retain existing integrity precedence.

BREAKING CHANGE: ASAR file occurrences may now contain unpacked: false. Consumers that validate only true must accept an optional boolean; absent historical fields remain unknown.
2026-10-27 05:59:40 +08:00
morluto 83b2d16ee9 fix(javascript): distinguish HTML script loads from data 2026-10-27 05:57:26 +08:00
morluto 8694dc30d9 fix(hopper): discover linked application bundles 2026-10-27 05:57:26 +08:00
morluto 67b9181010 fix(ida): separate function identity from source span
Normalize function address and name without requiring an unused upstream size. Preserve complete upstream observations and the explicit unknown body coverage across IDA protocol profiles.
2026-10-27 05:52:15 +08:00
morluto 81adb8977a fix(ghidra): honor current directory in PATH discovery 2026-10-27 05:49:17 +08:00
morluto e1ec737d9b refactor(artifacts): share extraction admission and reader construction
Reuse completed embedded-format observations only after validating the admitted file identity and state, and centralize reader construction across inventory and extraction. Preserve early refusal, selected-path checks, ZIP ownership, and each operation's traversal policy. A source changed between preflight and inventory now fails integrity validation.
2026-10-27 05:46:25 +08:00
morluto 3a6e4f2cb1 fix(managed): report graph method page coverage 2026-10-27 05:42:50 +08:00
morluto 19f9011511 fix(setup): honor VS Code user data overrides 2026-10-27 05:42:50 +08:00
morluto 52414282b9 fix(xrefs): trust provider address validation
After an invalid address-format result, resolve the caller's exact symbol instead of reclassifying address-like names with a separate heuristic.
2026-10-27 05:37:55 +08:00
morluto 4191974010 fix(environment): share retained response declarations
Preserve status independently of malformed content and use one declaration reader for route grouping, state extraction and manifest eligibility. Verify the partial facts through CLI and MCP views.
2026-10-27 05:37:47 +08:00
morluto 5f91616727 fix(hopper): confirm private display probe cleanup 2026-10-27 05:36:09 +08:00
morluto b6e58c22d4 fix(evidence): retain source and stream ownership 2026-10-27 05:36:09 +08:00
morluto 4803c4f9c2 fix(setup): preserve selected settings and Java paths 2026-10-27 05:36:09 +08:00
morluto 47fa4fbad6 fix(views): bind Mach-O rows to selected facets
Derive each facet's row schema from the Mach-O producer and validate it against the requested page. Preserve the existing wire shape and historical defaults.
2026-10-27 05:33:10 +08:00
morluto 06c8e14b34 fix(native): retain lipo field declarations
Distinguish omitted slice fields from declarations that cannot be interpreted, and preserve their source in native results and artifact limitations. Keep structural fallback and capacity failures explicit.
2026-10-27 05:33:01 +08:00
morluto ca3fd7baf9 fix(javascript): preserve artifact failures through cleanup 2026-10-27 05:23:53 +08:00
morluto 8b7ee48a46 fix(kuna): bind cache exports to their producing inputs 2026-10-27 05:23:53 +08:00
morluto 906f161dc6 fix(environment): share legacy recorded-time interpretation
Use one owner for exact recorded timestamps and documented native-Date fallback in state and probe replay. Retain the source and disclose native precision and host-timezone dependence in state limitations.

Validated historical chronology, timezone and unknown-duration regressions against prior behavior.
2026-10-27 05:19:43 +08:00
morluto 63220edcc3 fix(environment)!: withhold clock writes without an anchor
Keep missing or ambiguous scenario request durations unknown, retain learned clock rules, and withhold plans that cannot generate an observed clock. Refuse such writes in older imported states before changing state or generators.

BREAKING CHANGE: The write-plan reason can now be unknown-clock; strict clients must accept the new output value.

Validated state, CLI and advertised MCP workflows, documentation, and focused clock and cleanup regressions.
2026-10-27 05:19:35 +08:00
morluto 19b2972dfb fix(environment): separate request order from completion
Retain valid request starts independently of duration and require observed completion for interval-based state and write inferences. Bound indexed interference checks across captured writes while preserving ordinal fallback and unmodified Evidence.

Validated state, comparison, CLI and MCP workflows with known, unknown, overlapping and redacted timing controls; check:fast passed.
2026-10-27 05:08:14 +08:00
morluto 3233992733 fix(browser): redact startup cleanup diagnostics 2026-10-27 04:59:27 +08:00
morluto 855eb703af perf(setup): wait directly for prewarm completion 2026-10-27 04:59:27 +08:00
morluto d815367a11 fix(analysis): preserve failure and cleanup outcomes 2026-10-27 04:59:27 +08:00
Chip Liandgensokyo-dev 4767970105 fix(artifacts): explain recovery for existing DMG attachments (#7221)
Preserve producer-supported recovery guidance after an existing-image DMG attachment refusal, without borrowing or detaching existing mounts. Keep diagnostics, root provenance and unknown coverage; centralize guidance ownership and run the real mounted-image regression in macOS CI.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 04:58:43 +08:00
吴佳浩Alben 9e6dd8e372 docs(desktop): name the get_evidence_bundle tool in the MCP section (#7266)
The collection-paging paragraph says complete Evidence stays accessible through
`get_evidence`, but no tool by that name exists. The session family registers
`get_evidence_bundle`, which is the only name the catalog, the contracts and the
CLI equivalents use.

Every other page already uses the full name, so this was the only place a reader
or agent would look for a tool that cannot be called.
2026-10-27 04:56:17 +08:00
Saim Shafiqueandgensokyo-dev 0d1a454e25 fix(ghidra): admit newer 12.x releases as compatible builds (#6924)
Admit same-major Ghidra releases at or above the verified minor through the existing compatible/unverified policy. Preserve the observed version, verified12.1.4 pin, installation-declared Java floor and separate NativeAOT verification gate.

367 targeted tests/static/docs, all10 real Java bridge-source compilation, and actual Linux12.2/JDK25 CLI/MCP queries and cleanup pass. JDK21 remains refused for12.2. Exact-main controls reproduce all3 unrelated CI assertions; canceled shards and an apt-blocked binary-layout lane remain unverified.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 03:49:12 +08:00
morluto dd7a2da5bb fix(environment): keep invalid recorded dates unknown
Distinguish invalid supported timestamp spellings from unrecognized legacy formats at the recorded-date owner. Probe replay retains ordinal order for invalid dates without changing the existing legacy fallback.

Validated date parsing, state ordering and comparison workflows; the prior implementation reorders the invalid-date regression.
2026-10-27 03:29:11 +08:00
morluto 4e86bd0c1c fix(asar): disclose unverified block integrity
Retain a limitation when ASAR block integrity fields are present. Whole-file hash verification does not establish block verification or Electron runtime acceptance.

Validated inventory and extraction workflows, including partial and malformed block declarations.
2026-10-27 03:29:05 +08:00
morluto 4bbd87953f fix(environment): align recorded probe chronology
Use the shared recorded timestamp interpretation for replay ordering, including exact fractions and UTC interpretation of supported zone-less forms. Preserve native-Date fallback for unsupported historical spellings.

Validated environment comparison, state ordering and CLI tests; five new cases reproduce prior drift.
2026-10-27 03:25:06 +08:00
morluto 1008d5e302 fix(asar): retain unsupported integrity declarations
Carry reported algorithms and hashes into listing limitations when the declaration cannot be compared, retaining the existing verification and extraction policies. Bound escaped text before allocation.

Validated ASAR inventory, extraction, cleanup and MCP workflows; prior code omits the declaration details.
2026-10-27 03:25:06 +08:00
morluto 3a65ef0e4c fix(browser): redact scenario cleanup diagnostics 2026-10-27 03:23:50 +08:00
morluto f1535a1753 fix(artifacts): retain archive streams and source receipts 2026-10-27 03:23:50 +08:00
morluto ca75643dbc fix(setup): preserve registration choices and own prewarm 2026-10-27 03:23:50 +08:00
morluto 3ca1cc5466 fix(filesystem): complete plist and limits probes
Read bounded plist format probes and Linux resource-limit reports through positive short reads before classifying their contents. Preserve explicit EOF, report capacity, physical failures and descriptor cleanup ownership.

Validated 66 focused tests and check:fast; nine new fault-injected cases fail against the previous implementation. Linux procfs was not verified on a real Linux host.
2026-10-27 03:18:56 +08:00
morluto 615fbbbdd7 refactor(mcp): consolidate evidence tool execution
Share provider availability, admission, logging and Evidence delivery across browser, Electron and runtime tools. Reuse retained summary delivery for Python and BEAM selected views while preserving preflight and complete parent Evidence.

Validated 47 focused MCP and retained-view acceptance tests and check:fast.
2026-10-27 03:14:24 +08:00
morluto a7004c7df1 fix(filesystem): finish executable and project span reads
Reuse the positional range owner for target metadata, Hopper Mach-O selection and Kuna spans. Preserve physical failures, completed span evidence and cleanup ownership across cancellation and short reads.

Validated 147 focused file and lifecycle tests; ten new cases reproduced failures against the prior implementation.
2026-10-27 03:14:14 +08:00
morluto f68064e46c fix(evidence): exclude sensitive publication identities 2026-10-27 03:12:22 +08:00
morluto 5da50ab3f4 fix(artifacts): retain reader work through cleanup 2026-10-27 03:12:22 +08:00
SII-YDDandgensokyo-dev d4c511ae6e fix(javascript): honor effective symbolic descriptor values (#7222)
Resolve ordered descriptor values at the shared JavaScript fact owner and preserve uncertain intrinsic writes instead of stale exports. Normalize ordinary method values and reuse canonical primitive, prototype, ambient identity and ordering facts across consumers.

384 selected tests, format/static/docs checks, all final CI jobs and reviews pass. Actual Node controls cover 35 bundles; one possibly repeated factory explicitly stays unknown. Shipped Obsidian selected-source complete Evidence matches base/fixed CLI and MCP, with coverage and resource measurements in the PR.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-27 03:08:00 +08:00
morluto d54f5b07b4 fix(artifacts): complete positional reads across short results
Continue reading Mach-O and Electron ranges after positive short descriptor reads, while preserving explicit EOF, cancellation, file identity and cleanup semantics. Advance fuse scanning and hashing by the bytes actually read.

Validated focused filesystem, Mach-O, dyld, Electron and MCP workflows: 117 tests passed, two Windows-only tests skipped; check:fast passed.
2026-10-27 03:07:24 +08:00
morluto 64f053ab63 chore: remove unused internal exports 2026-10-27 03:02:38 +08:00
morluto 347494f3ab fix(captures): preserve cleanup ownership and sensitive evidence 2026-10-27 03:02:38 +08:00
morluto 7a237aa721 refactor(views): share immutable result readers
Use the common analysis-view reader for Rust and native inventory validation, preserving historical defaults and original Evidence while retaining schema-specific validation.

Validated 123 focused view and service tests and check:fast.
2026-10-27 03:02:09 +08:00
morluto bab49aaf7c refactor(artifacts): unify stable read failure ownership
Centralize completed file observations and cleanup error projection across stable-file providers while preserving each provider’s failure classification and exact cleanup leases. Cover BEAM finalization cancellation and devicetree cleanup retries.

Validated 223 focused lifecycle and MCP tests and check:fast.
2026-10-27 03:02:05 +08:00
morluto 99ff4344c0 fix(browser): centralize CDP network event facts
Parse shared request and response fields at the CDP boundary while preserving capture, session and runtime admission policies. Treat empty request IDs as unknown so unrelated events cannot be joined, and retain redirect occurrences and partial response evidence.

Validate 221 focused tests, fast checks, and real Chrome network and runtime verifiers.
2026-10-27 02:57:58 +08:00
morluto df6db781dd refactor(pyinstaller): centralize CArchive member roles
Derive member roles at the table-of-contents boundary and share declaration and Python-module interpretations across validation, projection, metadata, header reconstruction and nested PYZ traversal. Retain source type codes and generic-file behavior for other printable types.

Validation: 217 focused archive, CLI and MCP tests passed, including mixed-role byte and metadata checks. Typecheck and lint passed.
2026-10-27 02:52:25 +08:00
morluto afc79add7e fix(providers): bind signal and capture configuration per operation 2026-10-27 02:47:25 +08:00
morluto 416f16e2ed fix(managed): unify metadata row and coded-index ownership
Share Field scalar facts and use one coded-index tag registry for widths and decoding. Preserve reserved constructor tags, generic-instantiation restrictions, heap failure order and selected-field signature admission.

Derive native import member kind from the validated MemberForwarded coordinate independently of name lookup, retaining known field or method identity when its display name is unreadable.

Validation: 368 focused source, CLI and MCP tests passed; five lost-kind regressions reproduced on the previous commit. Heap-width, coded-width, reserved-tag and selection boundaries are covered. Typecheck and lint passed.
2026-10-27 02:46:26 +08:00
morluto 741f2de751 test(ci): align source preservation and catalog assertions 2026-10-27 02:35:54 +08:00
morluto b4b5c37ab3 fix(web): retain admitted provenance through incomplete traces 2026-10-27 02:35:54 +08:00
morluto f6478c056f fix(providers): keep each inspection on one environment snapshot 2026-10-27 02:35:54 +08:00
morluto 1f5c5c31c1 refactor(managed): centralize method implementation flag facts
Have the MethodDef row owner derive code type, managed kind and the P/Invoke flag for both body decoding and native-boundary inventory. Remove duplicate masks and unreachable fallbacks while preserving raw flags, zero-RVA precedence and ImplMap-specific declaration policy.

Validation: 280 focused source, CLI and MCP tests passed, including cross-workflow flag combinations and declaration/body policy boundaries. Typecheck and lint passed.
2026-10-27 02:34:59 +08:00
morluto 8ced814b96 fix(browser): unify script and context attribution facts
Centralize CDP script/context declarations and frame-claim interpretation across web capture, Electron, V8, active runtime and storage seeding. Preserve each workflow's admission, provenance and retention policies while removing superseded helpers and schemas.

Malformed or conflicting frame claims now leave passive scripts unattributed. Reused context IDs replace future frame authority without rebinding earlier scripts, and source readers reject empty script IDs.

Validation: 153 focused tests and check:fast passed; 13 regression failures reproduced on the prior commit. Real Chrome script export and runtime CLI/MCP verifiers passed, including correctly reported uncertain cleanup after intentional target closure. Electron regression coverage used simulated CDP.
2026-10-27 02:30:04 +08:00
morluto ff568afa34 refactor(artifacts): retain Node SEA admission facts through inventory
Pass validated blob location and decoded metadata with their admitted descriptor from classification to the reader. Keep root cleanup ownership, freshness checks, format validation order and direct-reader decoding intact, then release borrowed facts after preparation.

Validation: 147 focused source, CLI and MCP tests passed, including real Node blob production; check:fast passed. Optional injected-ELF Docker verification was unavailable because the Docker daemon is not running.
2026-10-27 02:20:00 +08:00
morluto 55f72b5d53 fix(workers): preserve failure receipts and their cleanup owners 2026-10-27 02:16:08 +08:00
morluto 82af5b5f52 fix(web): retain completed file identities across acquisition failures 2026-10-27 02:16:08 +08:00
morluto 620690c923 refactor(firmware): unify extraction problem interpretation
Derive dependency and generic-error facts once per owning task, chunk or multifile stage. Reuse those facts for failure classification and dependency aggregation, preserving unnamed identities, severity precedence and raw diagnostics.

Validation: 70 focused tests passed; 41 platform-guarded tests skipped on macOS. Typecheck and lint passed. Real Unblob/Linux provider behavior was not run.
2026-10-27 02:14:45 +08:00
morluto d06ca092fb refactor(python): consolidate bytecode identity across readers
Share CPython magic framing, version and header facts while keeping standalone marshal coverage and PyInstaller reconstruction eligibility distinct. Preserve legacy headers, opaque unknowns and source bytes.

Validated 272 focused tests across source, CLI and MCP workflows, a 38-test reader recheck, and check:fast.
2026-10-27 02:09:52 +08:00
morluto 669e617eec fix(sqlite): preserve completed member facts on acquisition failure 2026-10-27 02:02:27 +08:00
morluto e813c4c344 fix(pwntools): separate reply admission from debugger enrichment 2026-10-27 02:02:27 +08:00
morluto d77cf61866 fix(evm): retain admitted evidence through reply finalization 2026-10-27 02:02:27 +08:00
morluto 3b4c910fff fix(browser): unify CDP frame and document identity facts
Parse each frame event using its declared protocol fields and retain loader and ancestry only across same-document updates. This removes alias-based attribution, invalidates malformed committed frame facts, and prevents stale WebMCP tools surviving a same-URL document replacement.

Migrate Electron, screenshot, capture, timeline, and execution consumers to the shared facts owner and retire the old helper exports. Validated 428 focused tests, test discovery, typecheck/lint, and real Chrome CLI/MCP network capture; Electron and WebMCP regressions use simulated CDP.
2026-10-27 02:00:58 +08:00
morluto 624ebb2a1e refactor(elf): consolidate bounded string-table interpretation 2026-10-27 01:46:08 +08:00
morluto e405d685bc refactor(macho): centralize dylib command and string facts 2026-10-27 01:38:20 +08:00
gensokyo-devandHarsH 6d5532a204 feat(web): page retained bundle endpoint observations and inferences (#7259)
Page retained web-bundle endpoint observations and inferences through shared CLI/MCP projections, preserving source identities, scoped unknowns, coverage and complete Evidence access.

Based on HarsH’s original implementation in #7213 (6b1d5d42). Real Chromium/CLI/SDK verification and whole-task cost limits are recorded in docs/verification/web-bundle-paging-2026-10-26.md.

Explicit user authorization permits leaving human-readiness declarations unchecked and bypassing reproduced main failures (ADB listing fixture, package catalog expectation, untouched APK verifier formatting). No human-review attestation is made.

Co-authored-by: HarsH <[email protected]>
2026-10-27 01:37:39 +08:00
morluto c09fa01b37 refactor(managed): centralize MemberRef row facts 2026-10-27 01:29:43 +08:00
morluto 939009f6f9 fix(artifacts): preserve completed caller reads through finalization
Retain file identity after Rust, web artifact, shared selected-input and historical capture reads complete before close or cancellation fails. Preserve descriptor-set snapshots in diagnostics alongside sanitized capture observations.

Classify canonical physical capture failures before cleanup composition; source permission errors and bare syscall failures keep their meaning through late cancellation and retry. Keep explicit sensitive-value redaction on retained facts.

Validation: 247 focused cases passed, two Linux-only cases skipped; build, typecheck, lint and formatting passed. Real descriptors with controlled finalization faults establish reader ownership and facts. Worker reply and multi-file database completion semantics remain separate audit work.
2026-10-27 01:27:03 +08:00
morluto 31e8492faf refactor(artifacts): centralize completed snapshot observations
Normalize completed stable-file path, digest and length in one shared observation owner. Migrate thirteen identical provider projections while preserving their selected paths, schema checks and cleanup composition.

Keep specialized metadata and worker reply observations at their existing owners. Validation: shared real-descriptor coverage and provider lifecycle tests passed; build, typecheck, lint and formatting passed.
2026-10-27 01:27:03 +08:00
morluto 1ce9f317be refactor(pyinstaller): centralize PYZ member role ownership 2026-10-27 01:21:11 +08:00
morluto 662ec2e450 fix(verifiers): preserve source Evidence through shared admission 2026-10-27 01:11:51 +08:00
morluto f6805e29c3 refactor(pe): centralize resource tree facts across readers 2026-10-27 01:02:21 +08:00
morluto 9d2c087d83 fix(readers): preserve completed snapshots through cleanup retries
Retain exact completed file path, digest and length in Python pyc and Erlang BEAM failure observations. Translate Python close failures at the provider boundary and bind them to the retained descriptor cleanup leases so later successful retries resolve prior errors.

Exercise actual services and file handles with completed reads, cancellation during finalization and physical read failure followed by cancellation. Assert no fabricated snapshot on early failure and resolution of every prior cleanup report.

Validation: six focused descriptor regressions plus existing filesystem, parser and MCP coverage passed; build and static checks passed. Authored format fixtures establish portable reader behavior, not CPython/Erlang runtime execution.
2026-10-27 01:01:19 +08:00
morluto 74d5cab372 fix(native): account export and version text in UTF-8 bytes
Count retained UTF-8 text rather than UTF-16 code units, including exact-byte base64 spellings. Bound the decoded version-name map independently from output expansion, releasing replaced entries from its live storage charge.

Add real service/provider boundary controls for accepted and over-budget symbol names, version names and unused version metadata. Existing per-string bounds remain the allocation owner.

Validation: native parser, filesystem and CLI/MCP views passed; build, typecheck, lint and formatting passed. No serialized-report size cap, OOM reproduction or performance improvement is claimed.
2026-10-27 01:01:10 +08:00
morluto 3d74c480e0 fix(verifiers): consolidate Ghidra MCP result admission 2026-10-27 00:49:27 +08:00
morluto 60b55be7e7 fix(native): bound PE export names before intermediate retention
Charge PE alias names when storing them, before target interpretation can suppress output rows. Validate unused-slot names as bytes without retaining or decoding strings. Keep row and forwarder accounting separate so names are charged once.

Preserve alias ordering, malformed unused-name issues and ordinal-only suppression. Add production service/provider retention regressions using compact shared-pointer fixtures.

Validation: 43 focused parser, filesystem, CLI and stdio MCP tests; fresh build, typecheck, lint and formatting passed. Three bounded Node 24.22.1 measurement pairs showed median process RSS high-water 154000 to 93168 KiB; timing and sampled family memory have cache/startup/sampling limits. No Windows loader acceptance or OOM reproduction claim.
2026-10-27 00:40:35 +08:00
morluto 0973649cec refactor(captures): consolidate recorded exchange body facts 2026-10-27 00:38:06 +08:00
morluto c2b0c955cd fix(workers): reject malformed UTF-8 in JSON result files 2026-10-27 00:28:42 +08:00
morluto 1d32eb3aa4 fix(providers): keep operation environments consistent through launch
Capture one environment snapshot per admitted WABT, JADX, firmware and Wakaru operation. Use it for tool selection, session identity and child launches while preserving configuration updates between operations.

Regressions reproduce changed JVM options under an old session key, mixed engine/limiter selection and environment drift between subprocesses. Keep selected-wrapper and cleanup coverage in a shared fixture with separate scenarios.

Validation: 115 distinct focused tests passed across the final five-file selection, including seven selected-command cases after refactoring. Fresh build, typecheck, lint and formatting passed. Tests use real subprocess fixtures; no real JADX/WABT/Binwalk/Wakaru engine or native Windows verification is claimed.
2026-10-27 00:25:43 +08:00
morluto 2cdc8cb92e refactor(managed): centralize TypeDef row facts across consumers 2026-10-27 00:21:00 +08:00
morluto 8423c5c6d0 refactor(macho): consolidate prefix interpretation across readers 2026-10-27 00:13:36 +08:00
gensokyo-dev 5508b2affd fix(analysis): retain source evidence through directory exports (#7250)
Retain authenticated provider source records independently of snapshot replay eligibility and return them explicitly to plain application callers, including late cancellation. CLI/MCP commit source records before delivering links; function dossiers share the canonical Evidence constructor. Keep directory replies compact and preserve successful retry verification.

Real Ghidra CLI/MCP investigations verify 652-function ls, historical report provenance, cross-session resume, unchanged source/project content and complete offline Evidence. Final full 14,338-function libcrypto comparison matches all metadata/pseudocode facts and independent fresh-import observations: 14,194 written, 143 external, one recorded deadline; 494.03 s, 445.63 CPU s, 1.584 GiB sampled family peak under 2-CPU/8-GiB guards, with no OOM or cleanup failure.

144 focused tests and static/docs/site checks pass; latest-main integration passes 289 cases plus typecheck/lint and actual CLI snapshot source retrieval. Final CI source/static/build/docs/quality/package checks pass. Four ADB shard failures also occur in main CI 40349559391; cancelled coverage remains unverified and author declarations stay unchecked under the user's explicit bypass authorization. No human-review attestation is made.
2026-10-26 23:56:03 +08:00
morluto 13fba271e6 refactor(plist): centralize binary layout and offset ownership 2026-10-26 23:51:33 +08:00
morluto d27e3f8e89 fix(native): preserve import names across dyld text formats
Parse literal import framing and retain ambiguous boundaries. Consult undefined symbol-table names only when needed to distinguish producer spacing, with import and export captures owned separately.

Validation: 59 native tests passed and real Apple tools on inert arm64 fixtures reproduced the old loss and verified the fix. Fresh build, typecheck and lint passed. Small warm-task median elapsed was unchanged; cold costs and agent tokens were not measured.
2026-10-26 23:50:10 +08:00
morluto ab44487ff8 fix(adb): preserve process names and pin fixture interpreters
Consume Toybox’s single NAME separator without stripping source whitespace. Keep MCP subprocess fixtures independent of ambient PATH and validate calls through the existing named-schema helper.

Validation: focused ADB provider/parser and MCP tests passed; no real Android device was used.
2026-10-26 23:50:10 +08:00
YingNingandgensokyo-dev 4da7e46e9c fix(provider-selection): name whether a rejection came from availability or target support (#7220)
Distinguish local provider readiness failures from target-support refusals in shared CLI/MCP diagnostics, preserving original codes and evidence. Avoid claiming configured providers are unconfigured.

120 focused tests, static/docs checks and real ELF CLI/MCP verification pass. Hosted CI has the same four ADB failures as exact main d9908fd7a; three canceled shards remain unverified. Merge uses the user's explicit authorization to bypass proven main failures. Fresh Codex and Greptile reviews are clear.

Fixes #7214

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-26 23:49:11 +08:00
morluto a5ad164283 fix(browser): unify worker target admission across capture workflows 2026-10-26 23:44:08 +08:00
morluto cadd5b75b3 refactor(elf): consolidate header and section-name interpretation 2026-10-26 23:40:04 +08:00
morluto 81add3d23d refactor(pyinstaller): centralize PYZ header and version facts 2026-10-26 23:33:54 +08:00
morluto d9908fd7a4 refactor(ghidra): consolidate function and inventory admission 2026-10-26 23:27:43 +08:00
morluto b45a83368c fix(ci): preserve lint diagnostics without source locations 2026-10-26 23:21:57 +08:00
morluto 65819e8cc5 fix(pcap): capture provider environment once 2026-10-26 23:21:57 +08:00
morluto a7aad91320 fix(firmware): preserve report metadata before semantic validation 2026-10-26 23:21:57 +08:00
Wenyu Chiou 625e32744e fix(javascript): ignore async exec stdio options (#7087)
Respect Node asynchronous exec/execFile method-default stdio instead of interpreting an ignored option. Remove the superseded callback-shape query while preserving caller input, Evidence, uncertainty, argv and fork controls. Independently validated on current main through actual Node pipe capture, matching real-app CLI/MCP facts, 81 targeted tests and static checking. Closes #7217.
2026-10-26 23:16:28 +08:00
morluto a84e0cfcfe refactor(verifiers): consolidate capture result admission and parity 2026-10-26 23:12:34 +08:00
morluto 99062feaa7 test: bind MCP validation and cleanup faults to selected inputs 2026-10-26 23:06:36 +08:00
morluto fef76a02fb fix(apple): preserve own artifact metadata through schema projection 2026-10-26 23:06:36 +08:00
morluto b7e76a57cd fix(adb): preserve selected environment through provider execution 2026-10-26 23:06:36 +08:00
morluto a30ffd27a6 refactor(dos): centralize MZ admission and load-image evidence 2026-10-26 23:02:53 +08:00
Dery Ferdikaandgensokyo-dev aa6c1d49fd fix(mcp)!: require allowed hosts for non-loopback HTTP binds (#7172)
Require operator-declared hostnames before serving MCP HTTP outside loopback, preserving Host/Origin gates. Parse hostname identities once, share typed startup recovery across CLI/MCP, and announce an admitted client URL. Generated AgentEnv bundles declare their client/service names and validate DNS/HTTP name identity before export.

Validated through real packaged HTTP requests, direct SDK connection, compiled export refusals, 184 focused tests, static and documentation checks on the refreshed main integration. Final CI and review evidence is recorded in the PR body.

BREAKING CHANGE: non-loopback deployments require MCP_ALLOWED_HOSTS or rea mcp --http --allowed-hosts; bundle names must be unchanged HTTP hostnames within the 63-octet DNS label bound.

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-26 22:55:10 +08:00
morluto 0828917c79 refactor(verifiers): centralize named artifact output admission
Route six artifact verifiers through the canonical complete MCP output and error parsers. Remove duplicated JSON/schema interpretation while retaining Evidence authentication, provider provenance, independent CLI parsing and CLI/MCP parity checks. Validate malformed success/error envelopes at the shared boundary.

Validation: 11 boundary tests, Go compiler verifier (30 cases), and synthetic IL2CPP/Flutter verifier workflows passed; typecheck/lint passed. PCAP readiness passed with stub tools, but capture inspection is unsupported on macOS. Linux-only binary-layout and EVM verifier toolchains were unavailable.
2026-10-26 22:48:22 +08:00
morluto f02df45a46 fix(process): preserve selected Darwin helper environment through execution 2026-10-26 22:42:48 +08:00
morluto 921142efbc fix(hopper): own profile reads and retain completed coordinate evidence 2026-10-26 22:42:48 +08:00
morluto 5b983516ab fix(hopper): consolidate release metadata and archive identity ownership
Share release JSON decoding, vendor fields and package integrity checks across macOS setup and Linux publishing/cache reads. Reject malformed UTF-8 instead of repairing macOS metadata, retain established initial BOM tolerance, and preserve complete vendor rows through admission and cache retention. Keep platform channel, signature, installation and cleanup policies unchanged.

Validation: 134 focused tests passed with 21 platform skips; typecheck and lint passed. Linux-only signed-cache cases were updated but skipped on the macOS host. No live Hopper installation performed.
2026-10-26 22:38:53 +08:00
19447516a4 feat(ghidra): reuse existing projects through verified copies (#7236)
Reuse selected Ghidra projects through verified private read-only copies, with shared CLI/MCP configuration, committed project identity, import-SHA verification and explicit historical/unknown analysis semantics. Preserve exact cancellation and platform capability boundaries.

Actual Ghidra 12.1.4/JDK21 Linux CLI/MCP investigations matched fresh import on 652-function ls and 14,338-function libcrypto inventories and pseudocode; authenticated complete Evidence, mismatch/staleness refusal, saved historical reports and shared /tmp projects verified. 958 focused tests passed with four platform skips; build/static/docs/format passed. Real macOS/Windows reuse remains unverified; Windows reuse is unavailable. See PR verification for measured whole-task costs and final CI attribution.

Co-authored-by: akushonkamen <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-26 22:31:13 +08:00
morluto 49816235f5 refactor(providers): consolidate IDA and JEB MCP result admission
Give the object-structured provider envelope one owner that retains original dictionary members. Remove duplicate schemas, text extraction and JSON-or-text decoding plus the downstream casts that repaired lossy record clones. Keep provider-specific error, truncation, wrapper and payload-selection policies in each adapter.

Validation: IDA/JEB compatibility tests, pinned SDK transport tests and provider regressions passed; check:fast passed. Real IDA/JEB applications were not exercised.
2026-10-26 22:30:58 +08:00
morluto 2b84efe0da fix(contracts): align CLI omission and native capability coverage 2026-10-26 22:22:27 +08:00
morluto 4a8205456c fix(lifecycle): preserve source failures and native workspace diagnostics 2026-10-26 22:22:27 +08:00
morluto 77468d5043 fix(providers): preserve launch selections and refresh obsolete Kuna exports 2026-10-26 22:22:27 +08:00
morluto eb06e263cb fix(plist): own dictionary decoding across Apple workflows
Preserve XML, OpenStep and binary dictionary keys at their format owners. Share validated binary reference facts with materialization while retaining scalar precision, UID identity and bounded expansion. Migrate archive, Interface Builder, native and app-bundle consumers, then remove obsolete key-omission accounting and schema copies.

Validated source regressions and compiled CLI/MCP workflows, including native plist fidelity and archive identity. Typecheck, lint, docs and test discovery pass. No performance benchmarks were required.
2026-10-26 22:20:00 +08:00
morluto 3b26d7f80b docs(security): add fallback reporting email 2026-10-26 22:18:00 +08:00
morluto cd68181c90 fix(providers): preserve report bytes and dictionary facts 2026-10-26 22:01:51 +08:00
pablodiazjorge 4557791a2e fix(contracts): advertise a v-mode portable Windows absolute-path pattern (#7152)
Use portable hex escapes for Windows absolute-path separators in the shared local path schema. Preserve host path semantics and caller spelling; validate Annex B/u/v compilation and Node win32 agreement. Closes #7151.
2026-10-26 20:56:47 +08:00
Nianze Wu 63417bf3f0 fix(contracts): publish portable browser request patterns (#7185)
Publish portable browser request-path and HTTP-token patterns at their canonical owner, preserving input spelling and existing policy. Independently reproduced and verified through actual full-profile stdio MCP catalog and domain/MCP regressions on latest main. Closes #7168.
2026-10-26 20:52:19 +08:00
Wenyu Chiou 1a91630b88 fix(setup): preserve Linux Hopper HTTP failure status (#7183)
Preserve observed failed-response HTTP status through Linux Hopper setup diagnostics without changing download rejection, retry, or installation policy. Independently validated on latest main through red/green regressions, actual TLS sockets, focused checks and CI. Closes #7182.
2026-10-26 20:45:06 +08:00
김준호 0211a07ec5 fix(session): report a missing Linux ps as provider unavailability in status (#7180)
Keep binary_session available when Linux owned-child probes lack procps ps. Preserve typed ownership-prerequisite failures in direct execution and report per-provider readiness in status. Independently validated on latest main through real stdio MCP plus focused regression, static and documentation checks. Closes #7164.
2026-10-26 20:35:09 +08:00
gensokyo-devandtijo95 e3066c1705 fix(deps): upgrade zod without narrowing timestamp inputs
Upgrade Zod to 4.6.6 and refresh locked license inventories. Preserve REA's established timestamp spellings through one shared UTC/offset grammar, including completion-verifier consumers. Size the stdin resource regression from the child's actual heap capacity.

Fixes #7173
Includes the dependency and inventory changes proposed in #7184.

Co-authored-by: tijo95 <[email protected]>
2026-10-26 20:13:12 +08:00
b675073b73 feat(mcp): scope advertised tools by family (#7228)
Scope MCP discovery, setup, doctor, status and recovery guidance by selected tool families using one contract inventory and shared catalog identity. Preserve default discovery, canonical validation and complete Evidence.

Fixes #3246. Builds on #3026. Real libcrypto workflow verifies all 5,367 exports and identical complete Evidence. Static/source/build/docs/Windows/package/Greptile checks pass; ten assertions reproduced identically on clean main and feature across Node 24/22, with cancelled coverage explicitly unverified. Merge uses the user-authorized bypass for baseline failures and unchecked human-readiness declarations, without attesting human review.

Co-authored-by: 0xLLVEL <[email protected]>
Co-authored-by: Codebuff <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-10-26 19:50:12 +08:00
morluto e54bf1006e fix(environment): own JSON fields across capture, state and replay 2026-10-26 18:27:30 +08:00
morluto cc5a96933a test(mcp): validate selected tools without compiling the entire catalog 2026-10-26 18:26:33 +08:00
morluto dec4cf50cb fix(captures): preserve reply failures through descriptor finalization 2026-10-26 18:26:26 +08:00
morluto 68f2d67c56 fix(paths): preserve native selections through analysis and removal 2026-10-26 18:26:17 +08:00
morluto e7f986c994 fix(json): consolidate dictionary admission across analysis and verification 2026-10-26 18:14:50 +08:00
morluto c06dc2d356 fix(native): retain completed debugger evidence through reply cleanup 2026-10-26 18:12:01 +08:00
morluto f8161ad7b3 fix(setup): preserve selected home paths throughout skill installation 2026-10-26 18:11:54 +08:00
morluto 4ca3032e7f fix(json): preserve configuration and capture data at admission 2026-10-26 18:06:00 +08:00
gensokyo-devandAkshad Jogi 9085222ca1 feat(mcp): support compact output schema advertisements (#7218)
Add opt-in bounded output-schema advertisements while preserving the full default, canonical server validation and complete structured content. Verify real stdio discovery under a 1 MiB receive cap, all 5,367 libcrypto export coordinates, complete Evidence parity, whole-task serialization estimates and resource bounds.

Fixes #2512. Integrates #7054 by Akshad Jogi with original authorship retained.

Co-authored-by: Akshad Jogi <[email protected]>
2026-10-26 17:59:51 +08:00
morluto e596a48251 fix(sqlite): retain completed evidence through reply finalization 2026-10-26 17:56:47 +08:00
morluto f2b4468183 fix(paths): preserve native selections across launch and publication 2026-10-26 17:56:47 +08:00
morluto da526fc014 fix(providers): preserve reply bytes and structured JSON ownership
Share strict UTF-8 JSON admission across bounded worker reports. Preserve IDA/JEB structured dictionaries through negotiated SDK validation and adapter decoding, retaining inherited tool validation and protocol handling.

Validated 240 tests, including real SDK/HTTP exchanges and firmware file reads; 83 host-specific cases skipped. Typecheck, lint, and discovery passed. Real-provider execution and modern MCP protocol behavior remain unverified.
2026-10-26 17:52:32 +08:00
morluto 721dd3d1aa fix(javascript): preserve selected inputs and completed source evidence 2026-10-26 17:39:29 +08:00
morluto 3864c7d667 fix(config): preserve captured environments across provider launches 2026-10-26 17:39:23 +08:00
morluto ccdeda7a27 fix(inputs): centralize admission and preserve caller selections
Move shared request parsing into its application owner, migrate all consumers, and remove six duplicated admission blocks while preserving cancellation and validation policy.

Keep operator-selected JavaScript paths in verifier parent Evidence while retaining canonical comparison guards. Validated with 204 passing tests, 3 Linux-x64-only skips, typecheck, lint, and test discovery.
2026-10-26 17:35:18 +08:00
morluto 7d36d172ab fix(evidence): consolidate retained view and verifier ownership 2026-10-26 17:26:55 +08:00
morluto e2d4c0dabb fix(process): preserve environment snapshots and final cancellation 2026-10-26 17:26:16 +08:00
morluto eecadf35b2 fix(verification): unify client schema traversal and reference checks
Share ordered schema positions between Gemini literal checks, DeepSeek regex
checks, and catalog array-shape checks. Reuse the canonical keyword and local
reference owners; keep unreferenced instance literals opaque and unresolved
external or dynamic targets unknown.

Gemini and catalog checks now catch constraints in schemas reached through
references into examples. Preserve source paths, resource scopes, cycles and
regex occurrence counts. Align fixture first-error selection with the native
Gemini check and use consistent bracketed array locations in diagnostics.

Validation: 55 focused tests passed, including full/compact MCP catalogs;
typecheck, lint, discovery, and Node source-loader checks passed. The old
Gemini scanner reproduced the local-reference false negative. Installed
Gemini and DeepSeek clients were not run.
2026-10-26 17:15:58 +08:00
morluto 86ff3237e1 fix(paths): preserve selected command, output, and source identities 2026-10-26 17:14:02 +08:00
Kaoru0822andClaude Opus 5.5 18dc78fa43 revert(mcp): undo input schema fitting that broke main CI (#7135)
This reverts commit 7336a566ac (#7119).

After #7120 and the other merges of 2026-10-25 grew inspect_analysis_view's
complete input schema (71,762 bytes plus 3,003 bytes of examples), the 64 KiB
fitting from #7119 could no longer keep instance level 3 and fell back to
level 2. That summarized the inline Evidence source and the view selector
collections, so main CI failed in toolInputReferences, toolSchemaValidity,
analysisViewMcp, absoluteLocalPathSchema, javascriptFactsViewMcp and
godotPckMcp. #7119's own CI ran against bc143125e, before that growth.

Reverting restores the complete full-profile advertisement while the size
limit is redesigned against these contracts (#4081 stays open).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-26 02:06:57 -07:00
morluto 6c35d74a7d refactor: centralize input validation and schema interpretation
Migrate twelve application service files to the existing provider input owner,
preserving schema causes, cancellation precedence, caller selection, and
retained Evidence. Keep flows with distinct error policies separate.

Replace the DeepSeek verifier's recursive object scan with schema-position
traversal using the canonical keyword and local-reference owners. Literal
pattern fields no longer cause false regex failures; patternProperties keys
are checked alongside actual pattern constraints in native regex modes.

Validation: 271 focused workflow/service tests and six verifier regressions
passed; static checks, discovery, and actual Node source-loader smoke passed.
The baseline scanner reproduced the new regressions. Installed DeepSeek client
execution was not run.
2026-10-26 17:05:05 +08:00
morluto 8a65787507 refactor: consolidate parsed image ownership across consumers
Retain validated DOS relocation records in their domain owner and consume
those facts in Ghidra load-image preparation. Preserve ordered duplicate
aliases, overlapping writes, and original/final byte evidence.

Share FAT header facts across generic, native, and Hopper readers without
merging their distinct admission policies. Replace managed method bodies'
two identical mapping lookups with one owned offset-and-extent result.

Validation: 660 focused tests passed across 19 suites; typecheck, lint, and
test discovery passed. Hopper/Ghidra checks used fixtures and simulated
provider observations; no real external provider execution was performed.
2026-10-26 16:56:50 +08:00
morluto a7f50e3386 refactor: consolidate ELF and CLR record interpretation
Share ELF note headers across note inspection, Flutter build IDs, Node SEA,
and recorded-crash source binding. Decode REL/RELA records once for MIPS
shared-load analysis and IL2CPP linking, removing superseded scalar helpers.

Share the CLR header prefix between desktop detection and managed PE parsing
while preserving their distinct read bounds and admission policies. Cover
those differences and ignored R_NONE fields with production-boundary tests.

Validation: 493 focused tests passed, two existing skips; static checks and
test discovery passed. Public managed/desktop CLI and ELF notes CLI/MCP
workflows exercised; no real external analysis providers were run.
2026-10-26 16:50:08 +08:00
morluto eb51eb6915 fix(process): preserve selected launch paths and completed command output 2026-10-26 16:46:53 +08:00
morluto 86e7cd9b48 fix(verification): keep runtime packaging and boundary fixtures complete 2026-10-26 16:46:53 +08:00
morluto 09acf217d9 fix(evidence): preserve source and authenticated observation identity 2026-10-26 16:46:45 +08:00
morluto 6336fe5bb5 refactor: consolidate binary analysis admission and format ownership
Reuse the existing provider input admission helper across twelve services,
preserving cancellation, schema errors, path policies, and Evidence handling.
Share ELF dynamic records across Dart, MIPS, and IL2CPP readers, and reuse
Mach-O segment and section records for Node SEA location.

Require canonical DOS ownership before GPU PE section discovery so relocation
bytes cannot masquerade as a PE pointer. Cover valid short DOS headers and
the malformed case through both inventory and the public CLI.
2026-10-26 16:44:21 +08:00
morluto 7adfcc4467 fix: unify executable format ownership across artifact readers
Centralize ELF section and Godot trailer declarations while preserving caller admission policies. Route six PE entrypoints through canonical DOS ownership so relocation data cannot establish a PE header, and update NativeAOT interpretation identity and verifier dependency coverage.
2026-10-26 16:32:49 +08:00
morluto 9341b116ef refactor: consolidate binary artifact interpretation and fixture ownership
Share exact ELF program-header facts across nine consumers while preserving their admission and precision policies. Centralize conformance path and digest binding, retain verifier-specific checks, and route fixture-owner changes to CI qualification.
2026-10-26 16:21:11 +08:00
morluto 23bc51d5c4 test(package): migrate skill metadata fixture to frontmatter 2026-10-26 16:15:08 +08:00
morluto 28222089f4 refactor(zip): share end-of-directory declarations 2026-10-26 16:14:58 +08:00
morluto 91bacc5c14 refactor(config): share bounded decimal setting validation 2026-10-26 16:14:49 +08:00
morluto 2411d60b5a fix(verification): unify complete MCP catalog checks 2026-10-26 16:11:09 +08:00
morluto 74f9870b7a refactor(macho): share FAT evidence interpretation 2026-10-26 16:10:57 +08:00
morluto 90199ff0ec refactor(elf): centralize symbol record decoding 2026-10-26 16:10:46 +08:00
morluto 3d565fa42a refactor(windows): share native artifact validation 2026-10-26 16:04:44 +08:00
morluto 729684af9a refactor(png): share IHDR interpretation 2026-10-26 16:04:33 +08:00
morluto 92bdb4443f refactor(pe): share CodeView range interpretation 2026-10-26 16:04:18 +08:00
morluto a7d71f47ee refactor(elf): centralize raw table declarations 2026-10-26 16:04:09 +08:00
morluto c994870f6b fix(inspector): route file locations through URL semantics 2026-10-26 16:02:14 +08:00
morluto 1a1121c61f fix(android): retain selected Java across workspace launches 2026-10-26 16:02:14 +08:00
morluto ce5373e42f test(providers): verify malformed HTTP acquisition recovery 2026-10-26 16:02:14 +08:00
morluto 38ed5a3142 fix(browser): invalidate ASAR headers on file state changes 2026-10-26 16:02:14 +08:00
morluto 258a1dd3b7 fix(skills): parse metadata only from YAML frontmatter 2026-10-26 15:49:58 +08:00
morluto 0f64f1f855 refactor(macho): share bounded header declarations 2026-10-26 15:49:52 +08:00
morluto f8c5a6cc10 fix(providers): reject malformed UTF-8 before MCP decoding 2026-10-26 15:41:03 +08:00
morluto c849a97d2a fix(setup): preserve explicit Codex home traversal 2026-10-26 15:41:03 +08:00
morluto 628087a2db fix(process): preserve platform-aware environment precedence 2026-10-26 15:41:03 +08:00
morluto 554816becc refactor(verification): centralize native client result discovery 2026-10-26 15:40:34 +08:00
morluto 981d806924 refactor(macho): centralize segment and section decoding 2026-10-26 15:40:10 +08:00
morluto f4cc6c85ef refactor(verification): share native client fixture assertions 2026-10-26 15:34:11 +08:00
morluto 0929751798 refactor(macho): reuse canonical header declarations 2026-10-26 15:34:04 +08:00
morluto 36515a5c9b refactor(cli): derive desktop limits from the input contract 2026-10-26 15:33:59 +08:00
morluto deed39f7c4 refactor(pe): share optional-header facts across readers 2026-10-26 15:27:35 +08:00
morluto 971b48f702 refactor(mcp): reuse desktop retained-summary handler 2026-10-26 15:27:29 +08:00
morluto b9ee8fe057 refactor(pe): centralize fixed COFF header decoding 2026-10-26 15:20:02 +08:00
morluto 99d4ea9c05 fix(captures): preserve relative redirect URL evidence 2026-10-26 15:15:00 +08:00
morluto 2012efe5fd fix(artifacts): retain source identity failures after relocation 2026-10-26 15:15:00 +08:00
morluto 0c147a4a70 fix(snapshot): preserve authenticated replay provenance 2026-10-26 15:15:00 +08:00
morluto f4987281fa fix(setup): forward supported tool and runtime selections 2026-10-26 15:15:00 +08:00
morluto 53779b59bf refactor(pe): reuse canonical section header facts 2026-10-26 15:14:10 +08:00
morluto 943ae604df refactor(reference): derive import lookup from reader inventory 2026-10-26 15:14:05 +08:00
morluto 44b716f2f2 fix(runtime): preserve exact selected target identities 2026-10-26 15:09:04 +08:00
morluto d9e12c957b refactor(firmware): centralize extraction depth ceiling 2026-10-26 15:08:56 +08:00
morluto 90b793e0f4 docs: scope performance measurements to concrete needs 2026-10-26 15:04:08 +08:00
morluto b3818bbba2 refactor(il2cpp): share generic container byte decoding 2026-10-26 15:00:41 +08:00
morluto 9e4702b7a7 fix(sqlite): preserve schema-owned input issue paths 2026-10-26 15:00:30 +08:00
morluto 23d56e1abd refactor(config): use canonical resource limit constants 2026-10-26 15:00:23 +08:00
morluto 85075fa979 refactor(asar): let listing own parsed member facts 2026-10-26 14:53:42 +08:00
morluto 7cd65278ea refactor(config): remove legacy provider timeout re-exports 2026-10-26 14:53:20 +08:00
morluto 7404847f64 fix(update): bound and own metadata probe lifetimes 2026-10-26 14:49:10 +08:00
morluto bf876efbaa fix(kuna): bind cached export reads to verified file state 2026-10-26 14:49:10 +08:00
morluto b654ac8b13 fix(setup): retain auxiliary tool selections in registrations 2026-10-26 14:49:10 +08:00
morluto a116d32cdc chore: sync main before publishing cleanup migrations 2026-10-26 14:48:30 +08:00
morluto 8fe3b17ea7 refactor(tooling): move completion ledger generation out of domain 2026-10-26 14:46:35 +08:00
morluto 42f3ed7119 refactor(tooling): move verifier host helpers out of runtime 2026-10-26 14:46:23 +08:00
morluto d3946a3f50 fix(apk): reuse canonical subject identity in retained views 2026-10-26 14:46:14 +08:00
morluto 975900f565 docs(setup): state the FreeBuff minimum release (#7066) 2026-10-26 14:45:43 +08:00
morluto 2be39ef47a refactor(model): let format parsers own tensor range validation 2026-10-26 14:33:35 +08:00
morluto f4768eb5d3 fix(wasm): reuse canonical subject identity admission 2026-10-26 14:33:27 +08:00
morluto 06655af306 refactor(evidence): centralize publication source collection 2026-10-26 14:25:50 +08:00
morluto 8982f40d00 refactor(godot): share PCK header facts across inspections 2026-10-26 14:25:44 +08:00
morluto d7d9457661 refactor(gpu): reuse canonical ELF note layout 2026-10-26 14:25:34 +08:00
morluto ed7b3a2a10 refactor(macho): share header declarations across native readers 2026-10-26 14:20:01 +08:00
morluto c11fb48c98 fix(electron): share wrapped member identity parsing 2026-10-26 14:19:55 +08:00
morluto 83a6cdb859 fix(paths): preserve native traversal and unset PATH semantics 2026-10-26 14:17:27 +08:00
morluto 1fe1f1f8d6 refactor(managed): retire redundant verification facade 2026-10-26 14:14:26 +08:00
morluto 5a332d856a refactor(browser): resolve Link metadata identity once 2026-10-26 14:14:22 +08:00
morluto 38ebe11a52 refactor(pe): centralize section header interpretation 2026-10-26 14:14:18 +08:00
morluto ba7ac7908d refactor(elf): carry parsed header facts into native consumers 2026-10-26 14:08:35 +08:00
morluto 9fa5a87c73 refactor(javascript): remove obsolete Electron classifier facade 2026-10-26 14:08:34 +08:00
morluto 6362ac6b72 refactor(web): reuse shared provider input admission 2026-10-26 14:08:30 +08:00
morluto fab78501c7 refactor(services): centralize direct provider input admission 2026-10-26 14:04:57 +08:00
morluto 5c4a6c467c refactor(pe): share optional header image and alignment facts 2026-10-26 14:04:45 +08:00
morluto 72fa15a509 refactor(snapshots): derive workflow entries from canonical schema 2026-10-26 14:04:39 +08:00
morluto 52976ac5cd fix(doctor): bound and cancel diagnostic subprocesses 2026-10-26 14:02:55 +08:00
morluto fa6112b29f fix(providers): preserve native traversal in installation paths 2026-10-26 14:02:55 +08:00
morluto 6819cc5b8d fix(setup): preserve explicit configuration independently of readiness 2026-10-26 14:02:55 +08:00
morluto f6d9f1b05c refactor(browser): reuse worker URL exclusion classification 2026-10-26 13:59:22 +08:00
morluto 5e581fd3b1 refactor(workflows): share source caveat grouping identity 2026-10-26 13:59:11 +08:00
morluto 7439d0d5d8 refactor(artifacts): reuse Mach-O framing for Node SEA discovery 2026-10-26 13:59:04 +08:00
morluto 071a637a9f refactor(cli): share managed comparison finalization 2026-10-26 13:53:43 +08:00
morluto 634e566c09 refactor(capabilities): remove inert client requirement facade 2026-10-26 13:53:35 +08:00
morluto 941838fdd9 refactor(elf): centralize fixed header type and flags decoding 2026-10-26 13:53:25 +08:00
morluto 991a240606 fix(cli): preserve script export evidence through shared finalization 2026-10-26 13:47:15 +08:00
morluto 7ccecbcb00 refactor(cdp): centralize execution context identity parsing 2026-10-26 13:47:04 +08:00
morluto 60daca427d refactor(reconstruction): share canonical original cases across evaluation 2026-10-26 13:46:57 +08:00
morluto 1173c0a9dc test(setup): align launch fixtures with runtime and host requirements 2026-10-26 03:20:13 +08:00
morluto db4d3abcda fix(setup): retain bounded startup diagnostics without losing failures 2026-10-26 02:33:31 +08:00
morluto 37a7c2d652 fix(doctor): retain host uncertainty and resolved Java selection 2026-10-26 02:33:31 +08:00
morluto 82fe820060 fix(setup): retain explicitly selected ILSpy paths 2026-10-26 02:33:31 +08:00
morluto 14dd18a7fd fix(setup): preserve native configuration path traversal 2026-10-26 02:33:31 +08:00
morluto d69a54d592 fix(setup): register explicit MCP runtimes and preserve launcher identity 2026-10-26 02:33:31 +08:00
b8ce3c74cd feat(native): make function matching allowance configurable (#7159)
Allow native matching representation budgets to be selected at runtime or launch while retaining the 96 MiB default and main shared-provenance accounting. Preserve actionable configuration recovery through the setting owner and verify complete CLI/MCP matches for 20k/50k synthetic inventories and real stripped LLVM 14/15 inventories.

Fixes #6406.

Based on #7112 by Kaoru0822; original authorship and co-author credit retained.

Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-26 02:22:32 +08:00
morluto a174892e3e fix(website): improve layout and clarify homepage copy 2026-10-26 01:33:31 +08:00
morluto ff6d96bce8 fix(website): surface RL environments and adjust homepage navigation 2026-10-26 00:51:30 +08:00
morluto a0ddb83e45 docs(environments): lead with agent prompts 2026-10-26 00:40:56 +08:00
morluto 14631f698d docs(website): showcase RL environment workflows 2026-10-26 00:33:55 +08:00
gensokyo-dev 4c8647a706 feat(native): page retained observations and source context (#7146)
* feat(native): page retained observations and source context

* docs(native): record complete observation workflow measurements

* fix(native): preserve context coverage and overflow recovery

* fix(benchmarks): finalize partially initialized observation probes

* docs(native): publish reviewed workflow verification
2026-10-26 00:32:01 +08:00
morluto 447907f280 docs(environments): explain building RL environments with REA 2026-10-26 00:16:52 +08:00
Kaoru0822andClaude Opus 5.5 5d42839099 revert(mcp): undo input schema fitting that broke main CI
This reverts commit 7336a566ac (#7119).

After #7120 and the other merges of 2026-10-25 grew inspect_analysis_view's
complete input schema (71,762 bytes plus 3,003 bytes of examples), the 64 KiB
fitting from #7119 could no longer keep instance level 3 and fell back to
level 2. That summarized the inline Evidence source and the view selector
collections, so main CI failed in toolInputReferences, toolSchemaValidity,
analysisViewMcp, absoluteLocalPathSchema, javascriptFactsViewMcp and
godotPckMcp. #7119's own CI ran against bc143125e, before that growth.

Reverting restores the complete full-profile advertisement while the size
limit is redesigned against these contracts (#4081 stays open).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Refs: #7135
2026-10-25 23:50:18 +08:00
gensokyo-dev 6381aa0b1d fix(ghidra): scope rejected-input diagnostics to their request (#7140)
Scope expected Ghidra input failures to the active request after the authenticated startup handshake. Preserve operation output, identity and log coordinates while retaining cumulative output with its session owner; lifecycle and unexpected failures keep full diagnostics.

Share remote input-code classification with domain error projection. Verify startup rejection, healthy continuation, cleanup, unknown errors, OOM diagnostics and real CLI/MCP workflows. The measured unknown-symbol reply falls from 13,170 to 2,209 bytes with unchanged successful facts and Evidence IDs.
2026-10-25 22:37:32 +08:00
gensokyo-dev f69c52a1cf feat(native): page retained load-image observations (#7134)
Add optional native load-image summary delivery and retained selected-row pages through shared CLI/MCP analysis views. Preserve source verification, provenance, unavailable collections, exact row identity and opt-in byte/check payloads.

Preserve direct caller paths during shared CLI summary publication and cover raw/DOL projection contracts through the actual SDK. Real Ghidra ELF CLI/MCP captures, retained and file-backed pages, focused regressions and static checks passed; the PR records existing main schema failures and the JavaScript resource-recovery CI uncertainty.
2026-10-25 21:50:42 +08:00
Kaoru0822andClaude Opus 5.5 25f0f9934d fix(javascript): close member-path cycles through self stores (#7131)
A write below a slot that stores a value read from the same slot, such
as `s.c = s.c.p; s.c.i = 1` on a parameter, revisits the binding with
one more segment after the stored key on every turn. The cycle closure
only recognized segments added at the front of the path (`node =
node[key]`), so the work-list never terminated and exhausted the owned
worker heap (CodeMirror 5 `mode/stylus/stylus.js`).

Recognize a contiguous segment inserted after a stable base, check that
repeating it after that base can never be pruned, and escape through it
once, as the self-alias rule already does. Front insertions keep their
existing order and result.

Fixes #7130

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:32:29 -07:00
Kaoru0822andClaude Opus 5.5 70b96b65d9 fix(javascript): stop recording replaced slot values as store receivers (#7128)
Reference-store identity queries resolved the written target as a value,
so once a step consumed the written key the walk continued into the value
read from that slot and recorded it, and everything reachable from it, as
an object the store mutates. Every value stored into one local object at
unknown keys then received an entry for every such store, and each new
store re-queued every earlier one. Ambient-write indexing grew cubically
in the number of such stores.

Receiver queries now drop items whose projection no longer ends at the
written key. Stored-value selection has one owner, which the resolver and
the store fixpoint share. The fixpoint re-queues a dependent only when a
changed entry can reach it with a selection that its receiver query keeps.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:29:23 -07:00
Kaoru0822andClaude Opus 5.5 5a22ae6bc8 fix(mcp)!: return a compact binary_session status by default (#7127)
* fix(mcp)!: return a compact binary_session status by default

binary_session {} with no target open returned about 66 KB, most of it
provider rejection diagnostics and candidate capability sets that the
skill's recovery flow re-reads on every retry. 6.8.0 grew it by 21%
because the shared regex capability limitation began repeating setting
bounds and step-by-step recovery for every search operation of every
provider (#6099, #6267, #6301, #7049) and IDA gained that limitation for
its two search operations (#4862).

The regex limitation now states the effective deadline and heap, their
sources and where to change them; the deadline and heap errors keep the
full recovery. binary_session gains detail: summary (default) or
complete. The summary keeps open state, target identity, the binding,
route capabilities, run, activity and health status, each candidate's
availability and target-support status, code and reason, and every
tool's availability, reason, remediation and modes, and lists the
fields it leaves out in omitted_fields. Complete detail returns the
previous shape. The CLI capabilities and providers commands keep
complete provider facts through the same application projection.

BREAKING CHANGE: binary_session {} no longer returns
analysis_provider_candidates[].availability.diagnostics,
target_support.diagnostics or capabilities, or tool_availability group
surface, effects and annotations. Pass {"detail": "complete"} to get
them.

Fixes #7109

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

* fix(mcp): key binary_session output fields by detail

The output schema accepted detail: "complete" with summary candidates or
tool groups, and a summary without omitted_fields, so detail did not
tell callers which fields existed. Each session kind is now crossed
with a detail-specific shape: a summary requires omitted_fields and
summary-only candidates and tool groups; complete requires diagnostics,
candidate capabilities and static tool flags and forbids omitted_fields.
Both the canonical schema and the SDK-validated advertised schema reject
mismatched shapes.

The verifier helper accepts summary candidates only for a validated MCP
summary; CLI provider output still requires candidate capabilities.
Regex limitation tests check values, sources and setting names instead
of whole sentences.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:29:03 -07:00
Kaoru0822andClaude Opus 5.5 e06be06530 fix(cli): report JSON Lines selection refusals as caller errors (#7124)
* fix(cli): report JSON Lines selection refusals as caller errors

A --format jsonl refusal because the selected value is not a record
collection is the caller's output selection, not unreadable analysis
output. Report it as invalid_request/invalid_input that names the
selection and its observed shape, keep the --format json /
--filter-output remediation, and drop the rea doctor advice.

AnalysisInputError gains an optional producer remediation, mirroring
the other refusal classes. The JSON Lines owner examines its documented
boundaries once for both admission and refusal facts.

docs/cli.md now states that columnar results such as
inspect-sqlite-database --table rows are not record collections.

Fixes #7110

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

* fix(cli): carry JSON Lines refusal facts as structured observations

JSON Lines refusals now put the location and shape of each refused value
in details.issues[].observed, and the message is built from those facts.
Tests assert the classification, issue path, observed facts, and the
shared remediation constant instead of fixed sentences.

The filtered analysis-view export check now uses a non-empty request
and compares the emitted rows with the unfiltered export.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:26:13 -07:00
Kaoru0822andClaude Opus 5.5 81afb043f7 fix(javascript): evaluate each short-circuit left operand once (#7126)
Logical branch selection evaluated the left operand to decide which
branches stay reachable, and value evaluation then evaluated the selected
left branch again. In a left-nested `a || b || c ...` chain every level
repeated that, so semantic analysis doubled per operand and a 24-operand
chain exhausted the per-source deadline.

The selection now carries the left operand's value with the branch, and
value evaluation reuses it. Provenance still walks the branch nodes.
Results are unchanged.

Fixes #7115

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:25:54 -07:00
Kaoru0822andClaude Opus 5.5 7336a566ac fix(mcp): fit every advertised input schema within the 64 KiB client limit (#7119)
* fix(mcp): fit every advertised input schema within the 64 KiB client limit

Claude Desktop drops a local connector tool whose compact-JSON input
schema exceeds 64 KiB. The full input profile advertised
compare_web_captures at 171,867 bytes and inspect_analysis_view at
71,632 bytes, because the self-contained projection inlines the
complete producer capture schemas once per side.

The full advertisement now fits the limit at its producing boundary.
A property whose schema repeats an earlier sibling's points at it, and
schemas below the deepest instance level that fits are summarized as
their types, size constraints and descriptions. Every rewrite widens the
advertisement through the existing relaxation rules for oneOf, not,
if/then/else, contains and unevaluated keywords, so canonical inputs
stay accepted and the canonical contract still validates every call.
Schemas within the limit are unchanged, and the compact profile is
still presented from the complete projection.

Numeric const and enum literals are also relaxed in the advertisement,
like boolean literals, since Gemini accepts only string enum members.

Fixes #4081

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

* fix(mcp): keep fitted input summaries widening and at the deepest fitting level

Array summaries took item types only from `items`, so a tuple such as
`{prefixItems: [{type: string}], items: {type: number}}` was advertised
as a number array that rejected `["a", 1]`. Item types now include tuple
positions from `prefixItems`, a draft-07 `items` array, and
`additionalItems`, and stay unconstrained when later elements are open.

A summary can also be longer than the schema it replaces (a nullable
array becomes an anyOf), so candidate size is not monotone in the
retained depth and stopping at the first overflow could skip a deeper
candidate that fits. A schema is now kept whole when its summary is not
smaller, and fitting searches from the deepest level down.

Property tests check that every summary accepts what its schema accepts
over every catalog input subschema and synthetic tuples, and that the
fitted schema is the deepest fitting candidate. The browser comparison
boundary test now expects a malformed `after` capture to be rejected
canonically, since `after` is advertised as the same schema as `before`;
the same malformation on `before` stays advertised-invalid.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:25:07 -07:00
Kaoru0822andClaude Opus 5.5 aa79f59478 fix(javascript): bind defaulted and destructured Webpack factory roles (#7117)
* fix(javascript): bind defaulted and destructured Webpack factory roles

A Webpack factory whose module parameter was defaulted (`e = {}`) or
destructured (`{exports: x}`) reported an exact empty export list with
complete coverage, because roles were taken only from plain identifiers.

Bind factory roles once, by position, in javascriptBundlerFactoryRoles.
Defaulted module/exports parameters keep their role (the convention always
supplies the argument), `{exports: alias}` binds the alias to
module.exports, and a repeated sloppy parameter name binds its last
argument. Other role patterns (rest, array, nested or computed keys, a
non-identifier loader) receive no role and count as an unknown finding,
so the file's analysis is partial instead of claiming no exports. The
export collector, the factory require name and the standalone bootstrap
externals all use these parsed roles.

Fixes #7113

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

* fix(javascript): keep factory receivers through unordered reads

A defaulted role parameter kept the unused default in the binding facts
passed to write ordering, so a receiver read inside a loop, nested block
or closure was unresolved and its export silently dropped while the
module stayed complete.

The role owner now removes defaults the calling convention never runs
before ordering. Separately, an export-shaped write through a role
binding whose read stays unordered now counts as an unknown finding
instead of being dropped, so an incomplete list is never reported as
complete.

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

* fix(javascript): treat never-run factory parameter defaults as dead code

Removing unused defaults from binding facts let writes inside a default
expression reach the export collector: `(e = {}, t = (() => {
e.exports.ghost = 1; })())` reported `ghost` although Webpack supplies
both arguments. Loader calls in such defaults also produced require
references and async chunk keys.

The role owner now records the default of every role parameter, any
binding pattern included, as never run. The export collector, the
async-chunk walk and the static source traversal skip those subtrees,
so they yield no exports, references or chunk keys. Defaults of later,
non-role parameters still run and are still reported.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:23:23 -07:00
Kaoru0822andClaude Opus 5.5 d32d9f2902 fix(lldb): keep the bridge's no-target proof through interruption (#7111)
When cancellation or the deadline interrupted LLDB after it had published
its bridge result, the tracer never read that result, so an explicit
`target_process_created: false` (or `target_process_exit_observed: true`)
was lost. The run kept a `native-target:unknown` marker that no cleanup
retry could release, wedging the tracer for every later trace.

Read the published result once LLDB's process group is stopped, even for
an interrupted run, and apply the same target-gone proof in cleanup retry.
Cancellation or timeout stays the primary outcome; a created target, a
truncated result, or a missing PID alone stays unresolved.

Fixes #5385

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:21:26 -07:00
Kaoru0822andClaude Opus 5.5 cc2656be8d perf(native): charge function matching for the copies it makes (#7108)
* perf(native): charge matching for what it copies, not borrowed retained inventories

Native function matching deep-walked every retained list_procedures record
at about 2.8 KB per Ghidra-shaped row, although the session ledger lends
those immutable records without a copy, and charged each inventory's one
Evidence ID once per function. The report and Evidence copies of every
emitted member were not charged at all.

Charge each inventory row before parsing for the member it becomes,
including its report and Evidence copies, charge the shared inventory ID
once, and skip the walk only for borrowed immutable inventories and closure
records that matching never parses. Inline Evidence, bundle records and
selected observations keep their existing charges. Retained references now
fit about 22,000 rows per side instead of 10,000, and an oversized side is
refused before its rows are parsed.

Refs #6406

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

* fix(native): charge the envelope of borrowed matching inputs

Borrowed inventories skipped admission entirely, but matching copies their
limitations into the report and Evidence and digests their profile, so a
retained inventory with few rows and large limitations escaped the
allowance. Charge every envelope field and leave only the result payloads,
which row charging covers or matching never parses, uncharged.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:21:09 -07:00
Kaoru0822andClaude Opus 5.5 58a5275aa2 fix(mcp): advertise reduced array elements with Gemini-compatible item types (#7107)
Reduced compact input schemas gave every array property an any-value item
schema, a six-member type list beside nested `items`. Gemini function
declarations accept `items` only beside the single `array` type, so every
request through a Gemini-backed client failed with
`properties[functions].items.items: field predicate failed: $type == Type.ARRAY`.

Reduced array properties now advertise the element types their canonical item
schemas accept. Unknown, tuple, and mixed array elements use the empty item
schema, and properties that may be arrays or another type list typed anyOf
branches. An inline `not: {}` keeps the empty schema. The compact catalog test
now rejects `items` beside a non-array type as well as untyped item unions.

Fixes #5201

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:20:36 -07:00
Kaoru0822andClaude Opus 5.5 0eb3919d69 fix(artifacts): admit raw UDRW disk images (#7106)
* fix(artifacts): admit raw UDRW disk images

Raw (UDRW, UDTO) disk images have no UDIF koly trailer, so target
admission and root inventory both refused them as unsupported binaries.
Recognize disk images in one domain owner (koly trailer, GPT, Apple
partition map, MBR, or an unpartitioned HFS+/HFSX volume header) that
both boundaries consume.

hdiutil reports "has no checksum" for these images; the native reader
now treats that status line as an explicit absent checksum, records the
nonzero verify command, attaches read-only, and reports that integrity
was not verified. Checksum mismatches and unrecognized images are still
refused.

Positional range reads used by these probes now fill until end of file
so short host reads cannot hide a signature.

Fixes #5687

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

* fix(artifacts): read disk image signature windows at the recognizer

Target admission passed its first unfilled 4 KiB read to the raw-image
checks, so a short host read could hide a GPT, APM, MBR or HFS+
signature that inventory (which fills its prefix) still observed. The
recognizer now reads its own leading window through the caller's
filling positional read, and target admission fills its format-probe
prefix as well. The controlled short-read regression now covers target
admission alongside inventory.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:20:24 -07:00
Kaoru0822andClaude Opus 5.5 a8e5eaf1aa fix(macho): derive the runtime name of each Mach-O symbol (#7105)
inspect_macho reported only the symbol-table spelling (`_main`), while
LLDB, dlsym and observe_native_calls use the runtime spelling (`main`).
Passing a static name as a breakpoint silently selected a different
function, so results from the two tools could not be joined by name.

Keep `name` as the exact symbol-table fact and add a derived
`runtime_name` to every export and import row, produced by one shared
Mach-O symbol-name owner. Managed P/Invoke verification now uses the
same owner instead of its own underscore stripping.

Fixes #5298

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 05:19:54 -07:00
fc43e285ea feat(browser): retain compact scenario summaries and selected views (#7120)
Retain complete browser scenario Evidence while delivering compact summaries and selected step, event, transaction and text views through shared CLI/MCP workflows. Preserve initial CLI failures across cleanup retries and validate with real Chromium/TodoMVC.

Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-25 19:03:06 +08:00
morluto bc143125ed fix(release): reject missing manifest release boundaries 2026-10-25 17:25:18 +08:00
github-actions[bot]andmorluto 160d821ac9 chore(main): release rea-agents 6.8.0 (#5004)
* chore(main): release rea-agents 6.8.0

* docs(release): finalize contributor-aware 6.8.0 notes

* docs(release): pin the JSON Lines migration guide

* docs(release): cover the remaining reviewed user-facing fixes

* docs(release): refresh the reviewed contribution snapshot

* docs(release): correct migration references and gateway scope

Preserve the concurrent 6.8.0 editorial draft and verified credits. Cite the actual keyed-archive UID migration, move CLI version and superclass-classification references to their matching entries, retain unknown-date plist behavior, and avoid implying live gateway acceptance. Existing author readiness declarations remain unchanged.

* docs(release): include nine newly merged changes in 6.8.0

* docs(release): include recorded write views in 6.8.0

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: morluto <[email protected]>
2026-10-25 16:35:15 +08:00
113679c9a6 feat(environment): serve creates whose answers show less than reads, and whole-object updates (#7080)
* feat(environment): take created entities' read-backs and re-derived update members

- A create's observation takes the fields the first read of its entity
  shows that the answer lacks, and an item read's clock spellings, when the
  read agrees with the answer otherwise. The plan records the read
  (observation.read with its shape); an item read then observes rather than
  checks, a list read only adds fields and still checks. Serving merges the
  same recorded objects.
- An update member sent back as the clock the entity held and stored as a
  fresh clock near the call is a clock rule; one stored as the slug of
  another member stored as sent is a slug rule. Both are accepted with any
  value, as whole-object writes send what they read.

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

* docs(environment): record the write-views verification

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

* fix(environment): reconcile write views without hiding contradictions

* fix(environment): reject incompatible slug-only saves

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-25 15:06:27 +08:00
Adolanium 80673b22a4 fix(javascript): report fork's silent stdio as piped (#7083)
* fix(javascript): report fork's silent stdio as piped

Node's child_process.fork pipes stdin, stdout and stderr when options.silent is truthy and options.stdio is neither a string nor an array, and inherits them otherwise. REA never read silent, so fork("worker.js", { silent: true }) was reported with stdio_mode default (inherited). The unset-stdio case of fork now reads silent: truthy gives pipe, falsy or missing gives default, and an unresolved value gives dynamic.

* fix(javascript): apply fork's silent rule to other known stdio values
2026-10-25 14:23:46 +08:00
Adolanium c74d302cc4 fix(ghidra): report a DOL language override conflict as a configuration error (#7081)
* fix(ghidra): report a DOL language override conflict as a configuration error

A REA_GHIDRA_LANGUAGE_ID outside PowerPC:BE:32:* made an otherwise admitted GameCube DOL an unsupported target. docs/ghidra-provider.md and the other fixed-import lanes treat such an override as a configuration error naming the conflicting setting. The DOL lane now uses the same languageOverrideConflict owner and names only REA_GHIDRA_LANGUAGE_ID, since DOL accepts a compiler-spec override.

* test(ghidra): keep a valid compiler spec out of the DOL language conflict
2026-10-25 14:19:14 +08:00
2c38becc4e feat(binary-layout): admit ELF32 i386, ARM and MIPS layout profiles (#7052)
* feat(binary-layout): admit ELF32 i386, ARM and MIPS layout profiles

inspect_binary_layout refused every ELF32 object, so router and IoT
firmware (ARM32, MIPS32) got no layout facts at all.

The pwntools adapter now admits EXEC/DYN/REL objects in these profiles:
ELF64 x86-64, ELF32 i386, ELF32 little-endian ARM and ELF32 little- or
big-endian MIPS. Header fields are read in the declared byte order, the
ELF32 header is bounds-checked at 52 bytes, and pwntools' own reported
architecture, class and byte order must match the admitted profile.
Other machines, classes and byte orders keep an unsupported_target
refusal that names the admitted profiles.

The output schema admits exactly those architecture combinations, and
the Evidence subject now names the reported machine instead of always
claiming x86_64.

verify:binary:layout cross-compiles i386, ARM, MIPS and MIPSel
executables and relocatable objects (never executed) and checks every
section name, address, offset and size against GNU readelf through CLI
and MCP. The real-binary-layout workflow installs those toolchains.

Refs #6934

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

* ci(binary-layout): install target libc headers for ELF32 fixtures

--no-install-recommends skips the libc6-dev-*-cross packages the cross
compilers only recommend, so the i686 fixture fell back to the host's
/usr/include and failed to find bits/libc-header-start.h. Install the
four target libc development packages explicitly.

Refs #6934

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

* fix(binary-layout): size the minimum symbol entry by ELF class

The relocation reference check required a 24-byte Elf64_Sym entry, so
every positive symbol reference in an ELF32 report was refused as
unreadable output. Elf32_Sym is 16 bytes; take the minimum from the
reported class. A 64-bit table with 16-byte entries is still refused.

Refs #6934

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

* test(binary-layout): compare ELF32 symbols and relocations with readelf

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-25 14:18:44 +08:00
rodricksz4h5 0a2b7af905 fix(apple): keep the documented terminating process, parent pid and metadata members of IPS reports (#7079)
Apple's crash report JSON documents termination.byPid, byProc and flags, the body's parentPid, and
the metadata line's build_version and platform. The parser shapes did not list them, so they were
reported only by name in unlisted_keys / unlisted_body_keys and their values were dropped: a
watchdog kill no longer said which process ended it, and process kept parentProc without its pid.
#4297 asks for termination members beyond namespace/code/indicator to be retained as recorded, and
docs/apple-ips.md says documented fields are reproduced.

They are now kept: termination.by_proc, termination.by_pid and termination.flags, process.parent_pid
and metadata.build_version / metadata.platform. The integers are recorded integers, so they count
toward max_integer_text_bytes and a type mismatch is recorded like the other fields.
2026-10-25 13:56:51 +08:00
RRFZ cc146042d1 fix(cli): preserve literal Markdown table facts (#7073)
* fix(cli): preserve literal Markdown table facts

* test(cli): retain capacity diagnostics through Markdown escaping

* fix(cli): keep intraword underscores readable in Markdown tables

Underscore runs between letters or digits cannot open or close CommonMark emphasis, so escaping them only adds noise to identifiers such as source_index and invalid_request in raw Markdown. Escape underscore runs only at word boundaries and restore the original diagnostic assertions.
2026-10-25 13:55:18 +08:00
Wenyu Chiou fead4fdaa7 fix(artifacts): preserve integrity contradictions for one-sided members (#7076) 2026-10-25 13:47:41 +08:00
Adolanium 4973ce7202 fix(capture): report an out-of-range record ordinal as a selection fault (#7067)
inspect_web_network_capture reported a record_ordinals entry past the capture's records as an invalid_format issue on record_ordinals, with no bounds. It is now an out_of_range issue on that entry (record_ordinals[i]) with minimum 0 and maximum total_records - 1, or invalid_value when the capture has no records.
2026-10-25 13:46:26 +08:00
Adolanium 06a4c338fc fix(protobuf): exclude trailing whitespace from gRPC-web trailer values (#7069)
RFC 9110 section 5.5 excludes leading and trailing whitespace from a field value. The gRPC-web trailer parser stripped only leading SP/HTAB, so 'grpc-status: 0 ' was reported as '0 '. Trailing SP/HTAB is now excluded too; internal whitespace and the examined byte count are unchanged.
2026-10-25 13:45:22 +08:00
Adolanium 60ab148afa fix(ghidra): accept stock language IDs with LEBE endianness or a slash (#7070)
REA_GHIDRA_LANGUAGE_ID only allowed LE or BE as the endian field and no '/' in the processor, so it rejected four language IDs that ship with Ghidra: ARM:LEBE:32:v7LEInstruction and v8LEInstruction (ARM BE8) and M16C/60:LE:16:default and M16C/80:LE:16:default. The syntax check now allows LEBE and a slash between processor name parts; the installed .ldefs catalog check is unchanged.
2026-10-25 13:45:11 +08:00
morluto 8e6a957886 fix(release): exclude Copilot from human contribution credits
Preserve GitHub User metadata for Copilot while excluding it from generated human thanks and accepted prose credits. Extend the real inventory/render/check regression and document the known agent identity.
2026-10-25 12:28:21 +08:00
Hahaa909andmorluto 9750486e78 fix(mcp): normalize compact schemas for strict gateways (#3806)
* fix(mcp): make compact schemas gateway compatible

* test(mcp): bind input precision assertions to the advertised profile

* test(mcp): own schema assertions and validator lifetimes

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 12:11:42 +08:00
Ikko Eltociear Ashimineandgreptile-apps[bot] 4232399079 docs(i18n): add Japanese translation (#7061)
* docs(i18n): add Japanese translation

* Update docs/readme/README_ja.md

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

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-25 11:44:05 +08:00
Wenyu Chiou 26e155987f fix(javascript): invalidate Electron reads after direct eval (#7053) 2026-10-25 11:29:45 +08:00
69a2faa120 feat: add FreeBuff agent support for REA setup (#2736)
* feat: add FreeBuff agent support for REA setup

Register FreeBuff as a supported setup client using the shared user
agents directory (~/.agents/mcp.json with mcpServers, skills in
~/.agents/skills), which matches what FreeBuff Desktop loads. Adds the
client definition, boundary tests, supported-agent docs, and the skill
reference client list.

* fix(freebuff): detect FreeBuff from its profile directory, not shared .agents

The ~/.agents marker made FreeBuff appear installed for any home where
REA had installed shared skills (the default skill destination, also
Gemini CLI's), producing false doctor failures. Detect FreeBuff from
its own fixed profile directory ~/.config/freebuff-desktop instead,
and add a regression test covering a home with only the shared agents
skill directory.

* fix(setup): require product evidence for FreeBuff discovery

Address review findings at their producing boundaries and preserve directly affected consumers. Source inspection and git diff --check completed. Tests and automated checks were not run at the user’s explicit request.

* fix(uninstall): retain unverified shared client configuration

* fix(clients): preserve scoped removal evidence

* fix(doctor): omit undefined selectedClientIds under exactOptionalPropertyTypes

Passing selectedClientIds explicitly as undefined fails typecheck on this
branch (TS2379), which blocks the focused test workflow before any test
can run. Spread the option only when defined.

* fix(update): separate ownership evidence from product applicability

rea update's maintenance scope inspected only product applicability
evidence, so when a FreeBuff profile was absent but REA already owned a
version-pinned row in the shared ~/.agents/mcp.json, the plan omitted
freebuff and left the integration stale. FreeBuff now declares its
registration storage as shared, which proves ownership of an owned row
but never product applicability, and maintenance requests ownership
evidence through that declaration instead of reinterpreting the shared
paths. Doctor and setup keep readiness evidence, foreign rows stay out
of maintenance, and the Doctor exactOptionalPropertyTypes fix makes the
focused test workflow runnable.

* test(dotnet): split oversized operand extent diagnostics describe

The describe callback reached 162 lines (max 150), failing
max-lines-per-function and blocking the pre-push check for this branch.
Move the single-case extent scenarios into their own describe; no test
content or order changes.

* fix(update): preserve ZCode's disabled shared registration during maintenance

The shared user agents file doubles as ZCode's fallback, where
`enable: false` disables the registration. Ownership-based maintenance
read that row as a FreeBuff registration, whose format does not
recognize the flag, so the row was selected and the emitted setup
command rewrote it, dropping the flag and silently re-enabling a
registration the user had disabled.

registrationActivation now honors `enable: false` for any client whose
registration storage is shared, because the activation fact binds every
reader of that storage. Maintenance leaves such rows out; doctor
reports them as stale, matching ZCode's own treatment.

* fix(doctor): let explicit client selection authorize the config read

When an ACL or an inaccessible shared parent made every access probe
fail, a client selected by name was omitted before the configuration
read could return its unreadable-config detail, so doctor reported it
as not detected with installation remediation: an unresolved selected
path collapsed to absence.

An explicit selection now authorizes the read outright and the probes
serve automatic discovery only. A selected client with an unreadable
config reports invalid with the read failure; one with no config
reports missing; automatic discovery still probes before inspecting.

* fix(uninstall): share shared-storage checks between ZCode and FreeBuff

clients() queued ~/.agents/mcp.json twice: once as zcode_fallback and once
as freebuff. ZCode has no discoveryPaths, so its strict read still blocked
uninstall on malformed shared text, despite FreeBuff's new retain-and-continue
behavior. For a valid owned entry, ZCode removed it first, so FreeBuff reported
skipped rather than removed.

Make the shared file's checks and removal one shared decision:
- Add zcodeDiscoveryPaths to ZCode so it has its own applicability evidence.
- Gate zcode_fallback push on applicable, not just zcodeFallbackPath presence.
- Report shared storage ownership as one decision for both clients:
  FreeBuff and ZCode now use clientOwnershipEvidencePaths together, so a
  recognizable REA-owned row proves ownership once for the shared storage, and
  both clients report the same result consistently.

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

* fix(uninstall): merge duplicated host and prove shared ownership from owned rows

Uninstall.ts declared systemUninstallHost twice, preventing module load,
with the exported copy returning only clients and the second copy missing
clientWriteBlocker. Merge into one exported host carrying both blockers.

hasOwnershipEvidence used access() existence, so an unrelated malformed
~/.agents/mcp.json established ownership and blocked every removal.
Ownership now requires a readable, recognizable REA-owned row via the
injected filesystem, so unrelated shared data stays retained.

Also cover ZCode discoveryPaths in its path test.

* fix(uninstall): keep denied product-local configurations fail-closed

Unproven product applicability marked every configuration path of that client unverified, so a denied ZCode profile turned an unreadable product-local config into a retained item: uninstall continued and could report complete with REA still registered. The retain-and-continue exception now applies only to storage REA does not own, classified once by clientSharedRegistrationPaths (shared configuration plus another product's same-scope fallback).

Covers the Greptile P1 on Uninstall.ts. Validation: the new test fails on the pre-fix Uninstall and passes after it; freebuff/zcode/updateMaintenance/bom/dsh suites green; clientLifecycle, clientRegistrationStatus and claudeDesktop keep the same 8 host failures as the pre-merge baseline (symlink EPERM, stale-vs-aligned on Windows).

* fix(update): retain FreeBuff when ZCode reads the shared fallback

Maintenance deduplicated clients on the path each inspection read, so ZCode's fallback view of ~/.agents/mcp.json marked that file "covered" and dropped FreeBuff from the scope. ZCode setup writes ~/.zcode/cli/config.json and deliberately leaves the fallback untouched (docs/client-configuration.md), so the emitted command refreshed only ZCode while the shared row both products load stayed pinned to the old package version. Each setup action rewrites only its own configuration path and no other action rewrites the shared row, so every selected client keeps its own maintenance action; the read-path covered/deduped filter is removed.

Covers the Codex P2 on UpdateMaintenance.ts:104. Validation: the new regression fails on e2fdec384 with {clients:["zcode"]} and passes with {clients:["freebuff","zcode"]}; updateMaintenance 31/31, setupClientConfigCleanupOwnership, cliUpdateAlias, updateOutput, cliStartupImports green; tsc --noEmit, oxlint (0 errors) and oxfmt --check clean; clientRegistrationStatus keeps the same 4 symlink/stale-vs-aligned failures this Windows host has on base e2fdec384 (verified by stashing the change).

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* fix(uninstall): preserve completed reads as shared ownership evidence

hasOwnershipEvidence caught every read failure and kept scanning, so a handle-close failure after a successful read — a RegularFileCleanupFailure whose completed outcome still contains the exact shared-file text — was discarded as "no proof". FreeBuff's applicability-unverified storage therefore never got its ownership fact, and once the retained handle blocked later reads, readClientConfiguration downgraded them to retained: inspection passed, removals soft-failed, and runUninstall(true) could purge REA data while the active shared registration remained. The catch now parses the completed outcome for the ownership text while missing or unreadable storage and malformed data keep their proves-nothing semantics, so ownership holds and every later read failure stays blocking.

Covers the Codex P2 on Uninstall.ts:252. Validation: the new regression fails on 4dbfdf1a9 (freebuff reports no blocking failure while the poisoned-scope error lands on claude_code) and passes after; setupClientConfigCleanupOwnership 25/25; the other 21 Uninstall-consumer suites are byte-identical to base 4dbfdf1a9 (61 pre-existing Windows-host failures: symlink EPERM, stale-vs-aligned); tsc --noEmit, oxlint (0 errors, same 6 warnings) and oxfmt --check clean.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* fix(uninstall): keep owned-row proof through removal refusals

hasOwnershipEvidence treated any clientRegistrationRemoval refusal as no ownership: an effective REA-owned rea member shadowed by an earlier foreign duplicate (shadowed_unowned_member), or an owned native row beside a foreign V1 fallback (foreign_legacy_fallback), discarded the already-parsed positive row fact. With FreeBuff unverified and close failures recurring on the preflight/removal rereads, those failures were then downgraded to retained, so runUninstall(true) removed the skill and purged REA data while the active owned row remained. The refusal now carries the owned rows that motivated it — the refusal only refuses the edit — and hasOwnershipEvidence evaluates that fact on both paths; preflight and removal keep refusing the unsafe edit exactly as before.

Covers the Codex P2 on Uninstall.ts:269. Validation: the new regression fails on 225cb214a (freebuff reports no blocking failure while the poisoned-scope error lands on claude_code) and passes after; setupClientConfigCleanupOwnership 26/26; the 21 Uninstall-consumer suites are byte-identical to the recorded 225cb214a baseline (61 pre-existing Windows-host failures: symlink EPERM, stale-vs-aligned); tsc --noEmit, oxlint (0 errors, same 6 warnings) and oxfmt --check clean.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* fix(uninstall): keep unresolved discovery probes from softening shared failures

The discovery probe swallowed every stat failure, so a FreeBuff profile denied with EACCES/EIO was indistinguishable from an absent profile: shared storage was marked applicability-unverified and, when ~/.agents/mcp.json also remained unreadable, the ownership and removal reads softened to retained. runUninstall(true) then removed the skill and purged REA data while an installed FreeBuff could still hold an active registration — the regression even reports complete. Absence (ENOENT) is now the only state that leaves shared paths unverified; any other probe failure leaves applicability unresolved, and shared-config failures stay blocking.

Covers the Codex P2 on Uninstall.ts:318. Validation: the new regression fails on 7572a7082 with status complete and purged data and passes after; freebuffClientConfiguration 13/13; the Uninstall-consumer batches are byte-identical to the recorded baselines (61 pre-existing Windows-host failures: symlink EPERM, stale-vs-aligned); tsc --noEmit, oxlint (0 errors, same warning rules) and oxfmt --check clean.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* fix(uninstall): retain completed configuration reads as removal evidence

A close failure after a completed read discarded the text that read
produced, so an owned registration row the text proves softened to
retained. The completed text now drives removal, keeping shared-config
failures blocking when ownership evidence exists.

Co-Authored-By: Anthropic <[email protected]>

* fix(uninstall): stop removal when a cleanup retry leaves a handle unreleased

A completed reread whose handle the cleanup retry cannot release now fails
blockingly instead of serving the text: with no owned row, the soft path
let removeAll purge managed data before close() reported the incomplete
cleanup. The completed text is served only after the retry releases the
handle; an unreleased handle is REA's own failure, never applicability
uncertainty, so it bypasses the retained softening.

Co-Authored-By: Anthropic <[email protected]>

* fix(uninstall): keep the ZCode fallback when its profile probe is unresolved

A denied or failed profile probe leaves ZCode's applicability unresolved,
never absent, but the fallback branch dropped the shared row on
applicability alone: with the native config and FreeBuff's profile both
absent, an installed ZCode kept its uninspected registration while
uninstall purged skill and data. Unresolved probes now keep the fallback
in scope with the same fail-closed treatment as other unresolved shared
paths; definitive absence still keeps ZCode out of another product's
primary storage.

Co-Authored-By: Anthropic <[email protected]>

* fix(setup): preserve a shared row's enable:false during FreeBuff repair

The shared agents file doubles as ZCode's fallback, so its `enable: false`
binds every reader: doctor now reports the disabled row as stale for
FreeBuff, but following that remediation rebuilt the generic JSON entry
without the flag and silently re-enabled a registration ZCode disabled.
Repairs of shared storage now carry the activation fact forward, while a
client's own native storage keeps its established single-reader repair
contract; the denied-profile uninstall regression also pins the
fallback's new fail-closed inspection beside the denied read.

Co-Authored-By: Anthropic <[email protected]>

* test(uninstall): retarget the completed-reread regression to a released retry

The concurrent-writer regression held the reread handle open forever,
which the fail-closed path now serves: it duplicated the unreleased-handle
regression while documenting the opposite contract. The fixture's close
spy gains a failure limit so the cleanup retry releases the handle, and
the test again proves the completed text drives removal of the
concurrently added owned row, with purge verified against the cache
layout purge actually removes.

Co-Authored-By: Anthropic <[email protected]>

* fix(doctor): report a preserved shared disable as a manual conflict

Preserving a shared row's enable:false left doctor recommending a setup
run that registrationCurrent correctly treats as already current, so the
scoped doctor returned needs_human forever. The shared-enable
interpretation now has one owner, sharedEnableFlagBinds, used by both
the activation rule and the remediation: a row disabled through the
shared flag reports the manual conflict and states that rea setup will
not change it, instead of an endlessly ineffective repair.

Co-Authored-By: Anthropic <[email protected]>

* fix(doctor): keep a native ZCode disable on the setup repair path

The manual-conflict remediation matched a native zcode row through
sharedEnableFlagBinds, but setup drops enable:false on single-reader
storage, so a native disabled registration was told setup would not
change it. The remediation now reads setup's own writer rule,
exported as setupPreservesDisabledActivation: shared storage and the
zcode fallback view keep the manual-conflict message, while a native
row keeps the working rea setup instruction. Regressions pin both
remediations.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* revert: drop the unrelated managed-CIL describe split

The split does not belong to FreeBuff client support and only widens this
PR's diff.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>

* fix(uninstall): remove the shared agent registration once for both clients

ZCode's ~/.agents/mcp.json fallback and FreeBuff's own configuration queued the
same physical file twice, so ZCode removed the row and FreeBuff then reported
it absent, losing the outcome for the client that owns the file.

Resolve every configuration path to one owner -- the client whose
configuration it is -- and project that single inspection and removal to each
client that loads it. A fallback reader still contributes its discovery probe,
so an installed or unresolved reader keeps the shared file fail-closed.

Discovery also accepted a dangling ~/.config/freebuff-desktop link, which setup
and doctor reject because they follow the target, so an unrelated malformed
shared file blocked every other removal. Resolve the link before treating the
profile as installation evidence, keeping lstat for removal safety.

Also date the FreeBuff release wording against the published 6.7.0 package.

* fix(uninstall): keep a row per client when two clients own one file

OPENCODE_CONFIG can point OpenCode at FreeBuff's configuration file, where
OpenCode writes `mcp.rea` and FreeBuff writes `mcpServers.rea`. Grouping by
filename gave that path a single owner, so uninstall removed only OpenCode's
row, reported that removal for FreeBuff, and could purge managed data with
FreeBuff's registration still active.

Share the decision only through a fallback view of another client's row: every
client that declares the path as its own configuration keeps its own
inspection and removal, and the fallback reader reports each of them.

* fix(clients): preserve discovery and fallback registration identity

Follow product discovery link targets without losing configuration entry
presence or explicitly selected inspections. Associate fallback readers with
the format owner's registration table and project outcomes by owner and path,
so OpenCode removal cannot be reported as a ZCode fallback removal.

All three new boundary regressions fail against the previous source and pass
with the fixes. Validation: check:fast; 217 focused tests passed; the two
changed test files were rerun with 50 passing tests. No installed FreeBuff
client was launched.

---------

Co-authored-by: morluto <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Codebuff <[email protected]>
2026-10-25 11:29:17 +08:00
Adolanium bffe8a52a1 fix(apple): keep the recorded image name and frame symbol offset of IPS reports (#7059)
* fix(apple): keep the recorded image name and frame symbol offset of IPS reports

Apple's IPS format documents usedImages[].name and frames[].symbolLocation, and docs/apple-ips.md says documented body fields are reproduced, but the parser shapes did not list either member. They were syntax-checked and discarded without an unlisted_keys entry, so a symbolicated frame lost its +offset and every image its binary name. Both are now kept: images[].name and frames[].symbol_location as a recorded integer, with type mismatches recorded like other fields.

* fix(apple): count frame symbol offsets in the integer text check
2026-10-25 11:18:52 +08:00
Vinícius Andrade 4ed33efb0c fix(cli): format arrays of empty objects without keys as JSON (#7058)
When formatting arrays of objects that have no own enumerable keys as
Markdown, formatObjectTable derives empty headers and outputs a malformed
table with an invalid delimiter row. Require at least one item with keys in
isObjectArray and fall back to JSON serialization when headers are empty.

Fixes #7057
2026-10-25 10:46:15 +08:00
morluto 75e0d620bf fix(tests): accept retained module aliases in node views
Content-identical assets share a node whose observations are ordered by identity. Accept either retained display path when selecting that node, while retaining exact per-path reference and observation assertions.
2026-10-25 10:30:08 +08:00
morluto 4c680d099c fix(lint): preserve portable advertised patterns through autofix 2026-10-25 10:08:11 +08:00
Adolaniumandmorluto 046ec56d16 fix(browser): report an out-of-range script_index as a selection fault (#6979)
* fix(browser): report an out-of-range script_index as a selection fault

A valid export_web_scripts manifest without the selected script_index was reported as an invalid_format input issue, as if the manifest file were malformed. It is now an out_of_range issue on script_index with the valid bounds, or invalid_value when the manifest has no scripts.

* test(browser): keep the out-of-range index case in its own block

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 10:06:34 +08:00
morluto e9b21aadc5 docs(settings): explain runtime design and provider recovery 2026-10-25 09:56:11 +08:00
morluto f62a2baad8 refactor(javascript): own coverage facts and reuse block prefixes
Separate syntax, semantic uncertainty, examination limits and observed Electron applicability at the static producer, then preserve them through worker, graph and retained/inline summary views. Derive the established parse_status from the same facts and leave historical measurements unknown. Give semantic resource-limit classifications one schema owner.

Replace repeated block-prefix searches and copies with collector-scoped syntax indexes. Keep invocation environments separate, charge indexing/replay to the existing work budget and yield during prefix work. Remove the superseded synchronous collector entrypoint.

Validated producer, worker, CLI/MCP, historical and partial-coverage regressions, plus actual cancellation during indexing. Bounded authored 128/256-call controls preserved all prior facts while replacing repeated search/copy work with one index pass; this is not a throughput or agent-token benchmark.
2026-10-25 09:49:26 +08:00
morluto c8269ca497 fix(ci): own mock isolation and normalize Git patch directories
Convert native directory separators only at Git patch argument production. Exercise fresh, applied and invalid dependency patches in private copies, preserving protectNTFS, and select the regression for Windows CI.

Replace the spelling-based mock inventory with one binding-aware Oxlint rule. Canonical test projects own isolation for both Vitest and lint; external temporary CLI probes avoid concurrent test discovery.

Validated actual local Git and Oxlint execution, resolved-policy parity, CI selection and report tests. Native Windows verification remains with CI. Refs: #7050
2026-10-25 09:49:26 +08:00
morlutoandAdolanium cb26e8ca63 fix(contracts): share ADB limits and preserve Rosetta register facts
Use the domain-owned activity extra count in CLI parsing and advertisement. Preserve nested Rosetta registers at the selecting parser and qualified register projection, sharing the existing aggregate budget.

Port the Rosetta work from Adolanium commits 1889ac153 and c9e93bd88 in #6976, including morluto follow-up 07fa932ee. Add malformed-container and mixed-thread budget controls. CLI/MCP coverage uses a recorded ReportCrash excerpt in an authored report, without generating a native crash or contacting an Android device.

Refs: #6291, #6976, #6977

Co-authored-by: Adolanium <[email protected]>
2026-10-25 09:49:26 +08:00
morluto d23dc5d188 fix(browser): admit JSON shape expansion against shared retention
Require explicit shape output admission before allocating expanded paths. Share capture retention across console entries, WebSocket frames and selected request/response shapes, with exact shape replacement and removal receipts.

Preserve malformed-input versus capacity outcomes and peer facts. Validated deep, wide, Unicode, repeated-row, replacement, redirect, reset and mixed-retention regressions.
2026-10-25 09:49:26 +08:00
morluto e97f47b0b7 fix(lifecycle): retain IDA cleanup owners and drain JEB fixture work
Preserve primary copy failures, exact descriptor retry ownership and complete-session cleanup state. Make the authored JEB HTTP fixture join admitted callbacks after transport closure.

Validated with controlled real-file faults, cancellation/finalization and retry cases, plus local HTTP and existing CLI/MCP lifecycle regressions. No real IDA or JEB engine was exercised.
2026-10-25 09:49:26 +08:00
morluto f3ed1657f8 feat(mcp): support runtime analysis limit recovery (#7049)
* feat(mcp): support runtime analysis limit recovery

* refactor(config): remove unused limit re-exports

* fix(android): keep fixture release metadata dependency-free

* fix(settings): preserve provider budgets and recovery facts

* fix(tests): resolve lint plugins from their repository owner

* refactor(mcp): verify guidance delivery without frozen wording

* fix(android): align availability with runtime timeout

* test(recovery): assert limit facts instead of frozen wording

* fix(mcp): share runtime settings with the default JADX provider

* test(mcp): bound acknowledgements and release held work on failure

* fix(catalog): declare runtime settings tools as MCP-only
2026-10-25 09:45:26 +08:00
morluto ab720b4216 test(evaluation): preserve partial coverage in the Notes oracle 2026-10-25 09:07:57 +08:00
morluto bb17e90f27 chore: integrate concurrent lint and async fixes 2026-10-25 08:36:10 +08:00
morluto 627c085cee chore(lint): enforce reviewed promise and fallback contracts 2026-10-25 08:29:20 +08:00
morluto 5938581b37 fix(async): own completion across test and provider boundaries 2026-10-25 08:29:20 +08:00
morluto fdf5a33fe2 refactor(tests): keep fixture adapters outside runtime 2026-10-25 08:29:20 +08:00
morluto be698d4761 test: verify assertion contracts and the production evaluation gate 2026-10-25 08:27:16 +08:00
morluto 760d9f6019 fix(source-maps): retain primary failures through cleanup retries 2026-10-25 08:25:17 +08:00
morluto 42c16ba631 fix(tests): resolve lint plugins in boundary regression 2026-10-25 08:18:09 +08:00
morluto 76afdd35ae chore(lint): guard validation ownership and asynchronous assertions 2026-10-25 07:57:07 +08:00
morluto ed86f9c3bb fix(verifiers): propagate session assertion failures synchronously
Remove the redundant async declaration from the Ghidra session assertion helper so its existing callers receive assertion failures within their synchronous error boundary. Keep call order unchanged.

Validation: a public-helper Node subprocess accepts authored valid handshake facts and rejects incomplete analysis and a target-digest mismatch. The regression failed for the original async declaration with Missing expected exception, then passed with the fix. No installed Ghidra execution was performed.
2026-10-25 07:56:26 +08:00
morluto 475f09d5ca chore(lint): enforce buffer and cancellation regression guards 2026-10-25 07:31:42 +08:00
morluto f2fa951c00 fix(cancellation): retain caller reasons across digest and startup 2026-10-25 07:31:31 +08:00
morluto 9140deae63 docs: clarify ownership and cleanup review guidance
Clarify structural parsing, admitted facts and freshness, useful facade and compatibility responsibilities, immutable JSON proof, and partial Evidence retention. Document fixture cleanup ownership, semantic fault injection and the limits of static checks; extend the existing PR owner hint. Correct the session-record architecture label.

Validation: docs:check (including nine README consistency tests), rendered docs:build with link validation, touched Markdown formatting and git diff --check passed. Source and installed Vitest cleanup behavior were reviewed. No runtime behavior changed.
2026-10-25 06:51:31 +08:00
morluto efab65c2f9 docs: codify boundary regression checks
Turn recurring parser and projection failures into a selectable regression
guide covering format assumptions, raw identity, missing values, partial work,
independent limits, representation expansion, snapshots, and public delivery.
Link it from the agent instructions and PR template without adding checkboxes.

Clarify completed-observation ownership, digest representations, and exclusive
success/failure budget accounting. Preserve capacity fixtures and use byte
comparisons for large buffers to avoid diagnostic allocation blowups.
2026-10-25 06:49:10 +08:00
Wenyu Chiou 07eb044f77 fix(snapshot): decline ambiguous exact evidence replay (#7037) 2026-10-25 06:35:27 +08:00
Wenyu Chiou 238914974d fix(analysis-view): preserve current observation paths in summaries (#7039) 2026-10-25 06:35:12 +08:00
Dery Ferdika 1ff275fbf7 fix(browser): admit passive capture events against the shared retained budget (#7043)
Passive page capture retained console entries and websocket frames with no
aggregate ceiling. Every sibling retention surface already enforces
WEB_RUNTIME_LIMITS.retainedEventBytes (8 MiB): the observation session's
pre-arm buffer and document-request identities, the browser runtime sources,
requests, and metadata, the V8 inspector's retained metadata, and this same
capture's script metadata, which has its own count-and-bytes budget. A page
that logs a few hundred megabytes of console text, or sends text frames whose
JSON shapes carry hundreds of thousands of keys, grew the capture without
limit until the process ran out of memory; the MCP response budget runs after
the handler, so the growth happened before any typed refusal could exist.

Admit each console entry and websocket frame against the same shared budget
before retaining it. A refused entry is counted through the completeness
tracker's dropped-events vocabulary, which already carries console_events and
websocket_frames kinds and was so far used only for late scripts, so the
inspection reports a truncated inventory with the dropped count instead of
silently overflowing. Console entry bytes measure the retained representation:
the entry's type, url, argument-type strings, and the text capture's retained
bytes, which the handler already computed per entry. A websocket frame retains
its shape summary rather than the payload, and the shape grows with the
payload's own keys, so the frame is charged the serialized size of the shape
it retains. beginAuthorizedFrame resets the counter together with the rest of
the per-frame state.

Out of scope: response metadata and agent hints, whose rows are fixed-shape
facts already bounded per response, and any new setting. The shared 8 MiB
constant is reused as-is.

Validation: RED then green in src/browser/CdpCaptureEvents.eventBudget.test.ts,
where the console case previously retained every oversized entry and the
websocket case retained the over-budget frame; 26 tests across the neighboring
console, websocket, coverage, and completeness suites pass. On this Windows
host the wider browser project has 26 pre-existing failures in three
Electron/WebBundle worker-spawn files, reproduced identically with this change
stashed (26 failed, 644 passed on the pristine base; 26 failed, 647 passed
with it on the same file set). tsc --noEmit over the changed set exits 0,
oxlint reports 0 errors, oxfmt --check is clean, and verify:module-boundaries
passes. No real browser session was driven on this host; the browser CI lane
owns that coverage.
Each retained entry now carries a conservative fixed overhead, and each captured console text value a wrapper cost, so a stream of minimal entries cannot admit millions of objects into the budget when shapes are disabled; a mixed-kind test locks that console entries and websocket frames spend one shared allowance.
2026-10-25 06:34:26 +08:00
Syed Rishad 6bcd243ca4 fix(verifiers): expect the session-continuation regex overflow reason (#7045)
overflow into the remediation and ended the reason with the session
continuation sentence, but the real-Ghidra boundary check still required
the old reason text, so every lane that runs verifyGhidraBoundaries failed
on real Ghidra.
2026-10-25 06:34:11 +08:00
aba487eec0 feat(environment): learn creates from every call, with sorted arrays and referenced entities (#7034)
* feat(environment): explain values an application derives from other fields

Prompt-built environments withheld creates whose responses carry values the
application derives deterministically, which the write model kept as
constants or could not match across calls:

- A composed string is literal text plus parts read from the fields other
  rules write: a field's value, its slug, or a clock's calendar digits. A
  create composes a string only when another entity of the type spells it
  the same way and none spells it otherwise, and never from two sources
  serving could tell apart; an update composes an unsent change from members
  stored as sent and unchanged fields that also spell its value before the
  call. A composed field no longer pins the members it reads.
- A trimmed clock format drops trailing fraction zeros, as Go spells
  nanoseconds. It is learned from the type's values, a later create or a
  later update, and spreads to the plan's other clocks unless a value shows
  a fixed width.
- A large integer no entity of the type holds, among distinct ones of the
  same length and kind, is a generated unique value, served as a seeded
  integer.

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

* docs(environment): record the derived-values verification

Gates, tests, write plans over 69 recorded captures and served write tasks on the baseline and on this branch.

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

* feat(environment): learn a create plan from every call

A later create the plan does not take is explained on its own and merged when both calls show the same fields, every field both rule they rule alike, and a field neither rules holds one value: members either call sent are accepted, only those every call sent are required, and an echoed member a call left out takes the value that call stored, kept in the body's defaults.

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

* feat(environment): sorted arrays, referenced entities and nested view keys

- An echo is `sorted` when the application stored the sent strings in
  code-point order; serving stores them sorted, and a plain echo and a
  sorted one agree in update and create plans.
- A `reference` is an object field holding fields of the entity a sent
  member names, as the state holds it. The member is accepted with any
  value; replay and serving look the entity up, and serving refuses a call
  naming one the environment does not hold.
- A view names in `nested_fields` the keys it always shows of objects it
  shows with fewer keys than an entity stores; a stored object it never
  recorded renders with them. The view spelling learner moves to its own
  module.
- Every later create, not only one the plan refuses, is merged into its
  plan when it agrees, keeping the plan's rule order.

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

* fix(environment): keep clock spellings, linked ids and word boundaries honest

- A later update's clocks take the plan's spelling, sibling-trimmed
  clocks included, wherever the call's values fit it, so an update whose
  later call trims only one of two clocks is served.
- A held value that ends its fraction in a zero keeps a create's clock
  at a fixed width, whatever order the held spellings come in.
- A large integer that another field of the created entity also holds
  is linked, as a repeated token is: it is never unique, and the create
  is withheld as unexplained-change.
- Composed parts are delimited by whole characters through the shared
  joinsBefore/joinsAfter, so an astral letter joins a match to its word.

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

* test(environment): keep a same-width fixed clock out of trimming

A held .123456780 beside a held .12345678 keeps the create's clock at
nine digits in either order; the cases fail on 8ce1127b5.

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

* docs(environment): record the create-calls verification

Gates, tests, write plans over 69 recorded captures and served write tasks on the derived-values baseline and on this branch.

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

* docs(environment): note the create-calls census on current main

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

* fix(environment): check composed parts and clock fits before they shape a plan

- A composed string whose part has nothing to spell refuses the create
  or update through the apply Result instead of keeping a stale value.
- A composition learned on a later call is rebuilt from each earlier
  call's fields, so an earlier call that left the value otherwise
  contradicts it.
- An update adopts its fitted clock spellings only after the
  unknown-entity and contradiction checks, so a call on an entity an
  unmodelled write left unknown no longer trims a served plan's clock.
- Sibling trimming on a later create also checks that call's fields,
  so a sibling clock it shows with a trailing zero stays fixed-width.

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

* test(environment): split the derived-values view tests by rule

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

* fix(environment): keep fixed-width, zone and generated-integer evidence through a plan

- A plan keeps the first value that showed each clock at a fixed width,
  from held entities and every accepted create or update. No later fit
  or sibling trim widens that field, and a later call that shows it
  shorter is withheld.
- Two stamped clocks are one composed source only when they share a
  zone offset. A held entity that holds the target field as anything
  the parts do not spell, a non-string included, vetoes the
  composition. The token index is built once, and only when a string is
  left to compose.
- A generated integer that another field repeats as a number at any
  depth is linked. Compare treats two integers of one JSON kind and
  length under a unique rule as generated values.

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

* fix(environment): keep optional references across create calls and in answers

- A reference a later create added stays while further creates leave its
  member out, and agrees with a later call that shows it again.
- A create's answer shows optional reference and echo fields only when the
  call wrote them, whichever recorded call the answer came from.
- An update that re-sends the id of the entity a field already repeats keeps
  the plan's reference instead of pinning the member.
- An id naming entities of one type in several collections teaches no
  reference, and serving or replay refuses a call that names one.
- An echo is not promoted to sorted once a call stored its strings out of
  code-point order as sent.
- Nested view keys compare as sets and keep the first recording's order.

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

* test(setup): share registration prewarm host preparation

* fix(environment): retain write evidence and match trimmed response clocks

* fix(environment): withhold creates whose generated integer another string repeats

A unique integer whose digits show in another top-level string that no
template or composed rule rebuilds from it (an echoed serialText, or a
token like folder-<id>) withholds the create as unexplained-change,
since serving would seed the integer and keep the string. A templated
copy is still served.

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

* fix(environment): respect earlier clock widths and left-out members in later creates

- The create merge widens a clock to trimmed only against the value that
  showed its width: a held entity or accepted call at a fixed width keeps it
  untrimmed.
- A later create that left out a member the plan fills, with a default or a
  reference's absence, while its observation shows that field otherwise is
  unexplained-change, so the plan cannot serve the stale value.

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

* fix(environment): hold later creates and compare to the composed and integer forms

- A later create whose response spells a composed field otherwise than
  the plan rebuilds it is withheld as unexplained-change.
- A unique field learned from a generated integer accepts only an
  integer of the same length and kind from a later create, as serving
  seeds one.
- Compare counts a composed field as equal when each side's response
  spells it from its own fields, which compare under their own rules.

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

* fix(environment): preserve composed parts and unique generator families

* fix(environment): reject contradictory create references

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-25 05:31:46 +08:00
morluto ae54b96c6f refactor(tests): use fixture-owned temporary cleanup
Remove duplicate root lists and teardown deletion for directories already owned by createTestTempDirectory. Retain independent IDA workspaces, runtime closure, environment resets, permission recovery and explicit deletion assertions.

Validation: all nine affected suites passed (126 tests); installed Vitest cleanup ordering reviewed. Provider protocols use authored fixtures, not installed IDA or Ghidra.
2026-10-25 05:28:31 +08:00
morluto 599099f656 refactor(pe): share optional-header structural facts
Decode optional-header declarations once and pass detached directory words, locations and SizeOfHeaders to native consumers. Preserve executable machine admission, raw GPU/resource machine reporting, RVA mapping and certificate file-offset semantics.

Validation: fresh build/catalog, full static checks, 466 core/provider fixture tests, 122 workflow tests and 14 selected file-backed PE admission tests passed. Public CLI and actual stdio MCP used authored artifacts; installed external providers were not qualified. Added 17 structural cases and two GPU machine-policy controls. Eager directory decoding costs remain unmeasured.
2026-10-25 05:28:31 +08:00
morluto 9548953b47 refactor(clients): retain inspected command ownership
Carry the command identity already parsed during registration inspection into maintenance selection. Keep activation, validity and public status output separate.

Validation: fresh build/catalog and full static checks; registration status, update maintenance and setup cleanup ownership suites passed (90 tests).
2026-10-25 05:28:31 +08:00
morluto d24ad4f55d docs: make pull request draft status optional 2026-10-25 05:19:22 +08:00
Ahmedandmorluto 4b045e8f2f feat(cli): accept stdin for structured JSON request documents (#5486) (#7040)
* feat(cli): accept stdin for structured JSON request documents (#5486)

* fix(cli): preserve cancellation and admission for JSON stdin

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 05:10:29 +08:00
morluto 10b83a9b62 fix(browser): preserve completed source-map observations
Return typed fetch outcomes with completed map coverage when acquisition is
cancelled or a response resource fails. Compose those facts with the captured
inspection as partial bundle Evidence, preserving exclusions, exact abort causes,
and cleanup priority without claiming unperformed script analysis.

Keep complete response-body SHA-256 and byte counts when map interpretation
fails, while preserving compact failure rows and capacity for later valid maps.
Reuse the read owner digest for successful text artifacts and preflight their
JSON representation before parsing. Historical failed rows remain importable.

Exercise controlled HTTP/stream boundaries, CLI SIGINT, and SDK cancellation
with later MCP Evidence retrieval. Share the public cancellation fixture and
use one Result API across consumers. Real Chromium and Windows CLI cancellation
were not verified.
2026-10-25 05:09:26 +08:00
38719a6fa9 feat(environment): explain values an application derives from other fields (#7025)
* feat(environment): explain values an application derives from other fields

Prompt-built environments withheld creates whose responses carry values the
application derives deterministically, which the write model kept as
constants or could not match across calls:

- A composed string is literal text plus parts read from the fields other
  rules write: a field's value, its slug, or a clock's calendar digits. A
  create composes a string only when another entity of the type spells it
  the same way and none spells it otherwise, and never from two sources
  serving could tell apart; an update composes an unsent change from members
  stored as sent and unchanged fields that also spell its value before the
  call. A composed field no longer pins the members it reads.
- A trimmed clock format drops trailing fraction zeros, as Go spells
  nanoseconds. It is learned from the type's values, a later create or a
  later update, and spreads to the plan's other clocks unless a value shows
  a fixed width.
- A large integer no entity of the type holds, among distinct ones of the
  same length and kind, is a generated unique value, served as a seeded
  integer.

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

* docs(environment): record the derived-values verification

Gates, tests, write plans over 69 recorded captures and served write tasks on the baseline and on this branch.

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

* fix(environment): keep clock spellings, linked ids and word boundaries honest

- A later update's clocks take the plan's spelling, sibling-trimmed
  clocks included, wherever the call's values fit it, so an update whose
  later call trims only one of two clocks is served.
- A held value that ends its fraction in a zero keeps a create's clock
  at a fixed width, whatever order the held spellings come in.
- A large integer that another field of the created entity also holds
  is linked, as a repeated token is: it is never unique, and the create
  is withheld as unexplained-change.
- Composed parts are delimited by whole characters through the shared
  joinsBefore/joinsAfter, so an astral letter joins a match to its word.

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

* test(environment): keep a same-width fixed clock out of trimming

A held .123456780 beside a held .12345678 keeps the create's clock at
nine digits in either order; the cases fail on 8ce1127b5.

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

* fix(environment): check composed parts and clock fits before they shape a plan

- A composed string whose part has nothing to spell refuses the create
  or update through the apply Result instead of keeping a stale value.
- A composition learned on a later call is rebuilt from each earlier
  call's fields, so an earlier call that left the value otherwise
  contradicts it.
- An update adopts its fitted clock spellings only after the
  unknown-entity and contradiction checks, so a call on an entity an
  unmodelled write left unknown no longer trims a served plan's clock.
- Sibling trimming on a later create also checks that call's fields,
  so a sibling clock it shows with a trailing zero stays fixed-width.

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

* test(environment): split the derived-values view tests by rule

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

* fix(environment): keep fixed-width, zone and generated-integer evidence through a plan

- A plan keeps the first value that showed each clock at a fixed width,
  from held entities and every accepted create or update. No later fit
  or sibling trim widens that field, and a later call that shows it
  shorter is withheld.
- Two stamped clocks are one composed source only when they share a
  zone offset. A held entity that holds the target field as anything
  the parts do not spell, a non-string included, vetoes the
  composition. The token index is built once, and only when a string is
  left to compose.
- A generated integer that another field repeats as a number at any
  depth is linked. Compare treats two integers of one JSON kind and
  length under a unique rule as generated values.

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

* test(setup): share registration prewarm host preparation

* fix(environment): retain write evidence and match trimmed response clocks

* fix(environment): withhold creates whose generated integer another string repeats

A unique integer whose digits show in another top-level string that no
template or composed rule rebuilds from it (an echoed serialText, or a
token like folder-<id>) withholds the create as unexplained-change,
since serving would seed the integer and keep the string. A templated
copy is still served.

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

* fix(environment): hold later creates and compare to the composed and integer forms

- A later create whose response spells a composed field otherwise than
  the plan rebuilds it is withheld as unexplained-change.
- A unique field learned from a generated integer accepts only an
  integer of the same length and kind from a later create, as serving
  seeds one.
- Compare counts a composed field as equal when each side's response
  spells it from its own fields, which compare under their own rules.

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

* fix(environment): preserve composed parts and unique generator families

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-25 05:06:21 +08:00
morluto 58fb824105 refactor(evidence): centralize JSON payload admission
Give ordinary Evidence creation an explicit unknown-payload input contract
while retaining the strong JSON contract for producer helpers and cooperative
immutable creation. Keep schema admission, input snapshotting and identity
hashing with the Evidence owner.

Remove 32 generic JSON preparses that only fed ordinary Evidence construction.
Preserve semantic format validation, selected-source bindings, standalone
JSON boundaries, typed executions and scenario digest contracts. Invalid JSON
now reports its Evidence parameters/normalized_result/raw_result prefix.

Validation: fresh runtime/catalog build, 560 focused and README tests passed,
4 platform-specific cases skipped, full static/docs checks, test discovery,
duplication and diff checks. New regressions cover omitted semantic optionals,
raw-result defaults and nested undefined refusal without input mutation.
Installed providers, real browsers/Frida/JADX and Linux-only recovery remain
unverified; boundary providers are authored fixtures.
2026-10-25 05:01:36 +08:00
morluto 835904e4ee refactor(evidence): carry producer-owned source records
Carry the final artifact inspection's exact nested source Evidence through
AnalysisExecution. Derive links in the shared Evidence builder instead of
reparsing the result in CLI and MCP adapters. Retain validation for unknown
and historical Evidence inputs.

Move the shared source recorder into application ownership and migrate its
consumers. Record sources before parents; keep source recording failures
from publishing summaries. Decline snapshot entries whose source closure the
current entry format cannot represent, while retaining investigation Evidence.

Validation: actual ASAR regression failed before the change with missing
links and passes afterward; exact nested identity, source recorder refusal,
final-close cancellation and cleanup failure, historical authentication,
snapshot controls and public CLI/MCP workflows. Fresh runtime/catalog build,
full static/docs, discovery and duplication checks pass. Installed native
providers and platform-specific skipped cases remain unverified.
2026-10-25 04:37:11 +08:00
morluto 5ea2c280b8 refactor(doctor): retain parsed configuration diagnostics
Return native admission issues and an explicit Hopper timeout result from
the configuration owner. Doctor consumes those parsed facts instead of raw
timeout text plus a separate issue list. Reuse a successful aggregate parse;
validate the individual timeout independently when other settings reject it.

Preserve aggregate error precedence, JEB isolation, omission, constraints,
causes and provider scope. Keep partial diagnostics when unrelated NUL,
loader arguments, language selection or timeout settings are invalid.

Validation: configuration and Doctor workflow regressions, mixed-invalid
settings, full static/docs checks. No installed provider was exercised.
2026-10-25 04:37:11 +08:00
morluto aced9fa9b4 fix(wasm): retain acquired inputs across failed inspection
Keep completed module and candidate identities, glue observations, and the
admitted tool profile when WABT validation or later inspection fails. Recover
stable reads from their existing owner when descriptor finalization fails;
mark unparsed glue explicitly and preserve validation uncertainty. Forward
partial Evidence through output/cleanup errors and preserve pre-abort reasons.

Exercise producer failures, partial acquisition, decoding, finalization and
cleanup retry with simulated owned WABT commands. Public CLI and stdio MCP
coverage follows the retained Evidence reference through get_evidence_bundle.
Real WABT 1.0.42 and the Windows shell-fixture lane were not verified.
2026-10-25 04:24:03 +08:00
JANG BYUNGKUNandmorluto 149b43ee61 fix(il2cpp): link arm64 builds whose registrations were inlined (#6989)
* fix(il2cpp): accept arm64 module arrays loaded through adrp/add pairs

A Unity 2022.3 macOS Master (LTO) arm64 build keeps no static CodeRegistration, so the module-array fallback that only decoded x86-64 RIP-relative references reported code-registration-not-found.
The registration owner now also decodes aligned adrp + add pairs in executable ranges and keeps the same exact-module-set and single-reference rules.
A minimal Mach-O fixture failed on the previous reader (47 passed / 1 failed) and passes here with four fail-closed variants (48/48 binary tests).

* fix(il2cpp): name the matched instruction form in inferred module-array limits

The inferred-array limitation always described RIP-relative LEA/MOV, which is wrong for arm64 results.
The provider now names the adrp/add pair on arm64, and the tool contract and docs cover both forms and state that a located array alone does not finish linking a Master build.
The new arm64 provider case failed before the wording change (1 passed / 1 failed) and both cases pass now.

* fix(il2cpp): accept type tables loaded without a static MetadataRegistration

A Unity 2022.3.62f3 macOS Master build inlines MetadataRegistration as well as CodeRegistration, so linking stopped at metadata-registration-not-found after the module array was found. Registration now infers the only field linking reads, the Il2CppType table, from the one run of type-shaped pointers that covers every referenced type index and whose start is loaded by the existing RIP-relative or adrp/add forms; types_field is null and the Evidence is inferred. The new arm64 and x86-64 fixtures failed on the previous reader (52 passed / 2 failed). Four run-selection variants (interior reference, run extended before the loaded address, two loaded runs, run shorter than the referenced indices) keep metadata-registration-not-found; instruction decoding and record-shape checks are not covered by these fixtures.

* test(il2cpp): pin adrp page deltas, register pairing and type-record shape

Every adrp fixture targeted its own 4 KiB page, so dropping the page delta or its sign extension, the add register check, the type-kind bound or the type-definition bound left all binary tests passing. Fixtures now reach a later and an earlier page, pair an add with a different destination or source register, break the type run with an out-of-range kind or type-definition index, and bound type-table candidates when the metadata references few type indices. Each of those five source mutations now fails 1 to 3 of 85 binary and provider tests.

* refactor(il2cpp): share Il2CppType kind decoding and document type-table bounds

The type-table fallback decoded the Il2CppType kind and class/value-type kinds separately from the type namer; both now use the Types owner. Zero and unaligned words are rejected before mapping lookup (Master sample metadata-registration search 92 to 68 ms). Docs and the limitation state that types_count is an upper bound, that a run spanning mappings or a small referenced type range can fail, and credit Cpp2IL StrippedCodeRegSupport and the data-side MetadataRegistration searches of Il2CppDumper and Cpp2IL. Master sample output is unchanged (11,031 code addresses, 37,025 type rows).

* fix(il2cpp): refuse generic instances when the type table was inferred

An inferred type table has no static MetadataRegistration, so the
genericMethodTable it would anchor cannot be located. Report
generic-methods-unsupported instead of reading from a null field.

* fix(il2cpp): fail closed when an inferred type table is not unique

Adversarial review found that the type-table fallback could pick another
type-shaped pointer table when the real one was unloaded or excluded, could
replace a static MetadataRegistration whose counts matched, and accepted a
run cut short by a mapping end. Require exactly one long-enough run in the
image, ending inside its mapping and loaded by code; skip the fallback when a
record's two counts matched; accept only assigned Il2CppTypeEnum kinds; and
look records up through the indexed file mapping so many mappings cannot
multiply the scan. Docs and limitations now describe the run length as at
least every referenced type index rather than an upper bound.

* fix(il2cpp): keep the type-table fallback cheap and its refusals exact

Review follow-ups: check that an address can lie in a mapping before the
throwing indexed lookup, so words pointing into gaps or zero-fill no longer
build an exception each (64 MiB: 74 s to under 2 s); treat a run starting at
its mapping start like one reaching its end; stop at the second long-enough
run instead of a 64-candidate capacity error; ignore ADRP/ADD pairs on
register 31; and say whether a counted MetadataRegistration was unusable or
no code-loaded run was found. Docs no longer generalize from one build or
state tool history they do not source.

* fix(il2cpp): judge type-table slots by their loaded value

The run scan read stored words, so a dyld-bound slot's placeholder could
decide whether a run qualified. Confirm each slot through readSlot and give
a run bounded by a bound or unresolved slot an unknown extent, so the
placeholder no longer changes the choice.

* docs(il2cpp): keep the failure-reason table layout

Shorten the two changed meanings so the table keeps its column widths and the
diff shows only those rows.

* docs(il2cpp): keep the reason table at its formatted width

* fix(il2cpp): read type-table record fields through the loader too

The previous change confirmed table slots through readSlot but still read
each record's kind and definition index from stored bytes. Read them with
readUInt32 and readSlot, and treat a record field the loader binds or leaves
unresolved like an unresolved slot: the run it bounds has an unknown extent.

* perf(il2cpp): share image mappings across loader-aware reads

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 04:21:18 +08:00
morluto 22b88300d0 refactor(environment): reuse admitted capture facts in builds
Separate typed manifest/capture composition from unknown-input loading.
Builds retain the admitted replay facts locally for credential warnings and
record counts instead of parsing the same capture again. Keep full captures
out of the compact replay source and preserve manifest-first authentication,
parent identity checks, failure precedence and cancellation points.

Validation: environment source/build/report/scenario/input-snapshot/state
workflows and public build CLI; fresh runtime/catalog build, full static/docs,
test discovery and duplication checks. No external clients or targets ran.
2026-10-25 04:12:53 +08:00
morluto 162bd56c9e refactor(mcp): share pcap summary delivery handling
Use the existing named-contract retained-summary handler for pcap capture
inspection. Remove its duplicate validation, detail selection, preflight and
Evidence projection policy while preserving client inspection separately.

Validation: named pcap MCP schema/delivery, parent issue attribution and
workspace admission tests; fresh runtime/catalog build and full static/docs
checks. Native packet dissection and Linux-only retained-view tests were not
verified on this macOS host.
2026-10-25 04:12:51 +08:00
morluto a891bb80ae refactor(filesystem): preserve admitted file facts
Return the validated descriptor and its original metadata from regular-file
admission. Propagate that contract through owned reads, hashes, snapshots,
artifact inventory and provider file workflows instead of replacing initial
facts with another stat. Keep arbitrary output-handle operations separate.

Preserve final descriptor/path identity proofs, borrowed-source verification,
BigInt precision, cancellation provenance and failed-close ownership. Remove
the obsolete source and owned-ZIP baseline stages and update their consumers
and regressions. Tie read completion faults to actual reads instead of the
retired duplicate stat; instrument real handles without fake handle wrappers.

Validation: fresh runtime/catalog build; focused source, lifecycle, filesystem,
CLI/MCP and authored provider-fixture tests; full static checks; docs checks;
test discovery; duplication check. Platform-specific cases remain skipped on
macOS. No installed provider or device behavior was verified.
2026-10-25 04:09:11 +08:00
Dery Ferdikaandmorluto f196ebf806 fix(apple): refuse unpaired surrogates in IPS JSON strings (#6990)
The IPS JSON reader decodes \uXXXX escapes with String.fromCharCode and
returns raw slices verbatim, so an escaped unpaired surrogate reaches
Evidence as-is: malformed text reported under complete coverage, with no
refusal and no uncertainty. A crash report's procName or exception message
comes from the crashed process, so the escape is reachable input.

Refuse the string the way the parser already refuses other malformed
content. The check runs on the decoded value, so it covers escaped and
unescaped text alike, and a high surrogate followed by its low surrogate
escape stays valid text.

Validation: RED then green in src/native/apple/losslessJson.test.ts, where
the new case previously accepted the escaped surrogate; 311 tests across
the apple parser suites pass; tsc --noEmit over the changed set exits 0;
oxlint reports 0 errors; oxfmt --check is clean; verify:module-boundaries
passes.

Co-authored-by: morluto <[email protected]>
2026-10-25 03:59:10 +08:00
Wenyu Chiou c7ae27807f fix(electron): retain scoped IPC registration provenance (#7031)
* fix(electron): retain scoped IPC registration provenance

* fix(electron): defer mutation analysis until scoped receiver admission
2026-10-25 03:57:02 +08:00
morluto 2acdd54130 test(setup): simplify prewarm fixture ownership
Flatten the prewarm cases so suite grouping does not exceed the function-size gate. Let createTestTempDirectory own cleanup instead of deleting the same roots from a second afterEach owner. Preserve all registration and warning assertions.

Validation: all four focused filesystem cases passed using temporary client configurations and injected prewarm results; no package runner or provider was launched.
2026-10-25 03:42:49 +08:00
morluto f4f7c75c66 refactor(artifacts): consolidate selected source lifetimes
Use the shared source owner for Mach-O and PyInstaller admission, descriptor and selected-path identity, hashing and cleanup. Retain lipo joining and reader-owned PyInstaller admission, caller cancellation and private PYZ leases. Centralize admission failure projection and remove the superseded PyInstaller wrapper.

Preserve physical identity verification before late hash-completion cancellation. Close an owned source before returning failed initial metadata acquisition and retain the exact handle when close fails. A focused regression failed twice before that fix and now passes.

Validation: fresh runtime build, 259 focused fixture tests, nine documentation checks and all local static/discovery/duplication gates passed. An earlier check also ran four system-lipo suite cases (three read-only native inspections) before the final source fix; those were excluded from subsequent fixture qualification.
2026-10-25 03:42:49 +08:00
Adolaniumandmorluto a5bfd04401 fix(hopper): keep installed apt dependencies at their versions during Linux setup (#6974)
* fix(hopper): keep installed apt dependencies at their versions during Linux setup

apt-get install upgrades every listed package that is already installed, so Linux setup could upgrade the system python3 and X11 libraries while installing Hopper. Runtime dependencies are now installed with --no-upgrade in a separate transaction before the Hopper archive, which may still upgrade. The setup approval plan lists both commands.

* fix(hopper): narrow the apt upgrade claim and cover split transaction failures

* fix(evidence): preserve JSON ownership through metadata updates

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 03:37:20 +08:00
ShuaixinHuang ce4e48ccae fix(cli): exclude inherited values from Markdown rows (#6984)
* fix(cli): exclude inherited values from Markdown rows

* test(verifiers): follow shared command output and identity
2026-10-25 03:33:25 +08:00
Xinjian Wang 6a96f4fa4a fix(setup): materialize the pinned package-runner cache before the first client start (#7032)
* fix(setup): materialize the pinned package-runner cache before the first client start

Package runners key their package cache by the requested spec, so the
entry installed for the @latest invocation that ran setup cannot serve
the pinned rea-agents@<version> registration. The first client start
installed that entry inside the client's MCP startup deadline and timed
out (three consecutive 30 s timeouts observed in one client log; a
controlled cold install with every tarball already cached took 22 s).

Setup now materializes the pinned entry once after applying
registrations: best-effort, bounded by a timeout, launched from a
neutral working directory, and non-fatal on failure. The setup result
carries the prewarm summary, interactive output prints a warning when
the step fails, and direct launcher registrations keep skipping it.

Fixes #7027

* fix(setup): gate prewarm to applied registrations and supervise the runner tree

Address review findings on the prewarm step:

- Gate the prewarm on at least one applied client registration
  (configured_<client>), so an empty plan or a skill-only run never
  installs into the package-runner cache; the step now only follows
  registrations the user approved in this transaction.
- Replace execFileOutput with a supervised spawn: on timeout the whole
  runner tree stops through cleanupWindowsProcessTree on Windows and a
  process-group kill on POSIX, because killing only the direct child
  leaves npm holding the inherited output pipes past the deadline.
- Emit started and completed progress events around the prewarm so an
  interactive cold install no longer looks like a frozen setup.

* fix(setup): forward terminal interrupts to the detached prewarm runner

A detached POSIX package-runner group no longer receives the terminal's
Ctrl-C, so setup could exit while the runner kept installing in the
background. The supervised launch now registers SIGINT and SIGTERM
forwarding for the duration of the prewarm: the runner tree is stopped
through the same family cleanup as the timeout, and the default
termination is restored afterwards. An interrupted launch reports a
failure reason instead of a hang.
2026-10-25 03:32:58 +08:00
morluto 0354759370 fix(artifacts): retain admitted ZIP ownership through extraction
Transfer the inventoried ZIP descriptor after inventory reader cleanup, then
borrow it for materialization instead of reopening the pathname. Release the
exact owner before publishing output; preserve failed owners for cleanup retry
and completed inventory facts through cancellation or later tagged failures.
This uses the existing admitted-descriptor reader API without a third carrier
hash pass. Remove the superseded materialization wrapper.

Cover identical-member carrier replacement, directory/missing replacements,
late cancellation at both handoff and final close, mixed cleanup outcomes and
retry. Root-owner test seams preserve real output-handle cleanup. Compiled
CLI/MCP ZIP, archive and report regressions also run; no benchmark is claimed.
2026-10-25 03:30:39 +08:00
morluto 8dfc6832a4 fix(apple): preserve empty IPS text and source keys
Keep observed empty JSON strings separate from missing fields in IPS process,
image, frame, register and OS-version facts. Preserve empty unlisted keys,
retain malformed UUID source text, and keep unknown architecture interpretation
separate from raw spelling. Classification excludes empty supporting fields.

Validate producer cases plus compiled CLI and stdio MCP delivery against the
same output contract. This is saved-report inspection, without target execution.
2026-10-25 03:30:38 +08:00
morluto 7fe5e5ff5e fix(artifacts): retain raw archive keys across entry projections
Publish Godot res:// declarations and PyInstaller CArchive/PYZ keys through
shared occurrence source_path while preserving safe logical extraction paths.
Consolidate PyInstaller file, directory and namespace projection through one
owner, and account for the additional retained source spelling before allocation.

Cover producer inventory, Windows separator conversion, PYZ module/data keys,
CLI extraction bytes and retained MCP inventory views. Format fixtures are
synthetic; real Godot/PyInstaller producer lanes were not run.
2026-10-25 03:30:22 +08:00
gensokyo-dev 03ed0c63d9 fix(evidence): preserve JSON ownership across metadata rebinding (#7036) 2026-10-24 19:11:38 +00:00
morluto a834ed1cab refactor(artifacts): share persistent file source ownership
Compose one source owner for Godot PCK and Node SEA admission, descriptor identity, carrier hash binding and retained handle cleanup. Preserve format-specific parsing and member streams, drain their reader operations before source close, and leave borrowed descriptor release with its caller.

Validation: runtime build and 136 focused tests passed, including 17 real-descriptor integration cases for delayed admission, cached hash mismatch, close failure and retry, borrowed handles, admission cleanup cancellation and mixed peer outcomes. Tests use local authored carriers and controlled failure seams.
2026-10-25 03:04:21 +08:00
morluto 425776174c fix(artifacts): preserve opaque Node SEA asset keys
Separate raw SEA names from safe logical extraction paths. Accept empty asset
keys, retain absolute main paths and conflicting source spellings, and assign
deterministic paths through the shared artifact path registry. Keep duplicate
raw keys malformed and include sourcePath in owned entry equality.

Validate 102 focused regressions including compiled CLI and stdio MCP inventory
and extraction, the canonical SDK schema case, 9 documentation consistency
cases, cached build, typecheck/lint and formatting. A pinned Node-generated
preparation blob is verified in a synthetic ELF carrier; real injected native
executables and the optional Docker lane were not run.
2026-10-25 02:54:17 +08:00
morluto 31fc9762a3 refactor(go): parse module records through one byte contract
Parse the original unframed module bytes for every build-info result. Remove the duplicate text facade and string-source branch while retaining observed text, framed bytes, malformed lines, quoting and replacement semantics.

Validation: 163 Go parser, image, provider and public CLI/MCP tests passed using local binary fixtures; build, static and documentation checks passed.
2026-10-25 02:44:32 +08:00
morluto faf219e90b refactor(application): use current Evidence and CLI contracts
Require the immutable Evidence read APIs already implemented by production owners. Remove the detached-only compatibility fallbacks and forwarding helpers, and bind consumers directly to their owner. Preserve editable detached exports and existing snapshot semantics.

Use the shared canonical CLI error classifier for process commands instead of reinterpreting their projection. Validation: 172 focused application and process tests, build, static checks and documentation checks passed using local fixtures.
2026-10-25 02:44:21 +08:00
morluto 3f168af2e8 refactor(native): consolidate section and PE subject ownership
Specialize the buffer-backed directory driver for its actual ELF consumers and remove the unused PE branch. Share PE subject architecture interpretation at the existing machine owner, preserving unknowns for conflicting formats and unproven managed overrides.

Validation: runtime build and 108 focused service, native parser, GPU, and CLI/MCP tests passed using local fixtures. Live descriptor-backed PE readers remain supported.
2026-10-25 02:28:09 +08:00
morluto 92f32981da refactor(mcp): remove the duplicate result delivery class
Construct the canonical ToolResultDelivery through the native continuation policy factory. Move type and lifecycle spy consumers to the core owner while preserving explicit response budgets and the deferred runtime composition.

Validation: runtime build and 335 focused tests passed; 41 Linux-only firmware cases were skipped on macOS. Tests use authored local fixtures, not real external providers.
2026-10-25 02:28:09 +08:00
morluto 9b3a633298 fix(evidence): retain caller paths across snapshot replay
Preserve the verbatim direct CLI selection in caller_input_path, separate from canonical subject identity and provider/query parameters. Fresh publication uses the existing immutable metadata import path so provider auto-recording cannot discard request provenance. Replay returns the current selection without rewriting stored Evidence or partitioning the cache by alias. Historical records without the optional field still authenticate.

Use the domain semantic projection for ledger agreement, remove the duplicate projection and subject-only replacement names, and preserve request metadata at same-invocation summary delivery. Later retained-view questions do not inherit that context. Existing one-shot parameters.input_path remains for compatibility.

Validation: 149 focused tests, 28 public CLI/MCP cases, all canonical advertised schemas through the pinned SDK, and nine documentation tests passed across the affected checks. Build, typecheck/lint, and formatting passed. Two Windows-only replay cases were skipped on macOS. Native macOS and managed fixtures were exercised; no Hopper/Ghidra/IDA runtime was started.
2026-10-25 02:20:13 +08:00
Abel YagubyanandAbelo9996 955c7c80ec fix(android): report DEX static values of static final fields (#6981)
* fix(android): report DEX static values of static final fields

Fixes #6397

* fix(android): request DEX static values only for class inspection

* fix(android): range-check wide DEX static values

* fix(android): move the bridge to version 7 after main took 6

---------

Co-authored-by: Abelo9996 <[email protected]>
2026-10-25 02:17:13 +08:00
ShuaixinHuang fcebd79ed8 feat(elf): decode Zstandard section-name tables (#6987)
* feat(elf): decode Zstandard section-name tables

* test(verifiers): follow shared command output and identity
2026-10-25 02:16:33 +08:00
ShuaixinHuang 85bc8c8977 fix(browser): preserve media type parameter spelling (#6985)
* fix(browser): preserve media type parameter spelling

* test(verifiers): follow shared command output and identity
2026-10-25 02:16:18 +08:00
Wenyu Chiou 1ca7d38764 fix(evidence): link composed workflows to consumed observations (#7028) 2026-10-25 02:11:32 +08:00
ed79bf9361 feat(artifacts): inventory and extract Node.js single executable application blobs (#7000)
* feat(artifacts): inventory and extract Node.js single executable application blobs

Adds a node-sea artifact reader that locates the NODE_SEA_BLOB payload in
Mach-O sections, ELF notes and PE RCDATA resources, decodes the blob
header, main script, optional snapshot and code cache, and named assets,
and extracts their stored bytes through the existing safe output tree.
The executable is never run. Resource-tree errors in a PE without a
NODE_SEA_BLOB name stay with native classification; truncated, unknown
or over-limit SEA declarations fail closed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(artifacts): reuse PE resource facts and bound SEA asset tables

Move the bounded NODE_SEA_BLOB name probe into the shared PE resource
layout module so SEA admission uses the same header and section mapping
as the full parser; the layout reader now accepts a header prefix with
the file size. Bound SEA asset count and decoded name bytes before
records are created and report resource bounds as limit failures. Offer
the JavaScript next probe only for main scripts with a source suffix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(artifacts): make SEA metadata construction cancellable

---------

Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-25 02:10:41 +08:00
morluto 08b8e62914 refactor(mcp): consolidate retained-summary inspection workflows 2026-10-25 02:03:28 +08:00
morluto 9a13c4f420 refactor(config): centralize parsed registration environment keys 2026-10-25 02:03:28 +08:00
gensokyo-dev bc9efc9279 test(mcp): verify recovery before reading retained views (#7029) 2026-10-24 17:54:27 +00:00
Dhruvilkumar Ashokbhai Chodvadiyaanddhruvac29 ab30df08f7 fix(targets): keep original DOS header padding out of e_lfanew (#7013)
* fix(targets): keep original DOS padding out of e_lfanew

A complete DOS header with a nonempty relocation table that ended before
e_lfanew treated the padding dword at 0x3c as a Windows declaration. Without
a recognized Windows signature, fall back to the validated DOS structure so
original DOS images are not refused as malformed Windows targets. Preserve
unresolved-candidate handling when no relocation table delimits the padding.

* test(ghidra): cover DOS padding and unresolved header candidates

* fix(targets): read Windows signatures before dismissing DOS padding

* docs(targets): reconcile DOS padding admission contract

Document that relocation-table padding inside a complete DOS header is DOS data, while an unread Windows signature beyond the current probe is requested before that rule applies.

Aligns docs/installation.md with docs/ghidra-dos.md after the MZ admission fix.

---------

Co-authored-by: dhruvac29 <[email protected]>
2026-10-25 01:54:20 +08:00
Stoian Andrei-ȘerbanandStoian Andrei-Șerban 403fef0d40 docs(i18n): add complete Romanian README (#7026)
Co-authored-by: Stoian Andrei-Șerban <[email protected]>
2026-10-25 01:54:08 +08:00
niko3xandmorluto de37dfbcc9 fix(website): correct Turkish translation errors (#7015)
* fix(website): correct Turkish translation errors

Correct misleading technical senses in the Turkish catalogue: port,
caller, assembly, instruction, checks, script and oracle, plus
capitalized suffixes, hâlâ, and Turkish number formatting.

Leaves msgid, msgctxt, #: references, obsolete entries and placeholders
untouched across 308 msgstr values.

* fix(website): preserve Turkish words and quotation context

---------

Co-authored-by: morluto <[email protected]>
2026-10-25 01:51:24 +08:00
morluto 535926ca2b fix(analysis): retain one-shot target selection provenance
Retain the exact selected CLI path in one-shot provider Evidence while preserving canonical subject identity and provider inputs. The shared request-provenance boundary replaces the artifact-only special case across native, managed, and bundle-resource routes. Remove the redundant parameter fallback and make the private managed helper receive explicit Evidence parameters.

Validation: real macOS codesign fixtures through the compiled CLI retained app aliases for signature, plist, and absent asset inspections, with direct-target controls. Archive selection, managed direct analysis, and snapshot replay regressions passed; Windows-specific replay cases were skipped on macOS. Build, static, format, and docs checks passed. Snapshot-backed direct request provenance still follows its existing authenticated query contract.

Refs: #5479
2026-10-25 01:45:23 +08:00
morluto 84c30609e1 fix(browser): include IndexedDB versions in storage fingerprints
Hash the observed database version with object-store definitions at the CDP boundary. Missing, invalid, or imprecise versions preserve store evidence with incomplete coverage and an explanation. Mark the revised projection so historical store-only hashes cannot create false changes or prove unchanged versions. Preserve comparable metadata and independent storage changes.

Remove the redundant storage comparability wrapper and use each fingerprint's parsed scope instead of interpreting its identity string.

Validation: producer-to-comparison regressions and a compiled CLI to stdio MCP comparison passed, including version-only changes, invalid versions, legacy projections, and complete database additions/removals. Static and docs checks passed. CDP responses were simulated; no real browser runtime was exercised.
2026-10-25 01:45:23 +08:00
morluto 8b57dc630e fix(native): preserve metadata uncertainty in coverage
Explain unreadable ELF note section names without discarding complete note coverage. Keep export and PE import rows when only version or DLL-name metadata is unresolved, and stop claiming those retained rows were unreported.

Use native byte equality for the 4 MiB ELF extended-numbering source-preservation checks; generic deep equality enumerated every buffer byte and exhausted the test worker heap. The full fixture and byte coverage remain.

Validation: focused ELF note, native export, and PE import regressions plus complete CLI/MCP adapter files passed under the existing 512 MiB heap limit. Build, typecheck/lint, formatting, and documentation checks passed.
2026-10-25 01:45:23 +08:00
morluto 12f6e0974c fix(cli): retain Cutter discovery through shared finalization 2026-10-25 01:34:37 +08:00
morluto 900f21b982 fix(mcp): centralize validated partial Evidence delivery 2026-10-25 01:34:37 +08:00
Dhruvilkumar Ashokbhai Chodvadiyaanddhruvac29 97194c4b48 fix(ghidra): admit recordless MIPS-I legacy targets (#7014)
* fix(ghidra): admit recordless MIPS-I legacy targets

MIPS-I headers carry a zero-value ISA field (EF_MIPS_ARCH_1) and predate the ABI flags record. The recordless legacy admission only accepted EF_MIPS_ARCH_32 and EF_MIPS_ARCH_32R2, so every MIPS-I target was refused before provider selection even when its ELF32/ET_EXEC shape matched the existing inferred-o32 lane.

Admit MIPS-I through the same recordless legacy path, pin the endian-matched 32-bit MIPS language, and use a distinct support-lane identity so profile-bound snapshots invalidate. Declared-ABI MIPS-I headers remain refused because that lane is verified for MIPS32r2 only. The session reports that the generic 32-bit language also decodes later MIPS32 instructions and that MIPS-I subset conformance is not established from the header.

Fixes #6933.

* fix(ghidra): bind MIPS profiles to their target

Refuse a committed MIPS lane or ELF metadata that was resolved for a different target, including the same-endian MIPS-I and MIPS32 legacy lanes that share a language pin. Report the committed caller-selected MIPS-I language in the session limitation instead of claiming the provider pin.

---------

Co-authored-by: dhruvac29 <[email protected]>
2026-10-25 01:16:51 +08:00
Wenyu Chiou a1967fbbdb fix(lldb): retain completed trace facts through cleanup failures (#7023) 2026-10-25 01:16:26 +08:00
gensokyo-dev 422ca5a8f8 test(cli): follow prepared projection ownership during cancellation (#7022) 2026-10-24 16:46:34 +00:00
gensokyo-dev 397aa62f2e test(web): align manifest path errors with artifact ownership (#7021) 2026-10-24 16:18:25 +00:00
41cf869949 fix(environment): bound create learning and invalidate stale write proofs (#6929)
* feat(environment): report what an environment serves with an environment-coverage view

A new inspect_analysis_view kind over capture Evidence. It projects the
state an environment derives with the given template rules and reports
each tool as answered from state, replayed with its view reason, a served
write, or a withheld write with its reason, the records behind it and a
hint about what to record next. The hints cover every write reason, so a
new reason without one fails to compile.

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

* feat(environment): build an environment bundle in one command

rea environment-build <capture> [directory] inspects a HAR (or reads
capture Evidence), projects the environment manifest from the in-memory
Evidence, authenticates the pair, prepares the state and writes the bundle
environment-export writes. Every bundle file is byte-identical to the
three-step path for the same capture and options.

The report adds the capture and manifest identities, the coverage summary,
warnings (a loopback capture named after its file, credential-named body
members by pointer) and the next command. --dry-run writes nothing. The
default template rules are the four the published environments use.

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

* test(environment): cover environment-build and the coverage view

Domain tests check the hint table against the state view's reasons, the
summary, and the view against the environment-state view it summarizes.
Service tests build the fixture capture and compare every file with the
three-step path, a dry run, loopback naming and the refusals. A CLI test
compares a HAR built in one step with the three CLI steps byte for byte,
and an MCP test projects the view from every source kind.

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

* test(environment): build the real-agentenv lane's bundle with environment-build

The lane now runs the command users run: it builds the Grafana-shaped HAR
with environment-build and the packed checkout, checks the report, then
runs the bundle with agent-env as before.

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

* docs(environment): document environment-build and the coverage view

The agent environments guide opens with the one-step build: the report,
the hint for each withheld write reason, and what a bundle ships. The CLI
guide, the capture guide and the skill reference name the command and the
environment-coverage view.

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

* fix(environment): harden environment-build after review

- The credential scan walks with its own stack, so a deeply nested body
  cannot crash the build, and it also reports credential-named header
  names, query names, form body members and string arrays.
- The build checks SIGINT between stages: cancelled before the bundle
  stage it writes nothing, during the write it names the written bundle.
- A dry run opens the package tarball, so it refuses what a build refuses.
- Repeated template rules are refused, a HAR saved as .json gets a hint,
  file-derived names follow the bundle name rules, `localhost.` counts as
  a loopback origin, and the next command is shell-quoted.
- --capture-timeout-ms passes through to the HAR inspection, and a
  missing directory is refused before inspection.
- The no-observation hint covers updates and deletes of entities the state
  never held; the coverage view states that it does not apply the
  serve-time admission checks; the inspect_analysis_view description
  names the view.

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

* docs(environment): state the build's conditions and record its verification

The guides now state when the one-step bundle equals the three-step one,
which headers inspection excludes, what the credential warning scans, how
cancellation behaves, and that the coverage view does not apply the
serve-time admission checks. The spec folder records the census, the end
to end runs, the gates and the review.

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

* fix(environment): dispatch a pending interrupt before each build stage check

The manifest, state and coverage stages run synchronously, so a SIGINT
that arrived during them was only dispatched once the bundle write had
begun. Each check now yields first, so an interrupt before the bundle
stage cancels the build with nothing written.

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

* fix(environment): scan scenario credentials, own the dry-run tarball and serialize Evidence once

A browser scenario's retained header values and the query parameters of its recorded URLs are scanned for credential
names as a HAR's are, through a review projection of its records. A dry run admits and releases the package tarball
through the export's regular-file scope, so a close it cannot confirm keeps its owner, and checks for cancellation
after that last await. Evidence is written into one exactly sized buffer instead of chunks joined by a copy.

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

* docs(environment): record the review round's checks

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

* style(environment): format the build spec

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

* fix(environment): scan every form occurrence and MIME-less scenario bodies

The build's credential scan collapsed form bodies with Object.fromEntries, so a
repeated field kept only its last occurrence: `api_key=secret&api_key=` warned
about nothing although the Evidence ships the value, and repeated values were
undercounted. Form fields are now scanned as ordered name/value pairs, as
headers and query strings already are.

The review projection of a capture_browser_scenario dropped a request body
recorded without a media type, so the scan never saw it. Review keeps such a
body's text with an empty media type; the manifest projection still reads only
bodies it can classify.

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

* fix(environment): scan structured HAR form params and the request side of unattributed scenario transactions

A HAR request recorded with postData.params and no text is scanned by those parameters, each occurrence counting, and
the review projection keeps the request side of a scenario transaction whose response cannot be attributed, so the
credential warning covers what evidence.json ships in both cases.

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

* fix(environment): validate build paths and read dry-run tarballs

* fix(environment): report bodies too large for the credential scan

Text bodies over the scan's 4 MiB limit, including a response decoded only as a prefix, are reported as a
credential-scan-incomplete warning by record and side, never by content, so an absent credential-named-members
warning no longer reads as a complete check. The spec and agent-environments guide also state the path validation and
dry-run tarball read that e52ae1cd added.

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

* fix(verification): release cold source transform services

* fix(javascript): retain capture context along covered mutation paths

* fix(environment): admit serialized exports and scan HAR form fallbacks

* fix(environment): admit in-memory build inputs against heap headroom and scan SaveHar null-text forms

The inspected HAR's Evidence and the projected manifest are checked against live heap headroom before they are
serialized, with the same check the file route uses, so a capture too large to parse and seal is refused as
resource_constraint before anything is written. A request whose postData text is SaveHar's null is scanned by its
structured params.

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

* fix(environment): measure the credential scan limit in UTF-8 bytes

A body counts against the 4 MiB limit by its UTF-8 length, not its UTF-16 code units, so multibyte text over the
limit is reported as unscanned instead of parsed.

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

* fix(javascript): erase inline type imports before binding values

* fix(environment): parse credential form bodies by their media type

* fix(environment): review untyped retained form fields

* fix(environment): bound request comparisons and preserve write proofs

* fix(environment): keep write proofs and bundle review complete

* fix(environment): finalize entity overrides with simulated views

* test(environment): restore 0895c89b's untyped form tests and hint

Integrating main took main's credential scan and coverage view, which
dropped 0895c89b's tests and its body-unusable hint. Main's scan passes
the tests, now reading the scenario by event. The hint names the
response too, since extraction and serving also withhold a write as
body-unusable when its recorded response cannot be read.

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

* test(environment): scan a scenario content event recorded twice

Main's event scan reads both content events of a transaction whose
content the capture recorded twice; the test fails on 0895c89b, whose
review projection dropped that transaction's response. The spec names
event pointers among the first 20.

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

* docs(environment): record the review round's checks

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

* fix(environment): retain contradictions and trim retired state

* fix(environment): distinguish mixed array order from changed values

* fix(environment): keep dependent create state pinned

* fix(environment): keep checking retired view observations

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-24 23:57:30 +08:00
gensokyo-dev c94bd27e85 fix(runner): scope internal Node notices to the supervisor (#7020) 2026-10-24 15:57:23 +00:00
morluto d9189aebc4 fix(artifacts): retain completed acquisition facts through finalization
Forward the file owner's completed stable-read hash and byte count through descriptor-close failure or cancellation in PE resources, certificates and imports, ELF notes and kernel modules, native exports, Apple IPS, and APK signature inspection. Preserve the primary failure, exact cancellation reason, and failed-close cleanup lease.

Validation: 32 acquisition-finalization regressions across eight providers and 30 existing APK provider, lifecycle, and identity tests passed. Existing native filesystem regressions and typecheck/lint also passed. No external analysis engines or Android device were used.
2026-10-24 23:51:20 +08:00
morluto 72e75e9a48 fix(metadata): preserve declared phandles and relocation fields
Keep malformed canonical devicetree phandles unresolved instead of falling back to legacy identities. Report the PE dynamic relocation offset and section at their exact source widths with Size-gated absence and zero preservation. Clarify classic kernel-module version coverage and byte-preserving string encoding.

Validation: 158 focused parser, service, filesystem, and finalization regressions; 14 CLI/MCP tests; build, typecheck/lint, formatting, and docs checks passed. Fixtures establish parser behavior; no external analysis engines were exercised.
2026-10-24 23:51:20 +08:00
morluto e9abf39848 refactor(javascript): remove superseded full-IR analysis pipeline 2026-10-24 23:49:25 +08:00
gensokyo-dev cc7cc8155a test(javascript): bound route scan work independently of timing (#7016) 2026-10-24 15:37:10 +00:00
morluto 8c6cddcffb refactor(javascript): retire synchronous artifact graph runner 2026-10-24 23:33:09 +08:00
morluto 798450638e test(verifiers): retire superseded source-loading regression
The shared source-loading suite covers the former generic command and pre-exit lineage regression, plus package ownership, error-class identity, native module behavior, and preservation of a caller-owned compiler context. Remove the overlapping older fixture after validating those controls.
2026-10-24 23:14:20 +08:00
morluto 731109b7d7 refactor(analysis-view): consolidate result and budget ownership
Reuse the shared immutable-result reader for Interface Builder while checking its current parent binding on every read. Accept truthful optional schema outputs and validate normalized JSON before freezing or caching it, preserving explicit undefined facts without caching them.

Move accumulator construction and inline admission policy into the domain. One prepared projection retains the same budget across admission and projection while preserving standalone defaults, custom budgets, and authentication order.
2026-10-24 23:11:02 +08:00
morluto 399e2226ca refactor(resources): remove redundant helper exports and lease aliases
Keep ASAR header details and the Godot header parser module-local. Use the canonical lease types in Hopper consumers, including release-cache publication, while preserving acquisition and cleanup behavior.
2026-10-24 23:11:02 +08:00
gensokyo-dev 1184819cb9 fix(verifiers): load shared source without compiler processes (#7012) 2026-10-24 15:02:22 +00:00
morluto bff03bfc97 test(setup): retain Hermes policies across registration refresh 2026-10-24 22:54:05 +08:00
morluto 4f212c6a30 fix(analysis): preserve artifact and selection error ownership 2026-10-24 22:54:05 +08:00
69f99c5198 feat(environment): time browser scenario records so scenario environments learn a write clock (#6967)
* feat(browser): record when scenario requests were observed and how long they took

Request events carry observed_at, the wall-clock time the provider observed
the request start, and finished or failed events carry the browser's
elapsed_ms when it reports one. Both are optional, so earlier captures still
parse, and scenario comparisons leave them out as a committed built-in rule.
The real-Chrome network lane checks every request and finished response is
timed.

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

* feat(environment): time browser scenario records so their state learns a write clock

Scenario records take startedDateTime from when each request was observed and
time from the browser's duration, as HAR entries are timed. A timed scenario
of the book session learns the same clock and serves the same writes as its
HAR; an untimed one keeps record order and learns no clock, as before.

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

* docs(environment): document scenario request times and timed scenario records

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

* fix(environment): start scenario records at the browser's own request start

The provider observes a request up to tens of milliseconds after the browser
starts it while it reads other responses, so a record starting then and
ending after the browser's duration mixed two clocks. Finished and failed
events now carry the browser's started_at, records prefer it and fall back to
observed_at, and comparisons drop it too. Tests cover the fallback for a
request that never ended or ended twice, a partly untimed capture keeping
record order, comparing an untimed capture with a timed one, and the
real-Chrome lane checks every finished request carries the browser's start
and duration.

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

* docs(environment): document the browser start time, its fallback lag and older readers

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

* docs(environment): record the scenario timing verification

Gates, the real-Chrome lane, prompt-recorded and re-recorded published
scenario captures built with and without their times, the stale published
environments left unrepublished, and the review.

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

* test(browser): compare timed completion events in the request-time comparison test

The comparison test's captures had no finished or failed events, so dropping
started_at and elapsed_ms from them went untested. The captures now end one
request and fail the other, the test asserts both carry times, compares them
shifted by seven seconds and against an untimed capture, and a control shows
a changed failure on a timed completion is still reported.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-24 22:48:50 +08:00
b4ba96a9a5 feat(firmware): inspect flattened devicetree blobs (#7009)
* feat(firmware): inspect flattened devicetree blobs

Adds inspect-devicetree and inspect_devicetree for FDT v16/v17 blobs
without target execution or external dependencies. Results include
validated headers, reservations, a node/property tree with bounded raw
previews, and derived standard values and selected phandle references.
Parsing enforces file, tree, and decoded representation budgets and
rejects malformed input without partial success. Summary delivery
supports retained Evidence pagination, with tests covering parsing,
service validation, CLI delivery, and MCP access after the input is
removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(firmware): reject conflicting devicetree phandles and bound page summaries

A node whose phandle and linux,phandle disagree no longer resolves under
either value; the conflict is reported as a limitation. Finalization
failures after a completed read retain the file-snapshot-hash partial
observation, matching the Go provider. Devicetree view pages carry
fixed-size context and counts instead of repeating summary strings, so a
large bootargs no longer pushes every page over the MCP response budget.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

---------

Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-24 22:45:22 +08:00
Ahmed 7e57b8c212 fix(process): preserve caller-selected working directory in scenario validation errors (#7003)
* fix(process): preserve caller-selected working directory in scenario validation errors

* test(process): cover caller-selected working-directory alias preservation
2026-10-24 22:41:34 +08:00
dba8a31978 feat(elf): inspect Linux kernel module metadata (#7010)
* feat(elf): inspect Linux kernel module metadata

Adds inspect-kernel-module and inspect_kernel_module for ELF32/ELF64
ET_REL Linux kernel modules, read statically without executing them.
Reports ordered .modinfo entries with duplicates preserved, __versions
symbol CRCs, the presence of .gnu.linkonce.this_module and __ksymtab,
.init.text and .exit.text sizes, and an appended module signature
trailer with its PKCS#7 byte range and SHA-256. gzip, xz and zstd
containers are reported unsupported with external decompression as the
next probe. Malformed headers and exhausted entry budgets return no
partial success.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(elf): bound kernel module ksymtab listing and tighten contracts

Charge listed __ksymtab* sections against max_entries, omit section names
longer than 256 bytes with ksymtab_name_exceeds_limit, and aggregate the
non-file-backed limitation so repeated huge names cannot inflate Evidence.
Use fixedTupleSchema for the signature pad bytes with projection parity
tests, and assert the late-cancellation reason and retained observation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix: resolve merge integration errors

---------

Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-24 22:35:20 +08:00
morluto de088fcec6 fix(verifiers): join model fixture request finalization
Keep each admitted HTTP handler owned through Evidence writes and error rendering, including after its client disconnects. Close joins graceful transport shutdown and handler work before publishing completion, and propagates exact late failures while preserving ordinary request diagnostics.

Share lifecycle ownership across OpenAI, Anthropic, and Gemini fixtures while retaining protocol-specific parsing and responses. Exercise real disconnects, graceful concurrent and repeated close, ordinary 400s, and opaque late failures.
2026-10-24 22:28:39 +08:00
morluto 48ff37f6ea fix(verifiers): consolidate shared helper ownership
Use JSON.parse directly in package verification and retain one parsed Cursor recovery snapshot. Route native command source loading through the shared transform-service owner so completed command cleanup leaves no transform child.

Preserve JSONC validation and caller inputs. Cover the actual public source-loading lifecycle with an authored command and verified process lineage.
2026-10-24 22:28:39 +08:00
791524c2fa fix(importer): reach package-main selection for trailing-slash directory references (#4219)
* fix(importer): reach package-main selection for trailing-slash directory references

posix.normalize preserves a trailing slash in the specifier, so normalized was sub/ while dirname(match) was sub; the resolvedThroughIndex comparison failed and selectPackageMain never ran for require("./sub/"). Compare against the slash-stripped directory so trailing-slash directory references take the same package-main path as the plain spelling. Boundary rows pair each trailing-slash request with the existing manifest fixtures and are validated against Node's own resolver in the same table.

Fixes #4189

* fix(importer): derive trailing-slash directory identity with path semantics

The lookbehind strip kept a trailing slash when the final component ended in a line terminator (require("./pkg\n/")), so dirname(match) still never matched and the trailing-slash path skipped package-main selection for those directories. Join the normalized path with "." instead: path semantics collapse any trailing slash run regardless of the preceding character, and behave identically to the strip on every other spelling. Boundary fixture gains a newline directory with a package.json, an index and a declared main so the index path (not the no-match branch) exercises the comparison.

Addresses Codex P1 on #4219

* fix(importer): normalize package-main targets with path semantics

---------

Co-authored-by: Hemang Varshney <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-24 23:14:19 +09:00
c6a2576aba fix(contracts): reject NUL bytes in objdump and rizin local path inputs (#3600)
Co-authored-by: qorvr <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
2026-10-24 23:13:34 +09:00
3d84215bb2 feat(pe): inspect TLS callbacks and load configuration (#7008)
* feat(pe): inspect TLS callbacks and load configuration

Adds inspect-pe-load-config and inspect_pe_load_config for PE32 and
PE32+ images, with artifact hashes and file ranges in the returned
evidence. Reports callback addresses and mappings, plus size-covered
security-cookie, SafeSEH, CFG, EH-continuation and XFG fields;
flag-derived mitigation claims are explicitly labeled inferred. Missing
directories remain null, and malformed storage or exhausted read budgets
return errors without partial success. Adds documentation and tests for
service validation, CLI/MCP access, malformed inputs and stable-file
handling.

x86 images that declare the legacy 64-byte load-config directory are
read to the structure's own Size when those bytes are file-backed, as
the Windows loader does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

* fix(pe): retain snapshot facts and bound load-config section lookup

Keep the completed stable-read path, SHA-256 and byte count as a
file-snapshot-hash partial observation when descriptor close fails or is
cancelled. Report the checked Size span as structure_location and add it
to Evidence locations beside the declared directory range. Resolve
callback sections through a sorted segment index built once per image,
keeping the ambiguity failure. Pin every load-config field to offsets
transcribed from the PE/COFF spec and llvm COFF.h at its exact Size
boundary for PE32 and PE32+.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016y53PdpyMAp9RSW65fatHe

---------

Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-24 22:06:41 +08:00
02e049744f feat(environment): build agent environments from a prompt (#6946)
* feat(environment): report what an environment serves with an environment-coverage view

A new inspect_analysis_view kind over capture Evidence. It projects the
state an environment derives with the given template rules and reports
each tool as answered from state, replayed with its view reason, a served
write, or a withheld write with its reason, the records behind it and a
hint about what to record next. The hints cover every write reason, so a
new reason without one fails to compile.

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

* feat(environment): build an environment bundle in one command

rea environment-build <capture> [directory] inspects a HAR (or reads
capture Evidence), projects the environment manifest from the in-memory
Evidence, authenticates the pair, prepares the state and writes the bundle
environment-export writes. Every bundle file is byte-identical to the
three-step path for the same capture and options.

The report adds the capture and manifest identities, the coverage summary,
warnings (a loopback capture named after its file, credential-named body
members by pointer) and the next command. --dry-run writes nothing. The
default template rules are the four the published environments use.

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

* test(environment): cover environment-build and the coverage view

Domain tests check the hint table against the state view's reasons, the
summary, and the view against the environment-state view it summarizes.
Service tests build the fixture capture and compare every file with the
three-step path, a dry run, loopback naming and the refusals. A CLI test
compares a HAR built in one step with the three CLI steps byte for byte,
and an MCP test projects the view from every source kind.

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

* test(environment): build the real-agentenv lane's bundle with environment-build

The lane now runs the command users run: it builds the Grafana-shaped HAR
with environment-build and the packed checkout, checks the report, then
runs the bundle with agent-env as before.

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

* docs(environment): document environment-build and the coverage view

The agent environments guide opens with the one-step build: the report,
the hint for each withheld write reason, and what a bundle ships. The CLI
guide, the capture guide and the skill reference name the command and the
environment-coverage view.

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

* fix(environment): harden environment-build after review

- The credential scan walks with its own stack, so a deeply nested body
  cannot crash the build, and it also reports credential-named header
  names, query names, form body members and string arrays.
- The build checks SIGINT between stages: cancelled before the bundle
  stage it writes nothing, during the write it names the written bundle.
- A dry run opens the package tarball, so it refuses what a build refuses.
- Repeated template rules are refused, a HAR saved as .json gets a hint,
  file-derived names follow the bundle name rules, `localhost.` counts as
  a loopback origin, and the next command is shell-quoted.
- --capture-timeout-ms passes through to the HAR inspection, and a
  missing directory is refused before inspection.
- The no-observation hint covers updates and deletes of entities the state
  never held; the coverage view states that it does not apply the
  serve-time admission checks; the inspect_analysis_view description
  names the view.

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

* docs(environment): state the build's conditions and record its verification

The guides now state when the one-step bundle equals the three-step one,
which headers inspection excludes, what the credential warning scans, how
cancellation behaves, and that the coverage view does not apply the
serve-time admission checks. The spec folder records the census, the end
to end runs, the gates and the review.

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

* fix(environment): dispatch a pending interrupt before each build stage check

The manifest, state and coverage stages run synchronously, so a SIGINT
that arrived during them was only dispatched once the bundle write had
begun. Each check now yields first, so an interrupt before the bundle
stage cancels the build with nothing written.

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

* feat(browser): send declared requests from a scenario page with a request step

A capture_browser_scenario step `request` sends one declared HTTP request
through the page's own fetch, with its cookies and session, the way the
application's frontend would, and reads the whole response so the capture
records the transaction like any other. The path must resolve to the start
URL's origin, and the page must be on that origin when the step runs.
Query values and header values may be literals or declared secrets, and a
header may read its value from a cookie or a local storage entry, which is
then redacted from later records like a declared secret. `expected_status`
fails the step on any other status. Scenarios without a request step are
unchanged.

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

* test(browser): verify request steps in a real browser through to a built environment

verify:browser:request records a write session against a fixture item API
that refuses writes without its CSRF cookie header and stored bearer
token. It checks that every request lands in its own step, that the page
values authenticated each write and appear in no record, header or body,
that an unexpected status fails the step and cancels the rest, and that
environment-build serves the recorded create, update and delete.

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

* feat(environment): export environment bundles over MCP

export_environment_bundle writes the bundle environment-build writes, from
capture Evidence retained in the session or one capture Evidence file,
into an absolute output directory that must not exist and whose parent
must exist, as extract_firmware and export_web_scripts require; it never
creates the parent or reuses a directory. It returns the same report,
whose schema now lives in the domain so the CLI and the tool share it, and
dry_run reports without writing. A loopback capture without a file needs a
name. The build's refusal messages no longer name CLI flags.

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

* fix(environment): scan scenario credentials, own the dry-run tarball and serialize Evidence once

A browser scenario's retained header values and the query parameters of its recorded URLs are scanned for credential
names as a HAR's are, through a review projection of its records. A dry run admits and releases the package tarball
through the export's regular-file scope, so a close it cannot confirm keeps its owner, and checks for cancellation
after that last await. Evidence is written into one exactly sized buffer instead of chunks joined by a copy.

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

* docs(environment): record the review round's checks

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

* docs(skill): build agent environments from a prompt with request steps and the bundle tool

Add the environments reference to the skill and install it with setup: record
against a disposable instance, sign in through the UI, write with request
steps and read each write back, check coverage, re-record by withheld reason
and export the bundle. Route it from SKILL.md, name agent environments in the
skill description, and document the request step, the export tool and
recording writes.

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

* refactor(browser): split the request lane and header resolution into smaller functions

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

* style(environment): format the build spec

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

* fix(environment): say when an untimed capture learns no write clock

Coverage of a capture whose records are not all timed, which includes every
browser scenario capture, now states that the state learns no clock, so an
update stamped with a server-set modification time stays unexplained-change
however often it is recorded.

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

* docs(skill): give agents a whole scenario shape with step timeouts

Agents guessed the scenario schema field by field and two hung on steps
without timeout_ms. The reference now starts from a complete scenario with a
timeout on every step, and says a scenario capture cannot serve updates the
application stamps with a modification time.

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

* fix(browser): keep request steps on their origin and their page values redacted

- Follow no redirects: a redirect fails the step and nothing reaches the
  location it names (checked in real Chrome), and check the page's origin
  again in the page as the request is sent.
- Send nothing once the step's time has run out or it was cancelled while
  header values were read; the in-page deadline is what remains of the step's.
- Keep every page value redacted under a repeated or colliding label, keep a
  value's first marker when it is read again, and leave values shorter than
  8 characters unredacted rather than rewriting ids, ports and hosts.
- Refuse a header name given twice, User-Agent (Chrome drops it) and line
  breaks in literal values and prefixes; fail before sending when a secret or
  page value holds them.
- Append query entries after the path's literal query without re-encoding it.

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

* docs(environment): document request step limits and word bundle refusals for both surfaces

Document redirects, timeouts, page value redaction, HttpOnly cookie sources
and header refusals for request steps. Refusals shared by environment-build
and export_environment_bundle no longer name CLI flags, and the retained
loopback refusal no longer prints an empty file name. The skill reference
asks for read-backs after each write, shows the coverage view's source, names
package_tarball for unreleased builds and the browser path a scenario ships.

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

* fix(environment): scan every form occurrence and MIME-less scenario bodies

The build's credential scan collapsed form bodies with Object.fromEntries, so a
repeated field kept only its last occurrence: `api_key=secret&api_key=` warned
about nothing although the Evidence ships the value, and repeated values were
undercounted. Form fields are now scanned as ordered name/value pairs, as
headers and query strings already are.

The review projection of a capture_browser_scenario dropped a request body
recorded without a media type, so the scan never saw it. Review keeps such a
body's text with an empty media type; the manifest projection still reads only
bodies it can classify.

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

* fix(browser): close request step edge cases from the second review

- Append query entries correctly after a path ending in "?", "&" or "#".
- Refuse 3xx expected statuses, which an unfollowed redirect can never
  report, and say a redirected write may already have been applied.
- Redact a page value also as fetch sends it, without surrounding spaces
  and tabs.

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

* docs(environment): document page value body costs, redirects and MCP dry runs

Document that reading a page value shrinks the bodies a capture keeps under
the default allocation limit, that redirected writes may have landed, that
the MCP dry_run still checks its output directory and which refusals name
the tool, and keep the .har hint in the HAR-as-JSON refusal for both
surfaces.

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

* docs(environment): record the prompt-to-environment verification

Gates, the real-browser lanes, five environments built from prompts by an
agent on disposable VMs and evaluated with two models, and the two reviews.

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

* test(skill): count rollback failures from the managed skill files

The environments reference made the skill eight files, so the recovery tests now derive the counts they expect from
MANAGED_SKILL_FILES instead of fixing them at six and seven.

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

* fix(adb): restore the portable Java float pattern a merge rewrote

A lint autofix during a merge unescaped the hyphens in javaFloatValuePattern, which is invalid in a v-mode character
class, so the advertised start_adb_activity schema failed to compile in every JS regex mode. The file now matches main.

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

* docs(browser): show the request step inside a whole scenario

Every JSON example in the browser scenario guide is a capture_browser_scenario input the catalog test parses, so the
request step example now sits in a launch scenario instead of standing alone.

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

* fix(environment): scan structured HAR form params and the request side of unattributed scenario transactions

A HAR request recorded with postData.params and no text is scanned by those parameters, each occurrence counting, and
the review projection keeps the request side of a scenario transaction whose response cannot be attributed, so the
credential warning covers what evidence.json ships in both cases.

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

* fix(environment): validate build paths and read dry-run tarballs

* fix(environment): report bodies too large for the credential scan

Text bodies over the scan's 4 MiB limit, including a response decoded only as a prefix, are reported as a
credential-scan-incomplete warning by record and side, never by content, so an absent credential-named-members
warning no longer reads as a complete check. The spec and agent-environments guide also state the path validation and
dry-run tarball read that e52ae1cd added.

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

* fix(verification): release cold source transform services

* fix(javascript): retain capture context along covered mutation paths

* fix(environment): guard request dispatch and report export limits

* fix(environment): admit serialized exports and scan HAR form fallbacks

* fix(environment): admit in-memory build inputs against heap headroom and scan SaveHar null-text forms

The inspected HAR's Evidence and the projected manifest are checked against live heap headroom before they are
serialized, with the same check the file route uses, so a capture too large to parse and seal is refused as
resource_constraint before anything is written. A request whose postData text is SaveHar's null is scanned by its
structured params.

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

* fix(environment): measure the credential scan limit in UTF-8 bytes

A body counts against the 4 MiB limit by its UTF-8 length, not its UTF-16 code units, so multibyte text over the
limit is reported as unscanned instead of parsed.

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

* fix(browser): send request steps only before their deadline and keep page reads on the start origin

The page receives the step's absolute deadline and a one-second start window, so a busy page that runs the request
late, or after the step is cancelled, sends nothing; local storage is read only on the start origin; the response is
drained without keeping its body; an unparseable path is a field issue; retained Evidence is refused for heap headroom
before it is serialized; and the skill says how to reach server-assigned ids.

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

* style: format integrated export admission

* fix(javascript): erase inline type imports before binding values

* fix(browser): run the request lane under the shared verifier command owner

The browser request lane runs each CLI call through createPackageVerifierCommands on a private runtime root, so a
timeout, output overflow or failure stops the CLI and the browser it launched before the root is removed, and a failed
stop is reported with the root retained. The verification record states the lane's current counts.

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

* fix(environment): scan scenario content by event and read bodies by their media type

A browser scenario's credential scan reads every retained network-content event, including a response attached
mid-flight and a transaction no record represents, with event pointers, and each transaction URL's query once; the
review record projection it replaces is removed. A body is parsed as a form or as JSON by its recorded media type,
falling back to its shape, so valid forms such as `password=x&` are scanned and sentences are not read as forms.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-24 21:51:41 +08:00
d361c53c50 feat(environment): serve writes by what their recordings show (#6897)
* fix(environment): keep read-only POSTs out of the write list

A POST that repeats the latest read of its path, tunnels only read
operations, names a lookup in its path's last meaningful word, or renews
a session no longer counts as a state write: it is replayed, does not cut
initial windows, and does not end observation searches. A tool stays a
write when a related read line changes across one of its calls.

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

* fix(environment): take an entity an unplanned write made when a read first shows it

A successful item read of an entity the gate's copy never held, after a
write no built plan models, puts the entity into the copy instead of
failing the read, so later writes to it are checked as usual. An entity a
modelled write touched is never taken back.

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

* fix(environment): plan a create into the type its returned id is read under

A POST whose path names no state type becomes a create into the type
whose item read first names an id its response returned, when the id is
new to the state; serving creates in that read's collection. A successful
POST that returned an id a type's reads only show after it also ends that
type's initial window, so entities a capture created stay out of the
initial state.

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

* fix(environment): end a type's initial reads at the write its reads disagree across

When a type's reads disagree across a write under another root, the
type's initial window now ends at the earliest write within that change
and the state is extracted again, instead of leaving every view of the
type replayed. The cut composes with the creation cut; the earlier wins.

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

* fix(environment): replay request lines that show entities in another form

A read line that shows the same entities in another form, by an extra
query parameter that adds or drops members, or conflicts with a base
line on a shared entity, is a variant: it leaves the view's checks and
the entity observations, the view names it with the query keys that pick
it out, and a call matching those keys replays its recording. A filter
role whose name matches no field needs two agreeing values.

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

* docs(environment): specify and record the state-target changes

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

* fix(environment): keep a read-only POST a write when its evidence is incomplete

Body-level container operations are skipped like query ones, a lookup is named only
by the path's last literal segment and never alongside a write word, a tool with a
redirected or unanswered call is not reclassified, and the mutation veto is resolved
over all candidates at once so another read-only call cannot explain a read change.

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

* fix(environment): answer a field selection only when the state shows it exactly

Serving tests a field selection before any variant key: a projection value a replayed
read gave, or one no checked line gave other than `*`, replays. A list sets aside a
line asking for some fields only before the identity proof, and a dropped variant's
membership change no longer rewrites the type.

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

* fix(environment): hold adopted entities and linked creates to what the recording shows

An adopted entity is charged against the view's budget like a created one. A linked
create holds only for its exemplar's path arguments: serving refuses others and the
gate counts such a call as unmodelled. A response's identity-named id links at any
length, but a short one only to a type read before the call.

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

* docs(environment): record the review fixes' measurements

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

* fix(environment): release a discarded planning pass before planning again

A rewrite cut clears the first pass, so the budget rewinds to a checkpoint taken after
the write cutoffs and created ids before the state is extracted again. Rewrite spans are
charged and appended without spreading them into an argument list.

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

* fix(environment): link no create whose id an earlier read showed, and read only whole batches

A linked create is rejected when an earlier list of its type or read of its collection
showed the returned id. Body ids are walked without spreading children into an argument
list. An RPC call reads only when every operations entry names an operation of its own.

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

* fix(environment): let the gate skip exactly the reads serving replays

Serving's replay decision moves to the domain as replayReason, and the write gate uses it:
a read serving replays can neither approve nor veto a write, and a read of every field
beside a replayed selection is checked. A selection read missing a required field is
still skipped, since part of an entity can neither confirm nor check it.

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

* docs(environment): record the second review round's checks

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

* fix(environment): read writes, links and creation cuts only from evidence that rules out other causes

- A read-only POST repeats only a read that finished before it began; the veto compares
  the latest read finished before a call with the first begun after it, and counts a
  recorded absence on one side as a change. `modify` and `replace` are write words.
- A redirected or unanswered write lets a later item read adopt the entity it may have made.
- A creation cut counts a read still running when the POST began as after it, and a list
  read shows its listed entities' own ids, not those they nest.
- A linked create's item read must come before any other tool's write.
- Line shapes are charged to the budget as they are built.
- A `fields=*` list line is whole, not a field selection.

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

* docs(environment): record the third review round's checks

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

* fix(environment): tie linked creates to their returned ids and order state by the lines left

- A linked create is observed only in a read of an id its response returned,
  envelopes included, and serving takes its recorded path values in either spelling.
- A list read shows a suffix-named identity (`projectId`) distinct across its entities.
- A read-only POST compares the read that finished last before it began.
- An entity's first sighting comes from the lines left after variant lines are dropped.
- The pairwise variant pass stops at a work limit, leaving every line checked.

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

* docs(environment): record the fourth review round's checks

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

* fix(environment): keep foreign keys, stale reads and double decoding out of write evidence

- A list read counts a suffix-named field as its entities' ids only when no
  id-named field identifies them, so an `ownerId` beside `id` cuts no window.
- A read-only POST is compared with the read that finished last among those no
  other write follows, so a consuming call stays a write.
- A linked create's source path values compare as spelled on both sides, so no
  spelling is decoded twice.

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

* docs(environment): record the fifth review round's checks

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

* fix(environment): compare linked sources by position, decode path fields once and bound read and variant scans

- A linked create's source values compare segment by segment, so a template
  naming two parameters alike cannot accept a changed first one.
- Serving fills a linked create's path fields with its arguments decoded once,
  as the gate learned them.
- The read-only check reads each related line's records once per call, between
  the nearest other writes.
- Member signatures are JSON arrays, so a name holding the separator still
  conflicts; the variant work limit counts line pairs as well as entities.

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

* docs(environment): record the sixth review round's checks

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

* fix(environment): replay late and variant-only items, fill linked path fields from the exemplar and charge membership spans

- An item line first recorded after the initial window that names a new shaping
  parameter and shows other members is a replayed variant, like an initial one.
- An item only a replayed line showed, which the state never started with,
  answers with its own recording; one the state held and lost stays missing.
- A linked create fills its path fields from its exemplar's path decoded once,
  whichever accepted spelling the call sends.
- Each membership change span is charged against the budget before it is kept.

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

* docs(environment): record the seventh review round's checks

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

* fix(environment): blame a failed read on the writes whose fields it disagrees on

A failed item read with no interfering unmodelled write contradicts its creates and
deletes, and an update only when a field the update wrote or its response showed
differs. A create is contradicted when a later read embeds it elsewhere and the state
does not. A create member is fixed to its recorded value when a constant it would
have to change derives from it: its text inside a constant, a referenced entity's name,
or an instant beside a sent date, matched by related field names.

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

* fix(environment): explain unsent update changes and nested clocks by what the recording shows

An update's unsent change can be a modified-by stamp beside a created-by counterpart, a
value derived from the one sent member stored nowhere (accepted only while the target
holds the recorded before-value), or a sent clock the call left in its own second.
Objects whose clocks all moved with the request are nested clocks, opaque tokens of a
type's own length are generated, and an enveloped body is learned as its one object.
A write takes only its recorded query, an echoed reference to a nested object and a
state word paired with other fields stay at their recorded values, and a slug is tried
before a token.

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

* fix(environment): order collections by whole lines and sort only arrays a read-back proves sorted

A collection's initial order comes from the first list line that shows every entity its
type's sightings do and applies no order. A view renders a string-array field sorted
only after a counted read-back shows the written values in an order that neither the
written order nor existing-values-first explains, and never once a read showed it
unsorted. Sort and direction parameters share one vocabulary and are never learned as
inert, and a create placed by disagreeing sorts or a random id falls back to last.

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

* feat(environment): serve bulk writes at a collection and target item actions

A DELETE, PUT or PATCH at a collection, or a POST at its delete path, whose body names
held entities by id is applied to each named entity, and serving refuses it unless every
id is held. A POST to an entity's action path that answers nothing or the entity is an
update of it; it is served only when a recorded call found each scalar it sets already
at that value. Served bulk bodies advertise their items, and compare probes pass them.

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

* fix(environment): decide late item lines against the lines left, charge replay keys and bisect rewrite cuts

- Item lines first recorded after the initial reads are decided once variants
  are dropped, against the lines left: a new shaping parameter showing other
  members, or a selection of some fields leaving members out, replays.
- Each replay key is charged against the budget before it is kept.
- A rewrite span's earliest write is found by bisection over the writes.

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

* docs(environment): record the eighth review round's checks

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

* fix(environment): set aside response members a path mints anew on every read

A top-level UUID or clock that differs on every repeat of one path's successful reads,
with no write between, is left out of that path's item, list and document comparisons
and named in the view's per_response_members. Serving keeps the recorded value.

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

* fix(environment): count only reads finished before a linked create began as showing its id held

A list still running when the call began may show what the call made, as the
creation cut already decides, so it no longer refuses the link.

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

* fix(environment): count a list total nested in a paging object

An item total in a top-level object beside the list, such as pageInfo.totalRows, is a
counter on the same evidence as a top-level one: it must equal the number listed, and
one that differs marks the line partial. Serving and the gate render it from the state
through its JSON pointer and leave the object's other members as recorded.

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

* fix(environment): take a created entity's nested clocks from its read-back

After a create's generated values pass, its stored nested clocks take the instants the
first later read of the entity shows, as an update's already do, so a create whose
response and read spell the clock differently is confirmed.

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

* fix(environment): import the compare tool definition as a type

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

* docs(environment): describe the new write rules and trim repeated parsing

The environment and capture docs describe sorted arrays, per-response members, nested
counters, item actions, bulk writes, the new write rules and refusals, and their known
limits. `_order` is a direction only. Sorted-array retries and the per-response scan
parse only the reads they need, and identifier words come from one helper.

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

* fix(environment): confirm no update whose clocks or fields the evidence cannot show it writing

An unsent clock an update left in the second it ran keeps the update unconfirmed until
another call shows the clock stamped. A field the state lacks that the read and the
update's response both show is the update's change, so without a rule for it the read
confirms nothing. A modified-by name stamp brings its id sibling along, and created
entities are indexed by id when a failed read is checked for embeds.

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

* fix(environment): show pinned query and body values in the served catalog and apply bulk writes atomically

A pinned query parameter is required with its recorded value as the only choice, and a
call that leaves it out takes that value. A fixed body member shows its recorded value,
and an enveloped body its wrapping, in the schema, the description and the refusal.
Bulk writes go through the same query pin and update or remove their entities at once.

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

* docs(environment): specify and record the write-rule changes

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

* fix(environment): keep served creates' items missing, check same-type earlier reads, and bound late lines and read-only stretches

- An item only a replayed line showed replays only while no served create has
  added to its type, so a created and deleted item stays missing.
- A linked create's earlier item-read veto checks only reads of the candidate's
  type or collection that show the item, so another type's id does not hide it.
- An item read answered with JSON null shows no id to a creation cut.
- Late lines' member names are charged before they are kept.
- The read-only check precomputes nearest writes and passes once over each
  write-free stretch per path, skipping lines whose reads all agree.

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

* docs(environment): record the ninth review round's checks

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

* fix(environment): link creates past empty item reads, sweep repeated reads once and keep only returned ids for creation cuts

- A linked create passes over a later item read naming its id whose body shows
  no item, such as JSON null, and links the next one.
- Read-only repeated reads are found in one sweep: each path's reads wait by
  end time until a call's start passes them, and a write clears them.
- A creation cut keeps, and charges, only the shown ids some creation returned.

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

* docs(environment): record the tenth review round's checks

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

* fix(environment): take bulk ids literally, check each recorded call's query once, and keep learned sorts open to contrary reads

Bulk writes take body ids as written, refuse a call naming one entity twice, size updates from the replacements
installed, generate each entity's values from that entity, and delete only with the members their exemplar sent
besides the ids. Comparison translates bulk body ids with the target collection's mapping.

Update rules echo a creation clock sent back unchanged, require every held clock, and refuse a nested clock once any
recorded call kept its object. A recorded call sent with another query than its exemplar's, including item actions,
documents and deletes, neither confirms nor teaches its plan, and only successful calls with that query relax a
create's guards.

A counted read listing a learned array unsorted drops the sort from its view, sort evidence comes only from the
entities the read covers, and a top-level counter whose name starts with a slash is named by its escaped pointer.

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

* docs(environment): record the review fixes' checks

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

* perf(environment): compare variant lines through shared entities and parse each compared body once

The variant pass indexes a tool's lines by the entities they show and compares only lines that share one, charging
the index to the budget while it is held; the pairwise work limit still decides when a tool keeps every line checked.
Read-only decisions keep each response body's canonical digest, taken once while its parse is charged, across calls
and veto passes. The creation cut no longer parses reads at or after the write that ends the initial reads, and reads
the ids a body shows inside its lease. Adoption looks up a read's target only after an unplanned write before it.

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

* docs(environment): record the eleventh review round's checks

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

* perf(environment): list a type's collections only when the path's own holds no id, and parse variant base lines once

The write gate's item target lists the type's held collections, and the removed targets', only when the path's own
collection holds none of its ids, so a served item view's reads no longer walk every entity of the type. The variant
pass parses the base lines' queries once for all the variants they key, and not at all when it found none.

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

* docs(environment): record the twelfth review round's checks and the sweep's cost

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

* docs(environment): record the 50-capture sweep's wall, CPU and peak memory

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

* fix(environment): remove duplicate schema after main integration

* fix(environment): honor recorded bulk identity kinds

* fix(environment): retain scoped evidence and honor write defaults

* test(environment): keep SDK regressions independently scoped

* docs(environment): record scoped write verification and measured costs

* fix(environment): serve bulk writes whose response lists each entity they wrote

A bulk call answering an array with one element per entity its body names, in the body's order, is classified by
its elements, so the existing renderer serves it from the entities each call writes. Responses listing entities out
of order, with differing members or id spellings, wrapped elements or an envelope still refuse.

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

* docs(environment): measure the 50 captures at the scoped-envelope head

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

* fix(environment): classify bulk responses and release verifier transforms

* fix(environment): classify and verify complete bulk responses

* fix(javascript): retain capture context along covered mutation paths

* test(environment): verify bulk responses through the real MCP SDK

* test(environment): separate bulk response proof regressions

* fix(environment): preserve request absence and generated value relationships

* fix(environment): verify complete nested clocks on creates

* fix(javascript): erase inline type imports before binding values

* fix(environment): share complete generated value verification

* fix(verifiers): load the Windows source graph only when needed

* fix(environment): validate write responses before stored clock restamps

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-24 20:52:03 +08:00
gensokyo-dev 347b6e4d95 test(verifiers): assert shared command diagnostics and identity (#7002) 2026-10-24 12:51:53 +00:00
吴佳浩Alben 2e86294c86 fix(firmware): stop recommending a raise at the staging budget ceiling (#6971)
The staging refusal told callers to increase max_output_files or max_output_bytes within its schema maximum. At the schema ceiling that recovery does not exist, so the refusal was unactionable exactly where the least room remains.

firmwareStagingBudgetRemediation now branches on the selected value against the schema maximum: below the ceiling it names the room and the control, at the ceiling it states that a further raise is unsupported and points at a smaller firmware byte range. The schema maxima move into named constants so the advice and the accepted range cannot drift apart.

Verified with a module test for both budgets at the ceiling and one below it (red on the previous single-form remediation), plus the existing firmware suites.
2026-10-24 21:35:30 +09:00
gensokyo-dev 73320bb2c9 fix(ci): isolate browser verifier D-Bus sessions (#6999) 2026-10-24 12:15:46 +00:00
morluto 2250e67bc2 fix(verifiers): consolidate native client command lifetimes
Migrate the remaining native verifier commands to shared ownership with explicit stdin contracts, command-before-fixture cleanup, and failure receipts. Preserve observed catalog counts and unknowns, retained artifacts, native assertions, and selected launch inputs.

Remove the obsolete raw exec export and share output persistence across native verifiers. Exercise actual entrypoints with authored failed preflights and acknowledged descendants, plus filesystem failures and opaque error preservation.
2026-10-24 19:40:50 +08:00
morluto cd1878f3fe fix(javascript): bound loop capture and rest-copy expansion 2026-10-24 19:36:20 +08:00
gensokyo-dev be2811e414 fix(javascript): exclude erased imports from runtime bindings (#6994) 2026-10-24 11:22:23 +00:00
morluto 0402012a95 fix(verifiers): share command ownership and preserve stdin
Move command and retained resource lifetimes into a shared verifier owner, keeping package workspace release as a separate dependency. Migrate Pi and Hermes verification to that owner while preserving open pipes, explicit EOF, diagnostics, and retained labs.

Require positive writable finalization before accepting EOF delivery. Cover premature close, opaque failures, late ownership delivery, mixed cleanup outcomes, exact retry, and real descendant cleanup with authored process boundaries.
2026-10-24 19:16:42 +08:00
gensokyo-dev 9895f1870c test(evaluation): find failure gate within cleanup callbacks (#6988) 2026-10-24 10:50:50 +00:00
gensokyo-dev dd02d8ff69 fix(javascript): bound cyclic mutation captures by their paths (#6986) 2026-10-24 10:11:27 +00:00
morluto b22b0ef22a refactor: retire test-only fallback exports
Remove duplicate MCP defaults, the fixed CDP deadline message, and the canned Windows authority reason after verifying their only consumers were tests.

Pin independent expected budget facts, exercise active default and selected CDP deadlines, and verify exact authored native diagnostic propagation. Preserve malformed-input, cancellation, omission, and authority assertions. Verified 222 focused cases, 9 README cases, build, static checks, docs, discovery, and duplication at 3.58%.
2026-10-24 18:04:35 +08:00
morluto 89006609f1 fix(javascript): separate erased types from runtime semantics
Record erasure during definition indexing and reuse it across runtime references, captures, effects, module links, routes, Electron and Vite. Preserve source type links and emitted decorator and computed-key effects; keep actual runtime reads and escapes conservative.

Validation: 1,647 focused tests and 9 documentation tests passed, with build, static and documentation gates. The inherited reference-lifetimes stress file exhausts a 512 MiB worker on both unmodified 18451e479 and this change; that file remains unverified. TypeScript 7.0.2 emit was inspected for ES2022 and ES2018 without executing emitted code.
2026-10-24 18:00:43 +08:00
Ariel Fikru Avicennaandgensokyo-dev cab78de506 feat(ghidra): analyze GameCube/Wii DOL executables through Ghidra (#6240)
* feat(domain): parse and validate GameCube/Wii DOL headers

Add a pure parser for the 0x100-byte DOL header (7 text and 11 data
slots, BSS, entry point). Used slots must lie after the header and
inside the file and the 0x80000000..0x81800000 load window; text slots
are 4-byte aligned; sections may not overlap in memory or in the file;
the entry point must be inside a text section. BSS may overlap data
sections, as .sdata/.sdata2 do in shipped DOLs.

Refs #1741

* feat(binary): admit caller-selected GameCube/Wii DOL targets

* feat(ghidra): import GameCube/Wii DOL targets through a section-map pre-script

* test(ghidra): cover DOL launcher arguments, attestation and raw profile digests

* test(ghidra): add the real verify:ghidra:dol lane and DOL docs

* fix(ghidra): read only the DOL header and section ranges instead of the whole file

* test(ghidra): derive DOL lane addresses from readelf and admit Gekko from the install catalog

* fix(ghidra): attest DOL block permissions, space, overlay and load state

* fix(ghidra): keep load-image permissions optional for older inspections

* fix(contracts): accept DOL targets in binary_session output

* fix(ghidra): adapt the DOL lane to the current upstream address schema

Validate DOL load-image coordinates with the upstream observed-address
schema, drop a duplicate import left by the rebase, and re-pin the raw
profile digests to the unmodified upstream resolver.

* fix(ghidra): preserve bridge inventory symbol provenance

* fix(ghidra): align DOL verification with shared workflow contracts

* feat(domain): parse and validate GameCube/Wii DOL headers

Add a pure parser for the 0x100-byte DOL header (7 text and 11 data
slots, BSS, entry point). Used slots must lie after the header and
inside the file and the 0x80000000..0x81800000 load window; text slots
are 4-byte aligned; sections may not overlap in memory or in the file;
the entry point must be inside a text section. BSS may overlap data
sections, as .sdata/.sdata2 do in shipped DOLs.

Refs #1741

* feat(binary): admit caller-selected GameCube/Wii DOL targets

* feat(ghidra): import GameCube/Wii DOL targets through a section-map pre-script

* test(ghidra): cover DOL launcher arguments, attestation and raw profile digests

* test(ghidra): add the real verify:ghidra:dol lane and DOL docs

* fix(ghidra): read only the DOL header and section ranges instead of the whole file

* test(ghidra): derive DOL lane addresses from readelf and admit Gekko from the install catalog

* fix(ghidra): attest DOL block permissions, space, overlay and load state

* fix(ghidra): keep load-image permissions optional for older inspections

* fix(contracts): accept DOL targets in binary_session output

* fix(ghidra): adapt the DOL lane to the current upstream address schema

Validate DOL load-image coordinates with the upstream observed-address
schema, drop a duplicate import left by the rebase, and re-pin the raw
profile digests to the unmodified upstream resolver.

* docs(ghidra): qualify the DOL memory window

* refactor(ghidra): own DOL BSS ranges in the parsed header

* docs(ghidra): record real DOL workflows and task costs

* docs(ghidra): record final DOL integration verification

* fix(mcp): share the native procedure search row contract

* docs(ghidra): record public procedure search verification

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-24 17:56:31 +08:00
morluto d01ee7b19a fix(verifiers): own setup terminal and snapshot lifetimes
Join identity-verified PTY family cleanup and native exit before workspace removal, retaining failed cleanup capabilities for retry. Replace duplicate kill-and-reject loops with shared process ownership and deadlines.

Capture configuration before setup using owned file handles and compare raw bytes. Preserve interactive cancellation, terminal restoration, redirected output, and selected inputs; remove the test-only forwarding export. Verified 122 focused cases, 9 README cases, build, static checks, docs, discovery, and duplication at 3.58%.
2026-10-24 17:54:18 +08:00
Saiyam JainandSaiyam Jain ddde7276c4 test(erlang): cover lambdas names filter and chunks miss in BEAM views (#6973)
Add lambdas names-filter selector cases to checkSelectors:
- names: ['start'] returns the matching lambda with resolved fields
- names: ['missing'] returns an empty page

Add a chunks no-match case:
- names: ['Xyzz'] returns an empty page for an absent chunk tag

Also clarify in docs/erlang-beam.md what names selects for each
collection: the four-character IFF tag for chunks, the resolved
function name (never null) for lambdas, and the decoded atom name
string for atoms.

Co-authored-by: Saiyam Jain <[email protected]>
2026-10-24 17:39:57 +08:00
gensokyo-dev 48c203f356 fix(build): prepare release dependency types during installation (#6978) 2026-10-24 09:24:43 +00:00
morluto 18451e4797 test(release): type patched history options before runtime setup 2026-10-24 17:21:08 +08:00
morluto df5c48ffda docs(cutter): explain bridge descriptor discovery configuration 2026-10-24 17:21:08 +08:00
morluto 9dfa44f776 fix(cancellation): retain caller reasons at admission checkpoints 2026-10-24 17:21:08 +08:00
morluto 3fb26985c4 fix(native): classify dyld refusals before export fallback 2026-10-24 17:21:08 +08:00
morluto af3f025688 fix(il2cpp): accept names in native type-reference views 2026-10-24 17:21:08 +08:00
morluto 43c7255dbd fix(android): preserve binary class selectors across metadata calls 2026-10-24 17:21:08 +08:00
Dery Ferdikaandgensokyo-dev a45ee81167 fix(ghidra): enforce request limits before encoding (#6917)
* fix(ghidra): enforce the bridge request ceiling before writing

Hopper and Cutter both measure an oversized payload and refuse it before the
request reaches its bridge, using the shared REA_BRIDGE_MAX_REQUEST_BYTES
ceiling. Ghidra had no ceiling on either end: GhidraWire.request wrote the
stringified request straight to the socket, and the Java bridge reads request
lines with an unbounded readLine() after its bounded 64 KiB handshake.

Serialize once, measure the encoded payload, and refuse before the operation
is registered as pending and before any socket write, so an oversized request
never reaches the bridge. Carry the typed constraint as the cause and
re-attribute it in projectPrimarySessionError, so callers see the same
resource constraint Hopper and Cutter already report.

The Ghidra bridge reads no ceiling of its own, so its recovery text names that
only the REA client applies the setting instead of asking for a bridge restart.

Validation: src/ghidra/GhidraClientWire.test.ts (3 cases, red then green),
src/config/bridgeMaxRequestBytes.test.ts (16), src/ghidra/GhidraRequestQueue.test.ts
(25); tsc --noEmit over the changed set exits 0; oxlint reports 0 errors;
oxfmt --check clean; verify:module-boundaries passes. The two failures in the
wider src/config+src/ghidra+src/hopper+src/cutter sweep
(captureDecoderHeap, GhidraAnalysisSeeds symlink EPERM) reproduce identically
on pristine bded9c628 with this change stashed. The Ghidra fixture suite
cannot run on this Windows host: fakeGhidra.mjs renames bridge-endpoint.json
and hits EBUSY on 3 of 3 runs, so the fixture-level test was not added.

* fix(ghidra): preflight request bytes before encoding

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-24 17:17:52 +08:00
f2fa7f648d feat(android): project a structured manifest component inventory (#5025)
* feat(android): project a structured manifest component inventory

* fix(android): derive provider export defaults from the target SDK and match namespaced attributes

* fix(android): bound resolved names and authorities in the component inventory

* fix(android): separate nonliteral exported values from omission

* fix(android): bound manifest component projection

* fix(android): preserve permission override semantics

* fix(android): retain unresolved package identities

* test(android): cover multibyte manifest name budget

* fix(android): measure inherited permission once

* fix(android): preserve unknown symbolic permissions

* fix(android): preserve manifest component uncertainty

* fix(android): report unsupported manifest facets

* fix(android): preserve manifest identities

* fix(android): preserve alias target references

* fix(android): validate alias targets and authorities

* fix(android): bound alias target expansion

* fix(android): index alias target spellings

* fix(android): preserve alias identities and isolate legacy defaults

* fix(android): keep legacy component defaults typed as unavailable

* docs(android): record manifest projection and real JADX costs

* fix(android): preserve SDK defaults and manifest namespaces

* docs(android): verify SDK and namespace ownership corrections

* fix(android): accept DEX class names and hexadecimal SDK levels

* docs(android): record DEX and SDK follow-up verification

* fix(android): validate manifest package identity separately

* docs(android): record final package-identity verification

* fix(android): preserve exact DEX component and alias names

* docs(android): record exact-name integration verification

* fix(android): retain declared foreground service type text

* docs(android): record final declared-value validation

---------

Co-authored-by: BaaRai <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-24 17:14:23 +08:00
morluto d9660eb032 fix(verifiers): share package command and workspace ownership
Join canonical owned command cleanup before releasing package verifier workspaces. Replace raw package command wrappers, the updater supervisor, WASM cleanup composite, and boolean-only retention with explicit capability ownership.

Preserve selected environments, npm arguments, no-deadline calls, clean exit status, and distinct aggregate/per-stream output budgets. Exercise actual timeout descendants, cancellation, retained cleanup retry, and workspace admission.
2026-10-24 16:58:18 +08:00
morluto de9a8898dc fix(artifacts): preserve opaque close hook failures
Tag close outcomes with Result so rejected undefined remains a failure. Preserve all hook attempts, retained resource owners, aggregate causes, and cleanup retry.
2026-10-24 16:56:49 +08:00
4622 changed files with 531090 additions and 298093 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
{
"name": "rea",
"interface": {
"displayName": "REA"
},
"plugins": [
{
"name": "rea",
"source": {
"source": "npm",
"package": "rea-agents",
"version": "6.8.0"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
@@ -15,6 +15,11 @@ Follow the checkout's [performance priorities](../../../AGENTS.md#performance),
workflow, not additional permission to execute targets, publish, or merge.
Use the current contracts, provider prerequisites, and user authorization.
Reserve performance measurements for changes that target performance or have a
concrete regression concern. Routine cleanup uses correctness checks and does
not require this optimization workflow or synthetic benchmarks merely because
helper calls, imports, or representations change.
Read [the Kuna case study](references/kuna-case-study.md) when selecting a
reuse strategy or interpreting a benchmark. Its pinned observations illustrate
tradeoffs; they are not current-provider guarantees or universal speedups.
+4 -3
View File
@@ -68,9 +68,10 @@ closing-issue reporters. Grouping preserves all those credits in one line.
When Highlights, detailed entries or migrations reference the same work,
put its thanks in one of those entries, not all of them. Include issue links.
Direct-commit bullets carry the commit link and known human credit.
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor
and Codex co-author trailers resolve to `@claude`, `@cursoragent` and `@codex`
with GitHub type `User`; the credit policy excludes them while retaining their metadata.
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor,
Codex, and Copilot co-author trailers resolve to `@claude`, `@cursoragent`,
`@codex`, and `@Copilot` with GitHub type `User`; the credit policy excludes
them while retaining their metadata.
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
authorship in the inventory. Other human contributors remain eligible.
Use explicit GitHub links for cross-repository references to avoid ambiguous
+15 -1
View File
@@ -1,6 +1,6 @@
---
name: rea-tool-design
description: Design or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.
description: Audit, investigate, design, or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.
---
# REA tool design
@@ -10,6 +10,20 @@ contract semantics, provider ownership, and discoverability. Inspect the current
contract and nearest existing tool relevant to the analyst question; source,
documentation, and live catalogs may describe different versions.
For provider-backed work, exercise the analyst's task through REA and compare
relevant native provider and independent artifact observations where available.
Check applicable partial results, recovery and session boundaries; report what
was not exercised. For failure guidance, follow [design recovery around the
failure](../../../docs/tool-design.md#design-recovery-around-the-failure).
Use [workflow verification](../../../docs/testing.md#provider-backed-workflow-verification)
to select evidence for the proposed contract instead of seeking provider API
parity.
For an audit, follow [audit existing tools](../../../docs/tool-design.md#audit-existing-tools):
scope representative tasks, exercise the live workflow, and distinguish
reproduced bugs, supported limitations, design opportunities and unverified
claims. Report working alternatives and evidence limits as well as findings.
For a design request, return the proposed boundary, input/result shape, Evidence
semantics, nearest alternative, and verification approach. Implement when
requested, preserving the user's scope and existing authorization.
@@ -1,8 +1,8 @@
---
name: reverse-engineer-anything
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version: "46"
version: "59"
---
# REA
@@ -17,6 +17,9 @@ readiness or provider commands.
Installing this skill supplies instructions; it does not register the REA MCP
server, install analysis engines, or add tools to an already-running agent.
The exception is a Claude Code or Codex plugin install: the plugin bundles
this skill and registers its own `rea` server, outside the state that
`rea setup` and `rea doctor` manage.
If REA tools are available and their registration is not known to be stale,
proceed directly to the target. Do not run diagnostics or setup before every
@@ -37,42 +40,23 @@ repeat or known not to have started. If it may already have changed the
target, or it fails again, tell the user and ask them to restart the MCP
client. If REA could not start its connection, retry once, then tell the user
and ask them to restart the MCP client. Do not retry a startup failure that
returns the same unchanged result. Run `rea doctor` when REA could not start
returns the same unchanged result. For plugin installs, follow
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin).
For setup-owned registrations, run `rea doctor` when REA could not start
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
before asking for a restart. Read that guide before a second repair attempt.
1. Diagnose without changing REA or client configuration. `npx` may download
the package and write to npm's cache. For Codex, run
`npx -y rea-agents@latest doctor --client codex --json`. Substitute the current
supported client: `claude_code`, `claude_desktop`, `codex`, `cursor`,
`gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`, `copilot_cli`,
`commandcode`, `vscode`, `grok_build`, `omp`, or `grok_bot`. `grok_bot` has no local
file registration: doctor reports the Grok Bot chat step, and setup does not
write `mcp.json`. If the client is unknown, use `doctor --json` and
inspect its registration results before choosing a setup scope.
2. Distinguish the reason. Missing, malformed, or stale registration needs a
scoped configuration repair. An aligned registration with no tools in the
active session needs a restart/reconnection; doctor cannot prove that the
current agent has connected. A missing provider affects only tasks requiring
that provider: static JavaScript inspection needs neither Hopper nor Ghidra,
and Android inspection has separate bring-your-own JADX/Java prerequisites.
3. For a configuration repair, prepare the read-only plan:
`npx -y rea-agents@latest setup --client codex --dry-run --json`.
Use the current client's ID, show its exact proposed paths, backups, and
changes, and obtain approval before setup writes configuration or installs
Hopper. Setup normally installs the matching bundled skill too; include that
replacement in the reviewed plan. After approval, apply the same scope with
`npx -y rea-agents@latest setup --client codex --yes`. Add `--install-hopper`
only if that separate installation was needed and explicitly approved.
4. Restart/reconnect the affected agent. Verify that REA tools actually appear
in the session, then resume the original investigation. If they remain
absent, inspect the client's MCP launch error rather than repeating setup.
Choose the installation owner before repairing registration. Plugin installs use
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin);
only setup-owned registrations use the guide's
[registration repair plan](references/connection-and-recovery.md#repair-registration-when-needed).
A plugin-only doctor result of `missing` does not call for `rea setup`.
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
software to repair MCP registration. For an unsupported client, use manual stdio
registration with a version-pinned `rea-agents` package or continue through the CLI.
registration with an existing installation’s absolute runtime and entry script, or continue through the CLI.
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
@@ -85,8 +69,11 @@ graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
Configuration repair starts with a read-only setup plan; show paths, backups and
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
@@ -107,6 +94,9 @@ explicit path or endpoint and do not need it.
than extra calls and schemas. Runtime options are metadata, not files.
Compact delivery still inventories and hashes members. See the
[Python packaged applications guide](https://github.com/morluto/rea/blob/main/docs/python-packaged-applications.md).
- Bun `--compile` executable: `open_binary`, then `inspect_artifact`; the Bun
module graph is the application layer. Extract stored modules and analyze the
JavaScript. See [Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
- Apple app plist, Interface Builder, asset catalog, or keyed archive resources:
`open_bundle_resources`, then the selected resource tool. This does not
require `CFBundleExecutable` or a deep provider. Resource sessions have no
@@ -119,6 +109,8 @@ explicit path or endpoint and do not need it.
`extract_firmware` when extraction is requested, with a caller-selected new
absolute output directory. These tools use caller-supplied Binwalk/Unblob on
Linux; see the [firmware guide](https://github.com/morluto/rea/blob/main/docs/firmware-analysis.md).
- ESP32-family app or bootloader image (`0xE9` magic): `inspect_artifact`
without a provider; see the ESP section of `references/native-and-artifacts.md`.
- .NET NativeAOT PE/ELF: native Ghidra analysis. Native code can yield pseudocode;
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
optional metadata recovery and its supported host/layout boundary.
@@ -133,6 +125,9 @@ explicit path or endpoint and do not need it.
`capture_browser_scenario` with declared actions. See
[runtime observation](references/runtime-observation.md) for connect/launch
and the focused follow-up browser tools.
- Agent environment (AgentEnv bundle) of a web application: read
[environments](references/environments.md) before recording; it records with
`capture_browser_scenario` and writes the bundle with `export_environment_bundle`.
- Fiddler Everywhere traffic or a native `.saz` capture: read the
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
@@ -155,7 +150,9 @@ explicit path or endpoint and do not need it.
and field names/types before choosing fields for `inspect_packet_capture`.
Query/page the relevant records; registrations do not establish observed traffic.
- Recorded packet captures: `inspect_packet_capture` with selected TShark fields,
display filters and decode-as rules. Requires Linux, util-linux prlimit and `REA_TSHARK_COMMAND`; see
display filters and decode-as rules. Requires Linux, util-linux prlimit and
TShark 4.x on an absolute `PATH` entry or via the optional absolute
`REA_TSHARK_COMMAND` override; see
[capture guide](https://github.com/morluto/rea/blob/main/docs/packet-captures.md).
Packet limits count input packets before filtering. Output defaults to 50 matching
frames; use `match_offset` / `match_limit` for later pages, or explicit null
@@ -259,6 +256,14 @@ explicit path or endpoint and do not need it.
proof the image never imported APIs through another table. Bound snapshots
and loaded IAT contents remain unobserved. See the
[PE import guide](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
- PE TLS callbacks and load configuration: `inspect_pe_load_config` with the
explicit local PE32/PE32+ file. This built-in target-free inspection requires
no Windows SDK. Preserve callback VAs as strings with their RVA, file offset
and section, and the size-covered security cookie, SafeSEH, CFG,
EH-continuation and XFG fields. Guard-flag mitigation claims are inferred
linker declarations, not proof of enforcement. A missing directory is observed
absence of that declaration. See the
[PE load configuration guide](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
file. This built-in target-free inspection requires no disassembler. Preserve
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
@@ -273,6 +278,16 @@ explicit path or endpoint and do not need it.
body; other types, including `"288"` stackshots, return no crash result.
Opened Mach-O and dSYM UUID joins remain unobserved. See the
[Apple IPS guide](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
- Linux kernel module metadata: `inspect_kernel_module` with the explicit local
ELF32/ELF64 relocatable file. This built-in target-free inspection requires
no Linux host or `modinfo`. Preserve `.modinfo` key order and duplicate keys,
`__versions` CRC hex, section file ranges, and the appended signature
trailer's PKCS#7 SHA-256. A missing section is observed absence when section
names are known, not proof the module never carried that metadata.
`struct module` is not decoded. The signature is not validated. gzip, xz, and
zstd containers are unsupported; decompress into a separate file with an
external codec utility, then inspect the decompressed `ET_REL` image. See the
[kernel module guide](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
- CPython 3.7–3.13 `.pyc` header, nested marshal code objects and bytecode
identity: `inspect_python_pyc` with the explicit local file. This built-in
target-free inspection requires no Python installation. Preserve magic,
@@ -323,6 +338,11 @@ leaves its database open. The modern headless profile creates and closes an
owned private database without saving. Installation and registration reuse
[mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); see the
[IDA provider guide](https://github.com/morluto/rea/blob/main/docs/ida-provider.md).
For migration to the separate native IDA MCP plugin, follow the guide's
[legacy attached migration](https://github.com/morluto/rea/blob/main/docs/ida-provider.md#migrate-a-legacy-attached-registration).
Replace the upstream registration explicitly; modern mrexodia tools are not the
native profile. Native attachment preserves the database but does not provide
direct callers; check session availability and the guide's verification limits.
Proceed directly to `analyze_function`, pseudocode, or function/string searches
after opening; IDA does not supply `binary_overview`. Consult session availability
when composing broader workflows. Modern direct callers and unsupported dossier
@@ -353,18 +373,24 @@ native target whose analysis tool operates on the active target. Do not call an
unadvertised tool; inspect `binary_session` with `{}` and its
`result.tool_availability` for availability reasons and remediation. Expand each
group's `names` into entries, inheriting the inventory-level client requirements
unless the group overrides them. In its closed effect and annotation vocabularies,
listed flags are true and omitted known flags are false. The grouped name union
is the complete tool inventory; use `tools/list` for canonical tool schemas.
Provider candidate facts describe possible providers, while top-level session
capabilities describe the current route and `analysis_provider_binding` names the
selected provider. Do not treat those scopes as interchangeable.
unless the group overrides them. The grouped name union is the complete tool
inventory; use `tools/list` for canonical tool schemas. The default summary keeps
each provider candidate's availability and target-support status, code and
reason; `omitted_fields` lists what it leaves out. Request `{"detail": "complete"}`
only when you need an unavailable candidate's remediation, provider
diagnostics, candidate capability sets, or tool surface, effect and annotation
flags; in those closed flag vocabularies, listed
flags are true and omitted known flags are false. Provider candidate facts
describe possible providers, while top-level session capabilities describe the
current route and `analysis_provider_binding` names the selected provider. Do
not treat those scopes as interchangeable.
For a Ghidra startup timeout on a large native target, follow
[deadline recovery](references/native-and-artifacts.md#ghidra-startup-deadline-recovery)
before abandoning deep analysis or repeating the same cold import. The default
330 seconds is configurable; changing the server's environment requires a new
REA process.
330 seconds can be changed for new startup attempts through
`update_analysis_settings` using `ghidra.startup_timeout_ms`. Changing the
server's launch environment instead requires a new REA process.
## Work summary-first
@@ -376,9 +402,18 @@ call `analyze_javascript_application` or `inspect_binary_layout` with
a flag: its default reply is the summary, and the complete dossier stays
retained under `parent_evidence_id` for facet paging. Pass that ID as a
`retained-evidence` reference to `compare_functions`, or inside
`match_native_functions` `left_observations`/`right_observations`; request
`"detail": "complete"` only when the dossier must travel inline, as
`build_call_path` requires.
`match_native_functions` `left_observations`/`right_observations` or
`build_call_path.functions`; request `"detail": "complete"` only when the
dossier must travel inline.
Use the same exact-reference form in other advertised logical Evidence inputs,
including optional sources, arrays and nested sources across domains. Each
source can independently be inline or retained; preserve its source identity,
coverage, partial facts and unknowns. A summary or inspection page has its own
Evidence ID: use `normalized_result.parent_evidence_id` when the consumer needs
the complete source. For portable CLI follow-ups, commands advertising
`--evidence-bundle` resolve references from that selected exported bundle.
See [Evidence composition](references/evidence-workflows.md) for scope and
recovery; file selectors are supported only where explicitly advertised.
Page module identities and select the returned `node_id` before requesting an
item; a single file path can represent several graph roles. Do not repeat an
identical analysis or rejected call without a changed input or resolved blocker.
@@ -397,7 +432,10 @@ output also streams without a single MCP frame.
When the retained parent ID is unknown, use `get_evidence_bundle` with
`detail: "summary"` and exact filters for operation, target SHA-256,
analysis-profile digest or canonical procedure address. Select its returned
Evidence ID for the next view instead of requesting the complete bundle.
Evidence ID for the next view instead of requesting the complete bundle. When
that record has no supported view (for example an annotation), read it with
`detail: "records"` and an `evidence_id` filter; `missing_evidence_links`
names linked sources this session does not retain.
`retention: "complete-record"` means the whole record is stored; native
value-flow truncation and unknown metadata remain separate. For an exported
portable bundle, use `rea inspect-evidence-bundle` with JSON containing `path`,
@@ -471,10 +509,17 @@ as complete while required questions remain open.
[references/runtime-observation.md](references/runtime-observation.md)
- Evidence paging, comparisons, residual unknowns, and verification:
[references/evidence-workflows.md](references/evidence-workflows.md)
- Agent environments (AgentEnv bundles) of a web application, recorded from
a live instance or a HAR:
[references/environments.md](references/environments.md)
## Finish the task
Explain findings in plain language and tie them to returned evidence. When the
user asks to build something, use normal coding tools and separate observed
behavior from design choices. Close an opened native session with
`close_binary` when the investigation is complete.
`close_binary` when its engine is no longer needed. Evidence references and Unknown
history remain in memory on the same MCP connection. End the investigation with
`reset_investigation`; supply `save_path` to checkpoint the complete bundle before
clearing. Import the saved bundle to restore original references and Unknown
history in a new process. Retention is bounded and is not automatic persistence.
@@ -13,6 +13,27 @@ own APK path and explicit selectors; do not open a native session first.
Choose an `overload_index` from the class inventory when methods are ambiguous
instead of guessing.
For JNI implementations, discover `inspect_android_jni_bindings`. Select raw
DEX class/method identities and optional descriptors, ABI or logical library
paths. Prefer `detail: "summary"`; complete Evidence remains retained and
`android-jni` views page declarations, libraries, candidates or binding rows
with exact fields. Global native-pair ambiguity survives class selection.
Preserve slot/relocation provenance and source coverage: matched means observed
candidate presence; partial scans do not establish uniqueness or absence.
This reads DEX/ELF bytes without loading the APK into the decompiler or executing
target code. Runtime registration and loading remain unknown. See the
[JNI guide](https://github.com/morluto/rea/blob/main/docs/android-analysis.md#link-native-declarations-to-jni-candidates).
For an R8/ProGuard release build with its `mapping.txt`, pass the absolute
`mapping_path` to `inspect_android_class` or `inspect_android_method`. Prefer
`detail: "summary"` to receive normalized symbols or the selected body without
duplicate raw class metadata. Complete Evidence stays retained under the linked
parent ID and can be exported; CLI summary delivery requires `--evidence_output`.
Omitted or complete detail preserves the full inline response. Keep
selecting by observed names; the separate `mapping` block reports each symbol
as `mapped`, `ambiguous` or `unmapped` after checking DEX types and prototypes.
REA does not verify that the mapping belongs to the APK build.
The provider requires a separately supplied headless JADX MCP JAR 0.7.1 and
Java 17 or newer. `REA_JADX_MCP_JAR` selects the absolute JAR path; `JAVA_HOME`
can select Java. Use `rea doctor --provider jadx --json` to check the JADX
@@ -28,8 +49,11 @@ stdio. A full JDK, including `jdk.compiler`, is required on every host; Windows
verification coverage. Calls use one worker and provider deadlines; optional
`REA_JADX_HEAP_MIB` selects the JVM heap and `REA_JADX_OPERATION_TIMEOUT_MS`
the per-operation deadline (integer milliseconds from 1 to 2147483647, default
120000; a change applies on the next process start; the MCP client's tool
timeout must also allow it). The APK is never launched,
120000). To change that deadline on a running MCP server, set
`jadx.operation_timeout_ms` through `update_analysis_settings`, then retry after
work and cleanup settle. Newly accepted operations use the override; accepted
work keeps its budget. Changing the launch environment requires a new process.
The MCP client's tool timeout must also allow the selected deadline. The APK is never launched,
and no emulator is required.
Manifest entries and decompiled text are static observations, not execution or
@@ -17,12 +17,12 @@ does not establish which server an approval wildcard covers. See
| Observation | Meaning and next step |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| No REA tools in the active session | If REA was installed as a client plugin, follow the plugin section below. Otherwise inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| MCP connection dropped or REA could not start | If the connection drops, retry that call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or the same failure returns, tell the user REA's connection failed and ask them to restart the MCP client. If REA could not start its connection, retry once, then tell the user and ask them to restart the MCP client. Do not retry a startup failure that returns the same unchanged result. Run `rea doctor` when REA could not start or tools stay missing after restart. Doctor cannot restore a live connection by itself. |
| `MCP tool call requires approval, but approval policy is never` | Codex rejected the call before REA executed it. Do not install an engine or repeat setup to solve this client-policy decision. See the approval section below. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. |
| REA returns `provider_unavailable` or an unavailable tool entry | Read that operation's provider diagnostics or `binary_session.result.tool_availability`. Expand each group's tool names and inherit the shared client requirements unless overridden. Use the operation's availability, reason, and remediation; a complete tool catalog does not imply every operation is available. Request `{"detail": "complete"}` only for provider diagnostics. |
| `rea` is missing in a child shell, or Node is incompatible | Inspect the actual shell PATH and launcher/runtime. A prefix installation may be absent from a login shell's PATH; this does not establish a broken MCP registration. |
If the client cannot launch its shell/sandbox helper or read the installed skill,
@@ -116,6 +116,32 @@ is not a recommendation to change it. Do not invent a setting. Host memory and
- `invalid_request`: correct the reported fields from the current schema. Do
not repeat the unchanged call.
## REA installed as a client plugin
A Claude Code or Codex plugin owns its `rea` server and this skill. Doctor's
`--client` and `--skill` checks and `rea setup` read and write only
setup-owned state, so for a plugin-only install they report `missing`, and
applying setup would add a second `rea` server. If this skill was loaded from
a plugin, or the client lists a `rea` plugin, do not prepare a setup plan for
missing tools. Instead:
- Check that the plugin is installed and enabled with the client's own
listing: `claude plugin list` and `/mcp` in Claude Code, `codex plugin list`
in Codex.
- Restart or reconnect the client, then read its launch error for the `rea`
server if tools are still absent. The plugin starts its bundled runtime; a missing build or version mismatch
requires a reinstall, or a fresh build when developing from source.
- Check analysis engines through the connected server's
`binary_session.result.tool_availability`. `rea doctor --provider <id> --json`
from a shell reports that shell's environment, which can differ from the
environment the client gave the plugin server. If you still run it through
the package runner, use the version pinned in the plugin manifest's server
entry (`rea-agents@<version>`), not `@latest`, so it matches the server the
plugin starts.
Use the setup repair below only when the user wants a setup-owned registration
instead of the plugin, and remove or disable the plugin first.
## Repair registration when needed
For Codex:
@@ -128,9 +154,12 @@ rea setup --client codex --dry-run --json
Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
`cursor`, `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`,
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `trae`, `grok_build`, `omp`, `pi`,
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, `freebuff`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
For unsupported clients, use version-pinned manual stdio registration or CLI.
For unsupported clients, use manual stdio registration with the selected
installation’s absolute runtime and entry script, or continue through the CLI.
Acquire packages from a trusted directory outside the target project. Keep a
package-runner installation and its runtime available while registrations use it.
Show the plan's exact paths, backups, changes and bundled-skill replacement.
Obtain approval for writes when that concrete scope is not already authorized,
@@ -175,6 +204,17 @@ writing. Managed policy can constrain client settings. Consult the
for the installed client's supported settings. This is an optional recovery
choice, not a prerequisite for every REA task.
## Repair an editable REA limit
If `details.limit` names an editable setting and its bounds, call
`update_analysis_settings` directly with a `changes` entry containing `setting`,
`action: "set"` and `value`. `inspect_analysis_settings` is optional when those
facts are already present. Retry the same input after work and cleanup settle.
Runtime defaults affect new work only; explicit per-call arguments take
precedence and must be corrected directly when selected. Reset restores launch
values. Updates survive target close/open but disappear on REA restart. They do
not edit host deadlines, response buffers, registrations or fixed limits.
## Continue through an installed CLI
Check the command environment only if the launcher fails:
@@ -0,0 +1,287 @@
# Agent environments from a live application
Use this when the user asks for an agent environment (an AgentEnv bundle) of a
web application: a folder `agent-env` deploys, whose MCP tools answer from what
one recording of the application observed and serve the writes that recording
confirmed. The workflow records the application once with
`capture_browser_scenario`, checks what the environment would serve, records
again if a hint names a gap, then writes the bundle with
`export_environment_bundle`. For a HAR the user already has, skip to
[export](#export-and-hand-off) through `inspect_web_network_capture`, or use
`rea environment-build <capture.har> <dir>`.
## Before recording
- Record only against an instance the user says is disposable or seeded for
this purpose: every write step creates, edits or deletes its data. If the
user has not said so, ask before any scenario that writes. REA does not
start, seed or reset applications.
- Credentials come from environment variables the user names. Declare each as a
scenario secret, `{secret_id, environment_variable}`; the variable must be set
in the environment REA's MCP server started with. Never ask for a password in
chat. Declared secrets are redacted as the capture records.
- Map the application first with `inspect_web_page` (and `capture_web_screenshot`
when layout matters): the login form's locators, and the API calls behind the
pages that list and edit the entities the user asked about.
- `inspect_web_page` cannot sign in. To see pages behind sign-in, run a short
`capture_browser_scenario` that signs in and opens them, with
`"capture": {"after_each_step": ["accessibility"]}` (`"dom"` for markup).
Read a step's tree with `inspect_analysis_view`, `view: {kind:
"browser-scenario", collection: "text", step_index, artifact:
"accessibility", offset: 0, limit: 2048}`, paging with `offset`. Capture
options are only `after_each_step`, `at_end`, `events`, `network` and
`limits`.
## Record a session an environment can learn writes from
One `capture_browser_scenario` records the whole session. Start from this
shape, with the application's own locators, paths and headers:
```json
{
"browser": { "mode": "launch", "executable_path": "/usr/bin/chromium" },
"start_url": { "url": "http://127.0.0.1:3000/login" },
"secrets": [
{ "secret_id": "admin_password", "environment_variable": "APP_PASSWORD" }
],
"capture": {
"network": {
"request_body": true,
"response_body": true,
"header_values": true
}
},
"actions": [
{
"step_id": "user",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=username]" },
"value": { "source": "literal", "value": "admin" }
},
{
"step_id": "password",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"value": { "source": "secret", "secret_id": "admin_password" }
},
{
"step_id": "sign-in",
"action": "press",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"key": "Enter"
},
{
"step_id": "signed-in",
"action": "wait_for",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "nav a[href='/settings']" },
"state": "visible"
},
{
"step_id": "folders-1",
"action": "request",
"timeout_ms": 15000,
"method": "GET",
"path": "/api/folders",
"expected_status": [200]
},
{
"step_id": "new-folder-1",
"action": "click",
"timeout_ms": 15000,
"locator": { "kind": "role", "role": "button", "name": "New folder" }
},
{
"step_id": "folder-title-1",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=title]" },
"value": { "source": "literal", "value": "Scratch folder 1" }
},
{
"step_id": "create-folder-1",
"action": "click",
"timeout_ms": 15000,
"locator": { "kind": "role", "role": "button", "name": "Create" },
"save_response": {
"method": "POST",
"path": "/api/folders",
"save": [{ "name": "folder_1", "json_pointer": "/id" }]
}
},
{
"step_id": "read-folder-1",
"action": "request",
"timeout_ms": 15000,
"method": "GET",
"path": "/api/folders/{saved:folder_1}",
"expected_status": [200]
}
]
}
```
Give each step that accepts `timeout_ms` a deadline. Without one, a step waits
until it completes or the scenario is cancelled, so a locator that never
matches hangs the capture and the client with it. The `wait_for_timeout` pause
instead uses `duration_ms` and does not accept `timeout_ms`. Match `wait_for`
locators to exactly one element.
1. Sign in through the UI: `fill` the user and password fields (the password
as a secret reference), submit, then `wait_for` an element only a signed-in
user sees.
2. Read with `request` steps: each collection the environment needs, twice
before any write, so fields that change on their own show up; then each
object a list shows by id, up to ten per collection. An object known only
from a list that leaves out fields its own read shows cannot be read by id
or updated in the environment.
3. Write the way the application's UI does: fill its forms and click its
buttons, so each request carries the fields the UI sends, including ones
the server would otherwise derive. Give the click or key press that submits
a create `save_response` to keep the new object's ID, then `goto` its page
(`/items/{saved:item_1}`) or read it with a `request` step. Never guess an
ID, and never create a probe object to learn the next one. Write with
`request` steps instead only when the UI cannot reach the operation (an
API-only resource, or a UI that does not call the JSON API), or when coverage
withholds the UI-shaped write as `unexplained-change` or `contradicted`;
that counts as one of the two re-recordings. A `request` step write that
coverage withholds that way usually lacks fields the UI sends: record it
through the UI instead. If a journey cannot be scripted, leave the write
unserved and report the limitation.
Record two numbered scratch objects per write type,
with varied free text. After each write, and before the next one, read the
object back by id and then its list: create 1, read 1, list, create 2,
read 2, list; never several writes before their reads.
4. Leave more than a second between two creates and between a create and its
first edit (`wait_for_timeout` with `duration_ms: 1100`), so lists sorted
by creation time show them in order.
5. Update both objects and read each back, changing the fields a person
would change (a name, an email address, a status, a due date) rather than
only a description: a served update accepts only the members a recorded
update stored. Run an action that turns something on or off (star,
bookmark, archive) twice in a row on one object, reading it back after
each: the second call shows whether it sets or flips the field, and the
environment serves the action only once a call has shown it. Delete both
and read each back (expect 404, or the object marked deleted) and the list.
6. Vary what the two creates send, however you write them: the first every
field the application takes, the second only what a person must give (its
title or name), with the other fields left out or empty. A served create
requires each member both creates filled in and lets a caller leave out one
a create left out or sent empty, so an agent that names only a title is not
refused for fields it had no reason to set.
About `request` steps:
- `path` is on the start URL's origin; the page must be on that origin.
- Header values are literals, declared secrets, a cookie the browser holds
(`{source: "cookie", name}`), a local storage entry
(`{source: "local_storage", key, json_pointer}`), a saved value
(`{source: "saved", name}`), or Basic credentials
(`{source: "basic_credentials", parts: [username, password]}`, sent as the
base64 of `username:password` and redacted), with an optional `prefix` such as
`"Bearer "` or `"Basic "`. Each credential part is a literal, secret, saved
value, cookie or local storage entry, so a header the frontend builds from a
login token (the username and the token) can be copied too; the password is
a secret or saved value, never a literal. Copy the
headers the application's own requests send (a CSRF header such as
`X-CSRFToken` from the `csrftoken` cookie, a bearer token). Values read from the page are redacted from records made after they
are read, not from the login response that returned them, and values
shorter than 8 characters are not redacted at all.
- `save: [{name, json_pointer, secret}]` keeps a string or number from the
step's JSON response, such as the created object's `/id`, for later steps;
`secret: true` redacts it, as for a token the API returns, so mark every
saved token, key or session value secret. This also protects the selected
source response before its body or headers are retained. Compressed or
unknown-size UI responses cannot supply saved values. On a `click` or `press`,
`save_response: {method, path, save}` keeps values from the response to the
new request observed while the step waits (`{*}` in `path` matches one segment).
Matching does not prove UI causality: choose a path distinct from polling;
multiple observed matches fail as ambiguous. The step
fails unless a 2xx JSON response arrives before its `timeout_ms`. Later
steps send a saved value as `{saved:name}` in a request `path` (and its
query) or a `goto` URL; in the `json` body, where a string that is only
`{saved:name}` keeps the saved JSON type; or as `{source: "saved", name}` in
a header or credential part.
- Reading a page value makes later bodies costlier to keep: after a CSRF
token, bodies over about 47 KiB are kept as `unavailable`. When the API
returns larger responses, raise `capture.limits`
(`max_network_body_allocation_bytes`, `max_total_event_allocation_bytes`).
- Request steps follow no redirects: a redirect fails the step, and
`expected_status` cannot name a 3xx status. Use the path the redirect names,
such as the one with the trailing slash.
- Set `expected_status` on every step, so a refused call fails its step and
the later steps report not having run, instead of recording read-backs of
nothing.
Keep it to about 60 to 160 requests. A scenario with no `request` step is
recorded exactly as before.
The capture returns its whole Evidence, every recorded body included, which is
often more than a client shows inline. Before checking coverage, confirm that
every entry of `normalized_result.steps` has `status: "completed"`; if the
client saved the result to a file, search it for `"status":"failed"` and read
that step's `error`. A failed sign-in or write cancels every step after it.
Use the result's `evidence_id` for coverage and export; do not paste the
Evidence back into another tool.
## Check coverage
`inspect_analysis_view` with `source: {kind: "retained-evidence",
evidence_id}` (the capture's id) and `view: {kind: "environment-coverage",
template_rules: ["after-uid-literal", "opaque-token", "sibling-variance",
"prefix-variance"]}`. It reports read tools
answered from the state or replayed (`reads.recorded_reasons`), the served write
tools, and each withheld write with its `reason` and `hint`. It does not apply
the serve-time catalog and state size checks; the export reports those.
Re-record a withheld write at most twice, and only for a recording gap. Stop at
once when the reason is application behaviour, and report the write as
withheld with its hint:
| Reason | What to do |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `no-state-target` | Check the path template and recorded views; record missing reads. The route form may not be supported as a mutation target. |
| `no-observation` | Re-record: read the entity back by id after the write; read it first for an update or delete. |
| `unconfirmed` | Re-record: read the entity and its list after the write. |
| `incomplete-entities` | Re-record: read the entity through every view that lists it. |
| `unexplained-change` | Re-record once with a second, different call of the same write. |
| `no-call` | Re-record once with a write that succeeds. |
| `side-effect` | Stop: the application changes other entities. |
| `contradicted` | Stop: a later read disagrees with the modelled effect. |
| `body-unusable` | Re-record a UI write once with `request` steps (its body was not exposed); else stop. |
Use new scratch names on each attempt, and delete what an earlier attempt
created before recording again (through the UI, or a `request` step with the
id that capture shows): the environment starts from what the recording first
reads, so a leftover object becomes seeded data that write tasks then find.
New browser scenario captures include request times, so a fully timed capture
can learn a write clock and serve updates that stamp modification times. Older
captures or ones missing a request start keep record order and learn no clock.
When coverage reports that limitation, record again with the current version
before concluding that the timestamp change cannot be served.
## Export and hand off
`export_environment_bundle` with `source: {kind: "retained-evidence",
evidence_id}` (or `{kind: "evidence-file", path}`), an absolute
`output_directory` that does not exist yet and whose parent does, and a `name`
(required when the application runs on `localhost` or an IP address). It returns
the coverage again, `tool_count`, the written files and warnings; `dry_run:
true` reports without writing. The bundle installs `rea-agents` of this REA's
version from the registry; when the user runs an unreleased REA build, pass the
`package_tarball` they give you (a packed `rea-agents` tarball) instead.
Tell the user how many tools answer from the state, which writes are served and
which are withheld and why, and the run command (`agent-env run <folder>`). Say
that the bundle ships the recorded responses and the capture's local paths
(for a browser scenario, the browser executable's path), and
list any `credential-named-members` warning pointers for review. Never publish
or share the bundle yourself.
See
[agent environments](https://github.com/morluto/rea/blob/main/docs/agent-environments.md)
for what the state, replay and the bundle establish, and
[evidence workflows](evidence-workflows.md) for the manifest and state views.
@@ -11,6 +11,44 @@ returned inline. When complete `inspect_binary_layout` or
`inspect_analysis_view` for a summary, one section/symbol/module, or a stable
page instead of repeating the producer.
For any logical Evidence source that advertises retained references, pass
`{kind: "retained-evidence", evidence_id}` instead of copying the complete
record back into a call. The same contract covers individual, optional, array
and nested sources across native, process, JavaScript, managed, mobile and WASM
workflows. Sources can independently mix inline records and exact references.
Resolution preserves the source's identity, provenance, coverage, partial facts
and unknowns; it neither reruns analysis nor makes incomplete facts complete.
Keep domain role and identity requirements: a native dossier cannot substitute
for a managed member observation, for example.
When starting from a summary or inspection page, use its
`normalized_result.parent_evidence_id` to reference the complete source.
The view's own `evidence_id` names
only that view. `compare_artifacts` explicitly supports artifact summary inputs
and resolves their parents; do not assume every consumer does. When a workflow
follows source links, keep those linked records available too.
MCP references belong to the current connection's ledger. If the ID is known,
pass it directly; no discovery call or provider setup is needed. If it is missing,
import the exported bundle or supply complete inline Evidence. Do not retry an
unchanged reference or rerun a producer merely to move an existing record.
Export before closing the session when the source must survive it. For CLI JSON
workflows that advertise `--evidence-bundle`, the same reference JSON resolves
against one explicitly selected authenticated bundle:
```bash
rea trace-application-feature request.json --evidence-bundle /absolute/path/evidence.json --json
```
This includes JavaScript traces/comparisons and runtime reconciliation, managed
reconstruction/native verification/graph projection, Android/Apple/Harmony graph
projection, WASM interface comparison and retained analysis views. The managed
member comparison CLI still takes two assembly paths; process comparison takes
two capture Evidence JSON paths. Check command help for the installed version.
An input bundle grants no implicit file lookup: `evidence-file` and historical
graph file selectors are accepted only by contracts advertising them. See the
[CLI reuse guide](https://github.com/morluto/rea/blob/main/docs/cli.md#import-export-and-compare-evidence).
Completed `inspect_artifact` inventory also supports a summary and
`view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json", "main.js"]}`.
Omit `logical_paths` to page all available occurrences; `[]` selects none.
@@ -35,6 +73,14 @@ Parent Evidence ID plus source index preserves same-address
alternatives. Follow `coverage.next_offset` only as far as the question needs;
exhausting retained rows does not establish complete program discovery.
`batch_decompile` returns every body unless you select `detail: "summary"` when
the connected tool advertises it. Use its parent Evidence ID with
`view: {kind: "batch-decompile", collection: "items"}` for body-free rows and
exact `where.indices`, `addresses`, `procedure_addresses` or `statuses`. Add
`include_pseudocode: true` for several chosen bodies, or use
`collection: "pseudocode"` with one row's `index` to page a large body. Batch
limitations aggregate every item, so do not attribute them to one successful row.
Use `record_unknown` to track unresolved questions in the user's investigation
and name the authority or environment still required. Supply supporting and
contradicting evidence IDs. It updates the session registry directly.
@@ -46,18 +92,22 @@ Use comparisons with complete, compatible page sets when claiming equivalence
or absence:
- `compare_artifacts` compares inventory pages by occurrence path, content,
metadata, and graph relations.
metadata, and graph relations. Name each side as
`{kind: "retained-evidence", evidence_id}` (an `inspect_artifact` result,
its nested inventory or its summary) or
`{kind: "evidence-file", path, evidence_id?}` instead of resending Evidence.
- `compare_functions` compares explicit function Evidence, not fuzzy
whole-binary matches. It rejects an `analyze_function` summary reply; pass
the summary's `parent_evidence_id` as a `retained-evidence` reference or
the complete dossier.
- `compare_bundles` compares canonical bundle membership and unknown history.
- `find_changed_behavior` combines existing comparisons; static differences
remain candidates, not causal proof.
remain candidates, not causal proof. Its `comparisons` accept exact references.
- `build_call_path` needs explicit function dossiers from one artifact/provider;
an incomplete frontier makes absence unknown.
pass complete parent IDs in `functions`. An incomplete frontier makes absence
unknown.
- `correlate_static_and_runtime` uses explicit mappings; matching patterns are
hypotheses, not causality.
hypotheses, not causality. Both comparison arrays accept exact references.
- `verify_reconstruction` evaluates a finite typed specification. A pass covers
only declared comparable claims, not global equivalence.
@@ -106,6 +156,13 @@ each plan states why in its `reason`. It replays the capture instead with
`--replay-only` or when the state is unavailable (not derivable, or a catalog
over the discovery budget), and its stopped document's `state` says which
applied.
Use `view: {kind: "environment-coverage"}` with the same rules to see what an
environment built from a capture would serve: read tools answered from the
state or replayed with their reason, and each write tool served or withheld
with a hint about what to record next; it does not apply the serve-time
catalog and state size checks. `rea environment-build <capture.har> <dir>`
inspects, projects and exports in one step and prints the same coverage, or
`null` when the bundle serves replay.
For a PE/ELF export question, `inspect_native_exports` accepts `detail: "summary"`
and retains the complete table. Query `inspect_analysis_view` with
@@ -24,9 +24,13 @@ not an unfiltered request. `detail: "summary"` reduces delivery
and does not by itself reduce analysis work.
ASAR header/index admission defaults to 16 MiB / 16777216 bytes;
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB, set where REA starts) changes it.
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise it. Or
analyze an extracted directory.
an admission error names `asar.max_header_bytes` and its bounds (at most
256 MiB). Call `update_analysis_settings` with that setting, `action: "set"`
and enough bytes, then retry the same full-archive request. No inspection or
REA restart is required. `REA_ASAR_MAX_HEADER_BYTES` seeds future REA processes.
`source_paths`, `detail`, and `max_heap_mb` do not bypass or raise header admission.
Runtime JavaScript timeout/heap defaults are also editable; explicit per-call
`analysis_timeout_ms` and `max_heap_mb` take precedence.
Exact `#alias` specifiers resolve through the importer's
enclosing package `imports` map to package-local files; nested `node_modules`
@@ -74,6 +78,15 @@ classification, and unavailable sources never establish route reachability or
the complete application route denominator. See the
[route inventory guide](https://github.com/morluto/rea/blob/main/docs/javascript-route-inventory.md).
For captured endpoint observations and inference facts, `analyze_web_bundle`
also accepts `view: {kind: "web-bundle", collection: "endpoints", limit: 32}`
with `detail: "summary"`. Read later `endpoints`, `inferences`, or `summary`
pages from the retained parent. Filter before requesting detail; retain
`source_index` together with parent ID and collection, and request `location`
or `basis` when tracing sources. Page exhaustion does not mean producer
completeness: check `completeness`, structured `source_unknowns`, and limitations.
Endpoint candidates remain inferences rather than confirmed network behavior.
## Read a module without repeating analysis
Use the actual returned parent ID, on the same connection, as `source.evidence_id`.
@@ -156,7 +169,10 @@ leaving it, and `both` when the question needs both. Trace traversal includes
containment relationships, so even a narrow seed can expand many nodes; a
bidirectional trace can reach unrelated sibling modules. A module item or page
is preferable when its observations already answer the question. Do not treat
all reached nodes as participants in the feature.
all reached nodes as participants in the feature. For a broad trace, pass
`view: {"kind": "feature-trace", "collection": "paths"}` (or `summary`) to
retain the complete trace and read terminal paths first; page `nodes` or `edges`
with exact `where.node_ids` through `inspect_analysis_view` as needed.
Reuse retained parent Evidence when the server advertises it, or supply complete
Evidence inline. Include Hopper or Ghidra Evidence only when its artifact digest
@@ -223,10 +239,19 @@ This notation is
a template: replace it with the actual returned ID. Versions before 4.1.0 accept only full
inline Evidence. Use the exact ID
returned by the producer on the same MCP connection. Resolution does not run
analysis or select a provider; findings remain inline. Native observation
arrays still take full Evidence. `close_binary` clears retained references;
export a bundle before closing, or supply portable inline Evidence in another
connection. A missing reference includes its ID and recovery guidance.
analysis or select a provider; findings remain inline. For summary or inspection
replies, select the complete `normalized_result.parent_evidence_id`. Native
observation arrays and runtime reconciliation's `static_layers[].analysis` and
`runtime_observations[]` also accept exact references where advertised. Each
source can independently be inline or retained, preserving identity, coverage
and unknowns. CLI workflows advertising `--evidence-bundle` resolve these inputs
from the explicitly selected bundle. `close_binary` releases the engine while
retaining references and complete Unknown history on the same MCP connection.
`reset_investigation` explicitly clears them; optional `save_path` checkpoints the
complete bundle before clearing. Import that bundle in another connection to
restore its original references. Retention is bounded, in-memory and never silently
evicted; `binary_session.investigation` reports the budget. A missing reference
includes its ID and recovery guidance.
For exact file/native metadata or processing gaps, use
`view: {kind: "javascript-facts", where: {paths: ["package.json"]}, property_keys: ["name", "operation", "analysis_status"], limit: 32}`.
@@ -1,8 +1,11 @@
# Native, managed, and packaged artifacts
For release comparisons, export `list_procedures` Evidence from each target before
closing it. Close clears session records: import both bundles after collecting the
two targets, or supply portable inline Evidence. `match_native_functions` pairs unique exact names and, when supplied,
For release comparisons on one MCP connection, retain `list_procedures` Evidence
from each target and reuse its exact IDs after target switches or `close_binary`.
For a handoff or new process, export the complete bundle and import it there,
including its source closure. `reset_investigation` explicitly clears retained
records; optional `save_path` checkpoints them before clearing.
`match_native_functions` pairs unique exact names and, when supplied,
complete observed body bytes. Reuse optional same-context `list_names`,
`procedure_info`, `read_bytes` and existing `analyze_function` dossiers as
`left_observations`/`right_observations`; an MCP `analyze_function` summary
@@ -13,6 +16,20 @@ Select columns, batch IDs and page individual facts; aliases remain unresolved.
Unpaired observations do not prove additions/removals. Existing matched dossier
pairs include inputs for `compare_functions`; otherwise analyze just chosen pairs.
Aggregate completed comparisons with `find_changed_behavior`, selecting each
comparison's exact `retained-evidence` ID instead of resending full records.
Inline comparison Evidence remains accepted. References belong to the current
server: after handoff, import the original bundle including its source closure.
Do not substitute dossier IDs or summary views. Static differences remain
behavior candidates, with runtime behavior unknown unless separately observed.
For a matching representation refusal, inspect `details.limit` and raise
`native.matching_representation_bytes` with `update_analysis_settings` before
retrying the unchanged MCP request. CLI launch seed:
`REA_NATIVE_MATCHING_REPRESENTATION_BYTES`. The default is 96 MiB; the policy
range is 1 byte to 1 GiB and does not bound process RSS. Keep the complete
inventories: splitting them changes global name/body uniqueness.
## Apple app resources
Use `open_bundle_resources` with the selected app path, then `inspect_plist`,
@@ -125,6 +142,19 @@ table or overlay. Bound snapshots, loaded IAT contents, and export-forward
resolution remain unobserved. See
[PE import and delay-load tables](https://github.com/morluto/rea/blob/main/docs/pe-imports.md).
## PE TLS callbacks and load configuration
Use `inspect_pe_load_config` for an explicit local PE32/PE32+ image when the
question concerns code that runs before the entry point through TLS callbacks,
or the security cookie, SafeSEH, CFG, EH-continuation and XFG declarations in
the load configuration. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-load-config PATH`. Preserve artifact
SHA-256, directory and callback-array file ranges, and callback VAs as
hexadecimal strings. Fields outside the declared `Size` are `null`. Guard-flag
mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
## ELF note tables
Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
@@ -148,6 +178,25 @@ uppercase image UUIDs, and integer literals that exceed IEEE-754 range. Only
no crash result. Opened Mach-O and dSYM UUID joins remain unobserved. See
[Apple IPS crash reports](https://github.com/morluto/rea/blob/main/docs/apple-ips.md).
## Linux kernel modules
Use `inspect_kernel_module` for an explicit local ELF32/ELF64 relocatable
object when the question concerns `.modinfo` license, vermagic, alias,
depends, or parm strings, the `__versions` symbol CRCs, or an appended module
signature trailer. No `open_binary`, Linux host, or `modinfo` program is
required. The CLI equivalent is `inspect-kernel-module PATH`. Preserve
artifact SHA-256, key order, duplicate keys, CRC hex, and section file ranges.
A missing section is observed absence when section names are known, not proof
the module never carried that metadata. `.gnu.linkonce.this_module` is reported
as present or absent; `struct module` stays unknown because its layout changes
with the kernel version. `__ksymtab` records are not decoded. The PKCS#7 range
and its SHA-256 are observed; certificate, signer, and digest coverage remain
unvalidated. An absent trailer is not proof the module is unsigned in another
container. gzip, xz, and zstd whole-file containers are unsupported and name
`external_decompression` as the next probe: use an external codec utility to
decompress into a separate file, then call `inspect_kernel_module` on that file. See
[Linux kernel modules](https://github.com/morluto/rea/blob/main/docs/kernel-modules.md).
## CPython pyc files
Use `inspect_python_pyc` for an explicit local CPython 3.7–3.13 `.pyc` when the
@@ -284,40 +333,62 @@ run, use `observe_native_calls` with explicit breakpoints and a bounded
`duration_ms`/`max_events`. It launches a new process under LLDB, so the target
runs and may change files or use the network. Hardened-runtime targets need the
`get-task-allow` entitlement.
Function breakpoints use exact LLDB runtime names: a Darwin C `_main` static symbol
may be `main` at runtime. REA does not translate names. Set `module` to the image
Function breakpoints use exact LLDB runtime names: pass an `inspect_macho` export
or import `runtime_name` (`main`), not its symbol-table `name` (`_main`). REA does
not translate breakpoint names. Set `module` to the image
file name when selecting that image only; otherwise loaded system images also
match. Inspect retained locations and module paths before attributing a call.
Use `binary_session` with no arguments to check the open target, selected
provider, and alignment. Its default `result.tool_availability` carries a
complete inventory grouped by shared facts. Expand each group's `names` and
inherit the inventory-level client requirements unless a group overrides them;
availability, reasons, remediation, modes, and flags remain inline. The
`effects` and `annotations` arrays name true flags from fixed vocabularies, so an
omitted known flag is false. `tools/list` remains the canonical source for full
tool schemas when target or provider state changes.
provider, and alignment. Its default summary `result.tool_availability` carries
the complete tool inventory grouped by current status. Expand each group's
`names` and inherit the inventory-level client requirements unless a group
overrides them; availability, reasons, remediation, and modes remain inline.
Provider candidates keep availability and target-support status, code, and
reason. `omitted_fields` names what the summary leaves out; `{"detail":
"complete"}` adds each unavailable candidate's `remediation`, provider
diagnostics, candidate capability sets, and tool surface, `effects`, and
`annotations`. Those arrays name true flags from fixed
vocabularies, so an omitted known flag is false. `tools/list` remains the
canonical source for full tool schemas when target or provider state changes.
Provider capability groups carry provider identity and ordered limitations.
Prepend each set's `shared_limitations` to a group's `limitations` to recover the
operation's full ordered list. Candidate capabilities describe possible
providers. Top-level session capabilities describe the current route, and
operation's full ordered list. Candidate capabilities, returned with complete
detail, describe possible providers. Top-level session capabilities describe the current route, and
`analysis_provider_binding` identifies the selected provider.
## Reuse caller-selected Ghidra analysis
When the caller supplies an expanded project, configure `REA_GHIDRA_PROJECT_PATH`
(the `.gpr` marker) and optionally `REA_GHIDRA_PROGRAM_NAME` (one root program),
then restart/reconnect before `open_binary`. CLI uses the same configuration.
REA opens only a fingerprint-bound owned copy with `-process -noanalysis -readOnly`;
original project files are not opened by the engine or saved. Database edits,
import overrides and repair scripts are unavailable in this mode. Treat original
analysis completeness/settings as unknown and prior edits as caller-project state.
The program's recorded import digest must match the target; changed project content
requires reopening for a fresh profile. See the
[setup and preparation guide](https://github.com/morluto/rea/blob/main/docs/installation.md#reuse-an-existing-ghidra-project).
## Ghidra startup deadline recovery
`provider_timeout` during the first Ghidra query can cover import, auto-analysis,
bridge startup, and readiness. The default 330,000 ms is configurable through
`REA_GHIDRA_STARTUP_TIMEOUT_MS`; a smaller query still needs the same cold import.
Preserve useful Evidence before closing the session or restarting REA. Check
`close_binary` and resolve reported cleanup failures. Set a larger startup budget
in the environment that launches REA, then restart REA and reconnect the MCP
client before reopening the same target. Closing and reopening a binary in the
existing server keeps its original configuration and starts import again.
Environment changes in an agent's shell do not update an already running MCP
server. If its launch configuration cannot be changed, report that constraint
and the exact retry setting instead of claiming a fixed technical limit.
When the error names `ghidra.startup_timeout_ms`, call
`update_analysis_settings` with `action: "set"` and a larger value within the
reported bounds. Resolve retained cleanup before the next startup attempt.
No REA restart is needed; an already accepted startup keeps its original
budget. A new attempt repeats import. Preserve useful Evidence before closing
and reopening the target when remediation requires that lifecycle step.
For a per-function timeout, update `ghidra.decompile_timeout_seconds` and retry
the same function on the existing Program. Explicit `null` removes that
per-function deadline. Inspection is optional when the error supplies the
setting and bounds. Runtime updates disappear when REA exits; environment
changes only seed future REA processes.
The client's tool-call deadline must allow startup plus the requested query;
server settings and progress notifications cannot override it. CLI analysis
@@ -363,6 +434,21 @@ encoded in bounded batches.
The 25000 ms Cutter request deadline and the 256-candidate discovery budget
still have no runtime override.
## Selected native facts and call graphs
For a large `get_call_graph`, or repeated `procedure_address`,
`procedure_callees`, `procedure_callers` and `resolve_containing_procedure`
queries, select `detail: "summary"` when the first 64 recorded observations
suffice. Read further `native-observation` pages from the returned parent Evidence
ID. Graph message IDs index that page's exact-text dictionary; keep level,
source index, failed nodes, and partial/unknown relationship coverage.
Read referenced source context through `native-context` facets `overview`,
`analysis-profile`, `limitations` and `result-limitations`. Reuse inspected
context/profile values only for the same digest; until caveats are read,
interpretation beyond the returned facts remains unknown. Complete Evidence
remains retained and exportable. Small complete replies can cost fewer tokens
than additional context/page calls; count the completed task and discovery.
## Static memory and original file bytes
For Ghidra, `read_bytes` reads initialized analysis memory, including loader
@@ -390,6 +476,14 @@ loaded bytes, file mappings, relocations and the entry against the immutable
snapshot. Keep `mismatch` and `unsupported` explicit. Verified import covers the
reported static image, not runtime DOS or PC-98 hardware behavior.
For a large load-image report, select `detail: "summary"` and query its
`parent_evidence_id` with `inspect_analysis_view` kind `native-load-image`.
Choose a collection such as `relocations` or `mappings`, select only needed
fields, and page with offset/limit. Original indices preserve same-address
alternatives. Null collections remain unrecorded or inapplicable; paging does
not upgrade unsupported verification. CLI requires `--evidence_output` for
summary delivery so complete source Evidence remains accessible.
For Ghidra decompilation, `procedure_pseudo_code` also accepts `procedure: "file-offset:0x..."`; CLI `rea decompile <path> --file-offset=0x... --provider ghidra` resolves the source byte offset internally. An unresolved 0x-prefixed numeric procedure selector also falls back to a unique file mapping. Existing symbols and analysis addresses win; multiple mappings fail as ambiguous. Preserve the returned file-offset and address locations and raw selection observations. ELF virtual addresses are distinct from file offsets; never assume an arbitrary offset plus image base is an analysis address.
Complete function body ranges use inclusive ends and can contain gaps. Do not
@@ -433,6 +527,11 @@ names), coverage and per-collection counts. Page rows with
`view: {kind: "managed-members", collection: "call_edges", offset: 0, limit: 64}`
over `types`, `exported_types`, `fields`, `methods`, `member_refs`, `call_edges`,
`field_accesses` or `user_strings`.
Use exact `names` and `declaring_types` filters before paging supported
collections. For a compact method lookup, request `fields: ["name",
"declaring_type", "signature"]`; omit `body` to avoid anchors. Identity tokens,
original `source_index`, module/MVID and source coverage remain. Check
`unresolved_filter_rows` before interpreting an empty match as absence.
### .NET NativeAOT
@@ -490,6 +589,16 @@ REA. Symlinks and encrypted entries are inventory facts, not extractable files.
The result reports the materialized directory; callers do not choose the
destination. No separate permission grant is needed.
For classic iNES cartridges, `open_binary` recognizes the byte signature without
an executable architecture. `inspect_artifact` retains header facts and each
PRG/CHR file unit's offset, length and SHA-256; its nested inventory's
`ines.reconstruction` verifies the actual ordered region bytes against the
whole-file digest. Extraction includes the original header, optional trainer
and uninterpreted trailing bytes. These file units are not mapper/CPU bank
maps. NES 2.0, UNIF, dirty legacy headers and truncated payloads are explicitly
refused. No emulator or 6502 analysis is performed. See the
[iNES guide](https://github.com/morluto/rea/blob/main/docs/ines-containers.md).
Native DMG traversal automatically uses a read-only mount on supported macOS
hosts, with an owned temporary mount directory and cleanup. It needs no approval
flag. Unsupported hosts or mount failures remain explicit limitations; do not
@@ -518,9 +627,27 @@ semantics are unsupported. `m68k` requires an even entry and decodes the 68040
superset of the 68000 family; Amiga/Atari containers and vector-table entry
discovery are unsupported. Windows Ghidra P0 admits raw images only for `6502`. See [raw binary guide](https://github.com/morluto/rea/blob/main/docs/raw-binary.md).
Intel HEX, S-record, UF2 and DfuSe files are containers, not raw images:
`inspect_artifact` lists address regions (UF2 per familyID, DfuSe per element
with its alternate setting), and `extract_artifact` writes each region. Open an
extracted region as `raw-binary` with its start address as `entry_point`. UF2
numbering gaps leave coverage unknown; a DfuSe suffix CRC mismatch follows
`integrity_policy`. See [UF2 and DfuSe](https://github.com/morluto/rea/blob/main/docs/uf2-dfuse.md).
### Function annotations in Ghidra
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic and session-scoped; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits until close. CLI `annotate-native-function` returns the updated analysis before discarding the session. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier.
Fresh-import sessions admit annotations; existing-project reuse is read-only.
Use `annotate_native_function` for one function name and/or entry comments, with at least one explicit change. Review its annotation readback and refreshed dossier inline. Changes are atomic and session-scoped; empty comments clear them, omitted fields preserve them. Later MCP calls observe edits until close. CLI `annotate-native-function` returns the updated analysis before discarding the session. Original executable bytes are unchanged; edits invalidate immutable snapshots. Windows P0 does not admit database mutations. For naming only, `set_address_name` / `set_addresses_names` also work with Ghidra: they name function entries or data addresses (primary labels) and return compact per-address success booleans instead of a dossier. Ghidra rejects batch addresses that request the same name or a name another selected address holds; `raw_result.failures` gives each failure's reason.
For `compare_functions`, obtain `analyze_function` Evidence before and after the
edit while the MCP session is open; annotation Evidence is not a comparison
input. Export the needed Evidence before close if comparison will happen later.
Authenticated same-artifact coordinates preserve renamed entries as
`matched / same-entry`, with the canonical `entry_address` inline; name and
exact-text changes remain separate dimensions. This requires compatible
architecture, provider-owned mapping and selected-image commitments. Equal
numeric addresses alone cannot establish correspondence; inspect retained
source Evidence and coverage when the match remains ambiguous.
## Offline PE resource inventory
@@ -548,7 +675,9 @@ For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
interface equality is not runtime compatibility. CLI accepts complete inline
Evidence in `rea compare-wasm-interfaces input.json --json`.
Evidence in `rea compare-wasm-interfaces input.json --json`, or exact references
with `--evidence-bundle /absolute/path/evidence.json`. Use the complete parent
ID when the inspection returned a summary.
## Godot PCK
@@ -568,3 +697,84 @@ page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
## ESP images
For an ESP32-family app or bootloader image, `open_binary` then
`inspect_artifact` with `detail: "summary"`. `root_format_metadata` holds the
observed `header` and `descriptor` (its `kind` is the app or bootloader role)
and the derived `chip`, `checksum`, `appended_sha256` and
`undescribed_trailing` ranges. Segment rows (`segment-NN.bin`) carry
`load_addr`, `classification` (`mapped`, `loaded`, `not-loaded` or `unknown`,
per ESP-IDF `should_map`/`should_load`; RAM ranges are not checked) and
`source_location`; select them with `inspect_analysis_view` and
`logical_paths`. Signature blocks are `unverified`; `public_key_sha256`
matches eFuse key digests.
Unknown `source_coverage` means truncation or undescribed trailing bytes, such
as the flash segments of a `--ram-only-header` image; do not infer absence
from it. Erased flash is described. Integrity mismatches are facts, not
refusals. ESP8266 images are refused, and flash-encrypted images are not
recognized. Segments are not disassembled, and RISC-V or Xtensa segments
cannot be opened as raw-binary. See
[ESP images](https://github.com/morluto/rea/blob/main/docs/esp-image.md).
## ESP flash images
A whole-flash dump opens as `esp-flash-image`. `root_format_metadata` holds
`partition_table` (its `method`: `default`, `caller` or a derived
`scan`), `bootloader` and `apps` ESP image facts, the `otadata` records,
`partitions_outside_dump` and `boot_selection`. Every table has a
`boot_selection`: its `basis` names the bootloader's branch (`no-otadata`
without otadata, `none` when the bootloader rejects the table, `unknown`
when a record is missing), and it lists its `assumptions`;
`running_partition` is always `unknown`. Each image names its entry
`path`. With `detail: "summary"` the root kind is `esp-flash-image-summary`
and each image is a compact `summary` (chip, entry point, segment count,
classified load ranges, signature block count, descriptor identity,
integrity); complete detail keeps every segment. Partition rows
(`partitions/<label>.bin`) carry the declared `type`, `subtype`, names,
`flash_address`, `size` and flags; `encrypted` is a storage flag, not
proof of ciphertext. `written_offset`/`written_end` bound the bytes that
are not erased: an app slot whose facts say `erased` can still hold an older
image, and a limitation says so. For a dump read from another flash address, call
`open_binary` with `format: "esp-flash-image"` and `flash_offset`; an
`esp-image` whose `flash_dump` is set names that offset in a next probe; a
relocated table can be named with `partition_table_offset`. No table found
means unknown, often flash encryption. Extraction writes `nvs_keys` as
ordinary data. See
[ESP flash images](https://github.com/morluto/rea/blob/main/docs/esp-flash-image.md).
## Bun standalone executables
A `bun build --compile` executable stores its module graph in Mach-O
`__BUN,__bun`, a PE or ELF `.bun` section, or an appended trailer.
`open_binary` then `inspect_artifact` reports root format `bun-standalone` with
the carrier, structural layout, module count, entry point, raw flags and
compile exec-argv; each module keeps its `/$bunfs/...` source name, loader,
encoding, sizes and SHA-256, with sourcemap, bytecode and module-info regions as
separate children. Use summary root metadata `entry_point_logical_path` as the
exact inventory `logical_paths` selector, then read the selected content node by
its artifact ID; the reader owns virtual-name normalization and indexed fallback. `extract-artifact` writes stored contents through the safe
output tree; route extracted JavaScript to `analyze_javascript_application`.
Layout labels describe record shapes, not a detected compiler version; unknown
or ambiguous layouts are unsupported. Bytecode is retained, not decoded, and the
executable is never run. See
[Bun standalone executables](https://github.com/morluto/rea/blob/main/docs/bun-standalone.md).
@@ -86,6 +86,8 @@ Page-declared WebMCP tools are untrusted inventory and are never invoked by REA.
browser and profile. Check `browser.cleanup` for the outcome.
Request only the DOM, accessibility, or network details needed for the question.
The default final capture contains the URL; step outcomes are reported separately.
For multi-step flows with trees or events, select `detail: "summary"` and page
exact steps, transactions, text slices or events with `inspect_analysis_view` kind `browser-scenario`.
Add a wait action for the expected page state before capturing it. Report that
REA performed the declared actions.
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "rea",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"owner": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"plugins": [
{
"name": "rea",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"source": {
"source": "npm",
"package": "rea-agents",
"version": "6.8.0"
},
"category": "development"
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "rea",
"version": "6.8.0",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"author": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"homepage": "https://rea.tools/",
"repository": "https://github.com/morluto/rea",
"license": "MIT",
"keywords": [
"reverse-engineering",
"binary-analysis",
"decompiler",
"mcp-server"
],
"skills": "./skills",
"mcpServers": {
"rea": {
"command": "node",
"args": [
"${CLAUDE_PLUGIN_ROOT}/scripts/plugin-mcp-launcher.mjs",
"6.8.0"
]
}
},
"icon": "./assets/rea-plugin.png"
}
+13
View File
@@ -0,0 +1,13 @@
{
"mcpServers": {
"rea": {
"command": "node",
"args": [
"./scripts/plugin-mcp-launcher.mjs",
"6.8.0"
],
"cwd": ".",
"startup_timeout_sec": 30
}
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "rea",
"version": "6.8.0",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"author": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"homepage": "https://rea.tools/",
"repository": "https://github.com/morluto/rea",
"license": "MIT",
"keywords": [
"reverse-engineering",
"binary-analysis",
"decompiler",
"mcp-server"
],
"skills": "./skills",
"mcpServers": "./.codex-plugin/mcp.json",
"interface": {
"displayName": "REA",
"shortDescription": "Reverse engineer anything",
"longDescription": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"developerName": "morluto",
"category": "Developer Tools",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"websiteURL": "https://rea.tools/",
"defaultPrompt": [
"Use REA to explain how this binary implements a feature.",
"Use REA to compare two versions of this application.",
"Use REA to recover the sources from this JavaScript bundle."
],
"composerIcon": "./assets/rea-plugin.png",
"logo": "./assets/rea-plugin.png"
}
}
+12
View File
@@ -0,0 +1,12 @@
# Review changes to security-sensitive automation and dependency inputs.
/.github/CODEOWNERS @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/.github/dependabot.yml @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/.github/workflows/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/.github/actions/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/package.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/package-lock.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/scripts/verify-tools/skills/package.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/scripts/verify-tools/skills/package-lock.json @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/scripts/fixtures/http3-lab-requirements.txt @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/website/requirements.txt @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
/third_party/ @morluto @kaoru0822-kitauji @N0zoM1z0 @gensokyo-dev
+21
View File
@@ -0,0 +1,21 @@
name: REA production code
# Match the existing default-setup scan's local-input threat model.
threat-models: [local]
# Retain root traversal, including future production roots. Hidden directories
# need their own root; use a directory so Python can share this configuration.
paths:
- .
- docs/.vitepress
# Keep the default query suite and all production roots, including bridge/,
# install.sh, runtime scripts, release controllers, and GitHub Actions.
# Exclude test inputs at extraction time so they cannot taint production callers.
paths-ignore:
- tests
- scripts/fixtures
- "**/*.test.*"
- "**/*.fixture.*"
- "**/test_*.py"
- "**/*_test.py"
+33
View File
@@ -0,0 +1,33 @@
version: 2
updates:
- package-ecosystem: npm
directories:
- /
- /scripts/verify-tools/skills
schedule:
interval: weekly
cooldown:
default-days: 7
open-pull-requests-limit: 5
groups:
npm-minor-and-patch:
update-types:
- minor
- patch
- package-ecosystem: pip
directory: /website
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
actions-minor-and-patch:
update-types:
- minor
- patch
+9 -1
View File
@@ -36,6 +36,9 @@ unchecked. Maintainers can help resolve open questions.
Use "none" or "not applicable" only when established. -->
- Semantic owner and earliest changed stage:
<!-- Name the producer of parsed facts or resources and its affected consumers.
Summarize superseded paths removed and any distinct policy, lifecycle,
lazy-loading, or compatibility responsibility retained. -->
- Contribution scope fit and linked maintainer decision:
- CLI and MCP/tool-catalog contract:
- Provider, bridge, target-format, or platform compatibility:
@@ -68,6 +71,10 @@ source, link, or rendered-document checks. -->
- User-visible CLI/MCP output (if applicable):
- Remaining proof gaps:
For boundary fixes, identify the applicable [boundary regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#boundary-regressions)
and [lifecycle regressions](https://github.com/morluto/rea/blob/main/docs/testing.md#lifecycle-regressions)
covered by the evidence above, and explain relevant gaps.
For evidence-bearing changes:
- [ ] Observed, derived, and inferred claims remain distinguishable.
@@ -99,7 +106,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
## Author readiness
<!-- The human owner completes these declarations before requesting review.
<!-- The human owner completes these declarations before requesting review,
unless the PR author is exempt as listed in CONTRIBUTING.md.
The PR author is the owner by default. A different owner must explicitly accept
ownership on this PR.
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
+92 -17
View File
@@ -61,6 +61,9 @@ jobs:
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
code: ${{ steps.plan.outputs.code }}
reverse_tools: ${{ steps.plan.outputs.reverse_tools }}
python: ${{ steps.plan.outputs.python }}
packet_capture: ${{ steps.plan.outputs.packet_capture }}
signal_recording: ${{ steps.plan.outputs.signal_recording }}
sqlite: ${{ steps.plan.outputs.sqlite }}
runtime: ${{ steps.plan.outputs.runtime }}
package: ${{ steps.plan.outputs.package }}
@@ -144,6 +147,9 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Validate source architecture before dependency installation
if: needs.changes.outputs.code == 'true'
run: node scripts/verify/architecture-guards.mjs
- name: Cache npm package downloads
id: npm-cache
uses: ./.github/actions/npm-cache
@@ -264,6 +270,27 @@ jobs:
if-no-files-found: ignore
retention-days: 7
python:
name: Python producer tests
needs: changes
if: needs.changes.outputs.python == 'true' && needs.changes.outputs.tier == 'fast'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Cache npm package downloads
id: npm-cache
uses: ./.github/actions/npm-cache
- run: npm ci
# Analysis-efficiency checks import compiled process modules.
- name: Reuse portable build outputs across runs
uses: ./.github/actions/portable-build-cache
- run: npm run test:prepare
- run: npm run test:python
test-source:
name: Source tests
needs: changes
@@ -391,7 +418,9 @@ jobs:
needs.changes.outputs.web_network == 'true' ||
needs.changes.outputs.web_runtime == 'true' ||
needs.changes.outputs.web_source_map == 'true' ||
needs.changes.outputs.agentenv == 'true'
needs.changes.outputs.agentenv == 'true' ||
needs.changes.outputs.packet_capture == 'true' ||
needs.changes.outputs.signal_recording == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
@@ -750,9 +779,23 @@ jobs:
npm run verify:legacy-simulator
npm run verify:apple-dispatch
npm run verify:interface-builder
- name: Verify portable iNES CLI, MCP and source boundaries
run: >-
npm run test:focused --
tests/acceptance/inesContainer.test.ts
tests/boundary/filesystem/inesArtifact.test.ts
tests/boundary/filesystem/inesCancellation.test.ts
- name: Run native Apple and process boundary regressions
run: >-
npm run test:focused --
tests/boundary/providers/hopper/hopperMacLaunchArguments.test.ts
tests/acceptance/providers/hopperStartupRecovery.test.ts
tests/boundary/providers/hopper/hopperClient.helperFailure.test.ts
tests/boundary/providers/hopper/bridgeBootstrapCleanup.test.ts
tests/boundary/providers/hopper/bridgeTargetLease.integration.test.ts
tests/acceptance/artifactDmgAttachmentRecoveryCli.test.ts
tests/boundary/providers/native/nativeDmgArtifactReader.test.ts
tests/process-global/dmgCancellationReason.test.ts
tests/boundary/filesystem/installScript.test.ts
tests/boundary/filesystem/nibNativeHierarchy.test.ts
tests/boundary/filesystem/interfaceBuilderBundle.test.ts
@@ -762,9 +805,13 @@ jobs:
tests/boundary/cli/nativeDemangleCli.test.ts
tests/boundary/cli/nativeDispatchPropertyAttributes.test.ts
tests/boundary/mcp/nativeDemangleMcp.test.ts
tests/boundary/mcp/nativeMachoSummaryMcp.test.ts
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts
tests/boundary/process/nativeUiVerifierLane.test.ts
tests/boundary/cli/nativeSignatureCli.test.ts
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
tests/boundary/cli/nativeMachoEncryptionCli.test.ts
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
src/process/capture/FilesystemSnapshot.test.ts
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
@@ -852,12 +899,31 @@ jobs:
# The matching native artifact establishes real Job Object and private
# runtime boundaries for these fixtures. Ghidra and Java are not needed;
# real engine claims remain in the separate controlled-provider workflow.
# Run native proofs before the broad curated suite so an unrelated
# fixture failure cannot hide the real control and recovery checks.
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: windows-process-boundary-fixture
path: .cache/windows-process-boundary/
# Passing the fixture adds the breakaway, caller/child token, environment
# semantics and ancestor reparse observations, which the lane skips
# entirely when it is absent.
- run: >-
npm run verify:windows-native -- .
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: windows-native-inspection-fixture
path: .cache/windows-native-inspection-fixture/
- name: Verify native inspection failure ownership and recovery
run: node scripts/verify-windows-native-inspection.mjs
- name: Run curated Windows capability tests
run: >-
npx vitest run --coverage.enabled=false
src/process/capture/FilesystemSnapshot.test.ts
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
tests/boundary/filesystem/typescriptCompiler.test.ts
tests/boundary/filesystem/releaseProposal.test.ts
tests/boundary/filesystem/binaryTarget.test.ts
src/contracts/sessionPathInputs.test.ts
tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts
@@ -867,6 +933,7 @@ jobs:
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
tests/boundary/filesystem/setupUnavailableProvider.test.ts
tests/boundary/filesystem/windowsNpxRegistration.test.ts
tests/boundary/filesystem/claudeDesktopSkillUninstallWindows.test.ts
tests/boundary/filesystem/clientConfigurationEnvironment.test.ts
tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts
tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts
@@ -878,6 +945,7 @@ jobs:
tests/boundary/process/packageNpmInvocation.test.ts
tests/boundary/process/packedBridgeVerification.test.ts
tests/boundary/process/windowsOutputCapture.test.ts
tests/boundary/process/windowsExecutableSelection.test.ts
tests/acceptance/webModuleCli.test.ts
tests/acceptance/webSourceLocationCli.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
@@ -902,22 +970,6 @@ jobs:
tests/process-global/runnerCancellation.test.ts
tests/process-global/windowsOwnedCommand.test.ts
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: windows-process-boundary-fixture
path: .cache/windows-process-boundary/
# Passing the fixture adds the breakaway, caller/child token, environment
# semantics and ancestor reparse observations, which the lane skips
# entirely when it is absent.
- run: >-
npm run verify:windows-native -- .
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: windows-native-inspection-fixture
path: .cache/windows-native-inspection-fixture/
- name: Verify native inspection failure ownership and recovery
run: node scripts/verify-windows-native-inspection.mjs
- name: Verify real Node Inspector discovery and observation
timeout-minutes: 10
env:
@@ -1078,6 +1130,26 @@ jobs:
build_cache_artifact: rea-build-cache
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
packet_capture:
needs: [changes, static, build]
if: needs.changes.outputs.packet_capture == 'true'
secrets:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
uses: ./.github/workflows/real-packet-capture.yml
with:
build_cache_artifact: rea-build-cache
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
signal_recording:
needs: [changes, static, build]
if: needs.changes.outputs.signal_recording == 'true'
secrets:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
uses: ./.github/workflows/real-signal-recording.yml
with:
build_cache_artifact: rea-build-cache
remote_cache_write: ${{ inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
nativeaot:
needs: [changes, static]
if: needs.changes.outputs.nativeaot == 'true'
@@ -1104,6 +1176,9 @@ jobs:
static-deep,
build,
reverse-tools-quality,
python,
packet_capture,
signal_recording,
test-shard,
test-source,
sqlite-runtime,
+61
View File
@@ -0,0 +1,61 @@
name: CodeQL production
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "37 5 * * 1"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: codeql-production-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
analyze:
# A repository administrator must disable default setup before enabling
# this workflow; default setup rejects advanced CodeQL result uploads.
if: vars.REA_CODEQL_ADVANCED == 'true'
name: Analyze production (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
max-parallel: 1
matrix:
language: [javascript-typescript, python, actions]
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Initialize production analysis
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
with:
languages: ${{ matrix.language }}
build-mode: none
config-file: .github/codeql/production.yml
threads: 2
ram: 4096
- name: Analyze production code
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
with:
category: /language:${{ matrix.language }}
output: ${{ runner.temp }}/codeql-production-results
threads: 2
ram: 4096
- name: Retain complete analysis evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: codeql-production-${{ matrix.language }}
path: ${{ runner.temp }}/codeql-production-results
if-no-files-found: error
retention-days: 7
@@ -62,3 +62,36 @@ jobs:
> "${probe_root}/output.log" 2>&1 || { cat "${probe_root}/output.log"; exit 1; }
cat "${probe_root}/output.log"
grep -F 'REA_ARM_CONTEXT_OK arm=0 thumb=1 unavailable=null (injected provider value)' "${probe_root}/output.log"
# Each probe imports source-owned bytes at 0x1000, away from the Cortex-M
# vector symbols that ARMCortex.pspec defines at 0, and decodes them from
# the language's default context, so M-profile Thumb state must hold at
# the first byte. The bytes are never executed.
- name: Decode M-profile ARM and Xtensa bytes in their default context
shell: bash
run: |
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
export JAVA_HOME="${!java_home_variable}"
probe_root="${RUNNER_TEMP}/rea-decode"
mkdir -p "${probe_root}"
# mrs r0, msp; bx lr
printf '\357\363\010\200\160\107' > "${probe_root}/cortex.bin"
# mrs r0, msplim (ARMv8-M only); bx lr
printf '\357\363\012\200\160\107' > "${probe_root}/v8m.bin"
# entry a1, 32; retw.n
printf '\066\101\000\035\360' > "${probe_root}/xtensa.bin"
decode() {
local name="$1" language="$2"
shift 2
mkdir -p "${probe_root}/${name}-project"
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/${name}-project" Decode \
-import "${probe_root}/${name}.bin" -loader BinaryLoader -loader-baseAddr 0x1000 \
-processor "${language}" -noanalysis -readOnly -deleteProject \
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra" \
-postScript ReaDecodeProbe.java "${language}" "$@" \
> "${probe_root}/${name}.log" 2>&1 || { cat "${probe_root}/${name}.log"; exit 1; }
cat "${probe_root}/${name}.log"
grep -F "REA_DECODE_OK ${language} $*" "${probe_root}/${name}.log"
}
decode cortex ARM:LE:32:Cortex mrs/4/msp bx/2/lr
decode v8m ARM:LE:32:v8-m mrs/4/msplim bx/2/lr
decode xtensa Xtensa:LE:32:default entry/3/a1 retw.n/2
@@ -0,0 +1,63 @@
name: Real Android metadata bridge
on:
push:
branches: ["**"]
paths:
- .github/workflows/real-android-metadata.yml
- bridge/android/**
- src/android/JadxRelease.ts
- scripts/fixtures/android/**
- tests/conformance/android/**
- tests/fixtures/android/static-initializers/**
- tests/fixtures/android/direct-invokes/**
- tests/fixtures/android/external-invokes/**
- .nvmrc
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: android-metadata-${{ github.ref }}
cancel-in-progress: true
jobs:
android-metadata:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Fetch checksum-pinned engine and public APK fixture
run: node scripts/fixtures/android/fetch.mjs "$RUNNER_TEMP/rea-android"
- name: Compile and exercise the real metadata bridge
shell: bash
run: |
export JAVA_HOME="$JAVA_HOME_21_X64"
android_root="$RUNNER_TEMP/rea-android"
classes="$android_root/classes"
jar="$android_root/jadx-headless-mcp-0.7.1-all.jar"
mkdir -p "$classes"
"$JAVA_HOME/bin/javac" -J-Xmx512m -J-XX:ActiveProcessorCount=2 \
-classpath "$jar" -d "$classes" \
bridge/android/ReaJadxBridge.java \
tests/conformance/android/ReaJadxMetadataRegression.java \
tests/conformance/android/ReaJadxExternalReferencesRegression.java
"$JAVA_HOME/bin/java" -Xmx512m -XX:ActiveProcessorCount=2 \
-classpath "$classes:$jar" ReaJadxMetadataRegression \
"$android_root/ApiDemos-debug.apk" \
"$GITHUB_WORKSPACE/tests/fixtures/android/static-initializers" \
"$classes" "$GITHUB_WORKSPACE/tests/fixtures/android/direct-invokes" \
| tee "$android_root/metadata.log"
grep -F 'RAW_CLASS_SELECTOR_REGRESSION_PASS nested=true metadata=true references=true' \
"$android_root/metadata.log"
"$JAVA_HOME/bin/java" -Xmx512m -XX:ActiveProcessorCount=2 \
-classpath "$classes:$jar" ReaJadxExternalReferencesRegression \
"$GITHUB_WORKSPACE/tests/fixtures/android/external-invokes" "$classes" \
| tee "$android_root/external-references.log"
grep -F 'EXTERNAL_REFERENCE_REGRESSION_PASS prototypes=2 using_nodes=3 sites=4' \
"$android_root/external-references.log"
+6
View File
@@ -55,6 +55,12 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install AArch64 and ELF32 cross toolchains for source-owned fixtures
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
gcc-aarch64-linux-gnu gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
libc6-dev-arm64-cross libc6-dev-i386-cross libc6-dev-armel-cross libc6-dev-mips-cross libc6-dev-mipsel-cross
- name: Acquire exact upstream libraries in an isolated runner environment
shell: bash
run: |
+7 -1
View File
@@ -48,7 +48,13 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- run: npm run verify:browser
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
# The preceding verifier builds the runtime used by this request journey.
- run: dbus-run-session -- node scripts/verify-browser-request.mjs
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
+37 -31
View File
@@ -1,39 +1,35 @@
name: Real packet capture
on:
workflow_call:
inputs:
build_cache_artifact:
description: Portable build cache artifact from the calling CI run.
required: false
type: string
default: ""
remote_cache_write:
description: Trusted release qualification may publish the remote cache.
required: false
type: boolean
default: false
secrets:
TURBO_TOKEN:
description: Remote cache token. Only trusted callers pass it.
required: false
workflow_dispatch:
pull_request:
paths:
- "tests/boundary/captures/**"
- "tests/boundary/cli/captureCleanup.test.ts"
- "tests/boundary/mcp/packetCaptureMcp.test.ts"
- "src/captures/**"
- "src/pcap/**"
- "src/domain/pcap/**"
- "src/domain/analysisView/pcapCaptureView.ts"
- "src/application/pcap/**"
- "src/contracts/pcap/**"
- "src/server/registerPcapTools.ts"
- "tests/boundary/pcap/**"
- "src/domain/captures/**"
- "src/application/captures/**"
- "src/contracts/captures/**"
- "src/composition/captures.ts"
- "src/cli/captureCommands.ts"
- "src/server/registerCaptureTools.ts"
- "scripts/verify-packet-capture.mjs"
- ".github/workflows/real-packet-capture.yml"
permissions:
contents: read
env:
HUSKY: "0"
# Writes only on the default branch for push, schedule, manual dispatch,
# and workflow_call. Pull requests get an empty token and remote read-only mode.
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
# A called workflow sees the caller event, so workflow_call is not an event
# name here. Writes when remote_cache_write is set, or on the default branch
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
# and use the local cache only.
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
concurrency:
group: real-packet-capture-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
permissions:
contents: read
jobs:
tshark:
runs-on: ubuntu-24.04
@@ -48,8 +44,18 @@ jobs:
uses: ./.github/actions/npm-cache
- run: sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tshark util-linux python3
- run: npm ci
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- run: npm run build:cached
- run: npm run test:focused -- tests/boundary/captures/fieldDiscovery.test.ts src/process/LinuxResourceLimits.test.ts tests/boundary/captures/captureProvider.test.ts tests/boundary/captures/captureDescriptorCleanup.test.ts tests/boundary/pcap/pcapProvider.test.ts tests/boundary/pcap/pcapProviderCrossPlatform.test.ts tests/boundary/captures/resourceLimits.test.ts tests/boundary/captures/packetOutput.test.ts tests/boundary/mcp/packetCaptureMcp.test.ts tests/boundary/cli/captureCleanup.test.ts
- name: Run focused tests and require Linux resource and cleanup cases
run: |
report=".cache/rea-packet-required-tests.json"
mkdir -p .cache
rm -f "$report"
node scripts/run-exact-tests.mjs run --reporter=default --reporter=json --outputFile.json="$report" \
tests/boundary/captures/fieldDiscovery.test.ts src/process/LinuxResourceLimits.test.ts tests/boundary/captures/captureProvider.test.ts tests/boundary/captures/captureDescriptorCleanup.test.ts tests/boundary/pcap/pcapProvider.test.ts tests/boundary/pcap/pcapProviderCrossPlatform.test.ts tests/boundary/captures/resourceLimits.test.ts tests/boundary/captures/packetOutput.test.ts tests/boundary/mcp/packetCaptureMcp.test.ts tests/boundary/cli/captureCleanup.test.ts tests/acceptance/pcapVerifier.test.ts
node scripts/ci/require-provider-tests.mjs packet "$report"
- run: node scripts/verify-packet-capture.mjs
env:
REA_TSHARK_COMMAND: /usr/bin/tshark
+37 -24
View File
@@ -1,32 +1,35 @@
name: Real signal recording
on:
workflow_call:
inputs:
build_cache_artifact:
description: Portable build cache artifact from the calling CI run.
required: false
type: string
default: ""
remote_cache_write:
description: Trusted release qualification may publish the remote cache.
required: false
type: boolean
default: false
secrets:
TURBO_TOKEN:
description: Remote cache token. Only trusted callers pass it.
required: false
workflow_dispatch:
pull_request:
paths:
- "tests/boundary/signals/**"
- "tests/boundary/cli/signalCleanup.test.ts"
- "tests/boundary/mcp/signalRecordingMcp.test.ts"
- "src/signals/**"
- "src/domain/signals/**"
- "src/application/signals/**"
- "src/contracts/signals/**"
- "src/composition/signals.ts"
- "src/cli/signalCommands.ts"
- "src/server/registerSignalTools.ts"
- "scripts/verify-signal-recording.mjs"
- ".github/workflows/real-signal-recording.yml"
permissions:
contents: read
env:
HUSKY: "0"
# Writes only on the default branch for push, schedule, manual dispatch,
# and workflow_call. Pull requests get an empty token and remote read-only mode.
TURBO_TOKEN: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && secrets.TURBO_TOKEN || '' }}"
# A called workflow sees the caller event, so workflow_call is not an event
# name here. Writes when remote_cache_write is set, or on the default branch
# for push, schedule, and manual dispatch. Open pull requests stay empty-token
# and use the local cache only.
TURBO_TOKEN: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && secrets.TURBO_TOKEN || '' }}"
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_CACHE: "${{ (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')) && 'local:rw,remote:rw' || 'local:rw,remote:r' }}"
concurrency:
group: real-signal-recording-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
TURBO_CACHE: "${{ (inputs.remote_cache_write == true || (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'))) && 'local:rw,remote:rw' || 'local:rw' }}"
permissions:
contents: read
jobs:
sigrok:
runs-on: ubuntu-24.04
@@ -41,8 +44,18 @@ jobs:
uses: ./.github/actions/npm-cache
- run: sudo apt-get update && sudo apt-get install -y sigrok-cli util-linux python3
- run: npm ci
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- run: npm run build:cached
- run: npm run test:focused -- tests/boundary/signals/decoderDiscovery.test.ts src/process/LinuxResourceLimits.test.ts src/domain/signals/annotationTextWindow.test.ts tests/boundary/signals/recordingProvider.test.ts tests/boundary/signals/recordingDescriptorCleanup.test.ts tests/boundary/signals/resourceLimits.test.ts tests/boundary/mcp/signalRecordingMcp.test.ts tests/boundary/cli/signalCleanup.test.ts
- name: Run focused tests and require Linux resource and cleanup cases
run: |
report=".cache/rea-signal-required-tests.json"
mkdir -p .cache
rm -f "$report"
node scripts/run-exact-tests.mjs run --reporter=default --reporter=json --outputFile.json="$report" \
tests/boundary/signals/decoderDiscovery.test.ts src/process/LinuxResourceLimits.test.ts src/domain/signals/annotationTextWindow.test.ts tests/boundary/signals/recordingProvider.test.ts tests/boundary/signals/recordingDescriptorCleanup.test.ts tests/boundary/signals/resourceLimits.test.ts tests/boundary/mcp/signalRecordingMcp.test.ts tests/boundary/cli/signalCleanup.test.ts
node scripts/ci/require-provider-tests.mjs signal "$report"
- run: node scripts/verify-signal-recording.mjs
env:
REA_SIGROK_COMMAND: /usr/bin/sigrok-cli
+2 -1
View File
@@ -44,7 +44,8 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- run: env -u DBUS_SESSION_BUS_ADDRESS npm run verify:browser:runtime
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser:runtime
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
+2 -1
View File
@@ -46,7 +46,8 @@ jobs:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install the pinned compiler into an isolated fixture directory
run: npm install --prefix "$RUNNER_TEMP/rea-web-source-map-compiler" --no-audit --no-fund --package-lock=false [email protected]
- run: npm run verify:browser:source-maps
# Own a fresh bus so Chromium never depends on an inherited session address.
- run: dbus-run-session -- npm run verify:browser:source-maps
env:
# Retain browser launch argv/PID/stderr in the verifier's captured
# CLI failure diagnostics without changing its deadline or retrying.
+4
View File
@@ -21,6 +21,10 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
+50 -8
View File
@@ -54,6 +54,10 @@ jobs:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Set up Node.js for the website Worker checks
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
- name: Install sharing-image dependencies
run: |
sudo apt-get update
@@ -75,10 +79,21 @@ jobs:
PY
- name: Configure Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
# upload-pages-artifact excludes dot directories, including .well-known.
- name: Package the website including agent discovery
run: |
tar --dereference --hard-dereference --directory website/_site \
--exclude=.git --exclude=.github -cf "$RUNNER_TEMP/artifact.tar" .
tar -tf "$RUNNER_TEMP/artifact.tar" \
./.well-known/ard.json ./.well-known/ai-catalog.json \
./.well-known/agent-skills/index.json > /dev/null
- name: Upload the website
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
path: website/_site
name: github-pages
path: ${{ runner.temp }}/artifact.tar
retention-days: 1
if-no-files-found: error
deploy:
needs: prepare
@@ -130,14 +145,24 @@ jobs:
PUBLISH_TARGET: ${{ inputs.target }}
run: |
python3 -u - <<'PY'
import hashlib
import json
import os
import sys
import time
from urllib.error import URLError
from urllib.parse import urljoin
from urllib.parse import urljoin, urlsplit
from urllib.request import Request, urlopen
def open_public(url, accept=None):
headers = {
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
}
if accept:
headers["Accept"] = accept
return urlopen(Request(url, headers=headers), timeout=10)
expected = os.environ["GITHUB_SHA"]
pages_url = os.environ["PAGES_URL"]
if not pages_url:
@@ -153,11 +178,7 @@ jobs:
for name, base in list(pending.items()):
url = urljoin(base, "deployment.json") + "?commit=" + expected
try:
request = Request(url, headers={
"Cache-Control": "no-cache",
"User-Agent": "REA-Website-Verifier/1.0 (+https://github.com/morluto/rea)",
})
with urlopen(request, timeout=10) as response:
with open_public(url) as response:
actual = json.load(response).get("commit")
if actual == expected:
print(f"{name} serves {expected}: {base}")
@@ -172,6 +193,27 @@ jobs:
time.sleep(5)
if pending:
sys.exit("Could not verify published revision on: " + ", ".join(pending))
for name, base in hosts.items():
with open_public(urljoin(base, ".well-known/agent-skills/index.json")) as response:
skill, = json.load(response)["skills"]
# Fetch from each selected host, including the /rea/ mirror.
artifact_url = urljoin(base, urlsplit(skill["url"]).path.lstrip("/"))
with open_public(artifact_url) as response:
digest = "sha256:" + hashlib.sha256(response.read()).hexdigest()
if digest != skill["digest"]:
sys.exit(name + " serves a skill archive with the wrong digest")
for manifest in ("ard.json", "ai-catalog.json"):
with open_public(urljoin(base, ".well-known/" + manifest)) as response:
if not json.load(response).get("entries"):
sys.exit(name + " serves an empty discovery manifest")
if name == "Cloudflare":
with open_public(base, accept="text/markdown") as response:
vary = {value.strip().lower() for value in response.headers.get("Vary", "").split(",")}
if response.headers.get_content_type() != "text/markdown" or "accept" not in vary:
sys.exit("Cloudflare did not negotiate Markdown with Vary: Accept")
if 'rel="ard"' not in response.headers.get("Link", ""):
sys.exit("Cloudflare is missing its discovery Link header")
print(name + " serves the discovery manifests and verified skill archive")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
links = " and ".join(f"[{name}]({url})" for name, url in hosts.items())
summary.write(f"Published commit `{expected}` to {links}.\n")
+1 -1
View File
@@ -50,7 +50,7 @@ Thumbs.db
# Local reference sources and analysis artifacts
/_reference/
/experiments/analysis-efficiency/results/
/experiments/analysis-efficiency/
# Generated website download
/website/public/examples/notes-example.zip
+4 -1
View File
@@ -10,7 +10,10 @@
"node_modules/**",
"CHANGELOG.md",
"package-lock.json",
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
"_reference/**",
"third_party/**"
"third_party/**",
".codex-plugin/mcp.json"
]
}
+95 -3
View File
@@ -1,7 +1,13 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn", "oxc"],
"plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
"rules": {
"rea/no-buffer-deep-equality": "error",
"rea/cancellation-cause": "error",
"no-async-promise-executor": "error",
"no-unreachable": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-escape": ["warn", { "allowRegexCharacters": ["-"] }],
"complexity": ["warn", 20],
"max-depth": ["warn", 4],
"max-lines": [
@@ -23,7 +29,38 @@
"max-params": ["warn", 4],
"no-var": "warn",
"no-var-requires": "error",
"typescript/no-import-type-side-effects": "error"
"typescript/no-import-type-side-effects": "error",
"vitest/no-standalone-expect": "off",
"vitest/no-conditional-expect": "off",
"vitest/expect-expect": "off",
"vitest/no-disabled-tests": "off",
"vitest/require-mock-type-parameters": "off",
"vitest/require-to-throw-message": "off",
"import/no-duplicates": [
"warn",
{
"preferInline": false
}
],
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"rea/no-evidence-json-preadmission": "error",
"rea/no-positional-tool-contract": "error",
"rea/no-fixture-root-cleanup": "error",
"typescript/no-floating-promises": "error",
"typescript/no-misused-promises": "warn",
"typescript/return-await": ["error", "error-handling-correctness-only"],
"typescript/switch-exhaustiveness-check": [
"error",
{
"considerDefaultExhaustiveForUnions": true
}
],
"rea/no-shared-module-mock": "error"
},
"overrides": [
{
@@ -36,6 +73,12 @@
"no-var-requires": "off"
}
},
{
"files": ["src/**/*.ts"],
"rules": {
"typescript/no-misused-promises": "error"
}
},
{
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
"rules": {
@@ -47,7 +90,8 @@
"skipBlankLines": true,
"skipComments": true
}
]
],
"typescript/no-misused-promises": "warn"
}
},
{
@@ -91,6 +135,45 @@
}
]
}
},
{
"files": ["tests/**/*.test.ts", "src/**/*.test.ts"],
"rules": {
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"vitest/valid-expect-in-promise": "error",
"vitest/no-focused-tests": "error"
}
},
{
"files": [
"scripts/lib/http3-capture-assertions.test.mjs",
"scripts/lib/http3-verifier-lifecycle.test.mjs"
],
"rules": {
"typescript/no-floating-promises": [
"error",
{
"allowForKnownSafeCalls": [
{
"from": "package",
"package": "@types/node",
"name": "test"
}
]
}
]
}
},
{
"files": ["scripts/fixtures/javascript-recovery/code-split/entry.mjs"],
"rules": {
"typescript/no-floating-promises": "off"
}
}
],
"env": {
@@ -109,5 +192,14 @@
".devin",
".factory",
"third_party"
],
"options": {
"reportUnusedDisableDirectives": "error"
},
"jsPlugins": [
{
"name": "rea",
"specifier": "./scripts/lint/index.mjs"
}
]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "6.7.0"
".": "6.8.0"
}
+26 -9
View File
@@ -28,6 +28,10 @@ Assess brittleness in touched code during implementation, debugging, review, and
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
After confirming a bug, search sibling adapters, consumers and similar workflows
for the same failure. Verify suspected matches, record working comparisons, and
group findings by root cause instead of filing duplicate reports.
When addressing a review thread, inspect the producing boundary and affected consumers for brittleness. Complete directly related refactoring in the same change. Before resolving the thread, explain the root cause, structural fix, validation, and any remaining cleanup.
Parse unknown boundary values and model expected failures with the tagged error algebra and `Result`. Preserve partial facts and meaningful failure reasons. Keep identity and lookup values separate from display formatting; preserve source values and explicit unknowns when normalization loses information. Absolute paths, file URLs, and HTTP paths have different semantics.
@@ -53,9 +57,12 @@ Optimize in this order:
3. CPU and memory. Avoid unnecessary work and copies; bound concurrency,
retained state and serialization growth.
For changes affecting these costs, measure representative completed tasks and
compare equivalent workflows before and after where available. Report token
usage, elapsed time, CPU time and peak process-family memory. Separate cold
Reserve performance measurements for changes that target performance or have a
concrete regression concern. Routine cleanup uses correctness checks; changes
to helper calls, imports, or representations alone do not require synthetic
benchmarks. When measurement is warranted, measure representative completed
tasks and compare equivalent workflows before and after where available. Report
token usage, elapsed time, CPU time and peak process-family memory. Separate cold
and warm runs, label estimates, and state unmeasured costs. Fewer bytes alone
do not establish fewer tokens or faster analysis.
@@ -72,6 +79,9 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
When changing installers or setup recovery, follow
[Checking installers and publisher downloads](docs/testing.md#checking-installers-and-publisher-downloads).
Coordinate expensive checks across agents. If a test unexpectedly consumes heavy CPU, stop its owned process family and profile a bounded representative case before retrying. Inspect production work, fixture generation, subprocesses, and repeated setup; preserve regression coverage when optimizing. Follow the [test resource guidance](docs/testing.md#developer-commands).
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
@@ -80,7 +90,13 @@ Coordinate expensive checks across agents. If a test unexpectedly consumes heavy
For tool design, follow [docs/tool-design.md](docs/tool-design.md): start from the analyst question, reuse existing contracts, preserve caller choices, and return useful evidence inline. Limits must follow real format, protocol, authority, or resource constraints. Account for representation expansion before allocation; derive completeness from examined coverage.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
For user- or agent-facing tool problems, prioritize fixing the owning behavior,
contract, and actionable result or recovery at the point of use. Document the
result in product docs when explanation is needed. Add skill guidance only for
workflow judgment or context that the tool cannot reasonably convey; do not use
a skill as the primary repair for a discoverability or contract defect.
For test changes or provider claims, use [docs/testing.md](docs/testing.md). Select applicable [boundary regressions](docs/testing.md#boundary-regressions) for parser, inventory, snapshot, and projection fixes. Prefer real public CLI/MCP workflows, then production-boundary integration, then producer goldens. Keep module tests for distinct semantics stronger workflows cannot reliably reproduce. Preserve malformed-input, cancellation, cleanup, permission, capacity, and target-identity coverage when pruning. Report unverified coverage explicitly; simulated providers do not establish real-provider behavior.
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
@@ -108,11 +124,12 @@ distinguish source-derived conclusions, proposed checks, and unavailable coverag
Address actionable automated review findings and explain disagreements.
Leave the template's **Author readiness** declarations unchecked for the human
owner to complete. Never attest to human review on their behalf. Keep the PR in
draft until the human declares the PR initially ready. Later pushes do not
invalidate that declaration or require another human sign-off. Keep the PR body
and affected validation current as work changes. The readiness check validates
the filled declaration; CI and automated review results do not establish that
owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates the filled declaration or the author exemption
listed in CONTRIBUTING.md; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
<!-- BEGIN:turborepo-agent-rules -->
+104 -2
View File
@@ -1,5 +1,107 @@
# Changelog
## [6.8.0](https://github.com/morluto/rea/compare/rea-agents-6.7.0...rea-agents-6.8.0) (2026-10-25)
### Highlights
- Build local agent environments from network captures, browser scenarios, or a prompt, then serve derived reads and evidence-gated writes and compare them with fresh application captures.
- Inspect more shipped formats, including GPU code objects, BEAM, Python bytecode, Unity IL2CPP metadata, Godot PCK, PyInstaller, Node.js SEA, devicetree, and additional PE/ELF tables.
- Keep large investigations focused with retained summaries, selected fact queries, configurable recovery limits, and row-oriented CLI exports. Complete Evidence remains available.
### ⚠ BREAKING CHANGES
- **Function analysis:** `analyze_function` now defaults to a retained summary. Set `detail: "complete"` for the previous inline dossier; the CLI retains its complete default and requires `--evidence_output` when selecting a summary. [#5660](https://github.com/morluto/rea/pull/5660). Thanks @CryptoJones, @gensokyo-dev, @RishTheFish21.
- **Accessibility:** Accept JSON numbers and booleans in node `value`, alongside strings and null. Update consumers that previously assumed text-only values. [cc90771](https://github.com/morluto/rea/commit/cc90771af14f045538651615147d8bf86b576549), [#6759](https://github.com/morluto/rea/pull/6759), [#6467](https://github.com/morluto/rea/issues/6467). Thanks @coygeek, @gensokyo-dev, @mvanhorn.
- **JSON Lines:** `--format jsonl` now emits object-collection rows followed by a `$rea: "jsonl/v1"`, `type: "metadata"` record; scalar selections and unsupported reports are refused. Parse control records separately, select a single supported collection when necessary, and use `--format json` for the previous whole-document output. See [JSON Lines framing](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/cli.md#json-lines-record-output). [#5629](https://github.com/morluto/rea/pull/5629), [#5487](https://github.com/morluto/rea/issues/5487). Thanks @gensokyo-dev, @jayykernel, @vijaykiran06.
- **Interface Builder:** Keyed-archive graph identities use decimal table UIDs instead of authored `objectID`/`object-id`. Treat authored IDs as attributes and update persisted graph lookups; ibtool and native NIBArchive identities keep their source semantics. [6fbe67d](https://github.com/morluto/rea/commit/6fbe67d66e3d716db69929503a3a232a7ada0860).
- **Frida:** `instrument-with-frida` JSON returns the canonical Evidence directly. Read `normalized_result.cleanup_error` instead of the former top-level wrapper field. [48f700e](https://github.com/morluto/rea/commit/48f700ee35e0988ed973e5486dc9db33f2a5d944).
- **Reconstruction proof:** ID-array proof records no longer establish verified or ready ledgers. Supply implementation/revision bindings and individual claim outcomes; handle inapplicable, unavailable, and failed proof diagnostics. [f8b65e7](https://github.com/morluto/rea/commit/f8b65e78723b1e2169f94dd1226198ea3139707e).
- **Mach-O imports:** Handle a null import `name` when identity is ambiguous; use `raw_name` and `name_status` to distinguish observed expressions from exact symbols. [5d0d243](https://github.com/morluto/rea/commit/5d0d243a45c9132e1aff75d454ab53d86a74cc31), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b).
- **Node.js:** Use a runtime satisfying `^22.23.4 || ^24.22.1 || >=26.12.1`; Node 23 and 25 are unsupported. Upgrade Node before installing or updating REA; the installer does not upgrade it. [#6315](https://github.com/morluto/rea/pull/6315), [#6283](https://github.com/morluto/rea/issues/6283). Thanks @GamerDubz, @gensokyo-dev, @ShikamaruNaaraa.
- **Browser storage:** Historical hashed storage remains readable, but comparisons stay unknown until both captures are recaptured in the new representation. [#6341](https://github.com/morluto/rea/pull/6341), [#6289](https://github.com/morluto/rea/issues/6289). Thanks @coygeek, @gongfpp.
- **Environment serving:** `environment-serve` serves derived state by default, including write tools whose effects are confirmed by recorded evidence. Use `--replay-only` for stateless replay of manifest tools. Read stop-summary `state` instead of `state_evidence_id`. [#5351](https://github.com/morluto/rea/pull/5351), [#6158](https://github.com/morluto/rea/pull/6158). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **MCP schemas:** Input advertisements default to compact. Set `REA_MCP_INPUT_SCHEMA_PROFILE=full` for complete advertised guidance and nested constraints. [#5975](https://github.com/morluto/rea/pull/5975). Thanks @gensokyo-dev.
- **Browser console arguments:** Accept null `argument_types` and `text_capture.status: "unavailable"` when the protocol argument inventory is omitted or malformed; preserve that uncertainty. [#6016](https://github.com/morluto/rea/pull/6016), [#5934](https://github.com/morluto/rea/issues/5934). Thanks @coygeek, @kaoru0822-kitauji.
- **Reconstruction summaries:** `summary.by_evidence_authority` is keyed by Evidence authority; read the former source-state counts from required `summary.by_source_state`. Regenerate ledgers whose closure digests depended on the old projection. [#5875](https://github.com/morluto/rea/pull/5875), [#5838](https://github.com/morluto/rea/issues/5838). Thanks @coygeek, @kaoru0822-kitauji.
- **Swift bindings:** Conformance `type_name` and `protocol_name` may be null; consume typed `type_binding`/`protocol_binding` evidence. Replace the former Objective-C-specific import-binding type/schema with `MachoImportBinding`. [d0067ba](https://github.com/morluto/rea/commit/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f).
- **WebSocket frames:** Accept null opcodes and preserve unknown frame facts; numeric opcodes are restricted to the four-bit protocol range. [0bc2b42](https://github.com/morluto/rea/commit/0bc2b42001f684beffcf47b70e507443b1a5d320).
- **Resource-only inspection:** Evidence subjects and executable digests may be null. Use `resource_root` and each resource's reported provenance instead of requiring an executable target. [6a6adf7](https://github.com/morluto/rea/commit/6a6adf703fa44100643947d20fbc670f86260b1b).
- **Browser timestamps:** Console and session-timeline timestamps may be null when the protocol supplies no finite/comparable time. Keep unknown time explicit in sorting and comparisons. [690fc51](https://github.com/morluto/rea/commit/690fc518540bae982038a74f47ef7781201785aa), [2a0410f](https://github.com/morluto/rea/commit/2a0410f1717b3126e8a95063d77dea66a597010a).
- **Reference sources:** Read failed root enumeration from `rootFailure` rather than a synthetic failed child at `"."`; preserve the top-level source-graph limitation. [6296fdf](https://github.com/morluto/rea/commit/6296fdf560f1ac4aa55985558edaa98300aacdbc).
- **Filesystem capture:** Accept `entry_unavailable` and `identity_changed` enumeration omission reasons in addition to the previous reason literals. [c85b44e](https://github.com/morluto/rea/commit/c85b44e1b14851c54e65327a4807f79ad5cd5a61). Thanks @rudrakshagarwal4200-ai.
- **APK locales:** Replace numeric legacy selectors such as `es-r419` with `es-419` or `b+es+419`. Two-letter regions remain supported. [29910d1](https://github.com/morluto/rea/commit/29910d16b98fe4b607a7aa15928789a73614b0b9). Thanks @mertkayacs.
- **XML plist scalars:** Recognize `$plist_type: "unknown"` and read `element`, `literal`, and `reason`; unresolved XML scalars use this wrapper instead of the former real/null unknown marker or malformed scalar prefixes. Supported extended UTC years preserve their timestamp. [3537e58](https://github.com/morluto/rea/commit/3537e58c1ab29309f06eabf50bc52adba9817a0c).
### Changes
- **Binary layouts:** Inspect ELF32 i386, little-endian ARM, and little- or big-endian MIPS `EXEC`/`DYN`/`REL` layouts on Linux x64 using a caller-supplied pwntools environment. [#7052](https://github.com/morluto/rea/pull/7052), [#6934](https://github.com/morluto/rea/issues/6934). Thanks @roblennon, @salmanalmutairi1.
- **Agent environments:** Project network captures and Evidence files into manifests, preserve template path pairs and positions, export AgentEnv bundles, and serve manifests as replay environments. [#4954](https://github.com/morluto/rea/pull/4954), [#5293](https://github.com/morluto/rea/pull/5293), [#5664](https://github.com/morluto/rea/pull/5664), [#4999](https://github.com/morluto/rea/pull/4999), [#4975](https://github.com/morluto/rea/pull/4975). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **Capture to environment:** Build environments from browser scenarios or a prompt, extract the initial observed state, and compare the environment with application probe captures. [#5182](https://github.com/morluto/rea/pull/5182), [#6946](https://github.com/morluto/rea/pull/6946), [#5319](https://github.com/morluto/rea/pull/5319), [#5699](https://github.com/morluto/rea/pull/5699). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **State-backed tools:** Render read filters, search, sorting and paging from state; apply confirmed write effects with broader state targets and recording-based write admission. [#5352](https://github.com/morluto/rea/pull/5352), [293832b](https://github.com/morluto/rea/commit/293832bafc9a054914ec514fe906dd4936c7a372), [#5800](https://github.com/morluto/rea/pull/5800), [#5787](https://github.com/morluto/rea/pull/5787), [#5600](https://github.com/morluto/rea/pull/5600), [5375ff0](https://github.com/morluto/rea/commit/5375ff0f9aadb4fd0d46333815aa0431e3df1010), [#6169](https://github.com/morluto/rea/pull/6169), [#6385](https://github.com/morluto/rea/pull/6385), [#6897](https://github.com/morluto/rea/pull/6897). Thanks @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **State learning:** Model application-assigned values, preserve view-specific clock spellings, learn write clocks from scenario timing, derive fields, and learn creates across sorted arrays and referenced entities. [7ec9b6e](https://github.com/morluto/rea/commit/7ec9b6e56e1b3762285dca881db6d900a389ab9c), [#6136](https://github.com/morluto/rea/pull/6136), [957bcfa](https://github.com/morluto/rea/commit/957bcfa149c1a0593bb0aa2466cc56d1d4f62e7d), [#6138](https://github.com/morluto/rea/pull/6138), [#6967](https://github.com/morluto/rea/pull/6967), [#7025](https://github.com/morluto/rea/pull/7025), [#7034](https://github.com/morluto/rea/pull/7034). Thanks @earakely-scale.
- **Recorded writes:** Serve creates whose compatible read-backs add omitted fields or express timestamps at a different precision, and whole-object updates that regenerate observed slugs or modification times. Conflicting observations still withhold writes. State views and bundles carrying the new read-back data need REA 6.8.0 or later to serve. [#7080](https://github.com/morluto/rea/pull/7080). Thanks @earakely-scale.
- **Native investigations:** Page native inventory summaries, match functions across builds, export pseudocode to resumable directories, and select objdump disassembly by symbol. [#5378](https://github.com/morluto/rea/pull/5378), [#5390](https://github.com/morluto/rea/pull/5390), [#6170](https://github.com/morluto/rea/pull/6170), [#5171](https://github.com/morluto/rea/pull/5171), [#4249](https://github.com/morluto/rea/issues/4249), [#5343](https://github.com/morluto/rea/issues/5343), [#5159](https://github.com/morluto/rea/issues/5159). Thanks @coygeek, @CryptoJones, @gensokyo-dev, @kaoru0822-kitauji.
- **Engine workflows:** Bound a single Ghidra decompilation without closing its session, analyze GameCube/Wii DOL executables, and inspect static CUDA/HIP code objects. [#5662](https://github.com/morluto/rea/pull/5662), [#6240](https://github.com/morluto/rea/pull/6240), [#5813](https://github.com/morluto/rea/pull/5813), [#5340](https://github.com/morluto/rea/issues/5340), [#5345](https://github.com/morluto/rea/issues/5345). Thanks @arielfikru, @coygeek, @gensokyo-dev, @RishTheFish21.
- **PE/ELF:** Inspect export tables without an engine, PE imports/delay-loads, Authenticode certificates, TLS callbacks/load configuration, ELF notes, Linux kernel module metadata, and Zstandard section-name tables. [#6278](https://github.com/morluto/rea/pull/6278), [#6509](https://github.com/morluto/rea/pull/6509), [#4830](https://github.com/morluto/rea/pull/4830), [#7008](https://github.com/morluto/rea/pull/7008), [#4837](https://github.com/morluto/rea/pull/4837), [#7010](https://github.com/morluto/rea/pull/7010), [#6987](https://github.com/morluto/rea/pull/6987), [#4057](https://github.com/morluto/rea/issues/4057). Thanks @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia, @ShuaixinHuang.
- **Apple/native metadata:** Inspect IPS crash reports, decode Swift nominal declarations, and select universal Mach-O slices in Go/Rust metadata readers. [#6511](https://github.com/morluto/rea/pull/6511), [#6257](https://github.com/morluto/rea/pull/6257), [#6219](https://github.com/morluto/rea/pull/6219), [#5341](https://github.com/morluto/rea/issues/5341), [#4102](https://github.com/morluto/rea/issues/4102). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @mvanhorn, @Rinkia.
- **Packaged applications:** Triage desktop installation directories and inspect/extract Godot PCK, PyInstaller archives, and Node.js single-executable application blobs. [#5207](https://github.com/morluto/rea/pull/5207), [#3634](https://github.com/morluto/rea/pull/3634), [afa5da7](https://github.com/morluto/rea/commit/afa5da745e0b139e68029a96b776ae30e48ea2b2), [#2433](https://github.com/morluto/rea/pull/2433), [#7000](https://github.com/morluto/rea/pull/7000), [#4399](https://github.com/morluto/rea/issues/4399). Thanks @0xthearchitect, @coygeek, @gensokyo-dev, @mvanhorn.
- **Unity and managed code:** Inventory IL2CPP images/types/methods and metadata versions 24/27; read member names and literals, link native methods and generic instances, and export Ghidra seeds. Project managed call/field-access edges and page managed member summaries. [#3807](https://github.com/morluto/rea/pull/3807), [#5326](https://github.com/morluto/rea/pull/5326), [52d1a72](https://github.com/morluto/rea/commit/52d1a72af047a73b787b8935b032786a34966036), [#5327](https://github.com/morluto/rea/pull/5327), [#5785](https://github.com/morluto/rea/pull/5785), [#6260](https://github.com/morluto/rea/pull/6260), [#5167](https://github.com/morluto/rea/pull/5167), [#3784](https://github.com/morluto/rea/issues/3784), [#5112](https://github.com/morluto/rea/issues/5112), [#4056](https://github.com/morluto/rea/issues/4056), [#5148](https://github.com/morluto/rea/issues/5148). Thanks @8rulerstar, @alexlanghart, @elricfd, @gensokyo-dev, @jaelliot, @kaoru0822-kitauji, @Rinkia.
- **Android:** Inspect APK signing declarations and structured manifest components; report static-final DEX field values. [e93e3b1](https://github.com/morluto/rea/commit/e93e3b1e7d6e7e4f49c2de24b92452ec29e853f8), [#4158](https://github.com/morluto/rea/pull/4158), [#5025](https://github.com/morluto/rea/pull/5025), [#6981](https://github.com/morluto/rea/pull/6981), [#4537](https://github.com/morluto/rea/issues/4537), [#6397](https://github.com/morluto/rea/issues/6397). Thanks @Abelo9996, @BaaRai, @FerroLx, @gensokyo-dev, @sserrgo12.
- **Bytecode and firmware:** Inspect and query CPython pyc and BEAM facts, inspect flattened devicetrees, and attribute MMIO peripheral regions in native disassembly. [#4826](https://github.com/morluto/rea/pull/4826), [#6928](https://github.com/morluto/rea/pull/6928), [#4833](https://github.com/morluto/rea/pull/4833), [#6943](https://github.com/morluto/rea/pull/6943), [#7009](https://github.com/morluto/rea/pull/7009), [#5348](https://github.com/morluto/rea/pull/5348), [#6922](https://github.com/morluto/rea/issues/6922), [#6938](https://github.com/morluto/rea/issues/6938). Thanks @gensokyo-dev, @liziye627-design, @mvanhorn.
- **JavaScript:** Retain queryable static route inventories, link assignment values to writes, and accept source selections as a JSON array or file. [#6019](https://github.com/morluto/rea/pull/6019), [#6206](https://github.com/morluto/rea/pull/6206), [#5029](https://github.com/morluto/rea/pull/5029), [#4772](https://github.com/morluto/rea/issues/4772), [#6193](https://github.com/morluto/rea/issues/6193). Thanks @gensokyo-dev, @IvenKooLab, @jacobmalibiran, @JoshOsullivan-au, @kaoru0822-kitauji, @KJLavender.
- **Data inspection:** Decode selected protobuf/gRPC bodies, inspect GGUF/safetensors declarations, and produce byte-entropy profiles. [#5031](https://github.com/morluto/rea/pull/5031), [#5603](https://github.com/morluto/rea/pull/5603), [#2331](https://github.com/morluto/rea/pull/2331), [#4986](https://github.com/morluto/rea/issues/4986), [#3323](https://github.com/morluto/rea/issues/3323). Thanks @AayushJB03, @gensokyo-dev, @kaoru0822-kitauji, @KJLavender, @michaelkillgta, @mvanhorn.
- **CLI input/output:** Accept structured request documents on stdin and expose version/runtime output formats. Row-oriented JSONL exports use the migration above. [#7040](https://github.com/morluto/rea/pull/7040), [#5486](https://github.com/morluto/rea/issues/5486), [#6530](https://github.com/morluto/rea/pull/6530), [169512a](https://github.com/morluto/rea/commit/169512a35bb015ff209168fb8bc1c9b31a2c678b), [#5483](https://github.com/morluto/rea/issues/5483). Thanks @Abelo9996, @ahmed-husssain, @CassieFox23, @DeryFerd, @eco-null, @ezrabrilliant, @GamerDubz, @gensokyo-dev, @gongfpp, @hifzabuildsai, @jayykernel, @mhamzasakhi, @pablodiazjorge, @Plastic-time, @raidnouredden-ux, @SandstromPL, @ShuaixinHuang, @Sriram-PR, @XiaoBinGan.
- **MCP and setup:** Add opt-in Streamable HTTP and FreeBuff setup registration. FreeBuff support first appears in 6.8.0; installed native FreeBuff loading and skill discovery remain unverified. [#4955](https://github.com/morluto/rea/pull/4955), [#2736](https://github.com/morluto/rea/pull/2736), [#3243](https://github.com/morluto/rea/issues/3243). Thanks @0xLLVEL, @codebuff-team, @earakely-scale, @gensokyo-dev, @kaoru0822-kitauji.
- **Recovery controls:** Adjust per-call WASM/SQLite/firmware deadlines, MCP startup budgets, regex matching deadlines/worker heap, capture decoder heap, ASAR header admission, Apple dispatch input, and the bridge request ceiling. MCP runtime limit recovery reports actionable settings. [#5798](https://github.com/morluto/rea/pull/5798), [#5818](https://github.com/morluto/rea/pull/5818), [aa53c06](https://github.com/morluto/rea/commit/aa53c065387c58b4189d82c47f1f3445fc505e30), [#5863](https://github.com/morluto/rea/pull/5863), [#6304](https://github.com/morluto/rea/pull/6304), [#6267](https://github.com/morluto/rea/pull/6267), [#6301](https://github.com/morluto/rea/pull/6301), [#6299](https://github.com/morluto/rea/pull/6299), [#6209](https://github.com/morluto/rea/pull/6209), [#6233](https://github.com/morluto/rea/pull/6233), [#6376](https://github.com/morluto/rea/pull/6376), [#7049](https://github.com/morluto/rea/pull/7049), [#5797](https://github.com/morluto/rea/issues/5797), [#5812](https://github.com/morluto/rea/issues/5812), [#5851](https://github.com/morluto/rea/issues/5851), [#6176](https://github.com/morluto/rea/issues/6176), [#3917](https://github.com/morluto/rea/issues/3917). Thanks @gensokyo-dev, @jaychou813, @kaoru0822-kitauji.
- **APK output:** Set `string_tables_only` or CLI `--string-tables-only` to omit duplicate primary strings and avoid their output-capacity charge. [7c458f0](https://github.com/morluto/rea/commit/7c458f04b9155707c8ba406f8b455b10016f1b99), [#4990](https://github.com/morluto/rea/pull/4990), [#4982](https://github.com/morluto/rea/issues/4982). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
- **Retained analysis:** Reuse successful validation of authenticated immutable native/imported-JavaScript parents and captures. Query retained Go build identity, Rust dependencies, JavaScript source-to-bundle mappings, and native export facts without repeating full output. [8f7d12e](https://github.com/morluto/rea/commit/8f7d12e30d63f3d3b4cfe2a90336362841438f56), [#4962](https://github.com/morluto/rea/pull/4962), [#5349](https://github.com/morluto/rea/pull/5349), [#5524](https://github.com/morluto/rea/pull/5524), [#5815](https://github.com/morluto/rea/pull/5815), [#6128](https://github.com/morluto/rea/pull/6128), [#6907](https://github.com/morluto/rea/pull/6907), [#4810](https://github.com/morluto/rea/issues/4810), [#5499](https://github.com/morluto/rea/issues/5499), [#5802](https://github.com/morluto/rea/issues/5802), [#6112](https://github.com/morluto/rea/issues/6112), [#6900](https://github.com/morluto/rea/issues/6900). Thanks @gensokyo-dev, @kaoru0822-kitauji, @N0zoM1z0.
- **Compact results:** Use retained Frida/GDB/objdump summaries, omit redundant Ghidra raw results, and defer the analysis runtime during MCP catalog discovery. [#5661](https://github.com/morluto/rea/pull/5661), [#5614](https://github.com/morluto/rea/pull/5614), [#5355](https://github.com/morluto/rea/pull/5355), [#4424](https://github.com/morluto/rea/pull/4424), [#5640](https://github.com/morluto/rea/issues/5640), [#5584](https://github.com/morluto/rea/issues/5584), [#5337](https://github.com/morluto/rea/issues/5337). Thanks @coygeek, @gensokyo-dev, @kaoru0822-kitauji, @leeguooooo.
- **Analysis work:** Reuse Hopper literal-string records, JavaScript workers and captured prefix selections, Interface Builder validation and plist scalar schemas; bound stable reads to one snapshot buffer and avoid decoding omitted GGUF strings. [#5017](https://github.com/morluto/rea/pull/5017), [#5819](https://github.com/morluto/rea/pull/5819), [31de177](https://github.com/morluto/rea/commit/31de177404461d122761d8243abe4fd24564f4da), [#5885](https://github.com/morluto/rea/pull/5885), [85e38ac](https://github.com/morluto/rea/commit/85e38ac199dff15e8c43343d9170db7e0e46d284), [#6966](https://github.com/morluto/rea/pull/6966), [#5781](https://github.com/morluto/rea/pull/5781), [#5010](https://github.com/morluto/rea/issues/5010), [#5855](https://github.com/morluto/rea/issues/5855), [#6954](https://github.com/morluto/rea/issues/6954), [#5774](https://github.com/morluto/rea/issues/5774). Thanks @gensokyo-dev, @kaoru0822-kitauji.
- **Native scheduling:** Batch modern IDA callee lookups, schedule decompilation reads on demand, index NativeAOT type closure, and retain call-path ancestry once per visited node. [0c6fed9](https://github.com/morluto/rea/commit/0c6fed90b74c75a9fd23d1ec1b510a7274f85887), [06fe7ee](https://github.com/morluto/rea/commit/06fe7eecbc8c387414b4c1aa0d88ad6314f54f0c), [ce61680](https://github.com/morluto/rea/commit/ce61680c04c95a9cdfad5335632a99542af26d37), [5e4ec0f](https://github.com/morluto/rea/commit/5e4ec0fa1b678aa3bf20704ec9110224ba8c1676).
- **Selected processing:** Prepare CLI positional schemas only for the selected command, separate entropy reads from report resolution, prune typed-array reference escapes, index state-write cutoffs, decode only selected replay query values, and skip payload text during WAT framing. [315b234](https://github.com/morluto/rea/commit/315b23403f0819967878cee418c3f9cb4b1a69d9), [#6950](https://github.com/morluto/rea/pull/6950), [#5139](https://github.com/morluto/rea/pull/5139), [#5350](https://github.com/morluto/rea/pull/5350), [96ea32b](https://github.com/morluto/rea/commit/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9), [70835d3](https://github.com/morluto/rea/commit/70835d3d5f030deadf606e1c79c70a60883e50c9), [#5766](https://github.com/morluto/rea/pull/5766), [#6947](https://github.com/morluto/rea/issues/6947), [#5760](https://github.com/morluto/rea/issues/5760). Thanks @galleonli, @gensokyo-dev, @kaoru0822-kitauji.
### Fixes
- **IPS report fields:** Preserve the recorded terminating process and PID, termination flags, parent PID, and build/platform metadata. [#7079](https://github.com/morluto/rea/pull/7079), [#7078](https://github.com/morluto/rea/issues/7078). Thanks @rodricksz4h5.
- **Ghidra language selection:** Accept stock language IDs containing `LEBE` or slash-separated processor names, and report conflicting DOL language overrides as configuration errors naming `REA_GHIDRA_LANGUAGE_ID`. [#7070](https://github.com/morluto/rea/pull/7070), [#7072](https://github.com/morluto/rea/issues/7072), [#7081](https://github.com/morluto/rea/pull/7081), [#7082](https://github.com/morluto/rea/issues/7082). Thanks @Adolanium.
- **Setup and Hopper:** Preserve the Linux embedding runtime, supported x86_64 host restriction, readiness-based provider choices, orderly demo-helper shutdown, and installed apt dependency versions. Keep cleanup and skill publication ownership through retries. [#5041](https://github.com/morluto/rea/pull/5041), [#3422](https://github.com/morluto/rea/pull/3422), [f5d1012](https://github.com/morluto/rea/commit/f5d1012fabfe99af82523c3078ce9d93eb3780c1), [#4966](https://github.com/morluto/rea/pull/4966), [#6974](https://github.com/morluto/rea/pull/6974), [211e353](https://github.com/morluto/rea/commit/211e3533b34f81ae0910ad4f53f2a3d3c8587d3e), [#6920](https://github.com/morluto/rea/pull/6920), [#5015](https://github.com/morluto/rea/issues/5015), [#3419](https://github.com/morluto/rea/issues/3419), [#4921](https://github.com/morluto/rea/issues/4921), [#6975](https://github.com/morluto/rea/issues/6975). Thanks @Abel-I-Akhigbe, @Adolanium, @DavidFB-creator, @DeryFerd, @gensokyo-dev, @kaoru0822-kitauji, @sx4im.
- **Client setup:** Treat empty APPDATA as unset, distinguish MSIX Claude Desktop process views, preserve aliased Hermes server tables, reuse client dialect/alias bindings, and materialize the pinned package-runner cache before the first start. [#5126](https://github.com/morluto/rea/pull/5126), [#4903](https://github.com/morluto/rea/pull/4903), [#3785](https://github.com/morluto/rea/pull/3785), [b7eeded](https://github.com/morluto/rea/commit/b7eededf5af5061a834f93f57f2c888acf870344), [#7032](https://github.com/morluto/rea/pull/7032), [#5104](https://github.com/morluto/rea/issues/5104), [#4278](https://github.com/morluto/rea/issues/4278), [#3320](https://github.com/morluto/rea/issues/3320), [#7027](https://github.com/morluto/rea/issues/7027). Thanks @CmsChase, @coygeek, @myso-kr, @pablodiazjorge, @vaidehi-snk, @wxj006007.
- **Target admission:** Preserve thin arm64_32 Mach-O support, distinguish JVM identity before FAT admission, validate FAT format/CPU identity, preserve original DOS header padding, and reject NULs in local objdump/rizin paths. [#5788](https://github.com/morluto/rea/pull/5788), [6843dbe](https://github.com/morluto/rea/commit/6843dbea9bce4709ee2e8bf46c38f0bbf640716b), [497037e](https://github.com/morluto/rea/commit/497037e6e3975cbb37cfd89ea0ad300faa2a4769), [#7013](https://github.com/morluto/rea/pull/7013), [#3600](https://github.com/morluto/rea/pull/3600), [#5713](https://github.com/morluto/rea/issues/5713), [#6510](https://github.com/morluto/rea/issues/6510). Thanks @coygeek, @dhruvac29, @kaoru0822-kitauji, @Lew1sWong, @qorvr.
- **Mach-O and dylibs:** Preserve container framing, facts when dyld rejects a missing UUID, verified-signature snapshot identity, unsigned diagnostics, absolute paths inside the selected root, legacy platform rules, lazy-load payloads, and reasons for rejecting present dylibs. [cf3288d](https://github.com/morluto/rea/commit/cf3288d8cf7a09685acf89103b36c32359be56fa), [2b263a7](https://github.com/morluto/rea/commit/2b263a7fe9e9d4c185c7d6af6b4e67b21a73af30), [eb7acc7](https://github.com/morluto/rea/commit/eb7acc70bf06f119e8639e30a78081d5ba82b0bf), [1502cfb](https://github.com/morluto/rea/commit/1502cfbc8ca0d1d1d86eebf334018dfb418bbac0), [90062c0](https://github.com/morluto/rea/commit/90062c092228dea9d33fadc0d7e1b968105b8b2c), [#5650](https://github.com/morluto/rea/pull/5650), [3b67b36](https://github.com/morluto/rea/commit/3b67b362aa2cd79eac5af57d0965e81a7d0b8c2b), [#5551](https://github.com/morluto/rea/issues/5551). Thanks @coygeek.
- **Native facts:** Admit direct dispatch-metadata providers; preserve record-local Objective-C omissions, snapshot profiles, class-import binding identity, stripped class labels, version-like names, and qualified Swift protocol identities. [50b6975](https://github.com/morluto/rea/commit/50b6975d861069e2c6cfa202780621449f863802), [9eec57b](https://github.com/morluto/rea/commit/9eec57b469087fe9bff3d29dcc02f47074159e91), [0ba4d6f](https://github.com/morluto/rea/commit/0ba4d6f43ad610dacc861a1548be1a5c4712cd61), [4d7de00](https://github.com/morluto/rea/commit/4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0), [22c84ce](https://github.com/morluto/rea/commit/22c84ce64aa6c7601aa81af86d4117b5a4c2feac), [4852c83](https://github.com/morluto/rea/commit/4852c830af1e3b9df437235b7a8fa8970a6d1c59), [aa9043f](https://github.com/morluto/rea/commit/aa9043f6b61c453e4735795cfe6a559bf6b6cc16).
- **Apple resources:** Preserve independent plist scalars, processing instructions, nonstandard table widths, unsupported NIB connector coverage, and strict IPS JSON surrogate validation; classify Interface Builder objects by their declared superclass chains. [fd728f2](https://github.com/morluto/rea/commit/fd728f271e7de7bcfa63890b573715720d5f948c), [#5806](https://github.com/morluto/rea/pull/5806), [4526862](https://github.com/morluto/rea/commit/4526862728af01f1135714079c0d8a8c754eb364), [14cd78c](https://github.com/morluto/rea/commit/14cd78c3224edcf79fca152915077112f547b9ed), [#6990](https://github.com/morluto/rea/pull/6990), [#5805](https://github.com/morluto/rea/issues/5805), [#5228](https://github.com/morluto/rea/pull/5228), [#5218](https://github.com/morluto/rea/issues/5218). Thanks @coygeek, @DeryFerd, @kaoru0822-kitauji, @Mangolycheematcha, @mhamzasakhi, @Shreyash021104.
- **Archives and files:** Complete positional ZIP reads, preserve bounded ASAR signatures, report stable symlink loops as resolution failures, retain target applicability, and report unknown traversal coverage. Retain reported integrity contradictions for members present in only one compared artifact. [#4414](https://github.com/morluto/rea/pull/4414), [e8da7e1](https://github.com/morluto/rea/commit/e8da7e1edd075feec0e6beedec93d5645fed8125), [#5571](https://github.com/morluto/rea/pull/5571), [#5763](https://github.com/morluto/rea/pull/5763), [#5867](https://github.com/morluto/rea/pull/5867), [#5521](https://github.com/morluto/rea/issues/5521), [#5731](https://github.com/morluto/rea/issues/5731), [#5837](https://github.com/morluto/rea/issues/5837), [#7076](https://github.com/morluto/rea/pull/7076). Thanks @Afloat16, @coygeek, @jdotc1, @kaoru0822-kitauji, @WenyuChiou.
- **Extraction and file ownership:** Restrict destination-case recovery to owned collisions, retain physical-read failures through late cancellation/cleanup, preserve cleanup capabilities for retry, and retain independent firmware extraction failures. [932fbb7](https://github.com/morluto/rea/commit/932fbb7d84a9a995b9aee3c1be7da05a8c4d7bbb), [9499667](https://github.com/morluto/rea/commit/949966712e68d8857a50a165ecddf04c2d7e6d3b), [56679dd](https://github.com/morluto/rea/commit/56679dd2ae31dda58e181f8bf99775014f7bb82b), [b3c912a](https://github.com/morluto/rea/commit/b3c912ab79ad147a37b6cfbe1552a1a5eecec629), [7298bd0](https://github.com/morluto/rea/commit/7298bd08d134860b84b4073f6703021916a5a4c5).
- **Processes and cancellation:** Retain partial samples and shutdown gaps, unresolved descendants and caller abort reasons; bind cleanup errors to their owners, propagate comparison contradictions, and report no normal exit code for signalled targets. [ba866a2](https://github.com/morluto/rea/commit/ba866a26ecbe179a717e746cbc0d1605ae616ad5), [#5270](https://github.com/morluto/rea/pull/5270), [#6144](https://github.com/morluto/rea/pull/6144), [a721c42](https://github.com/morluto/rea/commit/a721c42c07dc674c32ab939627355b8a1bee97a5), [#6298](https://github.com/morluto/rea/pull/6298), [#6486](https://github.com/morluto/rea/pull/6486), [#5935](https://github.com/morluto/rea/issues/5935), [#5944](https://github.com/morluto/rea/issues/5944), [#6100](https://github.com/morluto/rea/issues/6100), [#6101](https://github.com/morluto/rea/issues/6101), [#6110](https://github.com/morluto/rea/issues/6110), [#6113](https://github.com/morluto/rea/issues/6113), [#6121](https://github.com/morluto/rea/issues/6121), [#6131](https://github.com/morluto/rea/issues/6131), [#6132](https://github.com/morluto/rea/issues/6132), [#6218](https://github.com/morluto/rea/issues/6218), [#6445](https://github.com/morluto/rea/issues/6445). Thanks @agorevski, @coygeek, @gongfpp, @kwakhyun, @mhamzasakhi.
- **Provider cleanup:** Preserve Windows native handles until close is confirmed, native/Inspector/Electron cleanup ownership, Kuna probe identity and cleanup, IDA registration configuration, and Hopper preparation ownership. [#5772](https://github.com/morluto/rea/pull/5772), [87ab054](https://github.com/morluto/rea/commit/87ab054ea384b3fcbae2998b1f94685edcd5f624), [ff78165](https://github.com/morluto/rea/commit/ff78165978de9dbcf9fa16c2acf74bf85a69dc44), [04ae07a](https://github.com/morluto/rea/commit/04ae07ab8c9ae1a6697b6ad15489bbde515da8b5), [dd0015a](https://github.com/morluto/rea/commit/dd0015aaf156c5701ef9598bcc6c885e6b8da803), [b231c28](https://github.com/morluto/rea/commit/b231c28cf64c2f799ada99343f8f21e0e6acb15e), [#4505](https://github.com/morluto/rea/issues/4505), [#5616](https://github.com/morluto/rea/issues/5616).
- **Provider diagnostics:** Give Ghidra restart recovery, stable Homebrew aliases, unsupported-host results for Linux-only integrations, correct IDA regex/readiness handling, and clear diagnostics when Windows refuses a verified native addon. [#5356](https://github.com/morluto/rea/pull/5356), [7c6142e](https://github.com/morluto/rea/commit/7c6142efdc19a42c148d0e8c96b69d18e63c7f87), [#5793](https://github.com/morluto/rea/pull/5793), [#4862](https://github.com/morluto/rea/pull/4862), [99ccfb6](https://github.com/morluto/rea/commit/99ccfb6e709ac4bc0afe176315280abde9ebcb9f), [#6832](https://github.com/morluto/rea/pull/6832), [#5730](https://github.com/morluto/rea/issues/5730), [#4860](https://github.com/morluto/rea/issues/4860), [#5723](https://github.com/morluto/rea/issues/5723). Thanks @coygeek, @dark13b, @gensokyo-dev, @jgt87, @jpmorris41488, @Lew1sWong.
- **MCP results:** Preserve protocol envelopes on oversized errors, enforce and advertise absolute Evidence paths, retain typed output-filter errors and selected JSON paths, and give client/provider timeout recovery. Compact advertisements now normalize reported enum and union shapes for strict gateway translation. [562dab0](https://github.com/morluto/rea/commit/562dab00c5cbf76a9b2695bad93792eccdc9a49d), [3cf6206](https://github.com/morluto/rea/commit/3cf6206614e65b5ebb6284a5709090dfb8f9aaee), [0e83d92](https://github.com/morluto/rea/commit/0e83d92c583ae36afe00b2c23dd2a0f1d025cbca), [4714c7e](https://github.com/morluto/rea/commit/4714c7eaa3d1d8b6a5df0aff586e844b9d9ab06d), [1bfa60a](https://github.com/morluto/rea/commit/1bfa60ac20e1a344bcf3132c08d15b6860b05c61), [#5608](https://github.com/morluto/rea/pull/5608), [#3806](https://github.com/morluto/rea/pull/3806), [#5598](https://github.com/morluto/rea/issues/5598), [#3800](https://github.com/morluto/rea/issues/3800). Thanks @gensokyo-dev, @Hahaa909, @wang1xiang.
- **Browser lifecycles:** Retain historical outcomes through cleanup, stop late navigation before session release, stop expired work, observe aborted fetch settlement, and mark later scenario actions skipped after failure or cancellation. [2e5171e](https://github.com/morluto/rea/commit/2e5171e391b83f8a467d0861fd452c1f78621fd8), [599fdac](https://github.com/morluto/rea/commit/599fdac029912f96c511f859660483405d2de26f), [b2fad36](https://github.com/morluto/rea/commit/b2fad362f2b37f81dc9a6f784f4521e4ca24d41e), [13a8640](https://github.com/morluto/rea/commit/13a864020414577006dce58d7fdaa877ddd0cb52), [#6023](https://github.com/morluto/rea/pull/6023), [#5943](https://github.com/morluto/rea/issues/5943). Thanks @coygeek, @kaoru0822-kitauji.
- **Browser comparisons:** Preserve frame identity, default origin scope, unknown body/shape coverage, correspondence and uncertainty, and independent retained-observation budgets. [#4040](https://github.com/morluto/rea/pull/4040), [#4413](https://github.com/morluto/rea/pull/4413), [#4476](https://github.com/morluto/rea/pull/4476), [a0c22ff](https://github.com/morluto/rea/commit/a0c22ff0125b8af7612db54d3708413e2bfecb25), [a0fcc60](https://github.com/morluto/rea/commit/a0fcc60d89eb0cc09b08181eb9f961acfd07d8e9), [#3878](https://github.com/morluto/rea/issues/3878), [#3910](https://github.com/morluto/rea/issues/3910), [#3941](https://github.com/morluto/rea/issues/3941), [#4385](https://github.com/morluto/rea/issues/4385), [#4477](https://github.com/morluto/rea/issues/4477). Thanks @Adolanium, @coygeek, @eco-null, @gensokyo-dev, @kaoru0822-kitauji, @XY-code1.
- **Electron observations:** Preserve listener callback ownership and count registrations by source callsite; block trash calls in the active hook, retain window handles through cancelled selection, and preserve cancellation during finalization. [dfe8378](https://github.com/morluto/rea/commit/dfe8378cf5a727e3d93d47a5986233175c6ef898), [5064007](https://github.com/morluto/rea/commit/506400748eb1d6c674f0cf51330073929c45f939), [816a9dc](https://github.com/morluto/rea/commit/816a9dc98886e6d824cd8ae88b68731b161d5dda), [1707d17](https://github.com/morluto/rea/commit/1707d171d750955bf6387e028e378183b360ffe3), [4e2a566](https://github.com/morluto/rea/commit/4e2a566d6c7e053ecbbee1b684311ed31ea163bf).
- **JavaScript source maps:** Keep unmapped and missing mappings unknown, preserve partial indexed-map facts and section paths, propagate preparation capability errors, and retain observed worker failures through cancellation. [a4dbb36](https://github.com/morluto/rea/commit/a4dbb36d8ea83e9f94dd289ac1a2d220d2c72c9b), [2b2e362](https://github.com/morluto/rea/commit/2b2e362a00fc5ba2f93fa6c62e2d8780072d1b4b), [498a667](https://github.com/morluto/rea/commit/498a667234b0140c1cfa43c819b35454ab11d14e), [#6149](https://github.com/morluto/rea/pull/6149), [#6214](https://github.com/morluto/rea/pull/6214), [f01dcca](https://github.com/morluto/rea/commit/f01dccab77f712fb4ac936d0a6e92a70d12f0789), [#6082](https://github.com/morluto/rea/issues/6082), [#6061](https://github.com/morluto/rea/issues/6061). Thanks @coygeek, @diyasharma05, @kaoru0822-kitauji.
- **JavaScript semantics:** Preserve overflowing timer uncertainty, unavailable-scope comparisons, directory-form URL/ESM path intent, and artifact confinement. Replaced globals lose built-in effects; erased imports no longer become runtime bindings. Treat a known truthy `fork()` `silent` option as piped stdio unless a string/array `stdio` override takes precedence; unresolved options stay dynamic. [#3456](https://github.com/morluto/rea/pull/3456), [8d198d6](https://github.com/morluto/rea/commit/8d198d6ec707402d75276f516e0090d71767b7e4), [#4646](https://github.com/morluto/rea/pull/4646), [#2700](https://github.com/morluto/rea/pull/2700), [#4565](https://github.com/morluto/rea/pull/4565), [#6994](https://github.com/morluto/rea/pull/6994), [#3400](https://github.com/morluto/rea/issues/3400), [#3882](https://github.com/morluto/rea/issues/3882), [#3367](https://github.com/morluto/rea/issues/3367), [#7083](https://github.com/morluto/rea/pull/7083), [#7084](https://github.com/morluto/rea/issues/7084). Thanks @Adolanium, @amyanger, @coygeek, @gensokyo-dev, @HawkForge, @rohitg00, @viniciusdsandrade.
- **JavaScript routes and imports:** Treat path data as route candidates, preserve route flag and lexical RPC identities, package-main trailing-slash selection, catch aliases, and factory parameter ownership. [#6360](https://github.com/morluto/rea/pull/6360), [38ecc02](https://github.com/morluto/rea/commit/38ecc0248e1993d25a2f7dd350024484cce797bb), [#4219](https://github.com/morluto/rea/pull/4219), [21097b1](https://github.com/morluto/rea/commit/21097b192b1ab94647e3446b1a2a4dd00e924d5e), [f774dc4](https://github.com/morluto/rea/commit/f774dc420b3befe2b8e68971b4116da2d62fc800), [#6349](https://github.com/morluto/rea/issues/6349), [#4189](https://github.com/morluto/rea/issues/4189). Thanks @coygeek, @hemv-857, @kaoru0822-kitauji, @Nikhi00718.
- **Managed/IL2CPP:** Preserve extension-only scope uncertainty and type discovery after member limits, nested names, ReadyToRun/declaration facts, selected type scope, valid shared-suffix strings, and truncated IL2CPP metadata prefixes. [#5674](https://github.com/morluto/rea/pull/5674), [#5312](https://github.com/morluto/rea/pull/5312), [87fa1b6](https://github.com/morluto/rea/commit/87fa1b613d2b6bda1722855a00f1390f27731b64), [bc2d494](https://github.com/morluto/rea/commit/bc2d494030efd1ccabaeaa0bd49226aa87c378a2), [1f0eb22](https://github.com/morluto/rea/commit/1f0eb228e50908ee2a2aaa72d329178ae1a2cdbd), [4c5fae5](https://github.com/morluto/rea/commit/4c5fae5179dd54c4a0d427cd2a115b9c887e3841), [b3aadaf](https://github.com/morluto/rea/commit/b3aadaf474e59b33e5ff465236008a67eccefb87), [#5467](https://github.com/morluto/rea/issues/5467), [#5146](https://github.com/morluto/rea/issues/5146). Thanks @coygeek, @eco-null, @elricfd, @umi008.
- **Format boundaries:** Preserve independent facts across malformed inputs in model, GPU, WASM, Rust, Android, and SQLite inspection; report optional metadata warnings without discarding core results. [eb41f35](https://github.com/morluto/rea/commit/eb41f358cf83d5626f09de68332f9718f9aa6482), [#5789](https://github.com/morluto/rea/pull/5789), [00a9e95](https://github.com/morluto/rea/commit/00a9e95de91a4bd4d2a41f8aee704a53fa575750), [fc63fe8](https://github.com/morluto/rea/commit/fc63fe8682654086261540dcf3c59afc36c7d58f), [39a91d4](https://github.com/morluto/rea/commit/39a91d4db96ca13224c25936330ea84fe78970e5), [6c6e2ad](https://github.com/morluto/rea/commit/6c6e2ad62b20f810b54f5a567e66fc55241514b2), [929a0a7](https://github.com/morluto/rea/commit/929a0a76c870d9991e015dde58308fd9df0280b8), [d137a4c](https://github.com/morluto/rea/commit/d137a4cdde0049de967f1d5cd429ed9e1f7d0a10), [ef475fc](https://github.com/morluto/rea/commit/ef475fcf8b0601c61052b7091568ba574546417d), [#5749](https://github.com/morluto/rea/issues/5749). Thanks @coygeek, @gensokyo-dev.
- **Capture decoding:** Preserve malformed protobuf trailer facts and wire/schema identity, HTTP/2 URI and UTF-8 failure facts, WebMCP property coverage, and offline capture availability without a browser provider. Report invalid record ordinals as selection errors with actual bounds where available, and trim surrounding SP/HTAB from gRPC-web trailer values while preserving internal whitespace. [9cf7a7c](https://github.com/morluto/rea/commit/9cf7a7cb0d3c72a1121fde280cb11bf911af8055), [b3c3b21](https://github.com/morluto/rea/commit/b3c3b21e43cab4e71b460d08829d8c6e4e9b9c21), [4cb1f1d](https://github.com/morluto/rea/commit/4cb1f1d820df4498ee464bcf72a5f47d40cf8dab), [51e5938](https://github.com/morluto/rea/commit/51e593877642ab42fb5bf25812f4a4110ee7ac26), [d23be5c](https://github.com/morluto/rea/commit/d23be5c0898c39c9f0c54e2e0b43ee746d92d316), [#6725](https://github.com/morluto/rea/pull/6725), [#3947](https://github.com/morluto/rea/issues/3947), [#7067](https://github.com/morluto/rea/pull/7067), [#7068](https://github.com/morluto/rea/issues/7068), [#7069](https://github.com/morluto/rea/pull/7069), [#7071](https://github.com/morluto/rea/issues/7071). Thanks @Adolanium, @coygeek, @Sriram-PR.
- **Evidence and errors:** Authenticate ledger Evidence before committing; preserve parent-schema diagnostics, canonical correction details and owned cleanup outcomes; identify the rejected function-Evidence side/kind. Fixed allowances now name their actual limits. [0407bbf](https://github.com/morluto/rea/commit/0407bbfbe65e8fa475e8bb0c78c30170e2c2f55e), [#6361](https://github.com/morluto/rea/pull/6361), [1629266](https://github.com/morluto/rea/commit/1629266e3c08c90fed6bba5383528af7986a12b7), [#6969](https://github.com/morluto/rea/pull/6969), [#5860](https://github.com/morluto/rea/pull/5860), [#6034](https://github.com/morluto/rea/issues/6034), [#5932](https://github.com/morluto/rea/issues/5932), [#5847](https://github.com/morluto/rea/issues/5847). Thanks @AyoubElleuch, @coygeek, @dhruvac29, @kaoru0822-kitauji.
- **ADB and firmware recovery:** Report an unparsed push checksum as unverified, normalize absent installer-package sentinels, describe extraction-relative Unblob links, and avoid recommending a budget increase at its fixed ceiling. [#6010](https://github.com/morluto/rea/pull/6010), [#4073](https://github.com/morluto/rea/pull/4073), [#4573](https://github.com/morluto/rea/pull/4573), [#6971](https://github.com/morluto/rea/pull/6971), [#5924](https://github.com/morluto/rea/issues/5924), [#4175](https://github.com/morluto/rea/issues/4175). Thanks @coygeek, @Ganesh2325, @kaoru0822-kitauji, @Rinkia, @XiaoBinGan, @yankikucuk.
- **CLI formatting:** Share Markdown report formatting, exclude inherited row values, and preserve arrays of empty objects as JSON. Render Markdown table headers and cells literally while keeping intraword underscores readable. [b784912](https://github.com/morluto/rea/commit/b784912811fd10ac9dc425696dc0e9af632afa34), [#6984](https://github.com/morluto/rea/pull/6984), [#7058](https://github.com/morluto/rea/pull/7058), [#7057](https://github.com/morluto/rea/issues/7057), [#7073](https://github.com/morluto/rea/pull/7073). Thanks @Plastic-time, @ShuaixinHuang, @viniciusdsandrade.
- **Format identity:** Preserve Python marshal scalar/decode limits, empty optional BEAM chunks, PE resource branch identities and certificate alignment, unsupported compressed ELF notes, and codec-framed GPU bundle extents. [0603223](https://github.com/morluto/rea/commit/06032237c6755e3dd5f9a8db732c89958b238cc7), [c46064c](https://github.com/morluto/rea/commit/c46064c3f6eaba121c5de7b17390bfdecdd132ef), [66671e8](https://github.com/morluto/rea/commit/66671e874888e98f7e2b27e68a7c6fb320bbb827), [03077d5](https://github.com/morluto/rea/commit/03077d54afc49613db4e38f70c506bc60f5223aa), [#6721](https://github.com/morluto/rea/pull/6721), [9fb8c11](https://github.com/morluto/rea/commit/9fb8c114a03ad60e57c11586967c6df845a3f97e), [f6a1e2c](https://github.com/morluto/rea/commit/f6a1e2c892dc23c7583c3d03af9edb01632ff5a5), [#6694](https://github.com/morluto/rea/issues/6694). Thanks @coygeek, @Deepak-Kambala.
- **WASM and APK metadata:** Preserve ambiguous WASM identity and partial facts, glue URL query/fragment text across line terminators, APK signer identity, and raw issuer identity when comparison rules are unknown. [af8f0ba](https://github.com/morluto/rea/commit/af8f0badf64fd3546392b6aa4625777904b2f051), [#6802](https://github.com/morluto/rea/pull/6802), [f783f0f](https://github.com/morluto/rea/commit/f783f0f1e82472cdddd4de2156d974f729e332a6), [115a78f](https://github.com/morluto/rea/commit/115a78fe241276c34917b496621954c5f29bfc15), [#6639](https://github.com/morluto/rea/issues/6639). Thanks @coygeek, @gensokyo-dev, @gongfpp.
- **Source identity:** Preserve Next route spelling, module grammar and loader request identity, directory package-main behavior, accessor effects, exact source targets, and resolver-discovery versus module-loading distinctions. [ddd7ac7](https://github.com/morluto/rea/commit/ddd7ac7b846b48191ead1f7dcd0e3bd735468fa8), [9974512](https://github.com/morluto/rea/commit/99745126009b15060d71c38809418858a07d5afa), [f2e5fd1](https://github.com/morluto/rea/commit/f2e5fd148a85fa737302dd194a33172c4561093e), [baf1678](https://github.com/morluto/rea/commit/baf16784af3592534687052431c145daef55cfd1), [6e324b2](https://github.com/morluto/rea/commit/6e324b224405f2befd953234f6dc210cf72ad50a), [75daf0c](https://github.com/morluto/rea/commit/75daf0c7df108f04ab41f34aec1a31d4a7ceb322), [f91b7f8](https://github.com/morluto/rea/commit/f91b7f8009c9af27a5e0148e3b07d20bfe3b7a2a).
- **Graphs and selected facts:** Preserve JavaScript graph omissions/version facts, Electron native-loader facts, web JSON identity/coverage, module-path selection, feature-trace qualifications, selected native paths, endpoint context, and mixed Swift demangler outcomes. [30db499](https://github.com/morluto/rea/commit/30db499db310f073d9bf3d3acfd77985e1cb7e2e), [5a7bddb](https://github.com/morluto/rea/commit/5a7bddb3aeb0345854e76b79d529aed1c40e2054), [79805f2](https://github.com/morluto/rea/commit/79805f244a1ec0850f3109929168a9c8ebb1b908), [f719dc4](https://github.com/morluto/rea/commit/f719dc46c51a55b3926eec9659d06e7dfb95132b), [7784fdb](https://github.com/morluto/rea/commit/7784fdbad78f8b384d513f0e7dae8e983cd48ef8), [76500f1](https://github.com/morluto/rea/commit/76500f12b54e07b1ea700a403b2593890d63b42a), [676d29f](https://github.com/morluto/rea/commit/676d29f9eef794cea668f5f09f0ff510c848a513), [edf7b3d](https://github.com/morluto/rea/commit/edf7b3d32c3248f221e27f7ce5a3d34e8590285b), [93de7bc](https://github.com/morluto/rea/commit/93de7bc7e324a761162a31625b838ba0d4e3824d).
- **Managed boundaries:** Honor native platform symbol lookup, consume resolved IL2CPP ELF declarations, and attribute malformed comparison inputs to the supplied argument. [64fa1db](https://github.com/morluto/rea/commit/64fa1db1309218283df19f3ee4dba61f5fbd2a67), [87745af](https://github.com/morluto/rea/commit/87745af52ab0ccd3a172c2534c516d41b0095035), [96ad94f](https://github.com/morluto/rea/commit/96ad94f69eda5d68c7335ff775b6927b1376fd2b).
- **Provider and worker recovery:** Preserve output loss after successful command exit, physical refusal diagnostics, bounded worker control framing, JavaScript worker-phase/budget attribution, admitted ambiguity choices, and selected EVM limiter availability. Scope objdump debug-link limitations to DWARF requests and keep process-capture preparation nonexecuting. [e639edf](https://github.com/morluto/rea/commit/e639edf6204c05285d96840478926592daf95876), [171b33b](https://github.com/morluto/rea/commit/171b33b2c4defc6cbea49545bedf4272b72d1ea2), [34563fc](https://github.com/morluto/rea/commit/34563fcc9bd92af30668820dcac5635e27181094), [3f9357b](https://github.com/morluto/rea/commit/3f9357b5990d166a38440f56f439452266650093), [cec8f1e](https://github.com/morluto/rea/commit/cec8f1e8f305d6332272457a6ee419d3c5fb6359), [a74d859](https://github.com/morluto/rea/commit/a74d859a19122fb8f1282b3632d320997b1caae0), [c284168](https://github.com/morluto/rea/commit/c284168c4b1cd214ce7f504d43da28528d82d603), [4fb936f](https://github.com/morluto/rea/commit/4fb936fe86bb7a07f94f819ae8510301f2ef69c9).
- **Setup and limits:** Retain unknown environment-budget facts, selected-skill/maintenance uncertainty, Hopper URL admission and bookmark verification, and listener failure recovery that matches the system cause. [a718037](https://github.com/morluto/rea/commit/a7180370332350bc0f998fbd643888221759570e), [d1fbebb](https://github.com/morluto/rea/commit/d1fbebb578538dc94fd5e376e6e1959218efa835), [60accad](https://github.com/morluto/rea/commit/60accad2de8f2862fe91901502fe26cc5390fb36), [fd7da77](https://github.com/morluto/rea/commit/fd7da77fc02dcfaf7d1bc9f2fb240d71cdd27c59), [0c440bf](https://github.com/morluto/rea/commit/0c440bfc07f3cb343e2e836d27cccaed539cfe9c).
- **State and Evidence:** Retain export tarball ownership and SQLite primary outcomes through cleanup retry, count emitted SQLite schema names across encodings, scope display refresh to matching snapshot bindings, and report retained-result validation failures consistently. Reconstruction uses one effective completion scope and kind-based labels when names are blank. [ab58edc](https://github.com/morluto/rea/commit/ab58edcdf50b4bd687900ee2dc362ecc5c3dce15), [1006714](https://github.com/morluto/rea/commit/100671427105791a5c5913a918b63a394f6bc83e), [a47788a](https://github.com/morluto/rea/commit/a47788a6a4abb067273f954275a9d0a269adf1e7), [8aa40e9](https://github.com/morluto/rea/commit/8aa40e9a61155cff8b6f3c138c601bf805b20653), [b8c1b45](https://github.com/morluto/rea/commit/b8c1b45316882beeab4a9de49381d94e131fab0f), [a35b933](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6), [#6137](https://github.com/morluto/rea/pull/6137), [#6126](https://github.com/morluto/rea/issues/6126). Thanks @carlogiovannineri-rgb, @mohithhhh.
- **Capture coverage:** Name browser bundle budgets and deadline recovery, separate domain readiness from worker attribution, attribute rendered-frame omissions to their producing stage, share offline-capture host admission with discovery, preserve selected signal-file semantics, and retain short Flutter GNU build IDs. [#6036](https://github.com/morluto/rea/pull/6036), [ba78133](https://github.com/morluto/rea/commit/ba781330d6d28b22d15498e63c144fb40e086156), [5934387](https://github.com/morluto/rea/commit/5934387f4185129efe9dc8b7338d8bc3fa5b25bd), [ad629b2](https://github.com/morluto/rea/commit/ad629b2e31c3a9495b27179360b508fa021fe86a), [928ba76](https://github.com/morluto/rea/commit/928ba76882bfd216dde8ed79118789b2c3aad4f9), [678c4cf](https://github.com/morluto/rea/commit/678c4cfa68e2fee12f620075e12dc87d9b1195b0).
- **Additional boundary facts:** Retain parser format/query occurrence identity and independent malformed-input facts, unresolved nested Swift identifiers, and completed signing-capture coverage; describe crash documents without assuming a provider. [5f702dd](https://github.com/morluto/rea/commit/5f702dd6ccd9e8e505e224854b91678439c3b41f), [99d2d7a](https://github.com/morluto/rea/commit/99d2d7ad22bcbd780919760008aba6a88e7d1077), [90e433d](https://github.com/morluto/rea/commit/90e433df06b3f83417176b8db1b0a905f9df1d3a), [#5548](https://github.com/morluto/rea/pull/5548), [ee12d51](https://github.com/morluto/rea/commit/ee12d517a25d34853d40e31fc039c7d1576c60e1), [#5538](https://github.com/morluto/rea/issues/5538). Thanks @coygeek, @ShuaixinHuang.
- **Documentation:** Add the Romanian README. [#7026](https://github.com/morluto/rea/pull/7026). Thanks @An3treiu.
Full contribution record: [1740 commits and 497 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.8.0/docs/releases/6.8.0.contributions.json).
## [6.7.0](https://github.com/morluto/rea/compare/rea-agents-6.6.0...rea-agents-6.7.0) (2026-10-18)
### Highlights
@@ -2581,7 +2683,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
* enforce conventional pull request titles ([3a21f16](https://github.com/morluto/rea/commit/3a21f16c1832430765083636511a8c12dbccf2dd))
* verify installed package with Linux Hopper ([4f97f69](https://github.com/morluto/rea/commit/4f97f699e4d5471557d1ce0ff91e144065d06a61))
## [0.3.0](https://github.com/morluto/rea/compare/rea-agents-0.2.1...rea-agents-0.3.0) (2026-07-12)
## [0.3.0](https://github.com/morluto/rea/compare/rea-0.2.1...rea-agents-0.3.0) (2026-07-12)
### Features
@@ -2634,7 +2736,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
* redesign and localize README ([051b6db](https://github.com/morluto/rea/commit/051b6db65687fd14826b6462cd623feb2504f123))
* redesign and localize README ([910eda6](https://github.com/morluto/rea/commit/910eda6f323ba1151b0771fe5de6c60a91e3d46d))
## [0.2.0](https://github.com/morluto/rea/compare/rea-0.1.0...rea-0.2.0) (2026-07-12)
## [0.2.0](https://github.com/morluto/rea/releases/tag/rea-0.2.0) (2026-07-12)
### Features
+56 -13
View File
@@ -16,15 +16,26 @@ import inventory. For current provider wiring, start with
[`src/composition/binary.ts`](src/composition/binary.ts) for deep candidates and
the other `src/composition/` factories for tool services, including
[`gpu.ts`](src/composition/gpu.ts) and [`model.ts`](src/composition/model.ts).
Live Frida and Cutter adapters are constructed directly in
[`src/cli/fridaCommands.ts`](src/cli/fridaCommands.ts),
[`src/cli/cutterCommands.ts`](src/cli/cutterCommands.ts), and
[`src/server/createServer.ts`](src/server/createServer.ts).
Live Frida, Cutter, GDB and Rizin construction also uses shared composition
factories. For service ownership, availability and catalog declarations, follow
the [capability composition guide](docs/capability-composition.md).
Production factories construct fresh providers without starting engines or
acquiring targets; optional adapter failures must preserve successful peers. Run
`npm run verify:test-discovery` after adding or moving tests. Resolved import
boundaries are checked by `npm run verify:module-boundaries`, included in lint.
Before retiring an alias or facade, trace runtime registration, dynamic imports,
JavaScript verifiers, subprocess scripts, and packaged entrypoints as well as
TypeScript references. Tests may be the last consumers of a superseded internal
path; migrate their distinct assertions to the current production path before
removing it. Preserve validation, lazy loading, caller policy, and resource
acquisition and cleanup supplied by the old boundary. Published APIs, persisted
formats, and independently versioned bridges require an explicit compatibility
decision; an internal representation alone does not establish that obligation.
See [interpreting boundary facts once](docs/tool-design.md#interpret-boundary-facts-once)
for admission ownership and the distinction between repeated validation and
independent checks.
For analysis workflows and agent interfaces, follow the
[performance priorities](AGENTS.md#performance). Explain the selected output
scope, reuse/cache decisions, and relevant before/after measurements when these
@@ -35,7 +46,9 @@ compare equivalent real tasks before claiming an improvement.
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
When adding or changing an admission ceiling, budget, or deadline, follow
[admission limits and recovery](docs/design/limits-and-recovery.md).
[analysis settings, limits, and recovery](docs/design/limits-and-recovery.md)
for ownership, operation snapshots, provider integration, and the steps for
adding a runtime setting.
## Reporting issues
@@ -80,7 +93,8 @@ Before requesting maintainer review, the human owner must:
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
for required checks and real-provider coverage.
- Complete the **Author readiness** declaration in the PR template, explicitly
stating that the PR is ready for initial review.
stating that the PR is ready for initial review, unless the PR author is
exempt from the declaration as listed below.
Keep the PR description concise and current. Explain the problem, observable
outcome, approach, and useful evidence; link long logs or put them in optional
@@ -89,7 +103,11 @@ Human ownership and review remain required when CI or an automated reviewer
reports success. Readiness requests maintainer review; it does not grant scope
approval or permission to merge.
Draft PRs may request early feedback with open questions and incomplete checks.
Draft status is optional. Draft PRs may request early feedback with open questions
and incomplete checks. Agents preserve the current draft status unless asked to
change it; missing readiness declarations or later updates do not require a PR
to become a draft.
Leave the readiness declaration unchecked until the human owner has completed
it. The declaration records initial readiness; later pushes do not invalidate
it or require another human sign-off. Keep the PR description and affected
@@ -97,9 +115,12 @@ validation current as work changes. AI agents must leave human declarations to
the human owner and must not attest to a human review on their behalf.
The `PR readiness` check reports pending for drafts and requires a named human
owner and all three template declarations for non-drafts. It checks that the
declaration is filled, not whether the code or evidence is correct. Existing PRs
can copy the **Author readiness** section from the current template.
owner and all three template declarations for non-drafts. PRs authored by
@morluto, @kaoru0822-kitauji, @N0zoM1z0, or @gensokyo-dev are exempt from
the declaration; their human ownership, review, and evidence responsibilities
above still apply. The check identifies authors by GitHub account ID. It checks
that the declaration is filled, not whether the code or evidence is correct.
Existing PRs can copy the **Author readiness** section from the current template.
Open PRs must have distinct head commits because GitHub shares commit statuses.
Maintainers enable merge enforcement after the workflow is merged: run
@@ -214,6 +235,8 @@ Use the development toolchain pinned in `.nvmrc` and
`package.json#packageManager`. Run `nvm use` before installing dependencies;
installed-package runtime support is documented in
[installation](docs/installation.md#start-setup).
Use Node.js and npm for source builds and verifier scripts; the installed
package's Bun runtime support does not extend to those development scripts.
```bash
npm ci
@@ -228,6 +251,15 @@ catalogs, documentation projections, or managed verification evidence. Turbo
caches deterministic builds and static checks across Git worktrees. After a package, lockfile, or managed-skill version change, run
`npm run metadata:generate` before building.
Dependabot proposes updates for the npm lockfiles, website Python requirements,
and GitHub Actions. Review dependency changes and run the checks relevant to
their consumers. For dependencies maintained outside Dependabot, including
`third_party/` and the HTTP/3 lab's pinned requirements, the maintainer should
review upstream security advisories when updating or using them. Preserve source
and version provenance, update license notices when needed, and report which
verification was actually repeated. The lab requirements describe a recorded
environment; a dependency update does not verify the revised environment.
Repository invariants live in [AGENTS.md](AGENTS.md). Keep provider-specific
protocols in adapters and shared CLI/MCP workflows in the application layer.
@@ -254,7 +286,10 @@ The general source suite (`npm run test:fast`, `npm run test:changed`, and
On a Windows checkout, some source tests fail because fixtures use POSIX paths,
`#!/bin/sh` tool stubs, or POSIX rename semantics. Compare failures against the
base revision, or run the suite on Linux or WSL, before attributing them to a
change. See [testing](docs/testing.md#developer-commands) for command scopes
change. The embedded-artifact parent-swap tests exercise the selected-path
replacement when the rename succeeds; if Windows refuses it while the admitted
file is open, they instead verify the refusal and cleanup. See
[testing](docs/testing.md#developer-commands) for command scopes
and lane selection.
`git submodule update --init --recursive` fails at the pinned Unblob submodule
@@ -368,9 +403,17 @@ Real-provider execution is uncached.
### Temporary files and diagnostics
Tests use `createTestTempDirectory` from `tests/fixtures/temporaryDirectory.ts`
for exact-path, awaited cleanup, including failures and timeouts.
for exact-path, awaited cleanup, including failures and timeouts. It registers
deletion with Vitest's `onTestFinished`; do not also add its result to a roots
array or an `afterEach` removal. `createTestWorkspace` from
`tests/support/workspace/workspaceFixture.ts` leaves cleanup with its caller,
which must await `removeTestWorkspace`. Keep explicit resource teardown before
directory deletion, using the case's `finally` or `afterEach`; Vitest runs
`afterEach` before `onTestFinished` and awaits finish hooks in reverse
registration order.
`npm run verify:test-temp-hygiene` runs the suite under a fresh `TMPDIR` and
rejects remaining REA-owned paths. Never glob-clean shared `/tmp/rea-*` content.
rejects remaining REA-owned paths. It cannot detect duplicate deletion owners or
prove resource-close ordering. Never glob-clean shared `/tmp/rea-*` content.
`REA_LOG_LEVEL` selects `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or
`silent`. Logs go to stderr: MCP defaults to `info`, and one-shot CLI logging
+67 -11
View File
@@ -1,7 +1,7 @@
<div align="center">
**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md)
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md) · [Română](docs/readme/README_ro.md)
# REA: Reverse Engineer Anything
@@ -20,7 +20,9 @@
**[Website](https://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [FAQ](#faq) · [Documentation](#documentation)
<a href="https://discord.com/invite/GkcryMnJDM"><img alt="Join the REA community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [Build RL environments](#build-rl-environments) · [FAQ](#faq) · [Documentation](#documentation)
<code>npx rea-agents setup</code>
@@ -62,7 +64,7 @@ See a feature in an app that you want in your own product? Ask your agent to inv
REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites. You can also use the same tools from your terminal. Analysis runs locally, and results include the evidence and limitations behind each conclusion.
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, or IDA installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, IDA or Kuna installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.
> **[Visit the REA website](https://rea.tools/)** for setup instructions, illustrated guides, and real case studies.
@@ -83,12 +85,15 @@ npx rea-agents setup
Choose your agents, review the proposed changes, and approve them. Setup adds
REA's MCP server and matching workflow instructions, with backups of existing
configuration. Restart your agent afterward.
configuration. Restart your agent afterward. Setup records the selected runtime and REA
entry script directly. If acquired through a package runner, keep its cache entry
available. Run package acquisition from a trusted directory outside the target
project. Installation and recovery details are in the setup guide below.
Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[other agents](docs/installation.md#supported-agents). See
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
manual MCP registration and Claude Code/Codex plugin installation.
MCP uses compact input schemas by default to reduce context overhead. Set
`REA_MCP_INPUT_SCHEMA_PROFILE=full` in your MCP registration when you need the
@@ -96,6 +101,12 @@ complete advertised field guidance and nested validation details. Both profiles
keep the full tool set and canonical server validation. See
"Compact input schema profile" in the MCP contracts guide.
Complete output schemas are advertised by default. If your client caps the
complete `tools/list` frame, set `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact` to
bound each advertised output schema, or `off` to omit output schemas entirely.
Both settings keep canonical server-side validation and complete structured
results. See "Output schemas" in the MCP contracts guide.
### Ask your agent
```text
@@ -183,7 +194,7 @@ Additional tools and host support depend on the target:
| Target | What REA returns | Requirements and guide |
| ----------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Desktop installations | Runtime candidates, supporting files, component markers and next probes | Built-in bounded directory/header inspection; [desktop applications](docs/desktop-applications.md) |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](docs/native-investigation.md) |
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra, IDA or Kuna; [native analysis](docs/native-investigation.md) |
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
| GPU code objects | CUDA/HIP targets, selected kernel parameters and resources, retained PTX text | Built-in static ELF/PE/fatbin/CUBIN/PTX inspection; [GPU code objects](docs/gpu-code-objects.md) |
@@ -195,6 +206,7 @@ Additional tools and host support depend on the target:
| PE imports | Import and delay-load DLL names, ordinals, IAT RVAs and pefile import hash | Built-in static PE inspection; [PE imports](docs/pe-imports.md) |
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id and ABI-tag fields | Built-in static ELF inspection; [ELF notes](docs/elf-notes.md) |
| Apple IPS crashes | .ips metadata, threads, images, registers and documented crash classes | Built-in static JSON inspection; [Apple IPS](docs/apple-ips.md) |
| Linux kernel modules | .modinfo keys, __versions CRCs, this_module presence, and signature trailer | Built-in static ELF inspection; [kernel modules](docs/kernel-modules.md) |
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas and the Code header | Built-in static BEAM inspection; [Erlang BEAM](docs/erlang-beam.md) |
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
@@ -275,11 +287,53 @@ historical compiler output.
[Read the case study](https://rea.tools/showcase/th04/) ·
[Reconstruction repository](https://github.com/N0zoM1z0/th04)
### Build RL environments with REA
Record a web app session, turn its API into tools, and export an environment an AI agent can use. Run it with AgentEnv, then add tasks and reward checks for RL training or evaluation.
[Read the guide](https://rea.tools/guides/rl-environments/) ·
[Watch the video](https://x.com/reatools/status/2112574822584918075?s=20)
If you've used REA on something interesting, we'd love to see it. Share your
case in an [issue](https://github.com/morluto/rea/issues) or a
[pull request](https://github.com/morluto/rea/pulls), including the target,
your question, how REA helped, and what you found.
## Build RL environments
Give an AI agent a local environment to practice tasks such as finding an issue or creating a folder, without running the original app. Use it for reinforcement learning (RL) or to test how well an agent completes tasks.
REA turns recorded API requests and responses into tools the agent can call. The environment uses the recorded data and supports changes only when the recording confirms their effects.
With REA set up in your agent, replace the example URL and folder tasks below with your own:
```text
Use REA to build an RL environment from my test web app at
http://127.0.0.1:3000.
Cover listing, creating, renaming, and deleting folders. You may create and
remove test folders. Leave existing data unchanged.
Save it in a folder named my-env. Check which operations work and tell me
how to run it.
```
<details>
<summary>Use the command line instead</summary>
Use these commands if you already have a file containing the app’s recorded browser requests and responses. Running the result also requires AgentEnv and Docker.
```sh
rea environment-build ./session.har ./my-env --name my-env --json
agent-env run ./my-env
```
</details>
REA exports a runnable environment with a basic test to check that it starts and responds. Use it with your RL framework to define task goals, add reward checks, and run training.
[Build an environment](https://rea.tools/guides/rl-environments/) ·
[Environment reference](docs/agent-environments.md) ·
[Watch the demo on X](https://x.com/reatools/status/2112574822584918075?s=20)
## FAQ
<details>
@@ -292,11 +346,13 @@ Any agent that supports local MCP servers. Setup configures the
</details>
<details>
<summary><strong>Do I need Hopper, Ghidra or IDA?</strong></summary>
<summary><strong>Do I need a native analysis engine?</strong></summary>
Deep native analysis uses one of them. Static JavaScript and .NET inspection
work without a native analysis engine. Setup can install Hopper after approval;
Ghidra and IDA use your existing installations. See [provider setup](docs/installation.md#hopper).
Deep native analysis uses Hopper, Ghidra, IDA or Kuna, depending on the target
and operation. Static JavaScript and .NET inspection work without a native
analysis engine. Setup can install Hopper after approval; Ghidra, IDA and Kuna
use your existing installations. See [provider setup](docs/installation.md#hopper)
for the options and their supported targets.
</details>
@@ -376,7 +432,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
## Star history
🎉 **300,000 GitHub stars — thank you!**
🎉 **400,000 GitHub stars — thank you!**
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
+1 -1
View File
@@ -10,7 +10,7 @@ Do not disclose vulnerabilities, proof-of-concept exploits, credentials,
private binaries, Hopper documents, or private Ghidra projects in a public
issue.
Use **Report a vulnerability** in the repository's **Security** tab to submit the report privately. Do not open a public issue to request a private contact channel.
Use **Report a vulnerability** in the repository's **Security** tab as the preferred way to submit the report privately. If GitHub private reporting is unavailable to you, email [[email protected]](mailto:[email protected]). Do not open a public issue to request a private contact channel.
Include the affected REA version, operating system/distribution, selected
provider and version, impact, reproduction conditions, and any suggested
+2 -1
View File
@@ -17,6 +17,7 @@ notices supplied with the software you actually obtain or redistribute.
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| npm dependencies | [Complete inventory](licenses/npm-dependencies.txt) of every entry in `package-lock.json`, including transitive, optional platform, development, and website dependencies. [Runtime notices](licenses/npm-runtime-notices.txt) preserve the copyright, license, and NOTICE documents from integrity-verified release archives, plus the MIT notice for Rolldown's generated runtime helpers. Development dependencies are inventoried but are not all distributed in REA's npm package. |
| pefile ordinal tables | Import-hash ordinal names are derived from [pefile 2024.8.26](https://github.com/erocarrera/pefile/tree/4b3b1e2e568a88d4f1897d694d684f23d9e270c4), copyright 2004–2024 Ero Carrera. Preserve the [MIT license](licenses/pefile-2024.8.26-LICENSE.txt). The Python package is not a runtime dependency. |
| UF2 family names | UF2 family IDs and short names are copied from [microsoft/uf2 `utils/uf2families.json`](https://github.com/microsoft/uf2/blob/cf3bd7f9c274b949528f8cd6a19eef2502a687d2/utils/uf2families.json), copyright Microsoft Corporation. Preserve the [MIT license](licenses/microsoft-uf2-MIT.txt). |
| NativeAOT parser | REA's derived parser uses the MIT-licensed [ghidra-nativeaot](https://github.com/Washi1337/ghidra-nativeaot/tree/effeb734fc570c32650f88b159608979dc7b423e) work of Washi and the NativeAOT layout definitions in [dotnet/runtime](https://github.com/dotnet/runtime/tree/a3b2d40328be0be3f8dd43f0e5cc925b8827b044). Preserve [Washi's MIT notice](licenses/nativeaot-parser-MIT.txt) and the [.NET Foundation MIT notice](licenses/dotnet-runtime-MIT.txt). |
| EVM analysis | `[email protected]`, MIT, copyright 2023 Maxim Andreev. The published npm archive omits its license document; REA retains the [original upstream license](third_party/evmole/LICENSE) and [provenance](third_party/evmole/README.md). It is also included in the runtime notices. |
| Windows native addon | The release addon is compiled with MinGW-w64 and statically links GCC runtime libraries. Preserve the [MinGW-w64 runtime notices](licenses/MinGW-w64-runtime.txt), [GPLv3 text](licenses/GPL-3.0.txt), and [GCC Runtime Library Exception 3.1](licenses/GCC-Runtime-Library-Exception-3.1.txt). The exception governs eligible compiled combinations; it does not relicense the runtime itself under MIT. The Node-API build headers are from Node 22.19.0; its [original license and component notices](licenses/Node-22.19.0-LICENSE.txt) are retained. Node itself is supplied by the user. Snapshot provenance and release-toolchain review requirements are in [licenses/README.md](licenses/README.md). |
@@ -59,7 +60,7 @@ pin inspection sources; they are not included in the npm package.
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.833/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.833/NOTICE) | Bring your own `kuna`; REA verified 1.833 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.990/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.990/NOTICE) | Bring your own `kuna`; REA verified 1.990 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
+10
View File
@@ -0,0 +1,10 @@
# Plugin icon
`rea-plugin.png` is a square adaptation of the left panel of REA's GitHub
social preview, retrieved on 2026-10-28:
https://repository-images.githubusercontent.com/1209966933/1a3fef9a-be4b-41f4-8830-a94c0e8288cc
The image was adapted with imagegen, preserving the cyan REA lettering,
pixel typography, orange rule, and taglines. Both plugin manifests reference
this shared asset; only the PNG is included in the npm package.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+528 -29
View File
@@ -1,20 +1,35 @@
import com.atxx.jhmcp.*;
import com.google.gson.*;
import com.android.tools.smali.dexlib2.Opcodes;
import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile;
import com.android.tools.smali.dexlib2.dexbacked.DexBackedClassDef;
import com.android.tools.smali.dexlib2.dexbacked.DexBackedMethod;
import io.modelcontextprotocol.kotlin.sdk.server.*;
import io.modelcontextprotocol.kotlin.sdk.types.*;
import jadx.api.*;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnIndexType;
import jadx.api.plugins.input.insns.Opcode;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.*;
import jadx.core.xmlgen.BinaryXMLParser;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.InputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import kotlin.Unit;
import kotlin.ResultKt;
import kotlin.coroutines.Continuation;
@@ -37,16 +52,25 @@ class ReaJadxBridge {
private Map<String, List<ClassSnapshot>> classes = Map.of();
private JadxSession manifestSession;
private ManifestSnapshot manifestSnapshot;
private ExternalInventory externalInventory;
private record MethodSnapshot(String name, String rawName, String shortId, List<String> bodyAbsenceMarkers,
Map<String, Object> summary) {}
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary) {}
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary,
Map<String, Object> summaryWithValues) {}
private record ManifestSnapshot(String text, Map<String, Object> summary) {}
private record ExternalMethod(MethodInfo info, Map<String, Object> summary) {}
private record ExternalInventory(JadxSession session, String className, String methodName,
List<ExternalMethod> methods) {}
private static final class InputFailure extends IllegalArgumentException {
final String field;
InputFailure(String field, String message) { super(message); this.field = field; }
}
private static final class ReferenceLimit extends IllegalArgumentException {
final String stage;
ReferenceLimit(String stage) { super("External reference representation limit"); this.stage = stage; }
}
private ReaJadxBridge(SessionHolder holder) { this.holder = holder; }
@@ -142,6 +166,14 @@ class ReaJadxBridge {
return value.getAsString();
}
private static boolean flag(JsonObject args, String name) {
JsonElement value = args.get(name);
if (value == null) return false;
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isBoolean())
throw new IllegalArgumentException(name + " must be a boolean");
return value.getAsBoolean();
}
private static int integer(JsonObject args, String name, int fallback) {
JsonElement value = args.get(name);
if (value == null) return fallback;
@@ -165,6 +197,206 @@ class ReaJadxBridge {
private void clearManifest() {
manifestSession = null;
manifestSnapshot = null;
externalInventory = null;
}
// One bounded DEX at a time, beside the already admitted analysis JVM. The
// response allowance accounts for worst-case JSON escaping before retention.
private static final int NATIVE_DEX_BYTES = 64 * 1024 * 1024;
private static final long NATIVE_TOTAL_DEX_BYTES = 256L * 1024 * 1024;
// This JSON is escaped again inside an 8 MiB MCP frame.
private static final long NATIVE_JSON_BYTES = 4L * 1024 * 1024;
/** The bundled decoder allocates its declared UTF-16 length before validating bytes. */
private static final class NativeDex extends DexBackedDexFile {
private OptionalIndexedSection<String> boundedStrings;
NativeDex(byte[] bytes) { super(Opcodes.getDefault(), bytes); }
@Override public OptionalIndexedSection<String> getStringSection() {
if (boundedStrings == null) {
var source = super.getStringSection();
boundedStrings = new OptionalIndexedSection<>() {
@Override public int size() { return source.size(); }
@Override public int getOffset(int index) { return source.getOffset(index); }
@Override public String getOptional(int index) { return index == -1 ? null : get(index); }
@Override public String get(int index) {
int offset = getBuffer().readSmallUint(source.getOffset(index));
int units = getDataBuffer().readerAt(offset).readSmallUleb128();
if (units > NATIVE_JSON_BYTES / 6
|| units > getDataBuffer().getBuf().length - offset)
throw new IllegalArgumentException("DEX string exceeds the native decoding allowance");
return source.get(index);
}
};
}
return boundedStrings;
}
}
private static void checkNativeInterrupted() {
if (Thread.currentThread().isInterrupted())
throw new java.util.concurrent.CancellationException("Native declaration inventory interrupted");
}
private static final class NativeInventory {
final List<Map<String, Object>> methods = new ArrayList<>();
final List<String> unknowns = new ArrayList<>();
long retainedBytes = 1024;
int dexEntries;
int examinedDexEntries;
int examinedClasses;
int examinedMethods;
boolean partial;
boolean diagnosticLimit;
void unknown(String reason) {
partial = true;
long bytes = 64L + reason.length() * 6L;
if (unknowns.size() < 16 && retainedBytes + bytes <= NATIVE_JSON_BYTES - 4096) {
unknowns.add(reason);
retainedBytes += bytes;
} else if (!diagnosticLimit) {
diagnosticLimit = true;
unknowns.add("Additional native-inventory limitations exceed the retained diagnostic allowance.");
}
}
boolean retain(Map<String, Object> method, long bytes) {
if (retainedBytes + bytes > NATIVE_JSON_BYTES - 4096) {
unknown("Native declarations exceed the 4 MiB retained JSON allowance; remaining declarations are unknown.");
return false;
}
retainedBytes += bytes;
methods.add(method);
return true;
}
Map<String, Object> result() {
return Map.of("scope", "apk-root-dex-declarations", "methods", List.copyOf(methods),
"coverage", Map.of("status", partial ? "partial" : "complete",
"dex_entries", dexEntries, "examined_dex_entries", examinedDexEntries,
"examined_classes", examinedClasses, "examined_methods", examinedMethods),
"unknowns", List.copyOf(unknowns));
}
}
/** Match Android's standard root multidex carriers, without a basename guess. */
private static boolean nativeDexEntry(String name) {
return name.equals("classes.dex") || name.matches("classes[0-9]+\\.dex");
}
private static byte[] readNativeDex(ZipFile archive, ZipEntry entry) throws IOException {
long size = entry.getSize();
if (size < 0 || size > NATIVE_DEX_BYTES)
throw new IllegalArgumentException("DEX exceeds the 64 MiB single-member read allowance");
byte[] bytes = new byte[(int) size];
CRC32 crc = new CRC32();
try (InputStream input = archive.getInputStream(entry)) {
int offset = 0;
while (offset < bytes.length) {
checkNativeInterrupted();
int read = input.read(bytes, offset, Math.min(65536, bytes.length - offset));
if (read < 0) throw new IOException("DEX is shorter than its ZIP declaration");
if (read == 0) continue;
crc.update(bytes, offset, read);
offset += read;
}
if (input.read() != -1) throw new IOException("DEX exceeds its ZIP declaration");
}
if (entry.getCrc() < 0 || entry.getCrc() != crc.getValue())
throw new IOException("DEX does not match its ZIP CRC32 declaration");
return bytes;
}
/** Raw DEX ownership precedes JADX's duplicate-class selection and aliases. */
static Map<String, Object> nativeDeclarations(Path apk) {
NativeInventory inventory = new NativeInventory();
try (ZipFile archive = new ZipFile(apk.toFile())) {
Map<String, Integer> names = new HashMap<>();
var directory = archive.entries();
while (directory.hasMoreElements()) {
checkNativeInterrupted();
ZipEntry entry = directory.nextElement();
if (!entry.isDirectory() && nativeDexEntry(entry.getName())) {
inventory.dexEntries++;
names.merge(entry.getName(), 1, Integer::sum);
}
}
long totalBytes = 0;
int entryIndex = -1;
directory = archive.entries();
while (directory.hasMoreElements()) {
checkNativeInterrupted();
ZipEntry entry = directory.nextElement();
entryIndex++;
String path = entry.getName();
if (entry.isDirectory() || !nativeDexEntry(path)) continue;
if (names.get(path) != 1) {
inventory.unknown("Duplicate root DEX entry name cannot be selected uniquely by the ZIP reader: " + path);
continue;
}
if (entry.getSize() < 0 || entry.getSize() > NATIVE_DEX_BYTES
|| entry.getSize() > NATIVE_TOTAL_DEX_BYTES - totalBytes) {
inventory.unknown("Root DEX member exceeds the 64 MiB member or 256 MiB total read allowance: " + path);
continue;
}
totalBytes += entry.getSize();
try {
DexBackedDexFile dex = new NativeDex(readNativeDex(archive, entry));
var classes = dex.getClassSection();
for (int classIndex = 0; classIndex < classes.size(); classIndex++) {
checkNativeInterrupted();
DexBackedClassDef cls = classes.get(classIndex);
String type = cls.getType();
if (!type.startsWith("L") || !type.endsWith(";"))
throw new IllegalArgumentException("Class definition has no reference type");
String className = type.substring(1, type.length() - 1).replace('/', '.');
// Passing false retains duplicate encoded method references too.
for (Iterable<? extends DexBackedMethod> group : List.of(
cls.getDirectMethods(false), cls.getVirtualMethods(false))) {
for (DexBackedMethod method : group) {
checkNativeInterrupted();
inventory.examinedMethods++;
if ((method.getAccessFlags() & 0x100) == 0) continue;
String methodName = method.getName();
String returnType = method.getReturnType();
long characters = 2L + returnType.length();
for (CharSequence parameter : method.getParameterTypes()) {
characters += parameter.length();
if (characters * 6L > NATIVE_JSON_BYTES) break;
}
long bytes = 512L + 6L * (className.length() + methodName.length()
+ path.length() + characters);
if (inventory.retainedBytes + bytes > NATIVE_JSON_BYTES - 4096) {
inventory.unknown("Native declarations exceed the 4 MiB retained JSON allowance; remaining declarations are unknown.");
return inventory.result();
}
StringBuilder descriptor = new StringBuilder((int) characters).append('(');
for (CharSequence parameter : method.getParameterTypes()) descriptor.append(parameter);
descriptor.append(')').append(returnType);
Map<String, Object> row = Map.of("source_index", inventory.methods.size(),
"class_name", className, "method_name", methodName, "descriptor", descriptor.toString(),
"dex_entry", path, "dex_entry_index", entryIndex, "class_index", classIndex,
"method_index", method.getMethodIndex());
if (!inventory.retain(row, bytes)) return inventory.result();
}
}
inventory.examinedClasses++;
}
inventory.examinedDexEntries++;
} catch (IOException | RuntimeException failure) {
if (failure instanceof java.util.concurrent.CancellationException) throw (RuntimeException) failure;
inventory.unknown("Root DEX native declarations could not be fully read from " + path + ": "
+ failure.getClass().getSimpleName()
+ (failure.getMessage() == null ? "" : " (" + failure.getMessage() + ")"));
}
}
} catch (IOException failure) {
// A failed archive close must fail the operation so the provider retires
// its owned JVM; do not cache a result with lost file-resource ownership.
throw new IllegalArgumentException("APK ZIP native declaration inventory could not finish its reader", failure);
}
return inventory.result();
}
/** Original archive identity, never a deobfuscated basename or name suffix. */
@@ -179,10 +411,7 @@ class ReaJadxBridge {
selected = resource;
}
if (selected == null) throw new IllegalArgumentException("Root AndroidManifest.xml not found");
var content = selected.loadContent();
if (content == null || content.getDataType() != jadx.core.xmlgen.ResContainer.DataType.TEXT)
throw new IllegalArgumentException("Root AndroidManifest.xml did not decode as text");
String xml = content.getText().getCodeStr();
String xml = manifestText(selected);
ManifestSnapshot snapshot = new ManifestSnapshot(xml, manifestSummary(xml));
manifestSnapshot = snapshot;
manifestSession = current;
@@ -190,6 +419,23 @@ class ReaJadxBridge {
return manifestSnapshot;
}
/** Decode with resource names but without JADX's class alias substitution. */
static String manifestText(ResourceFile resource) {
var decompiler = resource.getDecompiler();
// BinaryXMLParser resolves android:name against its root's class identities
// and replaces raw '$' and relative names with Java-style aliases. A fresh
// root owns no classes, while its resource-name table preserves reference
// rendering from the loaded target.
RootNode root = new RootNode(decompiler.getArgs());
root.getConstValues().setResourcesNames(decompiler.getRoot().getConstValues().getResourcesNames());
try {
return ResourcesLoader.decodeStream(resource,
(size, input) -> new BinaryXMLParser(root).parse(input).getCodeStr());
} catch (jadx.core.utils.exceptions.JadxException failure) {
throw new IllegalArgumentException("Root AndroidManifest.xml did not decode as text", failure);
}
}
/** One parsed XML owner supplies namespace-aware values alongside the exact decoded text. */
static Map<String, Object> manifestSummary(String xml) {
Element root;
@@ -233,7 +479,7 @@ class ReaJadxBridge {
return Collections.unmodifiableMap(summary);
}
private ClassSnapshot selected(JsonObject args) {
private void indexClasses() {
JadxSession current = session();
if (indexedSession != current) {
Map<String, List<ClassSnapshot>> index = new LinkedHashMap<>();
@@ -259,27 +505,60 @@ class ReaJadxBridge {
.map(Object::toString).collect(Collectors.joining(", "))
+ "): " + method.getReturnType().toString();
return new MethodSnapshot(name, rawName, shortId, List.copyOf(bodyAbsenceMarkers), Map.of(
"name", name, "signature", signature,
"name", name, "raw_name", rawName, "signature", signature,
"dex_descriptor", descriptor,
"is_constructor", method.isConstructor(), "def_pos", 0));
}).toList();
List<Map<String, Object>> fields = node.getFields().stream()
.sorted(Comparator.comparing(FieldNode::getAlias))
.map(field -> Map.<String, Object>of("name", field.getAlias(),
"type", field.getType().toString(), "def_pos", 0)).toList();
List<FieldNode> orderedFields = new ArrayList<>(node.getFields());
orderedFields.sort(Comparator.comparing(FieldNode::getAlias));
List<Map<String, Object>> fields = new ArrayList<>();
List<Map<String, Object>> valuedFields = new ArrayList<>();
boolean anyValue = false;
for (FieldNode field : orderedFields) {
// Raw DEX identity for mapping lookups; the display type can carry generics.
Map<String, Object> entry = Map.of("name", field.getAlias(),
"raw_name", field.getFieldInfo().getName(),
"type", field.getType().toString(),
"dex_type", TypeGen.signature(field.getFieldInfo().getType()), "def_pos", 0);
fields.add(entry);
Map<String, Object> value = dexStaticValue(field);
if (value == null) valuedFields.add(entry);
else {
anyValue = true;
Map<String, Object> valued = new LinkedHashMap<>(entry);
valued.put("dex_static_value", value);
valuedFields.add(Collections.unmodifiableMap(valued));
}
}
List<String> inner = node.getInnerClasses().stream().map(ClassNode::getFullName).toList();
List<String> innerRaw = node.getInnerClasses().stream().map(ClassNode::getRawName).toList();
Map<String, Object> summary = Map.of(
"full_name", cls.getFullName(), "name", cls.getName(),
"full_name", cls.getFullName(), "raw_name", cls.getRawName(), "name", cls.getName(),
"method_count", methods.size(), "field_count", fields.size(),
"inner_class_count", inner.size(), "inner_classes", inner,
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", fields);
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>())
.add(new ClassSnapshot(cls, methods, summary));
"inner_class_count", inner.size(), "inner_classes", inner, "inner_class_raw_names", innerRaw,
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", List.copyOf(fields));
// DEX static values are opt-in, so method and reference lookups keep the member-only reply.
Map<String, Object> summaryWithValues = summary;
if (anyValue) {
Map<String, Object> copy = new LinkedHashMap<>(summary);
copy.put("fields", List.copyOf(valuedFields));
summaryWithValues = Collections.unmodifiableMap(copy);
}
ClassSnapshot snapshot = new ClassSnapshot(cls, methods, summary, summaryWithValues);
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>()).add(snapshot);
// Keep the binary identity supplied by JADX; '$' can also be a literal
// name character, so replacing separators would invent lookup aliases.
if (!cls.getRawName().equals(cls.getFullName()))
index.computeIfAbsent(cls.getRawName(), ignored -> new ArrayList<>()).add(snapshot);
}
}
classes = Collections.unmodifiableMap(index);
indexedSession = current;
}
}
private ClassSnapshot selected(JsonObject args) {
indexClasses();
String name = string(args, "class_name");
List<ClassSnapshot> candidates = classes.get(name);
if (candidates == null) throw new InputFailure("class_name", "Exact class not found: " + name
@@ -289,6 +568,28 @@ class ReaJadxBridge {
return candidates.get(0);
}
/** A static final field's class-definition static value; a static initializer can still assign it. */
private static Map<String, Object> dexStaticValue(FieldNode field) {
if (!field.getAccessFlags().isStatic() || !field.getAccessFlags().isFinal()) return null;
EncodedValue encoded = field.get(JadxAttrType.CONSTANT_VALUE);
if (encoded == null) return null;
Object value = encoded.getValue();
// JADX also attaches the shared null to static finals without a value, so null is never reported.
// Wide and floating values keep their Java text instead of a lossy JSON number.
return switch (encoded.getType()) {
case ENCODED_BOOLEAN -> Map.of("type", "boolean", "value", value);
case ENCODED_BYTE -> Map.of("type", "byte", "value", value);
case ENCODED_SHORT -> Map.of("type", "short", "value", value);
case ENCODED_CHAR -> Map.of("type", "char", "value", (int) (Character) value);
case ENCODED_INT -> Map.of("type", "int", "value", value);
case ENCODED_LONG -> Map.of("type", "long", "value", value.toString());
case ENCODED_FLOAT -> Map.of("type", "float", "value", value.toString());
case ENCODED_DOUBLE -> Map.of("type", "double", "value", value.toString());
case ENCODED_STRING -> Map.of("type", "string", "value", value);
default -> null;
};
}
private List<MethodSnapshot> methods(ClassSnapshot cls, JsonObject args) {
String name = string(args, "method_name");
List<MethodSnapshot> result = cls.methods().stream().filter(method -> method.name().equals(name)).toList();
@@ -357,12 +658,18 @@ class ReaJadxBridge {
// JSON-RPC/CallToolResult envelope and generated numeric request id, and
// account for JSON text being escaped again inside TextContent.
private static final int MAX_XREF_TEXT_BYTES = 8 * 1024 * 1024 - 1024;
// Leave room for the prototype reply, reference reply, retained raw calls,
// normalized Evidence and their text/structured MCP copies, not just one frame.
private static final int MAX_EXTERNAL_XREF_BYTES = MAX_XREF_TEXT_BYTES / 8;
private static int escapedJsonBytes(Object value) {
return JSON.toJson(JSON.toJson(value)).getBytes(StandardCharsets.UTF_8).length;
}
private static final class InvokeBudget {
long remaining;
InvokeBudget(long remaining) { this.remaining = Math.max(0, remaining); }
boolean fitsText(long characters) {
return remaining >= 256 && characters <= (remaining - 256) / 12;
}
boolean admit(Map<String, Object> site) {
// Check a conservative two-layer escape bound before serialization.
long bound = 256;
@@ -377,26 +684,55 @@ class ReaJadxBridge {
}
private record DirectInvokes(List<Map<String, Object>> sites, boolean exhaustive) {}
/** Raw DEX invokes within one JADX using method; offsets are code units from its code start. */
private static DirectInvokes directInvokes(JavaMethod source, MethodInfo target, InvokeBudget budget) {
MethodNode node = source.getMethodNode();
@FunctionalInterface private interface InvokeVisitor {
void accept(MethodInfo target, int offset, int index, Opcode opcode);
}
/** Decode raw invoke identity once at the shared bridge boundary, without generating source. */
private static void visitInvokes(MethodNode node, InvokeVisitor visitor) {
checkNativeInterrupted();
ICodeReader code = node.getCodeReader();
if (code == null) return null;
List<Map<String, Object>> sites = new ArrayList<>();
boolean[] exhaustive = {true};
if (code == null) return;
code.visitInstructions(insn -> {
checkNativeInterrupted();
insn.decode();
if (insn.getIndexType() != InsnIndexType.METHOD_REF) return;
Opcode opcode = insn.getOpcode();
if (!opcode.name().startsWith("INVOKE_")) return;
IMethodRef ref = insn.getIndexAsMethod();
ref.load();
if (!MethodInfo.fromRef(node.root(), ref).getRawFullId().equals(target.getRawFullId())) return;
visitor.accept(MethodInfo.fromRef(node.root(), ref), insn.getOffset(), insn.getIndex(), opcode);
});
}
/** Raw DEX invokes within one JADX using method; offsets are code units from its code start. */
private static DirectInvokes directInvokes(JavaMethod source, MethodInfo target, InvokeBudget budget) {
return directInvokes(source.getMethodNode(), target, budget);
}
private static void visitDirectInvokes(MethodNode node, MethodInfo target, InvokeVisitor visitor) {
visitInvokes(node, (referenced, offset, index, opcode) -> {
if (referenced.getRawFullId().equals(target.getRawFullId()))
visitor.accept(referenced, offset, index, opcode);
});
}
private static boolean referencesMethod(MethodNode node, MethodInfo target) {
boolean[] found = {false};
visitDirectInvokes(node, target, (referenced, offset, index, opcode) -> found[0] = true);
return found[0];
}
private static DirectInvokes directInvokes(MethodNode node, MethodInfo target, InvokeBudget budget) {
if (node.getCodeReader() == null) return null;
List<Map<String, Object>> sites = new ArrayList<>();
boolean[] exhaustive = {true};
visitDirectInvokes(node, target, (referenced, offset, index, opcode) -> {
Map<String, Object> site = Map.of(
"dex_entry", node.getInputFileName(),
"source_raw_method_id", node.getMethodInfo().getRawFullId(),
"dex_code_unit_offset", insn.getOffset(),
"dex_method_index", insn.getIndex(),
"dex_code_unit_offset", offset,
"dex_method_index", index,
"opcode", opcode.name());
if (budget.admit(site)) sites.add(site);
else exhaustive[0] = false;
@@ -404,6 +740,143 @@ class ReaJadxBridge {
return new DirectInvokes(sites, exhaustive[0]);
}
private List<ClassSnapshot> sourceClasses() {
indexClasses();
Set<ClassSnapshot> seen = Collections.newSetFromMap(new IdentityHashMap<>());
List<ClassSnapshot> result = new ArrayList<>();
for (List<ClassSnapshot> group : classes.values())
for (ClassSnapshot source : group) if (seen.add(source)) result.add(source);
result.sort(Comparator.comparing(source -> source.node().getRawName()));
return result;
}
private static long displayTypeCharacters(ArgType type) {
if (type.isArray())
return 2L * type.getArrayDimension() + displayTypeCharacters(type.getArrayRootElement());
if (type.isPrimitive()) return type.getPrimitiveType().getLongName().length();
if (type.isObject() && !type.isGeneric() && !type.isGenericType()) return type.getObject().length();
throw new IllegalArgumentException("External prototype contains a non-DEX type");
}
private static Map<String, Object> externalMethodSummary(MethodInfo info, InvokeBudget budget) {
String name = info.getName();
long signatureCharacters = name.length() + 4L + displayTypeCharacters(info.getReturnType())
+ 2L * Math.max(0, info.getArgsCount() - 1);
for (ArgType type : info.getArgumentsTypes()) signatureCharacters += displayTypeCharacters(type);
long descriptorCharacters = info.getShortId().length() - (long) name.length();
// Use parsed type facts and the existing short-id length before copying/joining strings.
if (!budget.fitsText(2L * name.length() + descriptorCharacters + signatureCharacters))
throw new ReferenceLimit("method_inventory");
String descriptor = info.getShortId().substring(name.length());
String signature = name + "(" + info.getArgumentsTypes().stream()
.map(Object::toString).collect(Collectors.joining(", "))
+ "): " + info.getReturnType();
Map<String, Object> summary = Map.of("name", name, "raw_name", name, "signature", signature,
"dex_descriptor", descriptor, "is_constructor", info.isConstructor(), "def_pos", 0);
if (!budget.admit(summary)) throw new ReferenceLimit("method_inventory");
return summary;
}
/** Only directly referenced prototypes, not invented definitions or an external classpath. */
private List<ExternalMethod> externalMethods(JsonObject args) {
String className = string(args, "class_name");
String methodName = string(args, "method_name");
JadxSession current = session();
if (externalInventory != null && externalInventory.session() == current
&& externalInventory.className().equals(className) && externalInventory.methodName().equals(methodName))
return externalInventory.methods();
// Retain only the latest bounded selection, never results from a replaced APK/session.
externalInventory = null;
SortedMap<String, ExternalMethod> methods = new TreeMap<>();
boolean[] observedClass = {false};
InvokeBudget budget = new InvokeBudget(MAX_EXTERNAL_XREF_BYTES
- 4096L - 12L * (className.length() + (long) methodName.length()));
for (ClassSnapshot source : sourceClasses()) {
for (MethodSnapshot selected : source.methods()) {
visitInvokes(method(source, selected).getMethodNode(), (info, offset, index, opcode) -> {
if (!info.getDeclClass().getRawName().equals(className)) return;
observedClass[0] = true;
if (!info.getName().equals(methodName) || methods.containsKey(info.getShortId())) return;
methods.put(info.getShortId(), new ExternalMethod(info, externalMethodSummary(info, budget)));
});
}
}
if (!observedClass[0]) throw new InputFailure("class_name",
"No APK definition or direct DEX invoke references the exact class: " + className);
if (methods.isEmpty()) throw new InputFailure("method_name",
"No direct DEX invoke references " + className + "." + methodName);
List<ExternalMethod> result = List.copyOf(methods.values());
externalInventory = new ExternalInventory(current, className, methodName, result);
return result;
}
private Map<String, Object> externalSummary(JsonObject args) {
List<ExternalMethod> methods = externalMethods(args);
return Map.of("class_name", string(args, "class_name"), "reference_target", "external",
"observed_method_count", methods.size(),
"prototypes", methods.stream().map(ExternalMethod::summary).toList());
}
private record ExternalCaller(MethodNode node, Map<String, Object> row) {}
/** Reserve the complete using-node inventory before optional instruction positions. */
private List<ExternalCaller> externalCallers(MethodInfo target, InvokeBudget budget) {
List<ExternalCaller> callers = new ArrayList<>();
for (ClassSnapshot source : sourceClasses()) {
String className = String.valueOf(source.summary().get("full_name"));
Map<String, Integer> overloads = new HashMap<>();
for (MethodSnapshot candidate : source.methods()) {
int overload = overloads.merge(candidate.name(), 1, Integer::sum) - 1;
MethodNode node = method(source, candidate).getMethodNode();
if (!referencesMethod(node, target)) continue;
if (classes.get(className).size() != 1)
throw new IllegalArgumentException("External caller has an ambiguous APK class identity: " + className);
Map<String, Object> row = new LinkedHashMap<>(Map.of(
"kind", "method", "name", candidate.name(), "full_name", className + "." + candidate.name(),
"containing_class", className, "top_class", source.node().getTopParentClass().getFullName(),
"def_pos", 0, "source_selector", Map.of("class_name", className,
"method_name", candidate.name(), "overload_index", overload),
"direct_invokes", List.of(), "direct_invokes_exhaustive", false));
if (!budget.admit(row)) throw new ReferenceLimit("caller_inventory");
callers.add(new ExternalCaller(node, row));
}
}
return List.copyOf(callers);
}
private Map<String, Object> externalReferences(JsonObject args) {
List<ExternalMethod> methods = externalMethods(args);
if (methods.size() != 1 && !args.has("overload_index"))
throw new InputFailure("overload_index", "Select one of " + methods.size() + " observed external prototypes");
int index = integer(args, "overload_index", 0);
if (index >= methods.size()) throw new InputFailure("overload_index", "External prototype index out of range: " + index);
ExternalMethod method = methods.get(index);
MethodInfo target = method.info();
Map<String, Object> selected = method.summary();
InvokeBudget budget = new InvokeBudget(MAX_EXTERNAL_XREF_BYTES
- 4096L - escapedJsonBytes(selected));
List<ExternalCaller> callers = externalCallers(target, budget);
List<Map<String, Object>> items = new ArrayList<>();
for (ExternalCaller caller : callers) {
DirectInvokes invokes = directInvokes(caller.node(), target, budget);
caller.row().put("direct_invokes", invokes.sites());
caller.row().put("direct_invokes_exhaustive", invokes.exhaustive());
items.add(caller.row());
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("target", string(args, "class_name") + "." + target.getName());
result.put("reference_target", "external");
result.put("reference_scope", "direct_dex_invokes");
result.put("dex_descriptor", selected.get("dex_descriptor"));
result.put("reported_signature", selected.get("signature"));
result.put("overload_index", index);
result.put("total", items.size());
result.put("count", items.size());
result.put("resolve_line", false);
result.put("items", items);
return result;
}
private CallToolResult handle(String name, kotlinx.serialization.json.JsonObject input) {
JsonObject args = JsonParser.parseString(input.toString()).getAsJsonObject();
if (name.equals("rea_jvm_status")) return json(Map.of(
@@ -412,6 +885,8 @@ class ReaJadxBridge {
"available_processors", Runtime.getRuntime().availableProcessors(),
"java_version", System.getProperty("java.version"),
"metadata_scope", "parsed_members"));
if (name.equals("rea_native_declarations"))
return json(nativeDeclarations(Path.of(string(args, "path"))));
JadxSession current = session();
if (name.equals("get_app_info")) return json(manifest(current).summary());
if (name.equals("get_android_manifest"))
@@ -438,8 +913,15 @@ class ReaJadxBridge {
"scope", "top_level", "offset", offset, "limit", limit,
"items", items));
}
indexClasses();
if (flag(args, "reference_target") && !classes.containsKey(string(args, "class_name"))) {
if (name.equals("get_class_summary")) return json(externalSummary(args));
if (name.equals("get_xrefs_to_method")) return json(externalReferences(args));
throw new InputFailure("class_name", "External prototypes are only available to method reference queries");
}
ClassSnapshot cls = selected(args);
if (name.equals("get_class_summary")) return json(cls.summary());
if (name.equals("get_class_summary"))
return json(flag(args, "dex_static_values") ? cls.summaryWithValues() : cls.summary());
if (name.equals("get_method_body")) {
List<MethodSnapshot> overloads = methods(cls, args);
int index = integer(args, "overload_index", 0);
@@ -454,7 +936,7 @@ class ReaJadxBridge {
"markers", code.getMarkers(), "body", code.getText()));
}
List<JavaNode> uses;
String target = string(args, "class_name");
String target = (String) cls.summary().get("full_name");
MethodSnapshot selectedMethod = null;
int overloadIndex = 0;
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
@@ -510,6 +992,9 @@ class ReaJadxBridge {
try { return bridge.handle(name, request.getArguments()); }
catch (InputFailure error) { return text(JSON.toJson(Map.of(
"kind", "invalid-input", "field", error.field, "message", error.getMessage())), true); }
catch (ReferenceLimit error) { return text(JSON.toJson(Map.of(
"kind", "reference-output-limit", "stage", error.stage,
"limit_bytes", MAX_EXTERNAL_XREF_BYTES)), true); }
catch (IllegalArgumentException error) { return text(error.getMessage(), true); }
});
}
@@ -522,7 +1007,7 @@ class ReaJadxBridge {
SessionHolder holder = new SessionHolder(defaults);
ServerCapabilities capabilities = new ServerCapabilities(
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
Server server = new Server(new Implementation("rea-jadx-bridge", "5", null, null, null),
Server server = new Server(new Implementation("rea-jadx-bridge", "10", null, null, null),
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
SessionToolsKt.registerSessionTools(server, holder);
ClassToolsKt.registerClassTools(server, holder);
@@ -542,6 +1027,11 @@ class ReaJadxBridge {
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(Map.of()), List.of(), null),
"Observed worker JVM and metadata scope", null, null, null, null, null, null);
server.addTool(diagnostics, (connection, request, continuation) -> bridge.handle("rea_jvm_status", request.getArguments()));
registerBridgeTool(server, bridge, "rea_native_declarations",
new ToolSchema(null, new kotlinx.serialization.json.JsonObject(Map.of("path",
new kotlinx.serialization.json.JsonObject(Map.of("type",
kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))))), List.of("path"), null),
"Original ACC_NATIVE declarations from the admitted APK's standard root DEX members, without loading or decompiling the APK.");
for (String name : List.of("get_app_info", "get_android_manifest")) {
String description = name.equals("get_app_info")
? "Namespace-aware summary of the exact root AndroidManifest.xml resource."
@@ -564,9 +1054,18 @@ class ReaJadxBridge {
if (name.equals("list_classes")) properties.put("query",
new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))));
if (name.equals("get_class_summary")) properties.put("dex_static_values",
new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("boolean"))));
if (name.equals("get_class_summary") || name.equals("get_xrefs_to_method"))
properties.put("reference_target", new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("boolean"))));
if (name.equals("get_class_summary")) properties.put("method_name",
new kotlinx.serialization.json.JsonObject(Map.of(
"type", kotlinx.serialization.json.JsonElementKt.JsonPrimitive("string"))));
String description = switch (name) {
case "list_classes" -> "Pages of case-sensitive matching top-level class names; offset and next_offset are inventory positions, limit bounds matching rows. Omitted or empty query inventories all names.";
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation. Set dex_static_values to include encoded static final field values.";
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
default -> "Complete static using-node inventory for an exact class or selected method overload; one node may contain multiple invoke instructions, and source lines are unresolved.";
};
+337 -108
View File
@@ -40,6 +40,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -404,6 +405,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
writeSuccess(writer, request.id, result);
return;
}
decompileTimeoutSeconds = request.decompileTimeoutSeconds() == null
? descriptor.decompileTimeoutSeconds() : request.decompileTimeoutSeconds();
writeSuccess(writer, request.id, handleRequest(request, descriptor));
}
catch (RequestFailure failure) {
@@ -432,6 +435,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
"Ghidra auto-analysis did not complete for this Program"
);
}
if (descriptor.existingProject && isMutatingOperation(request.method))
throw new RequestFailure("method_unavailable", "Existing Ghidra project sessions are read-only");
return switch (request.method) {
case "read_binary_inventory_summary" -> binaryInventorySummary(request.params);
case "inspect_native_load_image" -> inspectNativeLoadImage(request.params);
@@ -484,6 +489,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
};
}
private static boolean isMutatingOperation(String method) {
return method.equals("annotate_native_function") || method.equals("set_address_name") || method.equals("set_addresses_names");
}
private JsonObject sessionInfo(SessionDescriptor descriptor) throws Exception {
boolean timedOut = analysisTimeoutOccurred();
JsonObject provider = new JsonObject();
@@ -509,14 +518,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.addProperty("run_id", descriptor.runId);
result.addProperty("profile_digest", descriptor.profileDigest);
result.add("provider", provider);
boolean readOnly = !descriptor.transport.equals("unix-socket");
boolean readOnly = descriptor.existingProject || !descriptor.transport.equals("unix-socket");
result.addProperty("read_only", readOnly);
result.addProperty("analysis_complete", !timedOut);
if (descriptor.existingProject) {
result.add("analysis_complete", JsonNull.INSTANCE);
result.addProperty("existing_project", true);
} else result.addProperty("analysis_complete", !timedOut);
result.addProperty("analysis_timed_out", timedOut);
result.add("capabilities", GSON.toJsonTree(
java.util.Arrays.stream(CAPABILITIES)
.filter(value -> !readOnly || !(value.equals("annotate_native_function")
|| value.equals("set_address_name") || value.equals("set_addresses_names")))
.filter(value -> !readOnly || !isMutatingOperation(value))
.toArray(String[]::new)
));
result.add("target", target);
@@ -538,6 +549,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
String softFloat = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA MIPS soft-float storage", null);
if (softFloat != null) result.add("mips_soft_float_storage", JsonParser.parseString(softFloat));
// Written by ReaGhidraRiscvCallingConvention after analysis; REA requires it
// exactly when the committed profile declares a RISC-V calling-convention policy.
String riscvCallingConvention = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
.getString("REA RISC-V calling convention", null);
if (riscvCallingConvention != null) result.add("riscv_calling_convention", JsonParser.parseString(riscvCallingConvention));
return result;
}
@@ -643,6 +659,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
mapping.addProperty("initialized", block.isInitialized());
mapping.addProperty("loaded", block.isLoaded());
mapping.addProperty("overlay", block.isOverlay());
mapping.addProperty("readable", block.isRead());
mapping.addProperty("writable", block.isWrite());
mapping.addProperty("executable", block.isExecute());
mapping.addProperty("length", source.getLength());
FileBytes backing = source.getFileBytes().orElse(null);
int sourceIndex = backing == null ? -1 : files.indexOf(backing);
@@ -674,6 +693,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
row.addProperty("status", relocation.getStatus().toString());
row.addProperty("type", relocation.getType());
row.add("symbol", GSON.toJsonTree(relocation.getSymbolName()));
row.add("values", GSON.toJsonTree(relocation.getValues() == null ? new long[0] : relocation.getValues()));
byte[] original = relocation.getBytes();
row.add("original_bytes_hex", original == null ? JsonNull.INSTANCE : GSON.toJsonTree(bytesHex(original)));
@@ -909,17 +929,33 @@ public final class ReaGhidraBridge extends HeadlessScript {
requireKeys(params, Set.of("document", "address"));
requireDocument(params);
Address address = requireAddress(params, "address");
String at = canonicalAddress(address);
// A well-formed address can still lack one exact source offset. Each
// outcome has its own code, so REA reports the reason rather than
// schema repair. Initialization, not the loaded flag, decides whether
// source bytes can exist: file-backed overlays are not loaded.
MemoryBlock block = currentProgram.getMemory().getBlock(address);
if (block == null || !block.isInitialized())
throw new RequestFailure("invalid_request", "Address has no initialized file-backed memory: " + canonicalAddress(address));
if (block == null)
throw new RequestFailure("address_unmapped",
"Address " + at + " is outside every Ghidra memory block");
if (!block.isInitialized())
throw new RequestFailure("address_uninitialized",
"Address " + at + " is in uninitialized Ghidra memory block " + block.getName());
TargetFileOffset mapping = targetFileOffset(block, address);
if (mapping.mappings() != 1)
throw new RequestFailure("invalid_request", "Address has " + mapping.mappings() + " authoritative target-file mappings: " + canonicalAddress(address));
if (mapping.mappings() == 0)
throw new RequestFailure("file_mapping_absent",
"Address " + at + " in initialized Ghidra memory block " + block.getName() +
" has no authoritative mapping to the admitted target file");
if (mapping.mappings() > 1)
throw new RequestFailure("file_mapping_ambiguous",
"Address " + at + " has " + mapping.mappings() +
" authoritative mappings to the admitted target file");
Long fileOffset = mapping.offset();
if (fileOffset == null)
throw new RequestFailure("invalid_request", "Address lacks a safe file offset: " + canonicalAddress(address));
throw new RequestFailure("file_offset_unrepresentable",
"Address " + at + " maps to an admitted target-file offset beyond the exact JSON integer range");
JsonObject result = new JsonObject();
result.addProperty("address", canonicalAddress(address));
result.addProperty("address", at);
result.addProperty("file_offset", fileOffset);
return result;
}
@@ -1300,7 +1336,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
flow.addProperty("computed", false);
if (instruction == null) {
result.addProperty("status", !currentProgram.getMemory().contains(address) ? "outside-memory" : currentProgram.getListing().getInstructionContaining(address) != null
? "not-instruction-boundary" : currentProgram.getListing().getDefinedDataContaining(address) != null ? "data" : "undecodable");
? "not-instruction-boundary" : currentProgram.getListing().getDefinedDataContaining(address) != null ? "data" : "not-in-listing");
for (String key : List.of("bytes", "length", "mnemonic", "raw_disassembly")) result.add(key, JsonNull.INSTANCE);
} else {
result.addProperty("status", "decoded");
@@ -1372,6 +1408,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
result.add("incoming_references", incomingReferences);
}
List<String> limitations = new ArrayList<>(List.of("Operand components are Ghidra Listing decoder tokens; effective memory base/index/displacement roles and per-instruction context mode are unavailable. The mode field is the program language variant."));
if (result.get("status").getAsString().equals("not-in-listing")) limitations.add("No listed instruction or defined data covers this in-memory address. REA did not attempt a decode; the memory may be uninitialized or contain valid instructions.");
if (flow.get("kind").getAsString().equals("terminal")) limitations.add("No RETURN operation was observed in the provider's instruction p-code; terminal classification remains generic.");
result.add("limitations", GSON.toJsonTree(limitations));
return result;
@@ -1917,41 +1954,62 @@ public final class ReaGhidraBridge extends HeadlessScript {
return symbol == null ? JsonNull.INSTANCE : GSON.toJsonTree(symbol.getName(true));
}
// Analyst naming: a function entry renames the function, other addresses get a primary label.
private boolean applyAddressName(Address address, String name) throws Exception {
// Analyst naming: a function entry renames the function; another mapped
// address renames its non-dynamic primary label or receives a new global
// label. Resolve that destination without editing the program.
private AddressNameTarget addressNameTarget(Address address, String name)
throws ghidra.util.exception.InvalidInputException {
if (!currentProgram.getMemory().contains(address))
throw new RequestFailure("invalid_request", "Address is not mapped: " + canonicalAddress(address));
validateAnnotationText(name, "name");
Symbol edited = editedNameSymbol(address);
return edited == null
? new AddressNameTarget(address, currentProgram.getGlobalNamespace(), name)
: new AddressNameTarget(address, edited.getParentNamespace(), symbolLeafName(edited, name));
}
private Symbol editedNameSymbol(Address address) {
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
if (function != null) return function.getSymbol();
Symbol primary = currentProgram.getSymbolTable().getPrimarySymbol(address);
return primary == null || primary.isDynamic() ? null : primary;
}
// Every address whose label or function symbol holds the destination.
private List<Address> addressNameHolders(AddressNameTarget target) {
Set<Address> holders = new TreeSet<>();
for (Symbol symbol : currentProgram.getSymbolTable().getLabelOrFunctionSymbols(target.leaf(), target.namespace()))
holders.add(symbol.getAddress());
return List.copyOf(holders);
}
// Apply one planned destination in its own transaction. Null means the
// edit committed with matching primary-symbol readback.
private String applyAddressName(AddressNameTarget target) {
Address address = target.address();
int transaction = currentProgram.startTransaction("REA address name");
boolean commit = false;
try {
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
SymbolTable table = currentProgram.getSymbolTable();
Symbol primary = function == null ? table.getPrimarySymbol(address) : function.getSymbol();
Namespace namespace = primary == null
? currentProgram.getGlobalNamespace()
: primary.getParentNamespace();
String expected = primary == null ? name : symbolLeafName(primary, name);
for (Symbol symbol : table.getLabelOrFunctionSymbols(expected, namespace)) {
if (!address.equals(symbol.getAddress())) return false;
}
Symbol edited = editedNameSymbol(address);
if (function != null) {
function.setName(expected, SourceType.USER_DEFINED);
function.setName(target.leaf(), SourceType.USER_DEFINED);
} else if (edited != null) {
edited.setName(target.leaf(), SourceType.USER_DEFINED);
} else {
if (primary != null && !primary.isDynamic()) {
primary.setName(expected, SourceType.USER_DEFINED);
} else {
Symbol created = table.createLabel(address, expected, SourceType.USER_DEFINED);
created.setPrimary();
}
Symbol created = currentProgram.getSymbolTable().createLabel(address, target.leaf(), SourceType.USER_DEFINED);
created.setPrimary();
}
invalidateAnalysisCaches();
Symbol readback = currentProgram.getSymbolTable().getPrimarySymbol(address);
commit = readback != null && expected.equals(readback.getName());
return commit;
commit = readback != null && target.leaf().equals(readback.getName());
return commit ? null : "readback_mismatch";
}
catch (ghidra.util.exception.InvalidInputException | ghidra.util.exception.DuplicateNameException exception) {
return false;
catch (ghidra.util.exception.InvalidInputException exception) {
return "invalid_name";
}
catch (ghidra.util.exception.DuplicateNameException exception) {
return "duplicate_name";
}
finally {
currentProgram.endTransaction(transaction, commit);
@@ -1961,7 +2019,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
private JsonElement setAddressName(JsonObject params) throws Exception {
requireKeys(params, Set.of("document", "address", "name"));
requireDocument(params);
return GSON.toJsonTree(applyAddressName(requireAddress(params, "address"), requireString(params, "name")));
String key = requireString(params, "address");
Address address = requireAddress(params, "address");
String name = requireString(params, "name");
// A single edit keeps request errors for unmapped addresses and
// unrepresentable text; other outcomes share the batch representation.
if (!currentProgram.getMemory().contains(address))
throw new RequestFailure("invalid_request", "Address is not mapped: " + canonicalAddress(address));
validateAnnotationText(name, "name");
Map<String, String> requested = new LinkedHashMap<>();
requested.put(key, name);
return applyAddressNames(requested);
}
private JsonElement setAddressesNames(JsonObject params) throws Exception {
@@ -1969,34 +2037,129 @@ public final class ReaGhidraBridge extends HeadlessScript {
requireDocument(params);
if (!params.has("names") || !params.get("names").isJsonObject())
throw new RequestFailure("invalid_request", "names must be an object of address -> name");
Map<String, String> requested = new LinkedHashMap<>();
for (Map.Entry<String, JsonElement> entry : params.getAsJsonObject("names").entrySet())
requested.put(entry.getKey(), entry.getValue().getAsString());
return applyAddressNames(requested);
}
// Decide every request from the names held before any edit, so object
// member order never selects which address receives a name. Reply with
// per-key results and reasons; a contested destination is listed once in
// conflicts and failures refer to it by index.
private JsonObject applyAddressNames(Map<String, String> requested) {
Map<String, Address> addressesByKey = new LinkedHashMap<>();
Map<Address, List<AddressNameRequest>> requestsByAddress = new LinkedHashMap<>();
JsonObject result = new JsonObject();
for (Map.Entry<String, JsonElement> entry : params.getAsJsonObject("names").entrySet()) {
for (Map.Entry<String, String> entry : requested.entrySet()) {
Address address = null;
try {
Address address = parseReaAddress(entry.getKey());
address = parseReaAddress(entry.getKey());
requestsByAddress.computeIfAbsent(address, ignored -> new ArrayList<>())
.add(new AddressNameRequest(entry.getKey(), address, entry.getValue().getAsString()));
.add(new AddressNameRequest(entry.getKey(), address, entry.getValue()));
}
catch (RequestFailure failure) {
result.addProperty(entry.getKey(), false);
// The key keeps its own invalid_address outcome below.
}
addressesByKey.put(entry.getKey(), address);
}
Map<Address, AddressNameFailure> failures = new HashMap<>();
Map<AddressNameDestination, List<AddressNameTarget>> destinations = new LinkedHashMap<>();
for (Map.Entry<Address, List<AddressNameRequest>> selected : requestsByAddress.entrySet()) {
Address address = selected.getKey();
String name = selected.getValue().get(0).name;
if (selected.getValue().stream().anyMatch(request -> !request.name.equals(name))) {
failures.put(address, new AddressNameFailure("conflicting_alias_names", null));
continue;
}
if (!currentProgram.getMemory().contains(address)) {
failures.put(address, new AddressNameFailure("unmapped_address", null));
continue;
}
try {
AddressNameTarget target = addressNameTarget(address, name);
destinations.computeIfAbsent(target.destination(), ignored -> new ArrayList<>()).add(target);
}
catch (RequestFailure | ghidra.util.exception.InvalidInputException exception) {
failures.put(address, new AddressNameFailure("invalid_name", null));
}
}
for (List<AddressNameRequest> requests : requestsByAddress.values()) {
String name = requests.get(0).name;
boolean conflictingAliasNames = requests.stream()
.anyMatch(request -> !request.name.equals(name));
boolean success = false;
if (!conflictingAliasNames) {
try {
success = applyAddressName(requests.get(0).address, name);
}
catch (RequestFailure failure) {
// Keep per-address failures in the existing batch result shape.
}
// Reject a destination that several selected addresses request, or
// that another address holds, instead of letting order pick a winner.
List<AddressNameConflict> conflicts = new ArrayList<>();
List<AddressNameTarget> independent = new ArrayList<>();
for (List<AddressNameTarget> requesters : destinations.values()) {
AddressNameTarget first = requesters.get(0);
List<Address> holders = addressNameHolders(first);
String reason;
if (requesters.size() > 1) {
reason = "duplicate_requested_name";
} else if (holders.stream().allMatch(first.address()::equals)) {
independent.add(first);
continue;
} else if (holders.stream().anyMatch(holder ->
!holder.equals(first.address()) && !requestsByAddress.containsKey(holder))) {
reason = "name_held_by_unselected_address";
} else {
reason = "name_held_by_selected_address";
}
for (AddressNameRequest request : requests)
result.addProperty(request.inputAddress, success);
AddressNameConflict conflict = new AddressNameConflict(
first.namespace(),
first.leaf(),
requesters.stream().map(AddressNameTarget::address).sorted().toList(),
holders
);
conflicts.add(conflict);
for (AddressNameTarget target : requesters)
failures.put(target.address(), new AddressNameFailure(reason, conflict));
}
// Independent edits have distinct destinations that no other address
// holds, so their results do not depend on application order.
for (AddressNameTarget target : independent) {
String reason = applyAddressName(target);
if (reason != null) failures.put(target.address(), new AddressNameFailure(reason, null));
}
// Requesters belong to exactly one destination, so ordering conflicts
// by their first requester gives indexes independent of member order.
conflicts.sort(Comparator.comparing((AddressNameConflict conflict) -> conflict.requesters().get(0)));
Map<AddressNameConflict, Integer> indexes = new IdentityHashMap<>();
JsonArray contested = new JsonArray();
for (AddressNameConflict conflict : conflicts) {
indexes.put(conflict, contested.size());
JsonObject value = new JsonObject();
if (conflict.namespace().isGlobal()) value.add("namespace", JsonNull.INSTANCE);
else value.addProperty("namespace", conflict.namespace().getName(true));
value.addProperty("name", conflict.leaf());
value.add("requested_by", canonicalAddresses(conflict.requesters()));
value.add("held_by", canonicalAddresses(conflict.holders()));
contested.add(value);
}
JsonObject results = new JsonObject();
JsonObject failed = new JsonObject();
for (Map.Entry<String, Address> entry : addressesByKey.entrySet()) {
Address address = entry.getValue();
AddressNameFailure failure = address == null
? new AddressNameFailure("invalid_address", null)
: failures.get(address);
results.addProperty(entry.getKey(), failure == null);
if (failure == null) continue;
JsonObject value = new JsonObject();
if (address == null) value.add("address", JsonNull.INSTANCE);
else value.addProperty("address", canonicalAddress(address));
value.addProperty("reason", failure.reason());
if (failure.conflict() == null) value.add("conflict", JsonNull.INSTANCE);
else value.addProperty("conflict", indexes.get(failure.conflict()));
failed.add(entry.getKey(), value);
}
JsonObject result = new JsonObject();
result.add("results", results);
result.add("failures", failed);
result.add("conflicts", contested);
return result;
}
private static JsonArray canonicalAddresses(List<Address> addresses) {
JsonArray result = new JsonArray();
for (Address address : addresses) result.add(canonicalAddress(address));
return result;
}
@@ -2086,10 +2249,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
requireKeys(params, Set.of("document", "address"));
requireDocument(params);
Address query = requireAddress(params, "address");
Function function = currentProgram.getFunctionManager().getFunctionAt(query);
if (function == null && query.isMemoryAddress()) {
function = currentProgram.getFunctionManager().getFunctionContaining(query);
}
Function function = functionAtOrContaining(query);
JsonObject result = new JsonObject();
result.addProperty("query_address", canonicalAddress(query));
if (function != null) {
@@ -2105,10 +2265,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
private JsonArray search(JsonObject params, boolean procedureSearch) throws Exception {
requireKeys(
params,
Set.of("pattern", "mode", "case_sensitive", "document")
);
Set<String> keys = new HashSet<>(Set.of("pattern", "mode", "case_sensitive", "document"));
if (params.has("literal_ascii_candidate")) keys.add("literal_ascii_candidate");
requireKeys(params, keys);
requireDocument(params);
String expression = requireString(params, "pattern");
String mode = requireString(params, "mode");
@@ -2116,14 +2275,39 @@ public final class ReaGhidraBridge extends HeadlessScript {
throw new RequestFailure("invalid_request", "mode must be literal or regex");
}
boolean caseSensitive = requireBoolean(params, "case_sensitive");
List<InventoryItem> inventory = procedureSearch
? procedures().stream().map(FunctionEntry::item).toList()
: strings();
ValueMatcher matcher = mode.equals("literal")
? literalMatcher(expression, caseSensitive)
: regexMatcher(expression, caseSensitive);
ValueMatcher matcher;
if (mode.equals("literal") && !caseSensitive) {
if (!params.has("literal_ascii_candidate"))
throw new RequestFailure("invalid_request", "Insensitive literal search requires an ASCII candidate");
matcher = literalCandidateMatcher(optionalString(params, "literal_ascii_candidate"));
} else {
if (params.has("literal_ascii_candidate"))
throw new RequestFailure("invalid_request", "ASCII candidate applies only to insensitive literals");
matcher = mode.equals("literal")
? literalMatcher(expression)
: regexMatcher(expression, caseSensitive);
}
JsonArray items = new JsonArray();
for (InventoryItem item : inventory) {
if (procedureSearch) {
// Search the names exact lookup accepts, keeping one row per
// procedure under its primary name.
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
ProcedureNameMatch match = matchProcedureNames(entry, matcher);
if (!match.matched()) {
continue;
}
JsonObject item = resultItem(entry.item, null);
if (!match.aliases().isEmpty()) {
JsonArray aliases = new JsonArray();
for (String alias : match.aliases()) aliases.add(alias);
item.add("matched_aliases", aliases);
}
items.add(item);
}
return items;
}
for (InventoryItem item : strings()) {
monitor.checkCancelled();
if (!matcher.matches(item.value)) {
continue;
@@ -2133,6 +2317,36 @@ public final class ReaGhidraBridge extends HeadlessScript {
return items;
}
/**
* The one definition of the names that select a procedure, shared by exact
* lookup and search: its primary name and every other symbol at its exact
* entry, each in leaf or namespace-qualified spelling. A label inside the
* body never names the procedure. Matching secondary symbols are reported
* by their qualified names, sorted.
*/
private ProcedureNameMatch matchProcedureNames(FunctionEntry entry, ValueMatcher matcher) throws Exception {
Function function = entry.function;
boolean primary = spellingMatches(function.getName(), entry.item.value, matcher);
Set<String> aliases = null;
for (Symbol symbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
String qualified = symbol.getName(true);
if (!spellingMatches(symbol.getName(), qualified, matcher)) continue;
if (qualified.equals(entry.item.value)) {
primary = true;
continue;
}
if (aliases == null) aliases = new TreeSet<>();
aliases.add(qualified);
}
return new ProcedureNameMatch(primary, aliases == null ? List.of() : List.copyOf(aliases));
}
/** A leaf is the suffix of its qualified spelling, so a literal found in it is found in both. */
private static boolean spellingMatches(String leaf, String qualified, ValueMatcher matcher) {
return matcher.matches(qualified) || (!leaf.equals(qualified) && matcher.matches(leaf));
}
private List<InventoryItem> names() throws Exception {
if (nameInventory != null) {
return nameInventory;
@@ -2235,26 +2449,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
// exact symbol name, even when every character is a hexadecimal digit.
boolean explicitAddress = isExplicitReaAddress(value);
if (!explicitAddress) {
// Imported labels (for example Mach-O _main) can share a function
// entry without becoming its primary symbol; search accepts the same.
ValueMatcher selector = value::equals;
List<Function> matches = new ArrayList<>();
for (FunctionEntry entry : procedures()) {
monitor.checkCancelled();
Function function = entry.function;
Symbol symbol = function.getSymbol();
String qualified = symbol == null ? function.getName() : symbol.getName(true);
boolean matchesName = function.getName().equals(value) || qualified.equals(value);
if (!matchesName) {
// Imported labels (for example Mach-O _main) can share a
// function entry without becoming its primary symbol. Only
// exact entries qualify; an interior label is not a selector.
for (Symbol entrySymbol : currentProgram.getSymbolTable().getSymbols(function.getEntryPoint())) {
monitor.checkCancelled();
if (entrySymbol.getName().equals(value) || entrySymbol.getName(true).equals(value)) {
matchesName = true;
break;
}
}
}
if (matchesName) matches.add(function);
if (matchProcedureNames(entry, selector).matched()) matches.add(entry.function);
}
if (matches.size() > 1)
throw new RequestFailure("ambiguous", "Ghidra procedure name is ambiguous: " + value +
@@ -2278,6 +2479,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
return function;
}
/** Exact entries include externals; containment applies only to memory addresses. */
private Function functionAtOrContaining(Address address) {
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
return function == null && address.isMemoryAddress()
@@ -2982,7 +3184,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (!currentProgram.getLanguageID().getIdAsString().startsWith("AARCH64:")) {
return null;
}
Function function = containingFunction(jumpTable.getSwitchAddress());
Function function = functionAtOrContaining(jumpTable.getSwitchAddress());
if (function == null) return null;
List<Instruction> instructions = new ArrayList<>();
@@ -3378,7 +3580,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
"decompile_timed_out",
"Ghidra decompilation of " + canonicalAddress(function.getEntryPoint()) +
" exceeded the " + decompileTimeoutSeconds +
"-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS"
"-second configured decompilation limit (ghidra.decompile_timeout_seconds; launch seed REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS)"
);
}
if (!results.decompileCompleted()) {
@@ -3482,7 +3684,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
private boolean unrepresentedCaller(Reference reference, Set<Function> callers) {
if (!reference.getReferenceType().isCall()) return false;
Function caller = containingFunction(reference.getFromAddress());
Function caller = functionAtOrContaining(reference.getFromAddress());
return caller == null || !callers.contains(caller);
}
@@ -3629,7 +3831,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
void observe(Reference reference) {
if (!reference.getReferenceType().isCall()) return;
// Reference identity follows the same containing-function owner as referenceEdge.
Function target = containingFunction(reference.getToAddress());
Function target = functionAtOrContaining(reference.getToAddress());
if (returnedCallees != null && (target == null || !returnedCallees.contains(target)))
unrepresented++;
if (target == null) unresolved(reference.getFromAddress(),
@@ -3799,8 +4001,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
JsonObject result = new JsonObject();
result.addProperty("source_address", canonicalAddress(reference.getFromAddress()));
result.addProperty("target_address", canonicalAddress(reference.getToAddress()));
Function source = containingFunction(reference.getFromAddress());
Function target = containingFunction(reference.getToAddress());
Function source = functionAtOrContaining(reference.getFromAddress());
Function target = functionAtOrContaining(reference.getToAddress());
result.add(
"source_procedure",
source == null ? JsonNull.INSTANCE : procedureIdentity(source)
@@ -3835,14 +4037,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
return result;
}
private Function containingFunction(Address address) {
Function result = currentProgram.getFunctionManager().getFunctionAt(address);
if (result == null && address.isMemoryAddress()) {
result = currentProgram.getFunctionManager().getFunctionContaining(address);
}
return result;
}
private static String procedureName(Function function) {
Symbol symbol = function.getSymbol();
return symbol == null ? function.getName() : symbol.getName(true);
@@ -4694,11 +4888,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
return source.name().toLowerCase(Locale.ROOT);
}
private static ValueMatcher literalMatcher(String pattern, boolean caseSensitive) {
String needle = caseSensitive ? pattern : pattern.toLowerCase(Locale.ROOT);
private static ValueMatcher literalMatcher(String pattern) {
return value -> value.contains(pattern);
}
// REA's host owns Unicode simple folding. Keep all non-ASCII source values
// for its final predicate, and filter ASCII-only values without Java regexes.
private static ValueMatcher literalCandidateMatcher(String asciiPattern) {
if (asciiPattern != null && asciiPattern.chars().anyMatch(character -> character > 127))
throw new RequestFailure("invalid_request", "ASCII candidate contains a non-ASCII character");
return value -> {
String candidate = caseSensitive ? value : value.toLowerCase(Locale.ROOT);
return candidate.contains(needle);
if (value.chars().anyMatch(character -> character > 127)) return true;
return asciiPattern != null && value.toLowerCase(Locale.ROOT).contains(asciiPattern);
};
}
@@ -4747,6 +4948,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
keys.add("analysis_extensions");
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("decompile_timeout_seconds"))
keys.add("decompile_timeout_seconds");
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("existing_project"))
keys.add("existing_project");
JsonObject object = requireObject(parsed, keys);
int decompileTimeout = 0;
if (object.has("decompile_timeout_seconds")) {
@@ -4763,12 +4966,24 @@ public final class ReaGhidraBridge extends HeadlessScript {
requireString(object, "provider_version"),
requireString(object, "profile_digest"),
object.has("analysis_extensions") ? object.getAsJsonArray("analysis_extensions") : new JsonArray(),
decompileTimeout
decompileTimeout,
object.has("existing_project") && requireBoolean(object, "existing_project")
);
}
private static Request parseRequest(String line, String expectedToken) {
JsonObject object = requireObject(JsonParser.parseString(line), REQUEST_KEYS);
JsonElement parsed = JsonParser.parseString(line);
Set<String> keys = new HashSet<>(REQUEST_KEYS);
if (parsed.isJsonObject() && parsed.getAsJsonObject().has("decompile_timeout_seconds"))
keys.add("decompile_timeout_seconds");
JsonObject object = requireObject(parsed, keys);
Integer timeout = null;
if (object.has("decompile_timeout_seconds")) {
JsonElement value = object.get("decompile_timeout_seconds");
timeout = value.isJsonNull() ? 0 : requireInteger(object, "decompile_timeout_seconds");
if (!value.isJsonNull() && (timeout < 1 || timeout > MAX_DECOMPILE_TIMEOUT_SECONDS))
throw new IllegalArgumentException("Invalid per-request decompile deadline");
}
int id = requireInteger(object, "id");
if (id <= 0 || !constantTimeEquals(requireString(object, "token"), expectedToken)) {
throw new IllegalArgumentException("Bridge request authentication failed");
@@ -4777,7 +4992,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (params == null || !params.isJsonObject()) {
throw new IllegalArgumentException("Bridge request parameters are invalid");
}
return new Request(id, requireString(object, "method"), params.getAsJsonObject());
return new Request(id, requireString(object, "method"), params.getAsJsonObject(), timeout);
}
private static JsonObject requireObject(JsonElement element, Set<String> keys) {
@@ -4948,7 +5163,20 @@ public final class ReaGhidraBridge extends HeadlessScript {
private record InventoryItem(Address address, String value, JsonObject facts, JsonObject symbol) {}
private record AddressNameRequest(String inputAddress, Address address, String name) {}
private record AddressNameDestination(long namespace, String leaf) {}
private record AddressNameTarget(Address address, Namespace namespace, String leaf) {
AddressNameDestination destination() {
return new AddressNameDestination(namespace.getID(), leaf);
}
}
private record AddressNameConflict(Namespace namespace, String leaf, List<Address> requesters, List<Address> holders) {}
private record AddressNameFailure(String reason, AddressNameConflict conflict) {}
private record FunctionEntry(Function function, InventoryItem item) {}
private record ProcedureNameMatch(boolean primary, List<String> aliases) {
boolean matched() {
return primary || !aliases.isEmpty();
}
}
private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations, JsonObject relationshipCoverage, boolean decompilerFailed) {}
private record CallBoundaryObservation(List<String> limitations, Integer unrepresentedCalleeCalls, JsonArray unresolvedCalls) {}
private record SwiftStringLiteralDecode(JsonArray literals, JsonArray omissions) {}
@@ -4969,9 +5197,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
String providerVersion,
String profileDigest,
JsonArray analysisExtensions,
int decompileTimeoutSeconds
int decompileTimeoutSeconds,
boolean existingProject
) {}
private record Request(int id, String method, JsonObject params) {}
private record Request(int id, String method, JsonObject params, Integer decompileTimeoutSeconds) {}
private static final class RequestFailure extends RuntimeException {
private final String code;
+193
View File
@@ -0,0 +1,193 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.mem.MemoryBlockSourceInfo;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Rebuild a GameCube/Wii DOL memory map from the validated load ranges REA's TypeScript
* admission wrote. BinaryLoader maps the whole file once at address 0; this
* script replaces that block with text, data, and BSS blocks at their load addresses.
*/
public final class ReaGhidraPrepareDol extends GhidraScript {
// Canonical v2 header, 18 section rows, 12 gap rows and entry, using uint32 hex.
private static final int MAX_PLAN_BYTES = 13 + 18 * 49 + 12 * 30 + 17;
private static final class Section {
final String kind;
final int index;
final long fileOffset;
final long address;
final long size;
Section(String kind, int index, long fileOffset, long address, long size) {
this.kind = kind;
this.index = index;
this.fileOffset = fileOffset;
this.address = address;
this.size = size;
}
}
private record BssGap(long address, long size) {}
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
if (arguments.length != 1) {
throw new IllegalArgumentException(
"GameCube DOL preparation requires exactly one section table path"
);
}
if (!currentProgram.getExecutableFormat().equals("Raw Binary") ||
!currentProgram.getLanguageID().getIdAsString().startsWith("PowerPC:BE:32:")) {
throw new IllegalArgumentException(
"GameCube DOL preparation requires a BinaryLoader import in big-endian 32-bit PowerPC"
);
}
Path tablePath = Path.of(arguments[0]);
if (!Files.isRegularFile(tablePath, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException("GameCube DOL section table is not a regular file");
}
if (Files.size(tablePath) > MAX_PLAN_BYTES) {
throw new IllegalArgumentException("GameCube DOL plan exceeds the canonical format's byte bound");
}
List<Section> sections = new ArrayList<>();
List<BssGap> bssGaps = new ArrayList<>();
int expectedSections = -1;
int expectedGaps = -1;
long entry = -1;
for (String line : Files.readAllLines(tablePath)) {
String[] fields = line.trim().split("\\s+");
switch (fields[0]) {
case "plan" -> {
requireFields(fields, 4, line);
if (expectedSections >= 0 || !fields[1].equals("2")) {
throw new IllegalArgumentException("Duplicate or unsupported GameCube DOL plan header");
}
expectedSections = Integer.parseInt(fields[2]);
expectedGaps = Integer.parseInt(fields[3]);
// A DOL has 18 slots, including at most 11 data slots and 12 BSS gaps.
if (expectedSections < 1 || expectedSections > 18 || expectedGaps < 0 || expectedGaps > 12) {
throw new IllegalArgumentException("GameCube DOL plan counts exceed the format's slot bounds");
}
}
case "section" -> {
requireFields(fields, 6, line);
if (expectedSections < 0 || sections.size() >= expectedSections) {
throw new IllegalArgumentException("GameCube DOL section exceeds its declared count");
}
sections.add(new Section(
fields[1],
Integer.parseInt(fields[2]),
Long.decode(fields[3]),
Long.decode(fields[4]),
Long.decode(fields[5])
));
}
case "bss-gap" -> {
requireFields(fields, 3, line);
if (expectedGaps < 0 || bssGaps.size() >= expectedGaps) {
throw new IllegalArgumentException("GameCube DOL BSS gap exceeds its declared count");
}
long address = Long.decode(fields[1]);
long size = Long.decode(fields[2]);
if (address < 0 || size <= 0) {
throw new IllegalArgumentException("GameCube DOL BSS gap is not a positive range");
}
bssGaps.add(new BssGap(address, size));
}
case "entry" -> {
requireFields(fields, 2, line);
entry = Long.decode(fields[1]);
}
default -> throw new IllegalArgumentException("Unknown GameCube DOL table line: " + line);
}
}
if (expectedSections != sections.size() || expectedGaps != bssGaps.size() || entry < 0) {
throw new IllegalArgumentException("GameCube DOL section table is incomplete");
}
Memory memory = currentProgram.getMemory();
List<FileBytes> files = memory.getAllFileBytes();
if (files.size() != 1) {
throw new IllegalStateException("GameCube DOL preparation requires exactly one imported file image");
}
FileBytes fileBytes = files.get(0);
AddressSpace space = currentProgram.getAddressFactory().getDefaultAddressSpace();
// Remove the whole-file block before creating section blocks, so no two blocks claim one address.
for (MemoryBlock block : memory.getBlocks()) {
for (MemoryBlockSourceInfo source : block.getSourceInfos()) {
if (source.getFileBytes().isPresent() && source.getFileBytes().get().equals(fileBytes)) {
memory.removeBlock(block, monitor);
break;
}
}
}
for (Section section : sections) {
if (section.fileOffset + section.size > fileBytes.getSize()) {
throw new IllegalArgumentException(
"GameCube DOL " + section.kind + " " + section.index + " exceeds the imported file"
);
}
boolean text = section.kind.equals("text");
if (!text && !section.kind.equals("data")) {
throw new IllegalArgumentException("Unknown GameCube DOL section kind: " + section.kind);
}
MemoryBlock block = memory.createInitializedBlock(
"." + section.kind + section.index,
space.getAddress(section.address),
fileBytes,
section.fileOffset,
section.size,
false
);
block.setRead(true);
block.setWrite(!text);
block.setExecute(text);
}
// Apply the domain owner's exact uninitialized ranges; do not reinterpret BSS here.
int gapIndex = 0;
for (BssGap gap : bssGaps) {
MemoryBlock block = memory.createUninitializedBlock(
".bss" + gapIndex++,
space.getAddress(gap.address()),
gap.size(),
false
);
block.setRead(true);
block.setWrite(true);
block.setExecute(false);
}
Address entryAddress = space.getAddress(entry);
MemoryBlock entryBlock = memory.getBlock(entryAddress);
if (entryBlock == null || !entryBlock.isExecute()) {
throw new IllegalArgumentException("GameCube DOL entry must lie in an executable section");
}
addEntryPoint(entryAddress);
if (!disassemble(entryAddress) || createFunction(entryAddress, "__start") == null) {
throw new IllegalStateException("GameCube DOL entry cannot be decoded as a function");
}
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA raw binary prepared", true);
currentProgram.getOptions(Program.PROGRAM_INFO).setBoolean("REA GameCube DOL prepared", true);
}
private static void requireFields(String[] fields, int count, String line) {
if (fields.length != count) {
throw new IllegalArgumentException("Malformed GameCube DOL table line: " + line);
}
}
}
@@ -0,0 +1,283 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.database.SpecExtension;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DoubleDataType;
import ghidra.program.model.data.Float16DataType;
import ghidra.program.model.data.FloatDataType;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.program.model.data.IntegerDataType;
import ghidra.program.model.data.StructureDataType;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.pcode.XmlEncode;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Apply the committed RISC-V calling-convention policy after analysis. Ghidra's generic RV32
* compiler specification models ilp32d. Each policy derives a program-specific prototype from the
* program's own default prototype, as its {@link Policy} row and docs/ghidra-riscv.md state. The
* script installs the prototype as a specification extension and assigns it, with dynamic
* storage, to every non-thunk function, external function and function definition.
*/
public final class ReaGhidraRiscvCallingConvention extends GhidraScript {
static final String REPORT_KEY = "REA RISC-V calling convention";
/** RISC-V floating-point registers by ABI name: ft0-ft11, fs0-fs11 and fa0-fa7. */
private static final Pattern FLOAT_REGISTER = Pattern.compile("f[ast]\\d+");
/**
* What a policy changes in the default ilp32d prototype. Every policy also passes values of 9
* bytes or more, such as {@code long double}, by reference.
*
* @param floatEntryBytes the largest value a floating-point register may pass or return; 0
* removes those entries and every rule that assigns floating-point storage
* @param preservesFloatRegisters whether fs0-fs11 stay preserved across calls
*/
private record Policy(
String id, String prototype, int floatEntryBytes, boolean preservesFloatRegisters) {}
private static final List<Policy> POLICIES =
List.of(new Policy("ilp32-prototype-v1", "__rea_ilp32", 0, false));
@Override
public void run() throws Exception {
String[] arguments = getScriptArgs();
Policy policy =
POLICIES.stream()
.filter(candidate -> arguments.length == 1 && candidate.id().equals(arguments[0]))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
"REA RISC-V calling convention requires one of the policies "
+ POLICIES.stream().map(Policy::id).toList()));
Language language = currentProgram.getLanguage();
if (!language.getProcessor().toString().equals("RISCV")
|| currentProgram.getDefaultPointerSize() != 4)
throw new IllegalStateException(
"REA " + policy.id() + " requires a 32-bit RISC-V program, not "
+ language.getLanguageID());
CompilerSpec spec = currentProgram.getCompilerSpec();
new SpecExtension(currentProgram)
.addReplaceCompilerSpecExtension(
derive(spec.getDefaultCallingConvention(), spec, language, policy), monitor);
PrototypeModel model =
currentProgram.getCompilerSpec().getCallingConvention(policy.prototype());
if (model == null)
throw new IllegalStateException("REA " + policy.prototype() + " was not installed");
selfCheck(model, policy, language);
int functions = 0;
List<Function> callables = new ArrayList<>();
currentProgram.getFunctionManager().getFunctions(true).forEach(callables::add);
currentProgram.getFunctionManager().getExternalFunctions().forEach(callables::add);
for (Function function : callables) {
// A thunk's signature is its target's; the target is in this list.
if (function.isThunk()) continue;
if (function.hasCustomVariableStorage()) function.setCustomVariableStorage(false);
function.setCallingConvention(policy.prototype());
if (policy.floatEntryBytes() == 0) {
List<VariableStorage> storage = new ArrayList<>();
storage.add(function.getReturn().getVariableStorage());
for (Parameter parameter : function.getParameters())
storage.add(parameter.getVariableStorage());
// An untyped function has no assigned storage yet; the decompiler applies the
// prototype when it recovers one.
requireIntegerStorage(
storage.toArray(VariableStorage[]::new), function.getName(true), true);
}
functions += 1;
}
int definitions = 0;
Iterator<DataType> types = currentProgram.getDataTypeManager().getAllDataTypes();
while (types.hasNext()) {
if (!(types.next() instanceof FunctionDefinition definition)) continue;
definition.setCallingConvention(policy.prototype());
definitions += 1;
}
// Written last: any failure above leaves no report, and REA refuses the session.
currentProgram.getOptions(Program.PROGRAM_INFO).setString(
REPORT_KEY,
"{\"policy\":\"" + policy.id() + "\",\"functions\":" + functions
+ ",\"definitions\":" + definitions + "}");
println(
"REA RISC-V " + policy.prototype() + ": functions=" + functions
+ " definitions=" + definitions);
}
/** The default ilp32d prototype changed as the policy states. */
private static String derive(
PrototypeModel base, CompilerSpec spec, Language language, Policy policy) throws Exception {
XmlEncode encoder = new XmlEncode();
base.encode(encoder, spec.getPcodeInjectLibrary());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Document document =
factory.newDocumentBuilder().parse(new InputSource(new StringReader(encoder.toString())));
Element root = document.getDocumentElement();
if (!root.getTagName().equals("prototype"))
throw new IllegalStateException("Unexpected default prototype encoding " + root.getTagName());
root.setAttribute("name", policy.prototype());
List<Element> floatEntries = new ArrayList<>();
NodeList entries = root.getElementsByTagName("pentry");
for (int index = 0; index < entries.getLength(); index += 1) {
Element entry = (Element) entries.item(index);
if (entry.getAttribute("storage").equals("float")) floatEntries.add(entry);
}
if (floatEntries.isEmpty())
throw new IllegalStateException("Default prototype has no floating-point entries");
if (policy.floatEntryBytes() == 0) {
List<Node> removed = new ArrayList<>(floatEntries);
NodeList rules = root.getElementsByTagName("rule");
for (int index = 0; index < rules.getLength(); index += 1) {
Element rule = (Element) rules.item(index);
if (usesFloatStorage(rule)) removed.add(rule);
}
if (removed.size() == floatEntries.size())
throw new IllegalStateException("Default prototype has no floating-point rules to remove");
for (Node node : removed) node.getParentNode().removeChild(node);
} else
for (Element entry : floatEntries)
entry.setAttribute("maxsize", Integer.toString(policy.floatEntryBytes()));
// Values over 2 x XLEN go by reference. Returns need no rule: a value that the return
// registers cannot hold already becomes a caller buffer passed in a0.
NodeList inputs = root.getElementsByTagName("input");
if (inputs.getLength() != 1)
throw new IllegalStateException("Default prototype has no single input list");
Element input = (Element) inputs.item(0);
Element last = null;
for (Node child = input.getFirstChild(); child != null; child = child.getNextSibling())
if (child instanceof Element element && element.getTagName().equals("rule")) last = element;
if (last == null || !joinsAnyValue(last))
throw new IllegalStateException("Default prototype's input list does not end with a join");
Element byReference = document.createElement("rule");
Element datatype = document.createElement("datatype");
datatype.setAttribute("name", "any");
datatype.setAttribute("minsize", "9");
byReference.appendChild(datatype);
byReference.appendChild(document.createElement("convert_to_ptr"));
input.insertBefore(byReference, last);
if (!policy.preservesFloatRegisters()) {
NodeList killedLists = root.getElementsByTagName("killedbycall");
Element killed = killedLists.getLength() == 0 ? null : (Element) killedLists.item(0);
if (killed == null) {
killed = document.createElement("killedbycall");
root.appendChild(killed);
}
List<Element> moved = new ArrayList<>();
NodeList preservedLists = root.getElementsByTagName("unaffected");
for (int list = 0; list < preservedLists.getLength(); list += 1) {
NodeList preserved = preservedLists.item(list).getChildNodes();
for (int index = 0; index < preserved.getLength(); index += 1) {
if (preserved.item(index) instanceof Element varnode
&& isFloatRegister(varnode, language))
moved.add(varnode);
}
}
for (Element varnode : moved) {
varnode.getParentNode().removeChild(varnode);
killed.appendChild(varnode);
}
}
StringWriter xml = new StringWriter();
var transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(document), new StreamResult(xml));
return xml.toString();
}
/** The installed prototype places each class of value as its policy states. */
private void selfCheck(PrototypeModel model, Policy policy, Language language) {
StructureDataType pair = new StructureDataType("rea_float_pair", 0);
pair.add(FloatDataType.dataType);
pair.add(FloatDataType.dataType);
VariableStorage[] storage =
model.getStorageLocations(
currentProgram,
new DataType[] {
DoubleDataType.dataType, FloatDataType.dataType, DoubleDataType.dataType,
IntegerDataType.dataType, pair, Float16DataType.dataType
},
false);
VariableStorage quad = storage[storage.length - 1];
if (quad.size() != currentProgram.getDefaultPointerSize() || quad.getRegister() == null)
throw new IllegalStateException(
"REA " + policy.prototype() + " does not pass a 16-byte value by reference: " + quad);
if (policy.floatEntryBytes() == 0)
requireIntegerStorage(storage, policy.prototype() + " self-check", false);
if (policy.preservesFloatRegisters()) return;
for (Varnode preserved : model.getUnaffectedList()) {
Register register = language.getRegister(preserved.getAddress(), preserved.getSize());
if (register != null && FLOAT_REGISTER.matcher(register.getName()).matches())
throw new IllegalStateException(
"REA " + policy.prototype() + " still preserves " + register.getName());
}
}
private static boolean joinsAnyValue(Element rule) {
List<Element> children = new ArrayList<>();
for (Node child = rule.getFirstChild(); child != null; child = child.getNextSibling())
if (child instanceof Element element) children.add(element);
return children.size() == 2
&& children.get(0).getTagName().equals("datatype")
&& children.get(0).getAttribute("name").equals("any")
&& children.get(1).getTagName().equals("join");
}
/** Whether a rule's action, at any depth, assigns floating-point storage. */
private static boolean usesFloatStorage(Element element) {
NodeList children = element.getChildNodes();
for (int index = 0; index < children.getLength(); index += 1) {
if (!(children.item(index) instanceof Element child)) continue;
if (child.getAttribute("storage").equals("float") || usesFloatStorage(child)) return true;
}
return false;
}
private static boolean isFloatRegister(Element varnode, Language language) {
if (!varnode.getTagName().equals("varnode")
|| !varnode.getAttribute("space").equals("register"))
return false;
Register register =
language.getRegister(
language.getAddressFactory().getRegisterSpace()
.getAddress(Long.decode(varnode.getAttribute("offset"))),
Integer.parseInt(varnode.getAttribute("size")));
return register != null && FLOAT_REGISTER.matcher(register.getName()).matches();
}
private static void requireIntegerStorage(
VariableStorage[] storage, String owner, boolean allowUnassigned) {
for (VariableStorage item : storage) {
if (item.isBadStorage() || (!allowUnassigned && item.isUnassignedStorage()))
throw new IllegalStateException("REA RISC-V storage unresolved for " + owner);
List<Register> registers = item.getRegisters();
if (registers == null) continue; // stack-only storage
for (Register register : registers) {
if (FLOAT_REGISTER.matcher(register.getName()).matches())
throw new IllegalStateException(
"REA RISC-V storage still uses " + register.getName() + " for " + owner);
}
}
}
}
+161 -5
View File
@@ -22,6 +22,9 @@ HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
# that does not fit is null, not an older descriptor path.
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
# The ceiling is the encoded payload; JSON expansion is measured before it.
# Encoded JSON allowance, in bytes, shared by one instruction list, one
# native-caller list, and one caller-scan page. This release has no runtime override.
CALLER_SCAN_ALLOWANCE_BYTES = 512 * 1024
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
@@ -232,6 +235,9 @@ EXHAUSTIVE_ANALYSIS_METHODS = frozenset(
"procedure_assembly",
"procedure_callees",
"procedure_callers",
"rea_caller_scan_page",
"rea_caller_scan_target",
"rea_caller_base",
"procedure_info",
"procedure_pseudo_code",
"procedure_references",
@@ -485,19 +491,30 @@ def _instruction_addresses(procedure):
return result
def _native_calls(procedure, direction):
def _iter_native_calls(procedure, direction):
references = procedure.getAllCallees() if direction == "outgoing" else procedure.getAllCallers()
kinds = {0: "none", 1: "unknown", 2: "direct", 3: "objective_c"}
calls = {}
unresolved = []
for reference in references:
source, target, kind = reference.fromAddress(), reference.toAddress(), reference.type()
if source in BAD_ADDRESSES:
yield None, None, None
continue
if target in BAD_ADDRESSES:
unresolved.append({"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind})
yield source, None, {"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind}
continue
calls[(source, target)] = {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
yield source, target, {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
def _native_calls(procedure, direction):
calls = {}
unresolved = []
for source, target, detail in _iter_native_calls(procedure, direction):
if detail is None:
continue
if target is None:
unresolved.append(detail)
else:
calls[(source, target)] = detail
return calls, sorted(unresolved, key=lambda item: int(item["address"], 16))
@@ -547,6 +564,136 @@ def _procedure_map(document):
return result
def _caller_target_data(procedure):
addresses = []
seen = set()
size = 0
for block in procedure.basicBlockIterator():
for address in _block_instruction_addresses(procedure, block):
if address in seen:
continue
encoded = _hex(address)
size += len(encoded) + 4
if size > CALLER_SCAN_ALLOWANCE_BYTES:
return {"target": _procedure_identity(procedure), "instructions": addresses, "instructions_complete": False}
addresses.append(encoded)
seen.add(address)
return {"target": _procedure_identity(procedure), "instructions": addresses, "instructions_complete": True}
def _caller_scan_target(document, params):
return _caller_target_data(_procedure(document, params.get("procedure")))
def _caller_base(document, params):
procedure = _procedure(document, params.get("procedure"))
callers = []
size = 0
complete = True
for item in procedure.getAllCallerProcedures():
address = _hex(item.getEntryPoint())
size += len(address) + 4
if size > CALLER_SCAN_ALLOWANCE_BYTES:
complete = False
break
callers.append(address)
return {
**_caller_target_data(procedure),
"callers": sorted(callers, key=lambda value: int(value, 16)),
"callers_complete": complete,
}
def _caller_scan_page(document, params):
"""Observe forward relationships in one bounded, stateless bridge request."""
start = params.get("start")
if isinstance(start, bool) or not isinstance(start, int) or start < 0:
raise InvalidRequestError("Caller scan start must be a non-negative integer")
segments = document.getSegmentsList()
counts = [segment.getProcedureCount() for segment in segments]
total = sum(counts)
end = min(start + 64, total)
if start > total:
raise InvalidRequestError("Caller scan start exceeds the procedure inventory")
items = []
item_bytes = 0
page_limit = CALLER_SCAN_ALLOWANCE_BYTES
oversized = False
identities = {}
containing = {}
def identity(procedure):
entry = procedure.getEntryPoint()
if entry not in identities:
identities[entry] = _procedure_identity(procedure)
return identities[entry]
def owner(address):
if address not in containing:
procedure, _ = _containing_procedure(document, address)
containing[address] = identity(procedure) if procedure is not None else None
return containing[address]
segment_start = 0
for segment, count in zip(segments, counts):
begin, stop = max(start - segment_start, 0), min(end - segment_start, count)
segment_start += count
for index in range(begin, max(begin, stop)):
candidate = segment.getProcedureAtIndex(index)
item = {"caller": identity(candidate), "callees": [], "references": []}
size = len(json.dumps(item, ensure_ascii=True).encode("utf-8")) + 1
remaining = page_limit - item_bytes
too_large = size > remaining
if not too_large:
for callee in candidate.getAllCalleeProcedures():
address = _hex(callee.getEntryPoint())
size += len(address) + 4
if size > remaining:
too_large = True
break
item["callees"].append(address)
if not too_large:
callee_entries = set(item["callees"])
for source, destination, call in _iter_native_calls(candidate, "outgoing"):
# Account for every native record, even when its target cannot
# be associated with an observed procedure.
size += 32
if size > remaining:
too_large = True
break
if destination is None:
continue
source_procedure = item["caller"] if _hex(source) == item["caller"]["address"] else owner(source)
target_address = _hex(destination)
if target_address in callee_entries:
target_entry = target_address
else:
target_procedure = owner(destination)
if target_procedure is None:
continue
target_entry = target_procedure["address"]
edge = {
"source_address": _hex(source), "target_address": _hex(destination),
"source_procedure": "caller" if source_procedure is not None and source_procedure["address"] == item["caller"]["address"] else source_procedure,
"target_procedure": target_entry,
"call": call,
}
size += len(json.dumps(edge, ensure_ascii=True).encode("utf-8")) + 1
if size > remaining:
too_large = True
break
item["references"].append(edge)
if too_large:
oversized = not items
break
items.append(item)
item_bytes += size
else:
continue
break
return {"start": start, "next": start + len(items), "total": total, "items": items, "oversized": oversized}
def _string_record(segment, address, display):
"""Read one native typed object, retaining its shortened display separately."""
length = segment.getObjectLength(address)
@@ -887,6 +1034,15 @@ def _dispatch(method, params):
if method in EXHAUSTIVE_ANALYSIS_METHODS:
_api().require_analysis_complete(document, method)
if method == "rea_caller_scan_target":
return _caller_scan_target(document, params)
if method == "rea_caller_base":
return _caller_base(document, params)
if method == "rea_caller_scan_page":
return _caller_scan_page(document, params)
if method == "analyze_function":
return _analyze_function(document, params)
if method == "read_function_instructions":
+4 -1
View File
@@ -65,7 +65,10 @@ func observe(_ request: Request) async throws -> [String: Any] {
guard SHA256.hash(data: executableBytes).map({ String(format: "%02x", $0) }).joined() == request.sha256 else {
try fail("target-mismatch", "Running executable bytes differ from the active target digest")
}
guard let windows = CGWindowListCopyWindowInfo([.optionIncludingWindow], request.window_id) as? [[String: Any]],
// .optionIncludingWindow omits a selected window while it is off screen, which
// is not absence: select from every window, and let capture report what it can.
guard let windows = (CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as? [[String: Any]])?
.filter({ ($0[kCGWindowNumber as String] as? NSNumber)?.uint32Value == request.window_id }),
windows.count == 1, let window = windows.first,
(window[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == request.pid,
(window[kCGWindowLayer as String] as? NSNumber)?.intValue == 0,
+72 -34
View File
@@ -15,6 +15,8 @@ struct ProcessIdentityObservation: Encodable {
let effective_uid: UInt32?
let lifecycle: String?
let relation: ProcessRelationObservation?
/** Display-only kernel accounting name (p_comm, at most MAXCOMLEN bytes). */
let name: String?
let reason: String?
}
@@ -31,6 +33,7 @@ struct ProcessMetadata {
let parentPid: Int32
let processGroupId: Int32
let pointerSize: Int
let name: String
}
struct ProcessSnapshot {
@@ -92,7 +95,8 @@ func readProcessSnapshot(
lifecycle: process.kp_proc.p_stat == SZOMB ? "zombie" : "non-zombie",
parentPid: process.kp_eproc.e_ppid,
processGroupId: process.kp_eproc.e_pgid,
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4,
name: processName(process)
)
)
},
@@ -103,6 +107,13 @@ func readProcessSnapshot(
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)")
}
/** Decode the NUL-terminated accounting name; invalid UTF-8 is replaced for display. */
private func processName(_ process: kinfo_proc) -> String {
withUnsafeBytes(of: process.kp_proc.p_comm) { bytes in
String(decoding: bytes.prefix { $0 != 0 }, as: UTF8.self)
}
}
func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] {
readProcessIdentities(pids: pids, snapshot: readProcessSnapshot())
}
@@ -130,6 +141,7 @@ func readProcessIdentities(
effective_uid: nil,
lifecycle: nil,
relation: nil,
name: nil,
reason: "not-in-snapshot"
)
}
@@ -144,6 +156,7 @@ func readProcessIdentities(
parent_pid: metadata.parentPid,
process_group_id: metadata.processGroupId
),
name: metadata.name,
reason: nil
)
}
@@ -157,12 +170,25 @@ private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityO
effective_uid: nil,
lifecycle: nil,
relation: nil,
name: nil,
reason: reason
)
}
enum ProcargsMalformedStage: String, CaseIterable {
case header
case argumentCount = "argument_count"
case executablePath = "executable_path"
case pointerSize = "pointer_size"
case pathPadding = "path_padding"
case arguments
case terminator
case trailingEntries = "trailing_entries"
}
enum RunTokenReadError: Error {
case invalidBuffer
case invalidBufferAt(ProcargsMalformedStage)
case duplicateToken
case invalidTokenEncoding
case environmentUnavailable
@@ -202,6 +228,22 @@ func processRunTokenObservation(
parse: Result<ParsedRunToken, RunTokenReadError>,
codeSigningFlags: () -> UInt32?
) -> RunTokenObservation {
// Native inspection restrictions apply even when argv is incomplete or
// overwritten, before parsing reaches the environment. Keep this fact at
// the native boundary; cleanup still decides whether the process is related.
func withheldEnvironment() -> RunTokenObservation? {
guard let flags = codeSigningFlags() else { return nil }
if (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return nil
}
func unavailableEnvironment(_ reason: String) -> RunTokenObservation {
withheldEnvironment() ?? unavailable(pid: pid, reason: reason)
}
switch parse {
case .success(let parsed):
if let runId = parsed.runId {
@@ -212,38 +254,25 @@ func processRunTokenObservation(
if parsed.environmentEntryCount > 0 {
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
}
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
if let withheld = withheldEnvironment() { return withheld }
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
case .failure(let failure):
switch failure {
case .environmentUnavailable:
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "environment_unavailable")
return unavailableEnvironment("environment_unavailable")
case .ambiguousEnvironmentBoundary:
if let flags = codeSigningFlags(), (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
return unavailableEnvironment("ambiguous_environment_boundary")
// These failures already establish that token bytes were available.
case .duplicateToken:
return unavailable(pid: pid, reason: "duplicate_run_token")
case .invalidTokenEncoding:
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
case .appleVectorUnavailable:
return unavailable(pid: pid, reason: "apple_vector_unavailable")
return unavailableEnvironment("apple_vector_unavailable")
case .invalidBuffer:
return unavailable(pid: pid, reason: "malformed_procargs")
return unavailableEnvironment("malformed_procargs")
case .invalidBufferAt(let stage):
return unavailableEnvironment("malformed_procargs_\(stage.rawValue)")
}
}
}
@@ -302,27 +331,30 @@ func parseProcessEnvironment(
in bytes: [UInt8],
pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size
) throws -> ParsedRunToken {
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBuffer }
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBufferAt(.header) }
let argc = bytes.withUnsafeBytes { $0.loadUnaligned(as: Int32.self).littleEndian }
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer }
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBufferAt(.argumentCount) }
var offset = MemoryLayout<Int32>.size
guard let executablePath = readString(in: bytes, offset: &offset) else {
throw RunTokenReadError.invalidBuffer
throw RunTokenReadError.invalidBufferAt(.executablePath)
}
guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBuffer }
guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBufferAt(.pointerSize) }
let pathRecordSize = Array("executable_path=".utf8).count + executablePath.count + 1
let padding = (pointerSize - pathRecordSize % pointerSize) % pointerSize
guard offset + padding <= bytes.count,
bytes[offset..<(offset + padding)].allSatisfy({ $0 == 0 }) else {
throw RunTokenReadError.invalidBuffer
throw RunTokenReadError.invalidBufferAt(.pathPadding)
}
offset += padding
let tokenPrefix = Array("REA_PROCESS_RUN_ID=".utf8)
var tokenInArgument = false
for _ in 0..<argc {
guard readString(in: bytes, offset: &offset, allowEmpty: true) != nil else {
throw RunTokenReadError.invalidBuffer
guard let argument = readString(in: bytes, offset: &offset, allowEmpty: true) else {
throw RunTokenReadError.invalidBufferAt(.arguments)
}
tokenInArgument = tokenInArgument || argument.starts(with: tokenPrefix)
}
guard bytes.last == 0 else { throw RunTokenReadError.invalidBuffer }
guard bytes.last == 0 else { throw RunTokenReadError.invalidBufferAt(.terminator) }
if offset == bytes.count {
throw RunTokenReadError.environmentUnavailable
}
@@ -340,7 +372,7 @@ func parseProcessEnvironment(
continue
}
guard let entry = readString(in: bytes, offset: &offset) else {
throw RunTokenReadError.invalidBuffer
throw RunTokenReadError.invalidBufferAt(.trailingEntries)
}
trailingEntries.append(entry)
}
@@ -361,22 +393,28 @@ func parseProcessEnvironment(
throw RunTokenReadError.appleVectorUnavailable
}
if trailingEntries[appleVectorStart...].contains(where: {
$0.starts(with: Array("REA_PROCESS_RUN_ID=".utf8))
$0.starts(with: tokenPrefix)
}) {
throw RunTokenReadError.ambiguousEnvironmentBoundary
}
let environmentEntries = trailingEntries[..<appleVectorStart]
var found: String?
for entry in environmentEntries {
if entry.starts(with: Array("REA_PROCESS_RUN_ID=".utf8)) {
if entry.starts(with: tokenPrefix) {
guard found == nil else { throw RunTokenReadError.duplicateToken }
let value = entry.dropFirst("REA_PROCESS_RUN_ID=".utf8.count)
let value = entry.dropFirst(tokenPrefix.count)
guard let decoded = String(bytes: value, encoding: .utf8) else {
throw RunTokenReadError.invalidTokenEncoding
}
found = decoded
}
}
// A changed process title can overwrite the original argument boundaries,
// causing a real environment entry to be consumed as an argument. A real
// argument can have this form too, so it proves neither ownership nor absence.
if tokenInArgument {
throw RunTokenReadError.ambiguousEnvironmentBoundary
}
return ParsedRunToken(runId: found, environmentEntryCount: environmentEntries.count)
}
+18 -10
View File
@@ -15,6 +15,11 @@ import sys
PROFILE = "[email protected];[email protected];[email protected]"
PACKAGES = {"pwntools": "4.15.0", "pyelftools": "0.33", "unicorn": "2.1.2"}
OUTPUT_BYTES = 64 * 1024 * 1024
# (e_machine, ELF class bits, byte order) -> architecture pwntools must also report.
PROFILES = {(62, 64, "little"): "amd64", (183, 64, "little"): "aarch64", (3, 32, "little"): "i386",
(40, 32, "little"): "arm", (8, 32, "little"): "mips", (8, 32, "big"): "mips"}
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, ELF64 AArch64 little-endian, ELF32 i386, "
"ELF32 ARM little-endian or ELF32 MIPS little- or big-endian objects.")
class LayoutFailure(Exception):
@@ -116,13 +121,16 @@ def inspect_elf(path, cache):
raise LayoutFailure("format", "Selected object does not contain an ELF header.")
if len(content) < 16:
raise LayoutFailure("format", "Selected ELF identification is truncated.")
if content[4] != 2 or content[5] != 1:
raise LayoutFailure("unsupported-target", "Initial layout profile requires ELF64 little-endian objects.")
if len(content) < 64:
raise LayoutFailure("format", "Selected ELF64 header is truncated.")
if int.from_bytes(content[18:20], "little") != 62:
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF objects.")
if int.from_bytes(content[16:18], "little") not in (1, 2, 3):
if content[4] not in (1, 2) or content[5] not in (1, 2):
raise LayoutFailure("unsupported-target", "Selected ELF declares an unknown class or byte order.")
bits, header_size = (32, 52) if content[4] == 1 else (64, 64)
order = "little" if content[5] == 1 else "big"
if len(content) < header_size:
raise LayoutFailure("format", f"Selected ELF{bits} header is truncated.")
profile = (int.from_bytes(content[18:20], order), bits, order)
if profile not in PROFILES:
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
if int.from_bytes(content[16:18], order) not in (1, 2, 3):
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
from pwnlib.context import context
with context.local(cache_dir=str(cache), log_level="warning"):
@@ -185,8 +193,8 @@ def inspect_elf(path, cache):
if type is None or section.header.sh_type == type)
try:
image = LayoutELF(str(path), checksec=False)
if image.arch != "amd64" or image.bits != 64 or image.endian != "little":
raise LayoutFailure("unsupported-target", "Initial layout profile requires x86-64 ELF64 little-endian objects.")
if (image.arch, image.bits, image.endian) != (PROFILES[profile], bits, order):
raise LayoutFailure("unsupported-target", PROFILE_SCOPE)
if image.header.e_type not in ("ET_EXEC", "ET_DYN", "ET_REL"):
raise LayoutFailure("unsupported-target", "Selected ELF type is outside the EXEC/DYN/REL layout profile; recorded core analysis is separate.")
length = len(content)
@@ -208,7 +216,7 @@ def inspect_elf(path, cache):
})
if h.sh_type in ("SHT_SYMTAB", "SHT_DYNSYM"):
if h.sh_entsize < image.structs.Elf_Sym.sizeof():
raise LayoutFailure("format", "ELF symbol entry is smaller than the decoded Elf64_Sym structure.")
raise LayoutFailure("format", f"ELF symbol entry is smaller than the decoded Elf{bits}_Sym structure.")
if h.sh_link >= len(all_sections) or all_sections[h.sh_link].header.sh_type != "SHT_STRTAB":
raise LayoutFailure("format", f"ELF symbol table {index} links to invalid string table {h.sh_link}.")
strings = all_sections[h.sh_link]
+9 -1
View File
@@ -34,8 +34,10 @@ export default defineConfig({
{ text: "Rust dependencies", link: "/rust-binaries" },
{ text: "PE certificates", link: "/pe-certificates" },
{ text: "PE imports", link: "/pe-imports" },
{ text: "PE TLS/load config", link: "/pe-load-config" },
{ text: "ELF notes", link: "/elf-notes" },
{ text: "Apple IPS crash reports", link: "/apple-ips" },
{ text: "Linux kernel modules", link: "/kernel-modules" },
{ text: "CPython pyc files", link: "/python-pyc" },
{ text: "Erlang BEAM", link: "/erlang-beam" },
{
@@ -51,7 +53,11 @@ export default defineConfig({
text: "Web network captures",
link: "/web-network-captures",
},
{ text: "Agent environments", link: "/agent-environments" },
{
text: "Build RL environments",
link: "/agent-environments-quickstart",
},
{ text: "Environment reference", link: "/agent-environments" },
{ text: "Electron observation", link: "/electron-observation" },
{ text: "Electron fuses", link: "/electron-fuses" },
{ text: "Process capture", link: "/process-capture" },
@@ -64,6 +70,7 @@ export default defineConfig({
{ text: "Apple applications", link: "/apple-application-analysis" },
{ text: "Desktop installations", link: "/desktop-applications" },
{ text: "Firmware", link: "/firmware-analysis" },
{ text: "iNES containers", link: "/ines-containers" },
{ text: "SQLite databases", link: "/sqlite-databases" },
{ text: "Packet captures", link: "/packet-captures" },
{ text: "Signal recordings", link: "/signal-recordings" },
@@ -80,6 +87,7 @@ export default defineConfig({
{ text: "Ghidra raw binary", link: "/raw-binary" },
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
{ text: "Ghidra PowerPC", link: "/ghidra-ppc" },
{ text: "Ghidra GameCube DOL", link: "/ghidra-dol" },
{ text: "Ghidra semantics", link: "/ghidra-provider" },
],
},
+8 -4
View File
@@ -103,6 +103,10 @@ existing analysis families directly, for example
| `start_adb_activity` | **mutates device** | Starts an intent (`am start -a` with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict |
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
Screen capture validates the PNG signature, IHDR framing, dimensions, and IHDR
checksum before publishing. It does not validate encoding fields or decode and
validate image contents; that limitation is retained in Evidence.
When `push_adb_file` receives an existing directory, adb places the local file
inside it under the local basename; Evidence reports that effective file path
while retaining the caller's directory path in the input parameters. The
@@ -253,12 +257,12 @@ beyond already-connected devices (`adb connect`) are not exposed yet.
## Real verification
`npm run verify:adb` exercises every operation against a live device. It
`npm run verify:adb` exercises observation and transfer operations against a live device. It
requires `adb` (or `REA_ADB_PATH`) and any attached emulator or phone;
`--serial` selects one when several are attached, `--pull PACKAGE` pulls a
real package and verifies the returned digests against the files on disk,
and `--install-apk PATH` drives a full install → resolve → uninstall
lifecycle with a lane-owned probe.
real package and verifies the returned digests against the files on disk.
The optional `--install-apk` lifecycle is unavailable until the selected APK
identity and device cleanup ownership can be established safely.
Record: verified against adb 34.0.5 on Linux against an Android 14
(API 34) x86_64 emulator — all operations above, a 224-package inventory,
+204
View File
@@ -0,0 +1,204 @@
# Build RL environments with REA
REA turns a recorded web app session into a runnable environment for training
and testing AI agents. An agent can practice tasks such as finding an issue,
creating a folder, or editing a record without running the original app.
REA builds the tools the agent calls, the local data those tools use, and a
test that checks the environment starts and responds. It supports changes
when the recording confirms their effects. You can then use the environment
with your RL framework to set task goals, check rewards, and run training.
## Before you start
Install [REA and its agent integration](installation.md). To record an app,
you also need a running test instance and a Chrome-family browser. Choose an
instance where the agent is allowed to create, edit, and delete data. REA
records the app you provide; you start it and prepare its test data.
To run the exported environment, install the
[AgentEnv framework](https://github.com/scaleapi/agentenv-framework#install)
and start Docker. REA's [compatibility check](testing.md#exported-environment-bundles)
uses AgentEnv version `0.9.1334` with Python 3.12.
There are two ways to build an environment: ask your agent to record the app,
or start from a recording you already have.
## 1. Record and build with your agent
Give your agent the app URL, the operations you need, your browser's executable
path, and an output directory. For an app with folders, adapt this prompt:
```text
Use REA to build an AgentEnv environment from my test web app at
http://127.0.0.1:3000. Cover listing, creating, renaming, and deleting folders.
You may create and remove test folders. Leave the existing data unchanged.
Sign in as admin using the password in APP_PASSWORD. Use the browser at
/absolute/path/to/chromium. Save the environment to
/absolute/path/to/my-env, which does not exist yet.
Record the session, check which operations the environment supports, and
export it. Report any unsupported operations and what is missing.
```
Replace the URL, account, paths, and folder operations with your own. Set
`APP_PASSWORD` in the environment that starts REA's MCP server so REA can use
it without putting the password in the prompt.
Your agent inspects the app, records a session, checks the result, and exports
a folder containing the runnable environment. It reports which operations
work and which need more evidence. The app is used during recording; the
exported environment runs from the saved data.
After your agent exports the folder, skip to
[run the environment](#3-run-the-environment). The next section is an
alternative for an existing recording.
## 2. Or build from a saved recording
A HAR file records a browser's network requests and responses. Export one
with API response bodies from your browser's network panel, or use a
recording you already have. To include create, edit, and delete operations,
record those actions and the reads that show their results.
Build the environment into a new or empty directory:
```sh
rea environment-build ./session.har ./my-env --name my-env --json
```
You can also pass capture Evidence JSON saved by REA's
`capture-browser-scenario` or `inspect-web-network-capture` commands. Here,
Evidence means REA's saved recording with its source information. The build
accepts HAR files and those JSON recordings. It reads local data and writes
the environment folder without contacting the app.
Add `--dry-run` to review the build report without writing the folder. For an
unreleased REA checkout, use `--package-tarball` with its packed package so
the exported environment runs the same implementation.
The build report tells you what the environment supports:
| Report field | What to check |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `state` | Whether the environment can answer from local state or uses recorded-response replay. |
| `coverage.reads` | Which read operations use state and which replay recorded answers. |
| `coverage.writes` | Which create, edit, and delete operations are supported. Each withheld operation includes a reason and a recording hint. |
| `warnings` | Recorded content or local paths to review before sharing the folder. |
A successful build means the folder was created. Check the report to confirm
it supports the operations your tasks need. `coverage: null` means the build
could not prepare state for this environment and it will use replay.
## 3. Run the environment
Whether your agent exported the folder or you built it from a HAR, run it
with AgentEnv:
```sh
agent-env run ./my-env
```
Use the folder path your agent reported if it differs. The first run builds
the container image, so it needs access to the image and package sources.
REA includes a basic test, called the smoke task. It checks the expected tool
count and, when a suitable read tool exists, calls it and checks its response
status. Passing this test shows the environment starts and responds in the
tested setup. It does not yet score an agent on an application task.
The folder contains the recording, a tool manifest describing the available
operations, a Dockerfile, environment configuration, and the test. The
[export reference](agent-environments.md#export-a-bundle) describes each file.
## 4. Add tasks and reward checks
Start with one task the exported tools support. Give it a clear goal and a
checkable result. For example, if folder creation is supported, ask your
agent:
```text
Use this exported environment to add an AgentEnv task that asks an agent
to create a folder named Practice. Check the saved environment state to
confirm the folder exists with that name, and use that check to score the
result. Reset the environment before each run so attempts start with the
same data. Use the AgentEnv task and verifier formats.
```
This asks your agent to help build the task and its grading check around the
environment REA generated. A retrieval task might check an answer against a
recorded value. A task that changes data should check the resulting state.
When state is available, the environment supports reset, load, and read-back
operations for repeatable runs.
Use the [AgentEnv documentation](https://www.agentenvframework.com/docs) for
task configuration, agent connections, scoring, and execution. Connect the
resulting tasks to your chosen RL training workflow.
## Improve an operation that is missing
For REA to support a change, the recording needs to show what happened before
and after it. When recording against your test app:
1. Read the list and an existing item before making changes. Repeat the
initial reads to show which values change independently.
2. Create a test item, read it back, and read the list before the next change.
Repeat with a second item and different text.
3. Edit each test item, then read the item and list again. Leave more than a
second between creation and the first edit to show time-dependent changes.
4. Delete the test items, then read them back and read the list to confirm
their removal.
Also record filters, searches, sorting, and paging if your tasks need them.
The agent should check that every recording step completed before using it.
The [recording workflow](https://github.com/morluto/rea/blob/main/.agents/skills/reverse-engineer-anything/references/environments.md)
has the detailed browser instructions, including timeouts and handling IDs
assigned by the app.
Use the report's hints to decide what to record next:
| Reason | Next step |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `unconfirmed` | Read the item and its list after the change. |
| `no-state-target` | Check the path template and recorded views; record missing reads. The route form may not be supported as a mutation target. |
| `no-observation` | Read back a created item. For an edit or delete, also read it before changing it. |
| `contradicted` or `side-effect` | Inspect the reported disagreement. The operation may need behavior REA cannot model yet. |
See the [full reason table](agent-environments.md#build-a-bundle-in-one-step)
for the other cases. Rebuild from the new recording and check the report
again. A new recording replaces the build input; combining separate recording
sessions is not supported.
## Compare with the original app
For a stronger check, record a separate sequence of calls against the real
app, starting from the same test data as the original recording. This is a
probe. Restore that starting data before each probe sequence, then compare
the saved probe recordings with the environment:
```sh
rea environment-compare ./my-env/envs/my-env/manifest.json \
--evidence ./my-env/envs/my-env/evidence.json \
--probe ./probe-create-edit-delete.json --probe ./probe-reads.json --json
```
Each probe file must be capture Evidence JSON from REA. The comparison runs
offline and reports differences in responses, unsupported calls, and missing
observations. It accounts for supported differences in IDs, times, and
modeled generated values. Agreement applies to the calls those probes check.
See the [comparison reference](agent-environments.md#measure-fidelity-against-the-application)
for the exact rules.
## Before sharing the environment
The exported folder includes retained response bodies and local source paths.
Standard authentication headers are excluded, but custom headers, query
values, and body tokens can remain. Review the content you plan to share.
Credential-name warnings point to values worth reviewing; they do not remove
those values or establish that the recording contains no credentials.
For direct MCP serving, replay behavior, state controls, and resource limits,
see the [environment reference](agent-environments.md). The
[implementation index](specs/agent-environments/README.md) links the contributing
PRs and their verification records.
+359 -32
View File
@@ -1,16 +1,27 @@
# Agent environments from recorded captures
Use `rea environment-serve` to serve an environment manifest as an agent
environment: an MCP server whose tools answer from what one retained web
network capture observed, plus the environment card the agentenv framework
reads to discover it. By default the server derives the
[state the capture observed](#the-state-a-capture-observed) at startup and
answers read tools from it, and the write tools whose effect the capture
confirmed by changing it; `--replay-only` replays the capture's records
For a first environment, start with the
[quickstart](agent-environments-quickstart.md): record an app or use a saved
session, build and run the environment, and add tasks and reward checks.
The [implementation index](specs/agent-environments/README.md) links
the contributing PRs and their verification records. This page is the detailed
command and behavior reference.
Use `rea environment-build` to turn one recorded capture into an agent
environment bundle in one offline step: a folder `agent-env run` builds and
runs, whose MCP server answers from what the capture observed. See
[Build a bundle in one step](#build-a-bundle-in-one-step).
The steps it composes are separate commands too. `rea environment-serve` serves
an environment manifest as an agent environment: an MCP server whose tools
answer from what one retained web network capture observed, plus the
environment card the agentenv framework reads to discover it. By default the
server derives the [state the capture observed](#the-state-a-capture-observed)
at startup and answers read tools from it, and the write tools whose effect the
capture confirmed by changing it; `--replay-only` replays the capture's records
instead. Nothing is fetched from the recorded application; every answer comes
from retained Evidence. `rea environment-export` writes the same pair as a
bundle folder `agent-env run` builds and runs; see
[Export a bundle](#export-a-bundle).
bundle folder; see [Export a bundle](#export-a-bundle).
```sh
rea inspect-web-network-capture /absolute/session.har har --format json > capture.json
@@ -36,6 +47,190 @@ The third command serves that manifest from the state it derives from the
capture; see [Serve from the state](#serve-from-the-state). Both served files
are complete REA Evidence JSON as the commands write it with `--format json`;
the `evidence` member of `view.json` is the whole of `capture.json`.
A `view.json` can also name the capture file as
`{"kind": "evidence-file", "path": "capture.json"}`, which projects it as
retained Evidence instead of charging the whole capture as inline input.
## Build a bundle in one step
```sh
rea environment-build grafana.har ./grafana --json
agent-env run ./grafana
```
`rea environment-build <capture> [directory]` takes a `.har` file, or capture
Evidence JSON from `inspect-web-network-capture` (HAR) or
`capture-browser-scenario`, and writes the bundle
[Export a bundle](#export-a-bundle) describes. It inspects a HAR as
`inspect-web-network-capture` does (Evidence input skips this), projects the
environment manifest from that Evidence as retained Evidence, authenticates
the pair, prepares the state the bundle's server derives at startup, and
writes the bundle with the export. For the same capture path, every file it
writes is byte-identical to what `inspect-web-network-capture`,
`inspect-analysis-view` and `environment-export` write, Evidence ids included,
when `view.json` lists the same rules in the views' order and the export is
given the build's name. It reads one file and writes one folder: it fetches
nothing and launches no browser (HAR inspection runs REA's own decoder
process), so record a browser scenario with `capture-browser-scenario` first
and pass its Evidence. Other capture formats are refused, as the manifest view
refuses them, and a HAR document saved as `.json` is refused with a hint to
name it `.har`.
- `--template-rule <rule>`, repeated, replaces the default opt-in rules
`after-uid-literal`, `opaque-token`, `sibling-variance` and
`prefix-variance`, the rules the published capture-built environments use;
`--format-rules-only` applies none, the views' own default. Rules apply in
the views' fixed order, the report lists them, and a repeated rule is
refused as the views refuse it.
- `--name` names the env folder and env id. Without it the name is the
manifest's, unless the capture's origin is a loopback name or an IP literal:
the capture file then names it (`grafana.har` and `grafana.capture.json`
become `grafana`) and a `name-from-file` warning says so. A file name that
yields no portable label (kebab-case, at most 63 characters for a DNS service
label, not a Windows device name or a label interpreted as an IP address) is
refused; pass `--name`.
- `--package-tarball` works as for the export, and `--capture-timeout-ms`
as for `inspect-web-network-capture`.
- `--dry-run` runs every stage and prints the report with `status: "planned"`
and the files a build would write, but writes nothing and does not check the
directory, which may then be omitted. It reads and hashes a
`--package-tarball` as a build does before copying it, so it refuses the
tarballs a build refuses, including one whose contents cannot be read.
The directory must be absent or empty, as for the export; the build never
replaces a folder. A refusal before the write leaves nothing behind. A capture
Evidence file, an inspected HAR's Evidence and the projected manifest must each
fit the live heap headroom for parsing and sealing, checked before the file is
read or the Evidence serialized; one that does not is refused as
`resource_constraint`. Each
refusal names its stage: `environment-build` for the build's own checks,
`inspect_web_network_capture` and `inspect_analysis_view` for inspection and
projection, `environment-export` for the bundle. SIGINT cancels the build
(`cancelled`); before the bundle stage nothing is written, and a cancellation
during the write names the bundle it wrote. Stage progress is logged to stderr
at info level; the report is the only output on stdout:
```json
{
"status": "built",
"directory": "/work/grafana",
"name": "grafana",
"capture": {
"evidence_id": "ev_…",
"operation": "inspect_web_network_capture",
"records": 105,
"input": "har"
},
"manifest": {
"evidence_id": "ev_…",
"tools": 48,
"excluded_groups": 0,
"template_rules": [
"after-uid-literal",
"opaque-token",
"sibling-variance",
"prefix-variance"
]
},
"state": { "served": true, "evidence_id": "ev_…" },
"coverage": {
"reads": {
"state": 36,
"recorded": 6,
"recorded_reasons": { "query-varies": 3, "…": 1 }
},
"writes": {
"served": [{ "tool": "delete_folders_by_uid", "effect": "delete" }],
"withheld": [
{
"tool": "post_folders",
"effect": "create",
"reason": "unconfirmed",
"hint": "No later read showed the effect. Read the entity and its list after the write.",
"exemplar_ordinal": 94,
"contradicted_by": null
}
]
}
},
"tool_count": 37,
"package_source": { "kind": "registry", "specifier": "rea-agents@…" },
"files": [{ "path": "README.md", "bytes": 4210 }],
"warnings": [{ "code": "name-from-file", "message": "…" }],
"next": ["agent-env run /work/grafana"]
}
```
`coverage` is the state's read views and write plans summarized, and `null`
when the state is unavailable and the bundle serves replay. The
[environment coverage](web-network-captures.md#environment-coverage) view
reports the same summary for any capture without building, so an agent can
check it after each recording. The view summarizes the state as derived; it
does not apply the serve-time admission checks, so for an environment whose
served catalog exceeds the discovery budget, or whose state exceeds what
`data/add` loads, it still reports the state while the build reports `coverage:
null` and the bundle serves replay. Each withheld write carries a hint about
what to record next; hints never change what is served:
| Reason | Hint |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `no-state-target` | The write could not be linked to a modelled target. Check the path template and recorded views; record missing reads. Some route forms may not be supported. |
| `no-call` | No recorded call of this tool was modelled, for example because every call failed. Record a successful call. |
| `body-unusable` | The retained request or response cannot be modelled. Record a successful call with a complete supported request and reproducible response. |
| `no-observation` | Nothing showed the entity or document this write changed. Read a created entity back by id right after the write; for an update or delete the state must already hold the entity, so read it before the write or record the create that made it. |
| `unexplained-change` | The write changed fields it did not send, in a way no rule explains. A second, different call of the same tool can show the pattern. |
| `contradicted` | A later read disagreed with the modelled effect. Check the read at contradicted_by. |
| `side-effect` | The write changed something besides its own entity, which the environment does not model. |
| `unconfirmed` | No later read showed the effect. Read the entity and its list after the write. |
| `incomplete-entities` | The write is confirmed, but the views that show its entity cannot render it in full. Read the written entity through every view that lists it. |
| `unknown-clock` | The learned rules generate timestamps, but the capture establishes no global served clock start. Record usable start times and completed durations for every request. |
The bundle ships `evidence.json`, the whole capture Evidence, response bodies
included. Inspection excludes the values of the standard authentication headers
(`Authorization`, `Cookie` and `Set-Cookie` among them) and URL userinfo; a
custom key header such as `X-Api-Key`, a token in the query string and tokens
an application returns in bodies remain. The environment views refuse captures
inspected with `--sensitive-value`, so scrub a capture before building it. The
report's `credential-named-members` warning counts recorded header names, query
parameter names and JSON or form body members whose names look like
credentials (whole name segments such as `token`, `secret`, `password`,
`api_key`, `authorization` or `session`) and hold a value, and lists the first
20 by record and pointer, never by value. A body is read as a form or as JSON by
its recorded media type, or by its shape when it has none or one naming
neither. A browser scenario is
scanned by event (`event 4 response /token` names
`normalized_result.events.items[4]`): every retained request and response
content event, including a response whose request began before the browser was
attached and a transaction no record represents, and each transaction URL's
query once. Bodies larger than 4 MiB of UTF-8 text are not
scanned; `credential-scan-incomplete` counts them and lists up to 20 record or
event pointers, including base64 bodies whose decoded scan was cut. An empty name
warning does not establish that these bodies were checked. A browser scenario's
saved values (`save` and `save_response`) named like credentials but not marked
`secret: true` are recorded unredacted; `credential-named-saved-values` lists up
to 20 of them by parameter pointer. Names do not establish sensitivity,
so the build never refuses on them. Capture
Evidence also records the capture file's absolute path (`subject.local_path`,
`parameters.capture_path`, `locations`, `artifact.path`), and the bundle
carries it: build from a neutral folder when that path should not ship.
Over MCP, `export_environment_bundle` builds the same bundle and returns the
same report. Its `source` is capture Evidence the session retained
(`{"kind": "retained-evidence", "evidence_id"}`), such as a
`capture_browser_scenario` or `inspect_web_network_capture` result, or a
capture Evidence file (`{"kind": "evidence-file", "path"}`); inspect a HAR with
`inspect_web_network_capture` first. Retained Evidence is written into the
bundle as `--format json` writes it, so the bundle is the one
`environment-build` writes from that file. `output_directory` must be an
absolute path that does not exist yet, whose parent does; the tool creates it
and refuses an existing directory, empty or not. `name`, `template_rules`,
`package_tarball` and `dry_run` work as `--name`, `--template-rule`,
`--package-tarball` and `--dry-run` do, and retained Evidence has no file to
name the environment after, so a loopback or IP origin needs `name`. Unlike
`--dry-run`, `dry_run` still checks `output_directory`. Destination refusals
name `export_environment_bundle`; the others name their stage as
`environment-build`'s do.
## From a browser scenario
@@ -125,6 +320,51 @@ the replayed body is the bytes the browser exposes for each response, with any
declared secret replaced by its redaction marker (a body that redaction had to
empty replays no body).
To record writes, sign in through the UI and then send the application's API
calls with [request steps](browser-scenario-contract.md#request-steps): each
sends one request to the start origin through the page's own `fetch`, with the
session's cookies, and a header can carry the CSRF cookie or stored token the
application's own requests send. A write is served only when the capture shows
its effect, so read each collection before writing, read every written entity
back by id and in its list right after the write, and leave more than a second
between a create and its first edit. A request step's path is fixed when the
scenario is written and cannot use a value an earlier response returned, so an
id must be known before the run: one the caller chooses, such as a `uid` in the
create body, or one an earlier scenario created. When the server assigns ids,
read the list after each create (the create's response or the list shows the
new entity), and record updates and deletes in a second scenario that reads
the first one's objects by id before editing them, or make them through the
UI. The environment coverage view names each
write it withholds and what to record next. A scenario with write requests
changes the application's data: record against an instance whose data may
change.
```json
{
"step_id": "create-folder-1",
"action": "request",
"method": "POST",
"path": "/api/folders",
"headers": [
{
"name": "X-CSRFToken",
"value": { "source": "cookie", "name": "csrftoken" }
}
],
"json": { "title": "Scratch folder 1" },
"expected_status": [200, 201]
}
```
Each record takes its start and duration from the browser's timing (the time
the scenario observed the request where the browser reported no start), so the
state orders and overlaps
records as it does a HAR's and learns a write clock: an update the application
stamps with a modification time can be served. A scenario captured before
these times were recorded has none; its records keep request order and the
state learns no clock, so such updates stay `unexplained-change` until it is
recorded again.
## What a manifest is
An environment manifest is a projection of one application's recorded request
@@ -166,7 +406,8 @@ returns that record's recorded response. Selection compares the supplied path
parameters against the record's path segments at the parameter indexes, then
the supplied query values against the record's query string, and keeps the
record with the most matches; ties keep manifest order. A query argument accepts
one string or an array of strings for repeated values; the array is matched
one string, a number or boolean sent as its JSON text, or an array of strings for
repeated values; the array is matched
against every occurrence in captured order. The raw recorded query remains the
replay evidence. The result reports
where the answer came from:
@@ -373,8 +614,9 @@ shape and carries no annotations.
Path parameters are required strings whose
description names the segment index, the templating rule and the observed
values (sampled within the list budget above; the manifest keeps them all);
query parameter names accept an optional string or array of strings (for
repeated values in captured order); write methods accept an
query parameter names accept an optional string, number or boolean (a number or
boolean is sent as its JSON text) or array of strings (for repeated values in
captured order); write methods accept an
optional `body` object described by the recorded top-level key types. Each
input keeps its own argument: a query name equal to a path parameter's name is
advertised as `query_<name>`, a query name longer than 64 characters as
@@ -433,7 +675,7 @@ Each read tool answers according to its view in the state:
| View | Answer |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `item` | The entity the path arguments name, rendered into the recorded envelope of `envelope_ordinal`. Arguments match as replay matches them: each collection segment equals the held collection's segment as written or after percent-decoding, and the identity is the last argument percent-decoded, then as written; values are never re-split on `/`. Any entity the state holds answers, including one `data/add` loaded; query arguments only decide `replay.exact_match`, except that a call matching one of the view's `projected_query_keys`, or on a view with `projected_record_ordinals` asking for some fields only with a value other than `*` no checked line gave or one a replayed read gave, replays its recording. An entity the state does not hold answers the tool's latest recorded 400, 404 or 410 response with `state.view` `missing`, or is a tool error when none was recorded; one lacking a field every recording carried is a tool error, unless a read showed the application leaves that field out; a body is never invented. |
| `item` | The entity the path arguments name, rendered into the recorded envelope of `envelope_ordinal`, whose `per_response_members` (such as a request id), like a list's or a document's, keep their recorded values. Arguments match as replay matches them: each collection segment equals the held collection's segment as written or after percent-decoding, and the identity is the last argument percent-decoded, then as written; values are never re-split on `/`. Any entity the state holds answers, including one `data/add` loaded; query arguments only decide `replay.exact_match`, except that a call matching one of the view's `projected_query_keys`, or on a view with `projected_record_ordinals` asking for some fields only with a value other than `*` no checked line gave or one a replayed read gave, replays its recording. An entity the state does not hold answers the tool's latest recorded 400, 404 or 410 response with `state.view` `missing`, or is a tool error when none was recorded; one lacking a field every recording carried is a tool error, unless a read showed the application leaves that field out; a body is never invented. |
| `list` | When the arguments name one of the lines the view checked exactly (its latest recordings before the first related write; every path and query value equal to the recording's, and every recorded query parameter supplied), that recorded envelope with its array rendered from the state: every entity of the collection in state order for `membership: "all"` or a line `all_record_ordinals` names, or the recorded members still held. A call with no query argument answers from such a line of the collection its path arguments name that only pages its list, with `replay.exact_match` false. On a line the view's `query` answers, and for a call whose every query argument has a role there (an argument-less call aside), the answer is the query evaluated over the state: filtered, searched, sorted and paged, with counters at the number listed and `query.totals` at the number matched, and `state.membership: "query"`; such a call answers on a query or whole line of its collection with `replay.exact_match` false. A value an `inert` parameter never recorded, a parameter without a role, a call matching one of the view's `projected_query_keys`, or a field selection the checked lines do not show (as for `item`), replays its recording. A member lacking a field every recording carried makes the call a tool error rather than a partial list. |
| `document` | When the arguments name the document's checked line exactly, the state's value for it; a tool error when the state holds none. |
| `recorded` | The recording, as replay answers it. |
@@ -451,6 +693,21 @@ An item or list view that always spells a clock field otherwise than the
entity's stored value names that spelling in `clock_formats`, and a value it
never recorded, such as a created entity's clock, is rendered in it: a list that
writes `+00:00` where item reads write `Z` lists a new entity the same way.
A view that names a field in `sorted_arrays` lists that string array sorted, as
a read-back showed the application does, while the state keeps it in the order
it was written, so a write's own answer echoes it. If a later counted read
contradicts that ordering, the view stays recorded and its earlier reads no
longer confirm served writes. A view that names a field in `nested_fields`
always showed that field's objects with the same keys, in any order, and fewer
than an entity stores, such
as a list showing a parent without the parent's own nested type; a stored
object it never recorded, such as a created entity's parent, is rendered with
those keys. Members a view names in
`per_response_members`, such as a request id each response mints, keep the
value its recording carried. A list's `counters` and `query.totals` are
top-level members or members one level down in a paging object
(`/pageInfo/totalRows`); either is set to the number listed or matched, and
members beside it such as `isLastPage` stay as recorded.
A list or document call that names no checked line, including a line recorded
after a write or with other query values, answers from the recording. Every
@@ -466,6 +723,8 @@ and `POST` tools the capture shows only reading (a lookup answering as a `GET`
did, an RPC tunnel naming read operations, a session renewal), stay served and
answer from their recordings with the reason `query-body`; they are not write
tools.
`HEAD`, `OPTIONS` and `TRACE` tools also replay their captured responses; their
state views report `method-replay` and coverage counts them as recorded reads.
A line establishes whole-collection membership only from the beginning:
`offset`, `start`, `_start` and `page` must be zero when present. Positive or
@@ -480,29 +739,58 @@ tools are not served, so no call claims a change it does not make; each one's
plan states why in its `reason`. A served write changes the live state, which
every MCP session and `data/get` then see:
| Effect | Call |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `create` | Adds an entity to the collection the path names, built from the observed entity with each ruled field taken from the call or generated, at the plan's `insert` position. A plan tied to its exemplar's collection writes only there. |
| `update` | Sets the sent members on the entity the path names, constants for members accepted only with their recorded value, and the clock, increment, slug, unique and renewed changes the plan models. |
| `delete` | Removes the entity the path names. |
| `document` | Merges the body into the plan's document object, or replaces it for `PUT`. |
| Effect | Call |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create` | Adds an entity to the collection the path names, built from the observed entity with each ruled field taken from the call or generated, at the plan's `insert` position. A plan tied to its exemplar's collection writes only there. |
| `update` | Sets the sent members on the entity the path names, constants for members accepted only with their recorded value, and the clock, nested clock, increment, slug, stamp, derived, composed, reference, unique and renewed changes the plan models. |
| `update` (action) | A `POST`, `PUT` or `PATCH` to an item path plus an action segment (`/{type}/{id}/archive`) sets, on the entity before the segment, each scalar the recorded calls showed the action set (`set`), or flips each boolean they showed it flipping both ways (`toggle`), with any sent members; it is served only when a recorded call found what it sets already set, or flipped back what it toggles. |
| `delete` | Removes the entity the path names. |
| bulk `update` or `delete` | A `DELETE`, `PUT` or `PATCH` at a collection, or a `POST` to it plus `delete`, `remove` or `destroy`, applies its effect to every entity the body names, as recorded: an array of ids or of objects carrying the id field, at the root or as the body's one array member (`{"IDs": […], "Read": true}`). |
| `document` | Merges the body into the plan's document object, or replaces it for `PUT`. |
An update's unsent changes follow the plan's rules. A `held-clock` is a clock
the client sends back as it read it and the application stamps anew (Ghost's
`updated_at`): the call must send the entity's value and the field takes the
write's clock. A `nested-clock` sets the clocks inside an object the entity
holds, in that object's recorded format. A `stamp` writes the acting user
the entity already names under its `created…by` counterpart into a
`modified…by` or `updated…by` field. A `derived` field takes a value showing
the one sent member's text that the entity stores nowhere (a field Ghost
derives from `html`), only with that member's recorded value. An action's
`set` rule sets a scalar to the value the recorded calls left it at, and its
`toggle` rule flips a boolean, refusing a call on an entity that holds none
there (Miniflux's `PUT /v1/entries/{id}/bookmark` stars and unstars).
Its answer has the status and content type of the plan's recorded response,
with the written entity or its identity rendered in, no body, or the recorded
body. A call whose body is not an object, sends a member the capture did not
show stored, sends a fixed member with another value or a member of another
JSON type, or lacks a required member is a tool error naming the accepted
members; so is a rendered answer missing a field the recorded response
carried, and an update or delete of an entity the state does not hold answers
members; so is a call sending query parameters other than the exemplar's (they
may change what the body does), a body not wrapped in the exemplar's envelope
(Ghost's `{"posts": [{…}]}`), a bulk call naming an id the state does not hold,
a `held-clock` member sent with another value than the entity holds, a call
sending a `derived` rule's member to an entity that does not hold the value the
recorded call found, a nested clock on an entity holding no such object, and a
rendered answer missing a field the recorded response carried, and an update or delete of an entity the state does not hold answers
the recorded 400, 404 or 410 of the tool or of an item view of the type, if
any. A refused call leaves the state unchanged. Values a write generates are
any. A served delete of a type whose item view has a `deleted_flag` (the
member, `deleted`, `isDeleted` or `is_deleted`, its reads set `true` on a
record the application soft-deleted, as EspoCRM does) keeps the entity until
`data/reset` or `data/add`, within the size `data/add` loads, so a read of it by
id answers its last fields with that flag `true`. A refused call leaves the
state unchanged. Values a write generates are
deterministic: the clock starts at `write_clock` and advances one second per
served write, sequences take one past both the largest held value and the
largest they generated, and UUIDs and tokens derive from the state Evidence
id, tool, field and write count, skipping held values. `data/reset` and
id, tool, field and write count, skipping held values. If `write_clock` is null,
plans needing `clock`, `held-clock`, or `nested-clock` retain their learned rules
but are withheld with `unknown-clock`; other confirmed plans can still be served.
`data/reset` and
`data/add` restart the clock, counter and sequences, so the same calls after a
reset give the same state. Answers carry `state: {"source": "state", "view":
"create" | "update" | "delete", …}` or `"document-write"`.
"create" | "update" | "delete", …}` or `"document-write"`; a bulk answer adds
`ids`, every entity it wrote in body order, with `id` naming the first.
The data plane serves three JSON-RPC methods:
@@ -602,8 +890,9 @@ names a `document` view once, at its initial document's `path`. The `version`,
and the `state_evidence_id` and `types` that `data/get` reports, must match
the served state, so a `data/get` result loads back unchanged. A number without
an exact native value, or whose serialized spelling would not be read as exact
(an integer from 2^53 below 1e21, such as `1e20`, in any spelling), or a
`__proto__` member is refused rather than altered.
(an integer from 2^53 below 1e21, such as `1e20`, in any spelling), is refused
rather than altered. Caller data preserves all own JSON members, including
`__proto__`; unknown members of the state document's structure are refused.
An invalid document is `-32602` naming the first problem by JSON pointer, as in
`/rea_state/entities/3/collection` (also in `error.data.path`); a problem
inside caller data (`fields`, `value`, `types`) is located at that member, so
@@ -657,6 +946,25 @@ after an update and referential rules on delete are not modelled. An exported
bundle carries no state file: its server derives the state at startup, as the
export does to predict what it serves.
Known limits of served writes:
- A recorded view answers from its recordings after a served write: Mailpit's
`get_messages`, recorded as `list-differs`, still lists a message a served
delete removed.
- Served n8n workflow updates accept any `versionId`, while served Ghost post
updates require `updated_at` to match the entity (a `held-clock`).
- A slug derived from a title can repeat across served creates with the same
title, where the application would make it unique.
- A `composed` string copies sent text as sent: an application that escapes or
renders it (HTML entities, Markdown) differs for text that needs it, as
BookStack's `description_html` would for a description holding `&`.
- A served create's sequence id is the state's next value, not the gaps an
application's own sequence would leave.
- Nested clocks are served in the spelling of the write's own response, which
can differ from what a later read of the application shows.
- A served write to an entity that a replayed (unserved) create made answers
as for a missing entity, since the state never held it.
## Export a bundle
`rea environment-export` writes the same pair as a bundle folder the agentenv
@@ -693,7 +1001,8 @@ serves replay. The complete tool catalog the bundle serves must also fit the
server's advertised catalog budget before any files are written. Privately parsed JSON is sealed once so authentication
and replay share immutable response trees rather than copying the capture repeatedly.
`--name` replaces the manifest's portable kebab-case name as the env folder and env id
(Windows device names such as `con` and `lpt1` are refused);
(at most 63 characters for its DNS service label; Windows device names and
labels interpreted as IP addresses are refused);
`--package-tarball <path>` copies a packed `rea-agents` tarball into the env
folder and makes the Dockerfile install it instead of the registry package and
remove it in the same layer, which is how a checkout rather than a release is
@@ -711,7 +1020,12 @@ Windows inherits ACLs and REA does not establish owner-only access there. Choose
an already private Windows destination when captured content needs it.
The Dockerfile sets `MCP_HOST=0.0.0.0` and `MCP_PORT=18765`, the port the
framework maps for an MCP server env; the framework's own deploy supplies
framework maps for an MCP server env, and `MCP_ALLOWED_HOSTS` naming the loopback
addresses and the environment name, because the container binds outside loopback
and the server refuses to serve on such a bind without an operator-declared
allowlist: the framework's published port is reached over loopback and its
gateway proxies to the env container by the compose service name (the
environment name). The framework's own deploy supplies
`MCP_HOST`, `MCP_PORT` and `ENVIRONMENT_NAME`, so the served card carries the
env's name. It installs Debian's `python3` because the framework's local
sandbox probes a container's readiness with `python3` inside it, which the slim
@@ -784,9 +1098,11 @@ Numeric arrays retain that containing field's clock context through nested
arrays; an object's own fields establish their own context. Numeric values at
the body root, in root arrays, or under measurement fields remain literal.
Generated strings normalize only where this confirmed, served write's entity
response rules identify generation. A `unique` rule or UUID identity rule must
still compare two UUIDs; a `renewed` rule applies only at its recorded pointers,
Generated values normalize only where this confirmed, served write's entity
response rules identify generation. A UUID identity rule must still compare two
UUIDs, and a `unique` rule two UUIDs or two generated integers (10 to 20
digits) of one JSON kind and length; a `renewed` rule applies only at its
recorded pointers,
with `*` matching actual array items. Token/sequence strings retain the existing
format check: equal length of at least eight, the same character classes, and
a digit. Numeric sequences continue to compare literally unless an ID mapping
@@ -798,6 +1114,12 @@ Admitted generated values must also preserve aliases and distinct identities
within that response. UUID identity uses the generator's case-insensitive
matching; token and sequence strings retain their literal identity. This
correspondence ends with the answer and is not inferred across calls.
A `composed` response field must spell the value its parts build from that
response's own fields. Its parts then compare by their rendered text or their
source fields' clock and ID rules. A coherent archive URL can therefore differ
only in normalized clock digits without reporting a value difference; an
incoherent URL still differs even when both sides repeat the same stale text.
Normalized compositions count under `generated`; source fields are counted once.
The report counts each kind of normalised
value; the generation rules remain model-derived facts, rather than new
observations of the application.
@@ -827,7 +1149,12 @@ global conflict involving an equal pair is not used to normalize either pair; th
A step agrees when the statuses and the normalised bodies are equal. The report gives agreement overall, per probe and
per tool, `agreement_ignoring_order` for steps that differ only in item order, and each step's differences, at most 20,
with a JSON pointer and both values cut short:
with a JSON pointer and both values cut short. Each step reports `differences_total` before that cap,
`differences_omitted`, and `differences_truncated` (true when entries were omitted). Agreement and categories use the
complete comparison. Omitted entries are not retained, so `--full-output` cannot recover them; inspect the original
capture and probe Evidence to investigate beyond the returned pointers.
The difference categories are:
| Category | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
+368 -7
View File
@@ -30,10 +30,115 @@ code units; follow `coverage.next_offset`. The page preserves `source_status` an
`reported_total_bytes`: exhausting retained text does not make a partial decoded
manifest complete. Complete source Evidence remains exportable under the existing
session lifetime. Servers without retention reject summary before starting JADX.
One-shot CLI output remains complete. Older servers require the existing complete
request after schema discovery. APK authenticity and runtime behavior remain
One-shot CLI output remains complete by default. Select `--detail summary` with
`--evidence_output /local/capture.json` to retain complete Evidence before returning
a linked view. Older servers require the existing complete request after schema discovery. APK authenticity and runtime behavior remain
unverified.
## Manifest component projection
`inspect_android_package` also returns a static inventory of declared
activities, aliases, services, receivers and providers from the decoded
manifest. The binary XML decoder preserves component class spelling, including
`$` in nested classes, without applying JADX class aliases. `declared_name`
preserves the decoded source attribute; `name` resolves a
relative name only when the manifest package is known and the resolved copy
fits the UTF-8 byte budget. An unresolved package or exhausted name budget
retains the declared name and marks the inventory partial. Empty component
names and names with forbidden DEX characters retain their raw spelling and
are not treated as identities. Valid DEX spaces, including boundary spaces,
are preserved when resolving component names and matching alias targets.
Whitespace in the manifest package is rejected by its separate package grammar.
Component permissions inherit the application
permission when no component permission is declared, except an activity alias
with no permission: Android documents that case as requiring no permission
through the alias. `permission_source` distinguishes a local declaration,
inherited value, no requirement (including an explicit empty override), and a
value omitted because its resource/manifest-placeholder token is unresolved
or the total permission-byte budget was reached. Resource and placeholder
references are not expanded at this parser boundary. Repeated inherited
values share one inventory-wide byte budget.
Provider `android:readPermission` and `android:writePermission` overrides are
not projected as separate fields; their presence marks the inventory partial
and is called out in `limitations`. Relative component names are not resolved
against a manifest package with surrounding whitespace.
Class-name segments follow DEX `SimpleName` characters, including a leading
digit or hyphen, rather than Java source identifier rules. Relative-name
expansion does not prove class existence, DEX-version compatibility or runtime
loadability. SDK defaults accept JADX's decimal and hexadecimal integer
spellings; the original SDK declaration strings remain unchanged.
`exported` is the declared literal `true` or `false`, otherwise null.
`exported_default` is the documented default only where it can be established;
it remains null for unknown provider target SDKs, nonliteral declarations, or
components with filters targeting API 31 or later that omit the now-required
explicit declaration. A limitation records the latter validation error.
Provider authorities and all inventory dimensions are bounded. `status:
"partial"` and `limitations` identify omitted declarations or values; a
capacity limitation appears only when an actual collection limit was reached.
The projection does not merge manifests, expand resource references, or
observe runtime enabled/export state.
`enabled` and `grant_uri_permissions` report only literal component-level
`android:enabled` and provider-level `android:grantUriPermissions` declarations.
They are `null` when absent or not a literal boolean; missing attributes are not
filled from application defaults. A nonliteral declaration gets a limitation.
`process` preserves the exact component-level `android:process` spelling (including
empty or symbolic values), or `null` when absent or omitted under the inventory-wide
1 MiB copy budget. Neither field resolves resources, merges manifest values or
establishes the effective runtime process or enabled state. The exact boolean
attributes are retained in `enabled_declaration` and
`grant_uri_permissions_declaration`, including resource references.
`intent_filters` preserves each filter's source ordinal, exact priority spelling,
actions, categories and individual data declarations (scheme, host, port, path,
path prefix/pattern/advanced pattern/suffix and MIME type). These are declarations,
not computed Android matching rules. `meta_data` lists names; values remain in the
complete decoded manifest. Component, filter, data and metadata `location` values
use one-based lines/columns and UTF-16 offsets into that manifest. A component's
`declaration_index` preserves its original declaration ordinal, including gaps.
`declaration_coverage` distinguishes complete, partial and unknown observations.
Historical Evidence omits newly introduced fields and defaults coverage to
`unknown`; omission never establishes absence. Current records contain the fields,
with null for absent or unresolved scalar declarations. The inventory retains at
most 32 filters and 64 nested action/category/data/metadata records per component,
and shares a 1 MiB structured-value copy budget across components. Omitted values
or unsupported filter constructs mark coverage partial and include a limitation;
complete retained manifest text remains accessible.
For a small answer, select `detail: "summary"` with an explicit view:
```json
{
"kind": "android",
"facet": "components",
"actions": ["android.provider.Telephony.SMS_RECEIVED"],
"fields": ["name", "permission", "enabled", "process", "intent_filters"],
"offset": 0,
"limit": 16
}
```
The same JSON works with CLI `--view` or retained `inspect_analysis_view`.
`kinds`, `names` and `actions` use exact matching; names accept either the recorded
declared or resolved name, actions match any requested action, and different
filters combine with AND. `exported` matches only explicit declarations, keeping
`exported_default` separate. Empty selectors match nothing. Every selected row
retains its parent-array `source_index` and `declaration_coverage`; source coverage,
parent identity and limitations remain alongside the page. Follow
`coverage.next_offset`. The 1 MiB projection budget can shorten a page; a single
oversized row requires fewer selected fields. Exhausting a partial source does
not establish complete manifest coverage. `facet: "component-summary"` applies
the same filters and counts kinds, declaration coverage and explicit/default
exported values separately. Both adapters use the same retained projection;
repeated pages do not reload JADX.
The service `foreground_service_type` field preserves the declared attribute,
including an empty value or surrounding spaces; `null` means the attribute is
absent or the component is not a service. This field does not validate service
type tokens or establish runtime eligibility.
## Supply tools
Use a **full JDK 17 or newer** (including its compiler module) and
@@ -147,6 +252,50 @@ queries accept the same `overload_index`; the bridge resolves the selected
method node and the result reports its index and displayed signature. These
indices are tied to the current artifact and engine, not stable across builds.
Method reference queries also accept an external class, such as
`android.app.NotificationManager --method-name notify`, when the APK directly
invokes that exact DEX class and method name. If multiple prototypes are observed,
the input error lists their exact DEX descriptors and local overload indices;
repeat with an explicit `--overload-index`. These are the prototypes observed in
the APK, not the complete method inventory of an Android framework or library.
Such results report `target_definition: "external"` and
`reference_scope: "direct_dex_invokes"`. Each using method has `source_selector`
with `class_name`, `method_name` and `overload_index`; pass those fields with the
same APK path to `inspect_android_method` to inspect the caller. Repeated direct
invokes in one method remain one using node with multiple instruction offsets.
This scans raw invoke instructions in JADX's admitted APK methods without
generating their source. It does not resolve reflection, JNI, method handles,
inherited method lookup or possible runtime dispatch. Class-only queries and
class/method inspection still require an APK definition; an external target has
no invented body. Unknown class/method names remain input errors.
External prototype and caller metadata use a bridge-frame-derived representation
allowance that reserves space for retained raw calls and CLI/MCP copies. Exhausting
it returns a typed `resource_constraint`, rather than a truncated caller list
reported as complete. Direct instruction offsets have their separate explicit
`direct_invokes_exhaustive` coverage. Older retained Evidence may omit the new
definition, scope and caller-selector fields; omission does not establish them.
The bridge checks prototype expansion before copying descriptors or joining
displayed signatures. It reuses one bounded prototype inventory for the exact
class/method selection in the current APK session; replacing the session or
selection invalidates that retained result.
`inspect_android_class` also reports `dex_static_value` for each `static final`
field whose class definition encodes a value in its DEX static values, so
compile-time constants are readable even when the class has no `<clinit>`.
Each value is typed: `boolean`, `byte`, `short`, `char` (UTF-16 code unit) and
`int` values are JSON booleans or numbers, while `long`, `float` and `double`
values are strings holding Java's text for the value, so 64-bit values stay
exact. The field reports `null` when no value is encoded, the encoded value is
null or of another kind, or the field is not `static final`. The value is read from parsed
metadata without generating source, and a static initializer can still assign
the field; use `inspect_android_method` on `<clinit>` for that evidence.
Only `inspect_android_class` asks the bridge for these values.
`inspect_android_method` and `trace_android_references` read the member-only
class summary, so large encoded strings cannot push their member lookup past
the 8 MiB protocol frame limit.
Smali fallbacks select one complete block using the original method name and DEX
descriptor, before applying the source byte limit. This preserves the selected
overload even when Java code generation renames a method. If the selected Java
@@ -168,6 +317,162 @@ instructions into field declarations, leaving an empty or incomplete method
body. The selected method's disassembly preserves those instructions and retains
the same source byte limit and explicit truncation reporting.
## Apply an R8/ProGuard mapping
Release builds shrunk by R8 or ProGuard rename classes and members. Select the
build's `mapping.txt` with `mapping_path` on `inspect_android_class` or
`inspect_android_method`, or `--mapping-path` on the matching CLI command:
```sh
rea inspect-android-class /path/Example.apk a.b --mapping-path /path/mapping.txt \
--detail summary --evidence_output /tmp/class-evidence.json
```
For agent workflows, select `detail: "summary"` on class and method inspection.
It preserves the complete normalized member inventory or selected method body,
including mapping annotations, while retaining duplicate raw provider calls in
the complete parent Evidence. The reply links that parent's identity and digest;
export it with `export_evidence_bundle` or read its summary again through
`inspect_analysis_view`. CLI summaries require `--evidence_output` and return
the portable bundle path. Omitted or `complete` detail preserves complete inline
Evidence. Search and reference operations retain their existing contracts.
Symbol summary views admit at most **1 MiB**, including view context. An admitted
method's source can already consume that amount; a larger combined summary
fails explicitly instead of silently truncating facts. MCP refusal includes
the retained parent ID. Select complete detail, or inspect one method rather
than a whole class, when the requested summary does not fit.
The mapping path must be absolute. Selectors still take the observed names, such as
`a.b`, and observed rows are unchanged. The result adds one `mapping` block with
the file's path, SHA-256 and size, the header values the file reports
(`compiler`, `compiler_version`, `min_api`, `map_version`, `pg_map_id`,
`pg_map_hash`), valid record counts and up to 16 malformed line samples. Its
`symbols` list has one entry per observed class, inner class, method and field;
`inspect_android_method` lists the class and the selected method. `coverage`
counts each resolution status. With summary delivery, these unchanged facts are
under the view's `summary` field; the view separately reports source coverage.
Lookups use the bridge's raw DEX names, not JADX display aliases such as a
renamed reserved word. Each symbol resolves as:
- `mapped`: exactly one record matches. A class needs one record for its raw
name (`verified_by: "class"`). A method must also match its DEX prototype
(`"descriptor"`) and a field its DEX type (`"type"`). R8 residual signature
metadata is used when present; otherwise original types are translated
through the file's class records.
- `ambiguous`: `multiple-records`, `descriptor-mismatch`, `type-mismatch`,
`ambiguous-class` or `residual-signature-conflict`, with `candidate_count` and
up to 8 candidates. Conflicting residual metadata never selects a winner. A name match
whose prototype or type does not verify is never reported as mapped.
- `unmapped`: no valid record matched the raw identity.
- `unknown` with reason `projection-budget`: this claim did not fit the combined
reply budget. The observed raw symbol remains present; `coverage.unknown`
counts these omissions. Inspect that specific class or method separately,
or consult the selected local mapping file for complete source records.
A line continues an inline frame stack when it repeats the previous line's
minified range and obfuscated name and gives a single call-site line, such as
`:7`. Only the outermost original of a stack becomes a candidate. Original
line numbers and source files are not projected. Malformed records are counted
and skipped, so an `unmapped` symbol might be described by one of them. A file
that is missing, not a regular file, not UTF-8, larger than the byte limit or
without any class record, such as `usage.txt` or `seeds.txt`, is refused before
JADX is used. Requests read their mappings one at a time, in the same queue as
the JADX work and under the same operation deadline. A mapping failure therefore never starts or retires the JADX
session, and a different mapping reuses the loaded APK.
## Link native declarations to JNI candidates
When advertised, `inspect_android_jni_bindings` links original `ACC_NATIVE`
declarations from standard root `classes*.dex` members to bundled ELF libraries.
The supplied JAR's DEX reader preserves duplicate declarations before decompiler
deduplication. This operation does not load the APK into the decompiler.
MCP defaults to retained summary delivery when `detail` is omitted; select
`complete` explicitly to receive the full report inline.
```json
{
"path": "/absolute/Example.apk",
"class_name": "org.conscrypt.NativeCrypto",
"method_names": ["EVP_PKEY_new_RSA"],
"abis": ["arm64-v8a"],
"detail": "summary"
}
```
Class/method names and optional `descriptors` select raw DEX identities.
`library_paths` selects exact logical APK paths such as
`lib/arm64-v8a/libconscrypt_jni.so`; each library's `source_path` preserves its
original ZIP spelling. Omitted filters include all scoped facts; `[]` matches
none. Class selection never narrows the global inventory used for table-class
attribution.
Independent `declarations`, `libraries`, `candidates` and `bindings` collections
have stable IDs and source ordinals. Candidates retain APK/library digests,
ABI, function addresses and file offsets. Table candidates also retain section,
table and entry identity plus all three name/signature/function pointer slots
and their relocation sources. JNI exports follow the
[UTF-16 escaping and short-before-long rules](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/design.html#resolving-native-method-names);
failed escaping does not acquire an invented export match. Tables require
NUL-terminated [Modified UTF-8 strings](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/types.html#modified-utf-8-strings),
valid descriptors and executable load mappings. REL, RELA and RELR retain
separate provenance; unsupported packed relocations and unresolved pointer
spans stay explicit unknowns.
A static `JNINativeMethod` entry names no class. Attribute it uniquely only
when exactly one declaration in the complete recorded native inventory has
that `(name, descriptor)` pair. Multiple classes remain ambiguous after a
class filter; a partial inventory cannot establish unique attribution.
Each selected declaration has a row per selected/observed ABI. `matched` means
candidate presence was observed; `ambiguous` means multiple observed targets
or class associations remain. Binding `coverage` is separate: a matched
partial row does not establish complete uniqueness, and an empty partial scan
is `unknown`, never `unmatched`. ELF/ABI label mismatches preserve facts without
asserting a binding. No result establishes library loading, runtime registration
or method invocation.
Summary returns the first 64 selected binding rows, their declaration/candidate
closure and every selected library's identity, coverage and failure reason;
counts and page coverage describe the full selected binding set. Selected
declaration IDs have separate preview coverage, so native declarations remain
visible even when the APK has no bundled ABI libraries. Complete
Evidence stays retained. Read exact fields or further pages without reopening
files or rerunning analysis:
```json
{
"source": { "kind": "retained-evidence", "evidence_id": "ev_<digest>" },
"view": {
"kind": "android-jni",
"collection": "bindings",
"offset": 64,
"limit": 64,
"fields": ["declaration_id", "abi", "status", "coverage", "candidate_ids"]
}
}
```
Collections also include `declarations`, `candidates` and `libraries`, with
exact filters and batch lookup by declaration/candidate/library IDs. `row_index`
is the original parent collection index; DEX and
ZIP source ordinals stay separate. Complete detail returns the full report.
CLI uses the same selection and delivery contracts. Its common one-shot
default remains complete; summary delivery requires an explicit Evidence path:
```sh
rea inspect-android-jni-bindings /absolute/Example.apk \
--query '{"class_name":"org.conscrypt.NativeCrypto","method_names":["EVP_PKEY_new_RSA"],"abis":["arm64-v8a"]}' \
--detail summary --evidence_output /absolute/fresh-jni-evidence.json
```
Optional `--view` accepts the same JSON page and requires summary delivery.
Evidence bundles never overwrite files. Complete declarations and one library
selection are cached within the immutable APK session. Changing ABI/library
selection replaces the scan; class/method selection reuses it. Fresh analysis
still validates current caller APK bytes. Idle/close releases the caches;
retained Evidence remains historical and independently readable.
## Interpret the result
- Package inspection retains decoded manifest XML, summary declarations,
@@ -193,10 +498,22 @@ the same source byte limit and explicit truncation reporting.
full reported display types, fields and inner class names. Synthetic members
that code generation hides can appear in the inventory; it stays consistent
after method decompilation within the retained session.
Class and method inspection and reference tracing accept either the exact
reported class name or its original binary name, including `$` in nested and
anonymous classes. The bridge keeps both identities from JADX; it does not
replace separators or guess suffix matches. Ambiguous selectors are refused.
Follow-up calls preserve the selected name, while results report the frozen
JADX display name. Quote `$` names in the shell, for example
`'com.example.Manager$WorkerThread'`.
Exact DEX prototypes are reported from frozen JADX method metadata as
`dex_descriptor`, for example `(I)V`. Display names can be JADX aliases;
the descriptor alone is not a complete raw DEX method ID. Overload indices
are tied to this artifact and engine, not stable identities across builds.
- A `mapping` block appears only when the caller selected `mapping_path`. Its
names are claims from that file, not observations of the APK. REA does not
check that the mapping belongs to this build; `pg_map_id` is reported, not
compared. Evidence keeps the selected path in its parameters and the file's
SHA-256 in the result.
- Method inspection reports Java or smali, fallback markers and complete/partial
returned text. Decompiled source is a derived representation. A complete text
result does not establish complete semantics or a byte-identical reconstruction.
@@ -286,6 +603,19 @@ target/configuration changes, failure, timeout, cancellation, MCP disconnect or
server shutdown. One-shot CLI commands always join cleanup before returning.
REA never writes to the original APK or launches target code.
JNI scans reuse the analyzer process without loading the APK into the
decompiler. They admit at most 64 MiB per DEX member and 256 MiB of DEX bytes,
with a 4 MiB native-declaration JSON allowance before MCP string escaping.
Native libraries are processed sequentially: one 64 MiB buffer, 256 MiB total
selected read work, and a 16 MiB parsed-library-facts allowance. ZIP inventory
and library counts also have explicit resource bounds. Unexamined occurrences
remain unknown. A complete relationship report has a separate 16 MiB serialized
allowance; summaries/pages allow 1 MiB including their provenance envelope.
These are resource admissions, not claims about maximum valid Android formats.
Select fewer ABI/library occurrences or smaller retained pages when needed.
Completed parsed library facts survive failed reader finalization in typed
partial observations; the exact reader owner remains available for cleanup retry.
On Windows, stdin writes preserve stream backpressure and run outside the Node
event loop. Cancellation terminates the retained job, including descendants,
before workspace cleanup. On Windows, a detached REA helper owns the private
@@ -346,11 +676,17 @@ export REA_JADX_OPERATION_TIMEOUT_MS=600000
The value must be a decimal millisecond count from 1 to 2147483647; anything
else is refused before Java starts. REA reads it from the environment captured
at process start. A change applies on the next process start; an already
running server keeps the deadline it started with. The same deadline bounds
each upstream call. A timeout names `REA_JADX_OPERATION_TIMEOUT_MS`, the
effective milliseconds, and that range. A passing JADX configuration check in
`rea doctor` reports the effective `operation_timeout_ms`. Explicit
at process start. Changing that environment requires a new REA process. On a
running MCP server, set `jadx.operation_timeout_ms` through
`update_analysis_settings` to change the default for newly accepted operations;
queued or running operations retain their accepted budget. The same deadline
bounds each upstream call. Timeout recovery reports the effective milliseconds
and range, with `details.limit` identifying the runtime setting. An invalid
provider-local launch value can also be repaired by a runtime override; reset
exposes that invalid launch value again. See the
[settings design](design/limits-and-recovery.md#invalid-launch-configuration).
A passing JADX configuration check in `rea doctor` reports its launch-derived
`operation_timeout_ms`; it does not inspect another running server's overrides. Explicit
cancellation still ends the operation immediately, and partial observations
and owned-process cleanup are unchanged. Raising the deadline does not extend
the bridge's 30-second startup handshake or the upstream **90-second**
@@ -365,6 +701,27 @@ complete, case-sensitive search contract. A protocol frame above **8 MiB**, or c
**32 MiB per operation**, fails with a diagnostic instead of returning an incomplete inventory
as complete. These are memory-safety boundaries, not undocumented result caps.
A selected R8/ProGuard mapping is re-read and SHA-256 checked on every call, up
to **64 MiB**. The serialized provider keeps one successful parsed index, keyed
by byte length and digest with fixed parser options. Identical bytes reuse that
index; changed bytes are reparsed, missing files still fail, and each response
preserves the currently selected path. The index is released on provider idle
retirement or close. Omitting `mapping_path` never applies the cached mapping.
During replacement, the old index can coexist with the new parse until it
succeeds; these transient allocations count toward process memory.
Before retaining records, the parser admits an estimated **128 MiB** index
representation and **8 MiB** of expanded mapped claims per residual class,
including shared header facts. The index allowance accounts for strings and
containers; it is not a measured V8 heap cap. Parameter and metadata expansion
is admitted before splitting or JSON decoding. These fixed resource bounds
have no runtime override; refusal reports `resource_constraint`, the source
line and mapping SHA-256. Ambiguous lookups count all candidates while keeping
only the first eight in stable file order. A separate **8 MiB** combined claim
budget applies to the actual query set, including outer and inner classes,
before cloning the report. Claims that do not fit become explicit `unknown`
entries; other admitted claims and all observed identities remain available.
## Real verification
Download the explicit public fixtures once; this installs no tools:
@@ -408,3 +765,7 @@ containing node's unknown `dex_offset`. `direct_invokes_exhaustive: false`
means instruction rows were bounded by the local bridge frame budget; `null`
means the producer did not establish instruction coverage. Using-node totals
and coverage do not imply complete instruction evidence.
`direct_invokes_coverage` reports counts of using nodes with complete, partial,
or unknown positions, plus the number of retained instruction sites. This
aggregate makes position truncation visible without scanning every caller;
older retained Evidence may omit it, which means the aggregate was unexamined.
+32 -14
View File
@@ -179,26 +179,31 @@ share the application workflow, projection, retention and publication owners.
- `schemes_agree_on_signer` (observed): every present block scheme presents
the same _set_ of signer certificates. An extra signer in one scheme is
disagreement; a signer without a certificate makes it `null` (unknown).
Build-tools 33+ rotate through v3.1 by default, so a rotated app usually
reads `false` here and `true` below.
A single present block scheme with known signers agrees with itself;
v1 is compared separately below.
- `signer_change_declared_by_rotation` (declared): the signers differ, but
each appears in a lineage the APK declares. Read the two together: a
rotated APK shows `false` / `true`; signers that differ with no declared
lineage containing them show `false` / `false`. It is `null` when a
signer's identity is unknown, or when the signers differ and a declared
lineage could not be decoded.
each appears in a lineage the APK declares. Read the two together: block
schemes with different signers covered by a declared lineage show `false`
/ `true`; differing signers without such a lineage show `false` / `false`.
A lineage alone does not establish a change between block schemes. It is
`null` when a signer's identity is unknown, or when the signers differ and
a declared lineage could not be decoded.
- `v1_signers_match_block_signers` (observed): the v1 signer certificates
equal what the lowest block scheme that has signers (v2, else v3, else
v3.1) expects v1 to carry. That is its signer set, except that a v3/v3.1
signer declaring a rotation lineage expects the lineage's first
(original) certificate, as apksig requires. `false` is notable: the JAR
signature names a different key than the block signature. A key rotation
keeps v1 (and v2) on the original key, so it reads `true` while
`schemes_agree_on_signer` reads `false`, with or without v2. `null` when
v1 or a block scheme is absent, any signer's identity is unknown (a
signature names a different key than the block signature. When v1 uses
the lineage's original key and v3 its newer key, this comparison reads
`true`. If v3 is the only present block scheme, agreement is `true` and
declared inter-block signer change is `false` even though rotation is
present. If v2 also uses the original key, agreement is `false` and the
declared inter-block change is `true`. `null` when
v1 or a populated block scheme is absent, any signer's identity is unknown (a
`decode_error`, a `null` certificate or a `subjectKeyIdentifier`
`SignerInfo`), or the lineage's original certificate cannot be
identified; a limitation then says why.
identified. When identities or a lineage origin are unknown, limitations
explain why; absence is shown in the scheme records.
- `strongest_scheme`, `rotation_present`, `source_stamp_present`,
`self_consistent`.
@@ -239,13 +244,26 @@ serialized size of the report together with its limitations (which repeat
archive names) is then checked against an 8 MiB output budget (the exact check
is authoritative); an over-budget report is refused, not truncated.
The whole-file reader retains read chunks and a concatenated buffer; total
process memory exceeds the selected file's size. It also computes the complete
Signing blocks admit at most 4096 ID/value pairs; at most 4096 signing-relevant
META-INF entries are retained. These fixed counts, signer/lineage counts and the
report byte budget return nonretryable `resource_constraint` errors naming the
effective allowance. This release has no runtime override for them. Waiting for
an active scan cannot raise a fixed count, and complete CLI JSON cannot raise the
report budget. Occupied scan-queue capacity remains retryable after a scan finishes.
The whole-file reader allocates the selected snapshot and bounded read scratch;
total process memory exceeds the selected file's size. It also computes the complete
file SHA-256, so inspection is not header-only. Content digests are recomputed
at most once per SHA-256/SHA-512 algorithm during one inspection. Input,
inflation, decoded-record and serialized-output bounds keep work finite;
resource refusals are distinct from malformed format errors.
If the stable read completes but closing its descriptor fails or is cancelled,
the error retains `details.partial_observation` with the selected path, complete
file SHA-256 and byte count. These facts describe the acquired file; they do not
establish any decoded signing facts. Failed cleanup remains visible until the
provider releases the descriptor on a cleanup retry.
A file that is not a ZIP is refused as an unsupported target. A damaged ZIP
(local headers but no end-of-central-directory), a central directory that
cannot be walked or whose declared record count does not account for all of
+6 -5
View File
@@ -100,12 +100,13 @@ still suppresses all string projection, including in this mode.
- **Metadata comes from apktool.yml as apktool printed it.** Version codes
keep their own spelling (`'7'` versus `7`); `package_name` is read from
the decoded manifest, not inferred.
Apktool 3's emitted `version: 3.x.y` header selects its single-line string
dialect: literal hashes, single quotes, empty strings, and the text `null`
stay part of the value; producer backslashes and Unicode escapes decode
once. Older/tagged documents retain YAML scalar semantics; double-quoted
values also retain YAML string semantics.
The `version: 2.9.0`-or-later header that Apktool 2.9+ and 3 emit selects
their single-line string dialect: literal hashes, single quotes, empty
strings, and the text `null` stay part of the value; producer backslashes
and Unicode escapes decode once. Older/tagged documents retain YAML scalar
semantics; double-quoted values also retain YAML string semantics.
This distinction follows the released
[2.9.0 metadata writer](https://github.com/iBotPeaches/Apktool/blob/b64e257c9b689eb9630ad0241f2fe4f4a749955b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java#L38-L42),
[3.0.3 metadata writer](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java#L49-L55)
and [string escape owner](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.j.yaml/src/main/java/brut/yaml/YamlStringEscapeUtils.java#L55-L116).
- **The manifest is the decoded text**, bounded at 512 KiB and reported as
+7 -2
View File
@@ -101,8 +101,8 @@ limitation reports how many inventoried entries were left out. IPA projection
still lists every archive component, including `SwiftSupport/`.
`platforms` lists `macos` for application roots with `Contents/`. It lists `ios`
for shallow application roots that have an `Info.plist`. An application root
with neither, such as an empty inventory, has no platform.
for shallow application roots that have an `Info.plist` at their root. An
application root with neither, such as an empty inventory, has no platform.
## macOS bundle anatomy
@@ -149,6 +149,11 @@ directories rather than `Versions/Current`. A framework's `info_plist_path` is
because `Versions/Current` decides which one applies) or when its single
version directory has no `Resources/Info.plist`.
A `shallow` bundle's `info_plist_path` is its root `Info.plist`. Without one,
it is `Resources/Info.plist`: a flattened macOS framework keeps its binary,
`_CodeSignature/`, and `Resources/` directly in the `.framework`, like a single
`Versions/<version>/` directory without the `Versions/` wrapper.
AppleDouble sidecar files (`._*` and `__MACOSX/`) are inventory facts, but they
describe neighbouring files. They are excluded from roots, bundle roles, and the
anatomy components.
+20 -2
View File
@@ -29,7 +29,12 @@ body is parsed only when that string is `"309"`. Other types, including `"288"`
stackshots, return no crash result.
`process`, `exception`, `termination`, `faulting_thread`, `threads`, and
`images` reproduce documented body fields when present. Integers keep their
`images` reproduce documented body fields when present, including each image's
`name` and each frame's `symbolLocation` (as `symbol_location`, the byte offset
from the start of `recorded_symbol`). `termination` keeps the terminating
process as `by_proc` and `by_pid` and its `flags`; `process.parent_pid` keeps
`parentPid` next to `parent`; and `metadata` keeps `build_version` and
`platform`. Integers keep their
decimal literals and a hexadecimal form, including arm64e registers above
2^53. Frame roles follow the recorded frame index: row zero of the resolved
faulting thread is `faulting_instruction`, row zero of other threads is
@@ -38,9 +43,22 @@ address uses that row's own image base and offset. The recorded `inline` flag
does not supply a missing offset or authorize borrowing another row's address.
When `faultingThread` is missing or does not resolve to a recorded thread
object, row-zero roles are `unknown`; the recorded lookup remains available.
A translated process records Rosetta's registers in a nested
`threadState.rosetta` object; each one is reported as `rosetta.<name>` and
counts toward the register budget. A [public ReportCrash `.ips` log](https://github.com/Mortennn/Dozer/issues/189)
shows `rosetta.tmp2`, `tmp1`, and `tmp0` beside ordinary x86 registers. The
test fixture quotes that register excerpt inside an authored report body; it
does not generate a new Rosetta crash during the test.
Observed JSON strings retain `""`; `null` means the field was missing or JSON
`null`. This includes image paths, frame symbols, process text, and the older
string `osVersion` form. Unlisted JSON keys and generic register names also
retain empty spellings. An empty image architecture remains recorded as `""`
while `uuid_unknown` stays true, since that spelling does not establish an
architecture. An empty UUID is malformed, so its exact spelling appears in
`coverage.type_mismatches` and its canonical UUID remains unknown.
`process.os_version` keeps the producer's dictionary (`train`, `build`,
`release_type`, `is_embedded`) or the older string form. Image UUIDs are uppercase 8-4-4-4-12; an all-zero UUID
or a missing `arch` is `uuid_unknown`. Image `source` letters keep their
or a missing or empty `arch` is `uuid_unknown`. Image `source` letters keep their
documented meaning (`P` process, `S` shared cache, `C` shared cache library,
`K` kernel, `U` kernel cache, `T` kernel text exec, `A` absolute). Keys the
documentation does not list remain in `unlisted_keys` or `unlisted_body_keys`.
+9 -9
View File
@@ -8,7 +8,7 @@ graph TD
end
subgraph "Production Composition"
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>static WASM/EVM descriptors: named contracts, factories, retryable lifetimes"]
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>typed capability bindings: construction, availability and retryable lifetimes<br/>pure capability declarations feed provider catalogs"]
end
subgraph "Adapters"
@@ -21,14 +21,15 @@ graph TD
subgraph "Application Layer"
APP["src/application/<br/>Shared CLI/MCP workflows<br/>enhanced analysis and investigations"]
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback<br/>ProviderInspectionScope: prerequisite observation lifetime"]
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots<br/>compatibility facade and post-commit notifications"]
LIFETIMES["CapabilityScope<br/>host-owned service close capabilities<br/>released peers removed; failed cleanup retained for retry"]
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots and session file ownership<br/>record coordination and post-commit notifications"]
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
end
subgraph "Provider Layers (selected adapters)"
PROVIDER_OWNERS["src/providers/<br/>shared deferred client and inspection owners<br/>exact cleanup retry and attributed failures"]
PROVIDER_OWNERS["src/providers/<br/>shared deferred client owners<br/>exact cleanup retry and attributed failures"]
PWNTOOLS["src/native/pwntools/ + src/native/pwndbg/<br/>BYO offline ELF/core adapters<br/>owned snapshots/Python and optional core-only GDB<br/>unchanged upstream parsers, ABI structures and mapping API"]
WASM["src/wasm/<br/>caller-supplied WABT; immutable artifact inspection<br/>owned process supervision and retryable workspace cleanup"]
APKSIG["src/apkSignature/<br/>in-process APK Signing Block + v1 reader<br/>bounded single read, no device or execution"]
@@ -75,7 +76,7 @@ graph TD
end
subgraph "Domain Layer"
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>evidence, comparisons, application graphs"]
DOMAIN["src/domain/<br/>Pure side-effect-free modules<br/>format declarations, provider selection, Evidence, comparisons, application graphs"]
end
subgraph "Configuration"
@@ -99,6 +100,7 @@ graph TD
MAIN --> COMPOSITION
CLI --> COMPOSITION
SERVER --> COMPOSITION
COMPOSITION --> LIFETIMES
COMPOSITION --> SESSION
APP --> FIRMWARE
FIRMWARE --> PROCESS
@@ -149,8 +151,7 @@ graph TD
OBJDUMP --> DOMAIN
COMPOSITION --> RIZIN
RIZIN --> DOMAIN
CLI --> GDB
SERVER --> GDB
COMPOSITION --> GDB
GDB --> PROCESS
GDB --> DOMAIN
HOPPER --> HOPPER_BRIDGE
@@ -206,8 +207,7 @@ graph TD
NATIVE_READERS --> DOMAIN
COMPOSITION --> FORMAT_READERS
FORMAT_READERS --> DOMAIN
SERVER --> RUNTIME
CLI --> RUNTIME
COMPOSITION --> RUNTIME
RUNTIME --> DOMAIN
PROCESS --> FILESYSTEM
AUX --> FILESYSTEM
+14 -5
View File
@@ -8,9 +8,14 @@ ordinary binary inspection use the same modular capability.
## Bring your own engine
Initial real verification covers **Linux x64, ELF64 x86-64 little-endian
EXEC/DYN/REL**. Provide an absolute Python executable whose environment already
contains unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2:
Real verification covers **Linux x64** hosts and **EXEC/DYN/REL** objects in
these profiles: ELF64 x86-64, ELF64 little-endian AArch64, ELF32 i386, ELF32
little-endian ARM and ELF32 little- or big-endian MIPS. Other machines, classes
and byte orders, including big-endian ARM, return `unsupported_target`. Other
hosts, including Linux arm64, return `capability_unavailable` naming the
detected host before any target is read. Provide an absolute
Python executable whose environment already contains unchanged pwntools 4.15.0,
pyelftools 0.33 and Unicorn 2.1.2:
```sh
REA_PWNTOOLS_PYTHON=/absolute/isolated-env/bin/python \
@@ -100,6 +105,8 @@ one section or symbol, or a stable page without re-running the decoder. CLI
one-shot invocations pass portable inline Evidence JSON; an MCP session uses
the retained `evidence_id`. Page `limit` is required and bounded by the
measured MCP stdio budget documented on the tool contract.
An empty retained section or symbol collection has no valid item index. Its page
view can still return an empty page.
Malformed authenticated layout input remains rejected with the owning schema's
safe field diagnostics under `source.normalized_result`, including the field
path and type or value reason. Evidence authentication and decoder validation
@@ -419,8 +426,10 @@ rea inspect-native-exports ./libexample.so --detail summary --evidence_output ./
without an export directory is a complete empty inventory.
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
binding and default or protected visibility. Each row keeps `value`
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
and `binding`. ELF string tables do not declare a text encoding. Names that
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`,
`binding` and `visibility` (`"default"` or `"protected"`; `"unknown"` in older
historical Evidence where visibility was not recorded). ELF string tables do
not declare a text encoding. Names that
contain non-ASCII bytes are decoded as UTF-8 when those bytes form valid
UTF-8; `name_utf8_decodable` records whether the non-ASCII name bytes are
valid UTF-8 and `name_bytes_base64` retains those exact bytes (`null` for
+68 -5
View File
@@ -52,7 +52,11 @@ observations may omit the coverage field; treat omission as `unknown` rather
than inferring completeness from the array.
Passive DOM comparison checks complete node inventories and complete frame
parent/child structure independently. Frame IDs and sibling enumeration order
parent/child structure independently. Node value lengths are comparable only
when both nonempty snapshots declare the same `node_value_length_unit`.
Missing or different units leave length comparison unknown; independent node
or frame structural differences can still prove change. Empty inventories have
no value lengths to compare. Frame IDs and sibling enumeration order
are capture-local and do not establish a change; duplicate IDs, missing parents,
and cyclic ancestry remain unknown. A complete frame difference can still prove
change when DOM nodes are incomplete, and vice versa. Partial DOM or metadata
@@ -94,6 +98,14 @@ content. Actual JSON properties with those names remain content. Reference-only
objects, unsupported values, and opaque cookie partition identities remain
incomplete. Previously exported fingerprints are not recomputed; recapture both
sides when comparing captures produced across this decoder change.
IndexedDB schema fingerprints with `projection_version: 2` include the observed
database version and object-store definitions. A missing, malformed, or imprecise
version leaves the schema fingerprint incomplete while retaining observed store
facts. Historical fingerprints without this marker covered object stores only:
matching historical hashes cannot establish unchanged database versions, and a
historical hash is not compared with a version-bearing hash. Recapture both
sides for a complete IndexedDB version comparison; other proven storage changes
remain reportable.
Script comparison groups observations by reported URL and compares unordered
content samples. Producer inventory keys include optional metadata, so a changed
@@ -392,7 +404,19 @@ contextual evidence with `timestamp: null`, accompanied by a limitation stating
that it does not establish activity in the selected window. Main-frame scope
enforcement still applies to contextual navigation events. Network redirect
events retain their paired prior-hop response context when the timestamped
next-request event falls inside the window. The pre-arm timeline buffer is bounded
next-request event falls inside the window. A redirect row's optional
`redirect_response` contains the prior response `url`, CDP-reported `status`,
and `scope`. The URL has transport credentials removed; URL and status are
withheld unless its origin is approved. Missing or malformed response URLs have
null scope and add `invalid_protocol_value` timeline coverage; a valid URL with an
unsupported protocol has `unsupported` scope. Outside-policy and unsupported
responses mark the timeline as policy-filtered with `disallowed_origin` or
`unsupported_url`. Missing status remains null rather than zero. A supplied
non-numeric status is also null and records `invalid_protocol_value` coverage;
the other valid response facts remain available. Historical rows may omit this
field; omission does not establish that no prior response existed. Response
headers and bodies are discarded. The row timestamp belongs to the next request,
not an exact prior response time. The pre-arm timeline buffer is bounded
by original CDP message bytes. If clock sampling completes after that budget is
exhausted, the operation returns `payload_limit` instead of a partial window.
Cancellation and clock-request failures retain their own outcomes. Session
@@ -407,7 +431,8 @@ allowance; exhaustion fails the observation explicitly instead of guessing scope
Response metadata parses repeated CDP header field lines independently. The
Link projection retains their declarations in order. `csp.directives` lists the
directives of every CSP policy in order, across field lines and comma-separated
policies, without recording policy boundaries, so a directive name can repeat.
policies, without recording policy boundaries, so a directive name can repeat
across policies. As in CSP3, a repeated name within one policy is ignored.
Single-valued policy fields combine their lines as one comma-separated value,
as RFC 9110 specifies. Each link keeps its target in `href` and
its relationship context in `context_url` with a separate `context_scope`;
@@ -426,10 +451,21 @@ complete Structured Field Item, including parameters such as `report-to`.
These are declarations, not an assessment of the document's effective policy;
absent, malformed or non-token items project as `null`.
Console entries, WebSocket frame summaries, and selected request/response JSON
shape outputs share one 8 MiB retained-observation allowance per capture. Shape
inference admits expanded path bytes before allocating those paths, including
escaped and Unicode property names. Replacing or removing a request's shapes
refunds their retained charges. A refused body shape preserves network metadata
and any captured peer shape with `resource_budget_exhausted` coverage; a refused
WebSocket frame is counted as dropped rather than reclassified as non-JSON text.
The allowance has no runtime override. Protocol messages and JSON input parsing
remain subject to their separate transport admission; this output allowance does
not bound the whole process heap.
REA retains local observation data and removes only URL userinfo credentials, explicitly declared scenario secrets, and values from structured credential/storage fields:
- URL observations preserve the complete local URL, including query values, fragments, duplicate parameters, and their order. Only username/password userinfo is removed; malformed original URL strings remain available with `origin: null`.
- DOM snapshots retain node types, node names, value lengths, and attribute names, but not text or attribute values.
- DOM snapshots retain node types, node names, value lengths, and attribute names, but not text or attribute values. `dom.node_value_length_unit` is `utf16-code-units` for web observations and applies to every node's `node_value_length`; it counts JavaScript string code units, not Unicode code points or UTF-8 bytes. Electron observations declare `utf8-bytes` instead. Historical captures without the unit leave it unknown; reading them does not infer a unit or convert their stored lengths.
- Supported DOM URL attributes are projected separately as resolved destinations. Empty `action` on HTML or XHTML forms and empty `formaction` on HTML or XHTML submit controls resolve to the document URL; other relative URL attributes resolve against the captured document base.
- Accessibility structure and reported roles are retained by default, while names and descriptions require `include_accessibility_text: true`. The browser protocol may omit an individual role; that role remains unknown for comparison. Other observed accessibility differences can still establish a change.
- Network observations retain method, status, MIME type, size, type, initiator stack location, and complete URLs with userinfo credentials removed. Redirects reported by CDP are attached as ordered `redirects` on the one request ID, with the prior request URL, CDP response URL, method/type, response status/MIME/encoded length, request timestamp, and the next `requestWillBeSent` event timestamp. CDP delivers `redirectResponse` with that next request event, so the latter is an event boundary rather than an exact response timestamp. Redirect response headers and bodies are discarded; hops outside the selected origins are discarded with the request chain. Headers are discarded after an allowlisted projection of length/encoding, structured CSP/Link/policy fields, and untrusted agent hints. Cookies and authorization headers are never retained.
@@ -464,7 +500,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
connection/frame inventories; matching direction/opcode sequences alone do
not prove alignment after drops. Redirect resource types use only shared known metadata:
an omitted type leaves equality unknown rather than proving a modification.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- Console observations with a stack source retain call type, argument types, the CDP `Runtime.Timestamp` in milliseconds since Unix epoch, and source location. An absent or invalid timestamp is `null` and makes `console_events` incomplete; an observed zero remains zero. Argument types are listed by CDP argument position. An absent or non-array argument inventory is `null`, and a malformed argument keeps its position as `unknown`; either makes `console_events` incomplete, while an observed empty inventory remains `[]`. A malformed stack or top stack frame is not replaced by its caller: the console event is excluded, and a request initiator location or WebMCP `registration_source` is `null`; each is reported as `invalid_protocol_value`. Source locations are CDP's zero-based lines and UTF-16 columns. A negative column, which Chrome reports on the first line of some inline scripts, is kept as reported and does not locate a column in the source URL; an invalid line or a non-integer column is `null` (WebMCP `registration_source` is `null`) and is reported as `invalid_protocol_value`. This epoch-millisecond clock is separate from the monotonic-second timestamps in `observe-web-session`. When console text capture is selected, already-delivered primitive values are retained verbatim with their original `argument_index`; objects, getters, and remote properties are never expanded. Text capture is `unavailable` when the argument inventory is unknown.
- WebSocket observations retain direction, opcode, and payload byte length. A missing or malformed CDP opcode is returned as `null`, with opcode-dependent payload byte length and shape also `null`, and marks `websocket_frames` incomplete. Missing or wrong-typed payload data is not treated as empty; its byte length is `null`, and a text payload shape is unknown. Numeric opcode `0` and an observed empty payload remain distinct from missing data. Opcode is nullable in the public inspection schema, so clients must handle unknown protocol values. When shape capture is selected, text messages are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw payloads are never retained.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache fingerprints hash all decoded response bytes, including bodies above 64 KiB, subject to the [64 MiB per-message CDP limit](#request-boundary). Missing or undecodable cache bodies and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
@@ -590,6 +626,22 @@ Network and console coverage starts only after REA attaches and enables the rele
Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Each fetch operation uses `source_map_fetch_timeout_ms` (default 30000) and a 64 MiB aggregate streamed-response budget across redirects. The byte budget has no runtime override. A fetch overrun is an item limitation, not a thrown analysis timeout. A deadline or byte-limit failure remains explicitly unavailable and makes bundle completeness partial. Source-map decoding also bounds raw resolved identities to 64 MiB and aggregate inline evidence to 32 MiB. Failed maps preserve their failure context when it fits; if another context cannot fit, collection stops with the full requested count, the returned processed count, and an explicit unknown-results limitation. Retained mappings and original sources are returned inline. See [source-location limits](web-source-location.md) for the structural decoder bounds. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
A fully read response that fails UTF-8, JSON, mapping, or structural validation
retains `artifact: null`, empty original-source, module-edge, and mapping
arrays, and an explicit failure limitation. Invalid content is `invalid`;
decoder or structural capacity refusal is `fetch_failed`. Its optional
`response_identity` records the complete body's SHA-256 digest, byte count,
and `fetch-response-body-bytes` representation without retaining malformed
text or charging that text to the 32 MiB output budget. These are bytes
delivered by Fetch after HTTP content decoding and before UTF-8 decoding, not
HTTP wire bytes. The identity is absent when the body was not fully read, and
older observations may omit it. A response whose text cannot fit the existing
output budget is likewise `fetch_failed` with text omitted and completed-body
identity; JSON parsing and source-map decoding are not attempted. Successful and partial maps keep their
full text artifacts. Their original text, including an initial BOM or XSSI
line, remains distinct from the parsing view that removes that framing.
Collection reads the body once and does not raise its response or output limits.
When fetching is selected, source-map `excluded` facts preserve the capture
ledger's reasons and counts for declarations rejected before download. No
disallowed URL details are retained or fetched. `requested` and `processed`
@@ -597,6 +649,17 @@ count the admitted fetch requests; exclusions are counted separately. Excluded
declarations make the collection unavailable, or partial when maps were retained.
An empty collection with no declarations remains `not_requested`.
If the caller cancels while later source maps are being fetched, the failure's
partial Evidence retains the captured page and every completed map item. Its
`normalized_result.kind` is `web-bundle-acquisition` and
`analysis_status` is `not_performed`: no script-analysis result is claimed.
`source_maps.requested` remains the admitted request count,
`source_maps.processed` counts completed items, and any unprocessed requests
have an explicit unknown-results limitation. Capture exclusions remain in the
same collection. The cancellation error keeps the caller's exact reason;
an already observed response resource or cleanup failure keeps its own error
category and cleanup owner.
Source-map items retain the script's `declared_url` separately from the observed
`effective_url`. Successful maps use the latter as their source-resolution base.
Failed fetches retain the last response URL observed within the approved origins;
+367 -6
View File
@@ -10,8 +10,10 @@ liveness deadlines. `INPUT_JSON` may be inline JSON or a JSON file path.
The minimal request contains only browser launch/connect selection, `start_url`,
and at least one `actions` entry. Launch `headless` defaults to `true` and can
be set to `false` where the host supports a visible browser. Navigations follow
the destinations named by the scenario and the page's ordinary browser requests;
there is no separate origin allowlist or request-interception layer. Environment
the destinations named by the scenario and the page's ordinary browser requests,
and [request steps](#request-steps) send only to the start URL's origin;
there is no separate origin allowlist. Request steps use a temporary dispatch
gate for cancellation; ordinary page requests retain their browser behavior. Environment
settings (including service workers blocked by default), empty storage, and a
final sanitized URL capture are supplied by default. Set
`environment.service_workers` to `allow` when the application requires them.
@@ -49,6 +51,30 @@ covers endpoint discovery only. The following WebSocket handshake and
`Target.attachToTarget` each use `command_timeout_ms`. `capture.limits` controls retained event and network-content
budgets; it does not change those liveness deadlines.
## Validate a scenario before running it
`validate-browser-scenario INPUT_JSON` checks one scenario against the same
`browserScenarioSchema` refinement that `capture_browser_scenario` admits, and
stops there. `INPUT_JSON` may be inline JSON, a JSON file path, or `-` for
standard input.
```bash
rea validate-browser-scenario ./browser-scenario.json --json
```
A valid scenario reports `{"valid":true,"actions":<count>,"secrets":<count>}`
and exits 0. An invalid one exits 1 and reports the same field paths capture
would report, so a correction made here is the correction capture needs:
duplicate or reserved step IDs, duplicate secret identities and
environment-variable names, undeclared secret references, request-step
violations, and unknown fields.
Validation performs no browser discovery, launch, attachment, or navigation and
constructs no provider. It does not read secret environment variables, so a
scenario whose declared variables are unset still validates; the missing-value
failure belongs to capture. A schema-valid scenario does not prove that the
executable or target is available on the host.
## Minimal launch example
Save this as `browser-scenario.json`, replacing the executable path with an
@@ -171,6 +197,12 @@ distinction: it closes and deletes provider-owned profiles, but only disconnects
from an external CDP browser. Real-browser verification checks both outcomes and
confirms that an attached external browser remains alive.
CDP connections leave Playwright's default-context overrides disabled. The
selected page receives the declared scenario environment and temporary focus
emulation, so actions can complete while that page is in the background.
Its environment owner clears these overrides before disconnecting; other pages
keep their media settings. Connecting does not create a bootstrap page.
That cleanup requires REA to remain running. Abrupt termination, including
Windows process-tree termination or POSIX `SIGKILL`, can leave the launch
profile and Playwright artifacts behind. A profile can retain cookies, history,
@@ -204,7 +236,9 @@ language environments.
HTTP(S) URLs may include ordinary query values and fragments directly. Use
ordered structured query entries when a value needs to reference a declared
environment-backed secret. Form, storage, and cookie values are either literal
strings or declared secret references. Raw URL userinfo credentials are rejected.
strings or declared secret references; request step headers can also read a
cookie or local storage value from the page, and request steps and `goto`
destinations can send [saved values](#saved-values). Raw URL userinfo credentials are rejected.
Captured credential-header
values are not retained, and declared secret values are redacted automatically.
A secret may be declared solely to
@@ -315,6 +349,263 @@ external browser without closing it. The request itself defines the operation;
there is no additional REA grant step. The browser and host still enforce their
own access and process rules.
## Request steps
A `request` step sends one HTTP request from the page with the page's own
`fetch`, so the browser attaches the session's cookies and the request is
recorded in the step's network events like any request the application makes.
Use it to record an application's API, including writes, when driving the UI
for each call would be slower or less exact than calling the endpoint the UI
calls. It is the only action that changes application state without a user
gesture, so run scenarios with write requests only against an instance whose
data you may change. This scenario creates one item:
```json
{
"browser": {
"mode": "launch",
"executable_path": "/absolute/path/to/chromium"
},
"start_url": { "url": "http://127.0.0.1:3000" },
"actions": [
{
"step_id": "create-item",
"action": "request",
"method": "POST",
"path": "/api/items",
"query": [
{ "name": "notify", "value": { "source": "literal", "value": "false" } }
],
"headers": [
{
"name": "X-CSRFToken",
"value": { "source": "cookie", "name": "csrftoken" }
},
{
"name": "Authorization",
"value": {
"source": "local_storage",
"key": "auth",
"json_pointer": "/token"
},
"prefix": "Bearer "
}
],
"json": { "title": "Scratch item 1" },
"expected_status": [201],
"timeout_ms": 10000
}
]
}
```
- `method` is `GET`, `HEAD`, `POST`, `PUT`, `PATCH` or `DELETE`. `json` is the
body, sent with `Content-Type: application/json` unless a header sets one; a
`GET` or `HEAD` step cannot carry it.
- `path` starts with one `/` and must resolve to the start URL's origin, which
validation checks before the browser starts. When the step runs, the page must
be on that origin, checked again in the page as the request is sent; add a
`goto` step first if an earlier action left it. A query in `path` is sent as
written (a fragment is not sent), and `query` entries are appended after it
in order and may reference declared secrets.
- Request steps follow no redirects: a redirect answer fails the step, and
nothing is sent to the location it names, though a write the redirect
answered may already have been applied. `expected_status` cannot name a 3xx
status. Manual redirects can be opaque to the page and may not produce a
response event; their status and `Location` can therefore be unavailable even
when network events and header values were selected. Such captures report
incomplete event coverage.
- Header values are literals, declared secrets, a cookie the browser holds for
the request URL (`{"source": "cookie", "name"}`, HttpOnly cookies included,
whose value the page's scripts can then see while the request runs), or a
local storage entry on the start origin (`{"source": "local_storage",
"key"}`, with an optional `json_pointer` to a string or number in a stored
JSON value). `prefix` is sent before the value. A value read from the page is
redacted like a declared secret, as `[REDACTED:page:<step_id>.<header>]`,
from records made after the step reads it; a response recorded earlier, such
as the login response that returned a token, keeps the value. A value read
again keeps its first marker, and a value shorter than 8 characters is not
redacted, since redacting it would rewrite ids, ports and hosts throughout
the capture. A value is redacted as stored and as `fetch` sends it, without
surrounding spaces and tabs. A missing cookie, key or pointer fails the step.
Reading a page value raises the estimated cost of redacting every later
body, so the bodies kept within `max_network_body_allocation_bytes` (1 MiB
by default) get smaller: after a 32-character `X-CSRFToken` value, a body over
about 47 KiB is kept as `unavailable`. Raise `capture.limits` when the
application's responses are larger.
- A header can send Basic credentials, `{"source": "basic_credentials", "parts":
[username, password]}`: the base64 of `username:password`, after a `prefix`
such as `"Basic "` or with none, as some APIs take encoded credentials in
their own header. Each part is a literal, a declared secret, a saved value, a
cookie or a local storage entry, so a header a frontend builds from a login
token can be sent too; the password is never a literal other than empty, since
Evidence records literals as written, and a username with a colon fails the
step, since Basic authentication ends the username at its first colon. The
encoded value is redacted however short, as
`[REDACTED:page:<step_id>.<header>]`, and parts read from the page are
redacted as page values are.
- Validation refuses a header name given twice in a step, header names a
page's `fetch` cannot set, such as `Cookie`, `Origin`, `Host` or `Sec-*`, and
`User-Agent`, which Chrome drops; it refuses line breaks and other ASCII
control characters in literal values and prefixes, and the step fails before sending
when a secret or page value holds them.
- The step completes when the whole response has been read. With
`expected_status`, any other status fails the step and the later actions are
recorded as `cancelled`; without it, any response completes the step. A
network error fails the step. `timeout_ms` covers reading the header values
and the request: a step whose time runs out, or that is cancelled, before
its request is sent sends nothing, but a request already sent can still reach
the application after the step fails.
An absolute deadline also covers browser evaluation queued behind page work.
A temporary route checks cancellation again at network dispatch and is removed
when the request evaluation settles. A page controlled by a service worker
is refused because its fetch can bypass that dispatch gate; use the default
blocked service-worker policy for request steps.
The request runs in the page's main world, so an application that wraps
`fetch` sees it, and it goes through the same network observation as other
page requests: select network content (bodies or header values) to record it.
Captures with request steps state in `limitations` that the steps sent their
declared requests through the page's fetch. A scenario without request steps
is unchanged.
## Saved values
A step can keep a string or number from a JSON response, by name, and a later
step can send it, so a scenario can read back, edit and delete an object whose
id the application assigns:
```json
{
"browser": {
"mode": "launch",
"executable_path": "/absolute/path/to/chromium"
},
"start_url": { "url": "http://127.0.0.1:3000/items" },
"actions": [
{
"step_id": "save-item-1",
"action": "click",
"timeout_ms": 10000,
"locator": { "kind": "role", "role": "button", "name": "Save" },
"save_response": {
"method": "POST",
"path": "/api/items",
"save": [{ "name": "item_1", "json_pointer": "/id" }]
}
},
{
"step_id": "open-item-1",
"action": "goto",
"timeout_ms": 10000,
"destination": { "url": "http://127.0.0.1:3000/items/{saved:item_1}" },
"wait_until": "load"
},
{
"step_id": "copy-item-1",
"action": "request",
"timeout_ms": 10000,
"method": "POST",
"path": "/api/items/{saved:item_1}/copies",
"json": {
"source_id": "{saved:item_1}",
"title": "Copy of {saved:item_1}"
},
"expected_status": [201]
}
]
}
```
- A `request` step saves with `save: [{name, json_pointer, secret}]`, from its
own response. A `click` or `press` step saves with `save_response: {method,
path, save}`, from a newly observed matching response while the step waits.
The recorder watches before the action and excludes requests already running,
such as an earlier autosave. Method/path matching does not prove that the UI
caused the request: use a path that distinguishes the intended operation from
background polling. Multiple observed matches before binding fail as ambiguous;
requests starting after this observation window cannot be ruled out. It matches the method and a path on the
start origin as the browser spells it, so a path may name `My List` or
`My%20List` (`{*}` matches one segment, and the query is ignored), and fails
the step unless a 2xx JSON response arrives before its `timeout_ms`; one still
being read when the step times out or is cancelled saves nothing. A pointer
that names no string or number fails the step, so later steps never send a
missing value, as does a response over 4,000,000 characters: a request step
stops reading and aborts its fetch there. A UI step reads a body only when
its decoded size is bounded at 12,000,000 bytes or less: its received size,
or the declared Content-Length of one served from cache, times 1032 for each
gzip or deflate coding, the most DEFLATE expands. Other codings and unknown
or zero sizes are refused before materializing the body; decoded text over
the character limit also fails. The UI action may already have applied.
- `{saved:name}` sends a saved value in a request step's `path`, including its
query, and in a `goto` destination's `url`, percent-encoded; a destination's
structured `query` values are sent as declared. A saved value that makes a
path segment `.` or `..`, alone or with the text beside it (including `%2e`),
fails the step, since the URL would drop it. In a request step's `json` body,
a string that is only `{saved:name}` becomes the saved value with the JSON
type it was saved with, so a saved number id is sent as a number, and
`{saved:name}` inside a longer string is filled as text. A header value or
Basic credentials part sends one as `{"source": "saved", "name"}`. Other
braces, such as `{id}`, are sent as written, and so is a percent-encoded
`%7Bsaved:name%7D` anywhere but a `goto` URL, whose parsed form spells the
braces that way.
- Validation refuses a saved value that no earlier step saves. A later save of
the same name replaces the value. An integer past 2^53, which JSON parsing
would round to another number, and a number that is not finite, such as
`1e400`, negative zero, or a value that underflows to zero, such as `1e-400`, fail the step
that saves it.
- A scenario keeps at most 4,000,000 characters of saved values at once,
counting distinct retained redaction spellings, including trimmed variants,
of values saved as secret. Replacing a binding releases its live value
charge, while secret spellings remain protected for the scenario lifetime.
A save past the budget fails before changing the binding.
- A value saved with `secret: true` is redacted however short, as a declared
secret is, as `[REDACTED:page:<step_id>.<name>]`. The selected source
response is protected before retaining its body or header values, including
when only headers were requested. If that body cannot be read safely, its
body and header values remain unavailable. Before publishing the capture,
the selected source transaction's earlier URL, request headers and request
body are also protected using the admitted values. Unresolved source values
withhold that transaction's affected metadata/content with an explicit
limitation. Before publication, retained events are rechecked
with the final marked-response values, including immediate follow-up requests
and their response bodies, console/page errors, WebSocket text and event URL/name fields. Learned redaction facts are independent of source-identity readiness. Unknown source values withhold only the selected
source transaction. Other saved values, such as ids, stay visible, since
environments model them. The environment build warns about saved values
named like credentials that are not marked secret
(`credential-named-saved-values`).
- Page-derived cookie/local-storage values and constructed Basic credentials
remain redacted only from observations made after they were read/built.
They do not retroactively rewrite earlier facts. The final event projection
borrows only declared and response-marked patterns; promotion of an already
held page value to an explicit response secret preserves its first marker.
Projection state is session-local, references admitted pattern/byte owners,
and is invalidated on admission or response classification changes.
- Screenshots are withheld as `missing` from the start when the scenario plans
any response save marked `secret: true`, page-derived or Basic authentication
header values, or whenever nonempty sensitive values
are already known. Raw pixels cannot be safely text-redacted, so the provider
does not request pixels in those cases. This also protects earlier steps and
partial failures without retaining an image to scrub later. Other requested
artifacts remain available; affected screenshot/aggregate completeness is
partial and is not eligible for equality.
- Saved URLs and expanded request JSON are admitted before building their expanded strings or
serializing. Its allowance is the smaller of Node's string limit and
one sixteenth of current V8 available heap bytes, allowing for the expanded
tree, serialization, Playwright transport and browser-side copies. Size
accounting includes JSON escaping and repeated references; each distinct
saved value's size is measured once per request. An oversized request fails
before browser dispatch; the complete request envelope, including repeated
header values, is checked as well. URL encoding uses a conservative
nine-characters-per-UTF-16-unit allowance before encoding a saved value.
Reduce the payload/repetitions, or raise the parent
Node heap and restart; this is a conservative allocation estimate, not a
measurement or guarantee of browser/process-family memory.
- Evidence records a request step's empty `save` list as absent, so a scenario
that saves nothing records the same parameters and `scenario_sha256` as
before saved values existed, and REA versions without saved values read its
captures.
## Network evidence
`capture.events: ["network"]` retains request, response, completion, failure,
@@ -355,6 +646,18 @@ and unfinished-request metadata. Select content independently:
All three options default to `false`. Any selected network content also enables
network metadata observation; listing `network` in `events` is then optional.
Each observed request object receives a capture-scoped `transaction_id`.
Each `request` event carries `observed_at`, the wall-clock time REA observed the
request start. Playwright reports the browser's own start only once a request
ends, so each `request-finished` or `request-failed` event carries `started_at`,
the browser's wall-clock start, and `elapsed_ms`, its time from that start to
the end of the response, when the browser reports them. The environment views
time scenario records from these, as HAR entries are timed: the browser's start
where it reported one, else `observed_at`, which trails it by up to tens of
milliseconds while REA reads other responses. A duration is retained only from a
unique terminal event that reports it; missing or ambiguous durations stay
omitted, while an explicitly reported zero remains zero. Captures made before these fields existed lack them and
are read in record order. REA versions before them refuse captures that carry
them. Scenario comparisons ignore all three (`ignore_network_request_timing`).
Requests sharing a URL remain distinct. Redirects link to the preceding
transaction only when it was observed; a null predecessor does not establish
that no earlier hop existed. Request events require both transaction fields;
@@ -452,8 +755,8 @@ and compared as an unordered collection after caller rules apply, because
concurrent responses arrive in any order and can land in a later step; a
redirect retains the complete predecessor ancestry, including each hop's own
request, response outcome, and selected network content. Each transaction's
capture-local IDs, sequence/index fields, and content source-sequence anchor
are removed. Caller rules normalize retained strings before hashing. The
capture-local IDs, sequence/index fields, content source-sequence anchor and
request times (`observed_at`, `started_at`, `elapsed_ms`) are removed. Caller rules normalize retained strings before hashing. The
`preserve_redirect_transaction_ancestry` built-in rule fingerprints each hop
as canonical SHA-256 of `{ events, redirected_from }`: `events` is the sorted
list of its normalized event SHA-256 digests, and `redirected_from` is its
@@ -474,7 +777,10 @@ scope/flags, value fingerprints, and duplicate entries even when redaction or
literal rules make names collide. Captured storage arrays retain their original
order. The `compare_storage_entries_as_unordered_collection` built-in rule
participates in the normalization commitment; new storage comparison digests
therefore differ from earlier order-sensitive comparisons.
therefore differ from earlier order-sensitive comparisons. The
`ignore_network_request_timing` built-in rule likewise joins the commitment, so
normalization digests differ from comparisons made before it, though captures
with and without request times compare alike.
Each aligned step reports changed, unchanged, or unknown. Changed and unknown
action, screenshot, DOM, accessibility, URL, history, storage, and event
@@ -497,3 +803,58 @@ rules and preserve the same missing fact.
Network normalization also commits the removal of capture-local transaction IDs,
attribution to the requesting step, and unordered transaction comparison. These
built-in rule identities participate in the normalization digest.
## Summary delivery and retained views
`capture_browser_scenario` accepts `detail: "summary"` to retain the complete
capture in the MCP session and return its summary. The default remains
`complete`. Delivery detail does not change scenario parameters or identity.
The summary reports each step's status, error, URL and captured/missing/truncated
section names. It omits `not_requested` sections and repeated `before_url` values.
The accessibility tree comes from the first unsuccessful step, or the final
step on success; when that step captured no tree, its nearest earlier captured
tree is identified explicitly. Event and network counts cover retained records,
with capture omissions reported separately.
For the one-shot CLI, save the complete Evidence before selecting a summary:
```bash
rea capture-browser-scenario scenario.json --detail summary --evidence_output /absolute/path/scenario-evidence.json --json
```
Use the returned `normalized_result.parent_evidence_id` with
`inspect_analysis_view` source `{kind: "retained-evidence", evidence_id: "..."}`.
For a CLI bundle, use an `evidence-file` source with its absolute path and the
parent Evidence ID. Views read the recorded capture; they never replay actions.
- `{kind: "summary"}` returns the same overview.
- `{kind: "browser-scenario", collection: "steps", step_indices: [2], artifacts: ["accessibility"], offset: 0, limit: 1}` returns selected recorded steps and artifacts.
- `{kind: "browser-scenario", collection: "events", event_kinds: ["response"], transaction_ids: ["request-2"], offset: 0, limit: 20}` pages selected events with original item indices.
Omitted filters select all; empty arrays select none. Pagination counts matching
retained rows. Exhausting a page does not establish capture completeness or
current application state.
Transaction pages use `{kind: "browser-scenario", collection: "transactions", limit: 20}`.
Their stable identity is the parent Evidence ID plus `transaction_id`; each row
keeps original event indices. Metadata events are returned by default. Select
`event_kinds: ["network-content"]` for recorded bodies and header values, or
`event_kinds: []` for identity and indices alone. A `step_indices` filter selects
a whole transaction when any retained event belongs to a selected step; this
is receipt attribution, not proof that the action caused the request.
For a large retained tree, request `{kind: "browser-scenario", collection: "text",
step_index: 10, artifact: "accessibility", offset: 0, limit: 2048}`. `dom` is also
supported. Offsets and limits count UTF-16 code units. The full artifact digest
and byte size remain attached to each page. Missing, truncated and unrequested
text stay explicit; source truncation counts are preserved without treating an
exhausted page as a complete capture.
Network summary counts include every retained transaction identity, including
those whose request event was dropped. Missing request events, conflicting
metadata and observed request failures have separate counts. `transactions` is
`null` when network observation was unrequested or its legacy selection is
unknown and there are no retained transactions; an observed empty inventory is
`0`. Full capture omissions remain available and each page carries source
completeness, dropped-event counts and omitted-transaction accounting.
+62
View File
@@ -0,0 +1,62 @@
# Bun standalone executables
`inspect-artifact` inventories embedded Bun module graphs in Mach-O `__BUN,__bun`,
PE `.bun`, ELF `.bun`, and appended trailers. `extract-artifact` writes their stored
module contents through the existing safe output tree. Neither operation executes
the executable or decompiles bytecode.
```sh
rea inspect-artifact ./hello --json
rea extract-artifact ./hello --json
```
In MCP, open the executable with `open_binary`, then call `inspect_artifact`.
Use `detail: "summary"` for large graphs and `inspect_analysis_view` to page retained
Evidence. The root metadata's `entry_point_logical_path` is the reader-owned
location of its entry module. Use it as an inventory `logical_paths` filter,
then read selected node fields with the returned content identity. This also
works for names mapped to indexed extraction paths. The complete Evidence stays
retained; no source-name normalization is needed in the caller.
The root reports the carrier, layout, module count, entry point, raw flags and
compile exec-argv. Each module retains its source name, loader and encoding values,
entry-point status, sizes and SHA-256. Sourcemaps, bytecode and module-info regions
are separate children. Source names such as `/$bunfs/root/main.js` remain in
metadata; extraction uses a relative path. Unsafe or non-UTF-8 names receive an
indexed path and retain their source spelling or bytes. Virtual names deeper
than 256 logical path parts also use an indexed path with an explicit limitation,
preventing unbounded path-tree work while retaining the source fact. Duplicate names remain
separate occurrences; ordinary extraction collision rules apply.
The recognized record shapes are the 36-byte Bun 1.3.3 layout and the 52-byte
Bun 1.3.9 layout. These labels identify structural layouts, not a detected compiler
version. Bun does not store an explicit revision identifier: unknown table sizes
and ambiguous known layouts are unsupported. A future layout that is byte-for-byte
compatible cannot be distinguished. Invalid pointers and truncated claimed sections
are malformed. An absent trailer without a named section does not establish a Bun
graph. Unknown enum values remain raw, with a null name.
Sourcemaps are stored Bun regions, not necessarily standalone JSON source maps.
Extracted contents retain their original encoding. Use JavaScript analysis only on
compatible source files; bytecode is retained as evidence, not decoded.
Module parsing reads exact metadata ranges rather than loading stored asset
bodies or whole pages around small pointers. A cache retains at most 256 KiB
and 1,024 ranges for one reader preparation. Cumulative uncached metadata reads
are limited to 256 MiB, checked before allocation or I/O. The module table,
decoded strings and combined logical path tree have separate admission checks;
at most 250,000 distinct path prefixes are admitted before inventory construction.
Stored bodies are range-streamed for hashing and extraction. Header/suffix observations are reused
only for the same admitted file state, with selected-path checks before publication.
See [limits and recovery](design/limits-and-recovery.md) for admission ceilings.
Synthetic tests cover the carriers and malformed declarations. Run
`node scripts/verify-bun-standalone.mjs` after `npm run build:cached` for local Bun
verification, and add `--docker` to compile a Linux fixture with `oven/bun:1.4.0`.
Bun and Docker are fixture producers only and are not installed by REA.
Layout references: [Bun 1.3.3 serializer](https://github.com/oven-sh/bun/blob/bun-v1.3.3/src/StandaloneModuleGraph.zig)
and [Bun 1.3.9 serializer](https://github.com/oven-sh/bun/blob/bun-v1.3.9/src/StandaloneModuleGraph.zig).
The current 52-byte record order can also be checked in the
[Bun 1.4.4 Rust serializer](https://github.com/oven-sh/bun/blob/bun-v1.4.4/src/standalone_graph/StandaloneModuleGraph.rs).
+107
View File
@@ -0,0 +1,107 @@
# Capability composition and ownership
REA keeps domain contracts and shared workflows separate from provider adapters
and CLI/MCP translation. A capability can have several provider identities; a
provider can contribute operations to several capabilities. Those associations
are source facts, while host availability is an observation.
## Source declarations and runtime bindings
`src/composition/capabilityDeclarations.ts` contains static provider identities,
named contract-group references and precise operation subsets. Complete groups
refer to `TOOL_CONTRACT_GROUPS` in the existing contract owner; family definitions
and provider catalogs read the same arrays, so a new group operation is included
without updating a copied provider list. Metadata imports only the group ID type
and pure metadata leaves; it must not load factories, schemas, engines or
generated catalogs. Catalog generation resolves groups and subsets against the
canonical contracts and rejects unknown groups/operations and conflicting
identities. New provider provenance must come from the implementation
that actually produces the Evidence, including format-specific capture adapters
and derived workflows.
`src/composition/capabilityServices.ts` binds typed service factories to those
capability keys. Each runtime entry owns construction, cleanup and any available
host/configuration facts or live probes. A service entry requires a close
capability; entries adapting managers retain their existing `closeAll` operation.
The host maps entries without erasing their individual service/port types. There
is no generic JSON operation dispatcher.
These two declarations have different loading needs. Static facts can be read
without loading a schema or provider graph. The full runtime composition is
materialized only when the MCP runtime needs it. One-shot CLI adapters import the
selected factory rather than constructing a complete capability host. CLI and MCP
reuse the same provider/service factories and application workflows, while
keeping their respective argument translation and lifetime.
## Resource lifetime
`CapabilityScope` registers a module's actual close capability when its instance
is composed. There is no independent server provider-close list to update.
Concurrent shutdown calls join the same cleanup attempt. A successful peer is
removed from pending cleanup; failed peers remain owned and are retried on the
next close. Synchronous throws and rejections without a reason remain failures,
and cannot prevent other peers from attempting cleanup. Shutdown seals further
ownership registration.
The host distinguishes these lifetimes:
- Host-owned service instances are released by that host.
- Caller-owned optional observations remain borrowed by SDK server instances;
their process-level owner closes them, including discarded discovery probes.
- CLI invocations use their existing finalizers to join cleanup and preserve the
original observation, cancellation and cleanup errors.
- Binary engines, bundle readers and investigation records retain their existing
owners. The `BinarySession` transition/drain barrier still coordinates target
changes, reset and disposal. A module refactor must not create independent
barriers that allow late work to repopulate reset records.
An application workflow may borrow a shared resource scope, such as byte entropy
using the host's artifact scope. Its wrapper does not acquire a second ownership
claim over the borrowed scope.
## Availability and registration
Module bindings provide host support, selected configuration and precisely typed
live availability probes through `CapabilityAvailabilityFacts`. Constructors do
not run these probes or launch engines. Session discovery invokes the existing
provider-owned probes only for advertised families; live failures remain fresh
observations. Caller policy, selected environments and injected providers retain
their existing precedence.
MCP registration remains in the MCP adapter. It consumes composed services,
record ports, admission and logging; it must not add fallback constructors that
can escape the host owner. Named contracts bind handlers and schemas. The complete
tool-family registrar map is checked by TypeScript, and catalog order never
selects a handler or schema.
## Extending a capability
1. Add or reuse the domain contract and application workflow, with explicit
Evidence provenance, coverage, unknowns and caller choices.
2. Put concrete construction in `src/composition/` and keep its default factory
lazy. Reference adapter-owned pure identity metadata from both the provider
and its static capability declaration.
3. Add the runtime binding if a host-owned service is needed. Bind cleanup and
availability/probes there, preserving borrowed versus owned resources.
4. Add the CLI/MCP argument adapters and their named registrations. A one-shot
command uses the shared selected factory and existing finalization helpers.
5. Run source-boundary, discovery, SDK-schema and relevant public workflow checks.
Exercise partial cleanup, retry, cancellation and caller-owned peers at the
applicable production seam; use actual applications/providers for routing and
complete Evidence verification. Recording ports do not prove real providers.
The constructor boundary guard covers the centralized Frida, Cutter, GDB and
Rizin constructors without rejecting legitimate type-only injected interfaces.
Other settled pure/provider boundaries continue checking type imports too. The
shared parser records module-edge erasure once. Whole-declaration type imports
are erased; inline type bindings retain their runtime module edge under
`verbatimModuleSyntax`. Static dynamic imports remain runtime edges too.
Tests belong with their semantic owner and must be
found exactly once; provider roots need the corresponding adapter discovery lane.
This is an internal composition contract. External plugin distribution, automatic
provider installation and a universal provider interface are separate concerns.
The [initial verification report](verification/capability-composition-2026-11-01.md)
records the executed public workflows, cleanup boundaries, resource measurements
and remaining provider qualification gaps for this composition increment.
+407 -24
View File
@@ -51,6 +51,15 @@ module page. See
[JavaScript application workflows](javascript-application-workflows.md) for
views, feature traces and comparisons that take the same saved Evidence.
Add `--timings` to a command to write one JSON timing record to stderr. For
example, `rea inspect-elf-notes program --json --timings > result.json` keeps
stdout as a parseable result and writes
`{"rea_timings":{"version":1,"command_ms":42}}` to stderr. `command_ms`
measures CLI command dispatch and result delivery; it is not provider-only or
analysis-only time. The flag is off by default, and the record omits stages
that REA did not measure. Root version reporting does not dispatch a command
and has no command timing record.
## JSON Lines record output
Use `--format jsonl` to consume row-oriented results one record per line:
@@ -73,9 +82,14 @@ arbitrary nested graph array.
A top-level array emits its elements in source order. An object must have one
clear `rows`, `items`, `records`, or `occurrences` array, directly or under
`page`. Evidence uses the same boundaries inside `normalized_result`.
An arbitrary report or a scalar selection is refused with an actionable error;
use `--format json` for a complete document. If several collections exist,
select one with `--filter-output`, for example `normalized_result.rows`.
Any other selection is refused before a record is written, including an
arbitrary report, a scalar, a nested array elsewhere, and a columnar object such
as the `inspect-sqlite-database --table` `normalized_result.rows`
(`{columns, values}`). The refusal is the caller error `invalid_request`
(`invalid_input`) and names the selection and its observed shape; the analysis
itself succeeded, so use `--format json` for the complete document. If several
collections exist, select one with `--filter-output`, for example
`normalized_result.rows` for an analysis view.
Object collections finish with a control record:
@@ -151,6 +165,15 @@ into the current shell:
eval "$(rea completions bash)"
```
For zsh, initialize completion before loading the hook. If your shell already
runs `compinit`, place the hook after that existing initialization:
```zsh
autoload -Uz compinit
compinit
eval "$(rea completions zsh)"
```
The Bash hook supports macOS system Bash 3.2. It uses optional completion
features when the shell provides them and falls back to portable registration.
Completion includes standard global options and the active command's options;
@@ -236,6 +259,96 @@ Formats 2, 3 and 4 retain header, directory and integrity facts. Compact deliver
keeps complete Evidence in the requested bundle. See [Godot PCK](godot-pck.md)
for embedded packs, batch queries and unknown encrypted coverage.
Inspect a UnityFS AssetBundle without running Unity:
```sh
rea inspect-artifact content.bundle --detail summary --evidence_output evidence.json --json
rea extract-artifact content.bundle --json
```
Format versions 6 through 8 retain header, block and directory facts with
none/LZ4/LZ4HC block decompression. See [UnityFS](unityfs.md) for encryption
and LZMA coverage boundaries.
Inspect an ESP32-family app or bootloader image without esptool:
```sh
rea inspect-artifact firmware.bin --json
rea extract-artifact firmware.bin --json
```
The inventory lists each segment with its ESP-IDF load classification, the
checksum and SHA-256 results, the descriptor and any Secure Boot v2 signature
blocks. See [ESP images](esp-image.md) for recognition, coverage and refusals.
Inspect an ESP flash dump, or one read from a flash address other than 0:
```sh
rea inspect-artifact flash.bin --json
rea inspect-artifact dump.bin --target-format esp-flash-image --flash-offset 0x1000 --json
rea extract-artifact flash.bin --json
```
The inventory lists the bootloader, the partition table and each partition,
with ESP image facts for app partitions and the OTA records. See
[ESP flash images](esp-flash-image.md) for table location, inputs and limits.
Inspect a Node.js SEA carrier without running it:
```sh
rea inspect-artifact ./application --json
rea extract-artifact ./application --json
```
See [Node.js SEA](node-sea.md) for supported layouts, script and asset paths,
fuse observations, and the real-fixture verification lane.
Inventory and extract Intel HEX or S-record regions:
```sh
rea inspect-artifact ./firmware.hex --json
rea extract-artifact ./firmware.hex --json
```
See [Intel HEX and S-record images](hex-records.md) for recognition, region
names, metadata and limits.
Inspect and extract stored U-Boot legacy uImage payloads and components:
```sh
rea inspect-artifact ./firmware.bin --detail summary --evidence_output firmware.json --json
rea extract-artifact ./firmware.bin --json
```
See [U-Boot uImage](uimage.md) for verified CRCs, physical component ranges,
unknown names, resource limits and retained views. Compressed bytes stay stored.
`inspect-artifact` and `extract-artifact` also read ZIP members compressed with
Zstandard (standard method 93). They retain the shared ZIP CRC, declared-size,
overlap and archive expansion checks. The native streaming decoder keeps the
existing 128 MiB Zstandard window cap; frames requiring a larger window are
refused. Deprecated method 20 and other unregistered ZIP codecs remain unsupported.
Inventory and extract UF2 or DfuSe regions:
```sh
rea inspect-artifact ./firmware.uf2 --json
rea extract-artifact ./firmware.dfu --json
```
See [UF2 and DfuSe images](uf2-dfuse.md) for recognition, families, numbering,
the DfuSe suffix CRC and limits.
Inventory and extract U-Boot FIT images:
```sh
rea inspect-artifact ./boot.itb --json
rea extract-artifact ./boot.itb --json
```
See [U-Boot FIT images](fit-images.md) for recognition, data sources, hashes
and limits.
Inspect local model declarations without loading weights:
```bash
@@ -321,6 +434,22 @@ The workflow reports first-line metadata, process/exception/termination facts,
threads, usedImages, lossless integer registers, and documented crash classes
when `bug_type` is `"309"`. Other DiagnosticReports types return no crash
result. See [Apple IPS crash reports](apple-ips.md).
Inspect Linux kernel module metadata without executing the module or requiring
`modinfo`:
```bash
rea inspect-kernel-module /absolute/path/to/module.ko --json
```
The workflow reports ordered `.modinfo` keys (duplicate `alias` and `parm`
entries preserved), `__versions` symbol CRCs, presence of
`.gnu.linkonce.this_module` and `__ksymtab` sections, observed `.init.text`
and `.exit.text` sizes, and an appended signature trailer. `struct module` is
not decoded and the signature is not validated. Other ELF types are not
applicable. gzip, xz, and zstd containers are unsupported and name
`external_decompression` as the next probe: decompress with an external
utility, then inspect the decompressed file. See
[Linux kernel modules](kernel-modules.md).
Inspect a CPython 3.7–3.13 `.pyc` header and nested marshal code objects
without executing bytecode or installing Python:
@@ -375,7 +504,7 @@ Run `rea --help` or a command's `--help` for arguments and output options.
The [generated catalog](mcp-contracts.md#generated-catalog) lists all CLI commands and MCP
tools. Provider-specific target support is described in the
[native guide](native-investigation.md), [DOS guide](ghidra-dos.md),
[raw binary guide](raw-binary.md), and
[raw binary guide](raw-binary.md), [GameCube DOL guide](ghidra-dol.md), and
[Windows Ghidra guide](windows-ghidra-p0.md).
## Choose a provider
@@ -412,7 +541,13 @@ The selected provider remains bound to the session until an explicit switch
or close. Provider failures are returned with their original reason. For an
`ambiguous` selection error, choose from `details.candidate_ids`; for
`provider_unavailable`, follow `message`, `remediation.action`, and the original
reasons in `details.rejections`. For Hopper, Ghidra, IDA, JADX, and Kuna, run
reasons in `details.rejections`. Each rejection names its `origin`: `availability`
entries describe local readiness failures, including missing configuration or an
invalid existing registration; they do not establish target support. `target`
entries record a refused kind, format, architecture, or role. An availability-only
message describes the recorded rejections without claiming that every provider
is unconfigured or that target support is known.
For Hopper, Ghidra, IDA, JADX, and Kuna, run
`rea doctor --provider ID --json` to diagnose the selected engine. Other adapters,
such as `pwntools-elf`, report their configuration requirements directly and are
not accepted by `doctor --provider`. See
@@ -426,6 +561,25 @@ be verified. See [MCP contracts](mcp-contracts.md) for session lifecycle and
[Ghidra first-query deadlines](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
for import, client timeouts and recovery.
A one-shot command releases its provider session and REA runtime before it
prints a failure, so it omits the same-session reuse and
`update_analysis_settings` guidance an MCP connection receives. Its
`details.limit` reports `scope: "rea-invocation"` and
`applies_at: "next_invocation"`; set the named `environment` (or the per-call
`parameter`) for a new invocation and rerun. For example, a Ghidra decompile
deadline names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS`.
`rea debug-with-gdb` and `rea debug-with-rizin` hold their debugger only for the
command's own process. If cleanup cannot confirm the debugger stopped, they print
`cleanup_incomplete` and exit nonzero without stopping it. `remediation.action`
names the process, as does `retained_debugger_pids` in `details.diagnostics`, or
in `details.diagnostics.cleanup_error.details.diagnostics` when the command also
failed or was interrupted. Exiting closes the debugger's command and output pipes.
REA does not establish what the debugger or its target does next, so check that
process. An MCP server that shuts down likewise exits with status 1 instead of
waiting on a debugger it could not stop. It names the process on stderr and as
`retained_debugger_pids` in its error log.
If one-shot analysis fails and closing its session also fails, the original
error details remain inline and `details.cleanup_error` describes the later
close failure. Each diagnostic retains its own reported resources; the later
@@ -439,7 +593,14 @@ an exact result when the target bytes, operation, parameters, provider and
settings match. Mutations and cursor-dependent calls are excluded from the
cache. Snapshot files are local and use owner-only permissions.
Importing an Evidence bundle refreshes retained subject display names and paths.
One-shot binary CLI Evidence reports the exact path argument in `caller_input_path`,
including relative spelling and symlink aliases. Its subject identifies the
inspected artifact; `caller_input_path` does not change the Evidence ID or
snapshot query. A replay reports the current invocation's path while leaving
the saved Evidence unchanged. Provider inspections continue to include the
established `parameters.input_path` field.
Importing an Evidence bundle refreshes retained request and subject display metadata.
It preserves cached analysis results, including observations for other targets.
Snapshot bindings that use a replaced record retain its updated display path.
@@ -447,6 +608,14 @@ Dispatching a provider metadata write invalidates the active session's snapshot
even if its reply fails: the provider may already have applied the write.
Close and reopen the target before importing or saving another snapshot.
Saved snapshots include only cache entries whose subject identifies the selected
target. Unbound observations and observations of child artifacts remain available
in the open session and its Evidence bundle; they are excluded from the target's
saved replay entries without changing their subjects.
The snapshot also retains available Evidence linked by selected records, including
transitive sources with an unbound or different subject. Those sources are not
replay entries for the selected target. Unrelated records remain excluded.
Snapshots retain eligible target-scoped question histories in full, including
mutation Evidence recorded while another target was active. Observations and
related questions must still belong to the saved target. If those dependencies
@@ -468,6 +637,30 @@ cached result. `close_binary` accepts `snapshot_path` and optional
`overwrite: true` to save before releasing provider resources. A failed save
leaves the session open so the caller can resolve the output failure.
When a validated snapshot does not match the selected binding, the error's
`details.binding_mismatch` reports confirmed target identities, provider
identities, or profile digests that differ. `snapshot` is the saved value and
`selected` is the requested value. Multiple differences can be reported. A
`profile_comparison: "not_compared"` means the profile was not available at
that boundary; it does not establish a match. A profileless snapshot can report
`workflow_scope: "incompatible"` even when its target and profile match.
Choose a matching target, provider, and profile, or analyze without the snapshot
and save a new one for the intended binding.
For a profile mismatch, the saved snapshot contains
`binding.analysis_profile.parameters`. If `binary_session` reports an active
profile whose digest matches `binding_mismatch.profile_digest.selected`, its
`analysis_provider_binding.analysis_profile.parameters` can be compared with
those saved parameters. A rejected switch leaves the previous session active,
so its profile may be different from the attempted selection; check the digest
before using it for this comparison. A configuration difference does not by
itself establish why analysis results differ.
If no target is open and you intend to inspect the selected binding, open that
target without the snapshot, then check its profile digest in `binary_session`
before comparing parameters. This may start the selected provider; the saved
snapshot remains a separate file.
If a selected snapshot's file descriptor cannot close, `open_binary` reports
`cleanup_incomplete`. `binary_session` reports `cleanup_pending` even when no
target opened. Retry `close_binary` to release the retained descriptor before
@@ -502,6 +695,14 @@ rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/righ
rea compare left.json right.json --record_pairs '[{"left_evidence_id":"ev_<left-sha256>","right_evidence_id":"ev_<right-sha256>"}]' --json
```
Migration: replace `rea compare-bundles` with `rea compare`; the former command
has been removed. The MCP tool remains `compare_bundles`.
`rea compare` compares records by Evidence identity and explicit pairs. It does
not read artifact contents: the inventories of two builds are unresolved
records, or one opaque change when you pair them. Use `compare-artifacts`,
described at the end of this section, to compare their contents.
`--record_pairs` supplies an explicit JSON array of observation pairs, matching
MCP `compare_bundles.record_pairs`. Pair IDs must exist on their named sides and
be one-to-one. Without pairs, one-sided membership remains unknown; REA does
@@ -518,12 +719,63 @@ with `details.reason: path-limit` and `boundary: filesystem-path`.
For application traces and version comparisons, see
[JavaScript application workflows](javascript-application-workflows.md).
MCP [retained Evidence references](mcp-contracts.md#retained-application-evidence-inputs)
belong to one connection. Separate CLI calls can consume full saved records.
`inspect-analysis-view REQUEST.json --evidence-bundle BUNDLE.json` resolves
the request's `retained-evidence` source ID from that authenticated bundle,
including bundles exported from an MCP session. The same view JSON works for
every supported parent kind; no target is reopened and no record is selected
by file order. Omit `--evidence-bundle` when the request supplies inline Evidence.
belong to one connection. Separate CLI calls can consume full saved records or
resolve exact retained IDs from an explicitly selected local bundle:
```bash
rea trace-application-feature request.json --evidence-bundle /absolute/path/evidence.json --json
```
Here `request.json` uses the same logical Evidence input as MCP, for example
`"application": {"kind":"retained-evidence","evidence_id":"ev_<64 lowercase hex characters>"}`.
Inline records and references may be mixed in the request's declared source
fields, including arrays and nested inputs. The bundle is read and authenticated
once for that command; no target is reopened, no producer is rerun, and no
record is selected by file order. Source identity, provenance, coverage and
partial or unknown facts agree with the complete inline form. When starting
from summary or inspection Evidence, select its
`normalized_result.parent_evidence_id` for a workflow requiring complete source
facts. Omit `--evidence-bundle` when all sources are inline.
The JSON workflow commands supporting this option are:
- `trace-application-feature`, `trace-javascript-semantics`,
`compare-application-versions`, `compare-javascript-export-shapes`,
`compare-source-to-bundle`, and `reconcile-javascript-runtime`.
- `import-managed-reconstruction`, `verify-managed-native-boundaries`, and
`project-managed-application-graph`.
- `project-android-application-graph`, `project-apple-application-graph`, and
`project-harmony-application-graph`.
- `compare-wasm-interfaces` and `inspect-analysis-view`.
For `inspect-analysis-view`, the request's source reference selects a parent
from the bundle for every supported view kind. Bundles exported from MCP work
for these commands. Its inline source form cannot also select a bundle.
A missing ID is refused with `details.reason: "missing"`;
supplying a bundle does not make incompatible Evidence valid for a workflow.
`compare-managed-members` keeps its two assembly paths, and
`compare-process-captures` keeps its two capture Evidence JSON paths. The bundle
option above selects an input; producer options that write a new bundle, such
as `inspect-managed-members --evidence-bundle`, retain their existing meaning.
File selectors remain available only on contracts that explicitly support them.
Compare two artifact inventories with `compare-artifacts`. It takes the same
`left` and `right` input as MCP `compare_artifacts`: complete Evidence, or an
`evidence-file` source with an optional `evidence_id` that selects one record
from a bundle. Relative paths resolve from the working directory:
```bash
rea inspect-artifact old.zip --json > old.json
rea inspect-artifact new.zip --json > new.json
rea compare-artifacts '{"left":{"kind":"evidence-file","path":"old.json"},"right":{"kind":"evidence-file","path":"new.json"}}' --json
```
A one-shot `compare-artifacts` command keeps no session Evidence, so it refuses a
`retained-evidence` source with `details.reason: "missing"`. Select the record
from a saved bundle instead.
For an artifact summary selected from a bundle, include its complete parent
inventory in the same bundle. The command resolves that parent from the
authenticated file records; a summary-only file cannot supply missing facts.
## Import historical source
@@ -700,8 +952,24 @@ rea mcp --http --port 8765
```
`--host` and `--port` default to `MCP_HOST` and `MCP_PORT` from the environment,
then to `127.0.0.1` and an ephemeral port. The bound URL is written to stderr as
one JSON log line (`"msg":"MCP transport listening"`) whatever `REA_LOG_LEVEL`
then to `127.0.0.1` and an ephemeral port. A bind outside loopback requires
`MCP_ALLOWED_HOSTS` or `--allowed-hosts`, a comma-separated list of client host
names or IP addresses without URLs or ports.
Repeat `--allowed-hosts` to append names, or put multiple names in one
comma-separated value. All occurrences are validated together and deduplicated
after hostname normalization; the explicit list replaces `MCP_ALLOWED_HOSTS`.
For example:
```sh
rea mcp --http --host 0.0.0.0 --allowed-hosts 127.0.0.1,rea.lan
```
Requests with a different `Host` or browser `Origin` receive `403`; a missing
`Origin` is accepted. The announced client URL uses the bound hostname when
admitted, otherwise the first declared hostname, with the actual bound port.
It is written to stderr as one JSON log line (`"msg":"MCP transport listening"`) whatever `REA_LOG_LEVEL`
is set to; nothing is written to stdout. `rea mcp --http --help` lists the accepted options. The listener serves
one client at a time: a session ends with the client's `DELETE`, and a client
that connects while another session is open takes it over, so a client that
@@ -739,13 +1007,47 @@ rea environment-serve manifest.json --evidence capture.json --port 8765
```
`--host` and `--port` resolve like `rea mcp --http`: flags, then `MCP_HOST` and
`MCP_PORT`, then `127.0.0.1` and an ephemeral port. `ENVIRONMENT_NAME` renames
`MCP_PORT`, then `127.0.0.1` and an ephemeral port. A non-loopback bind requires
`MCP_ALLOWED_HOSTS` with the same hostname rules and Host/Origin gates as above.
`environment-serve` uses this environment setting; `--allowed-hosts` belongs to
`rea mcp --http`. For example:
```sh
MCP_ALLOWED_HOSTS=127.0.0.1,rea.lan rea environment-serve manifest.json --evidence capture.json --host 0.0.0.0
```
`ENVIRONMENT_NAME` renames
the served environment; set but blank, it is refused as `configuration_invalid`
like a blank `MCP_HOST`. The bound URL is written to stderr as one JSON log line
like a blank `MCP_HOST`. The admitted client URL is written to stderr as one JSON log line
(`"msg":"MCP transport listening"`). See
[agent environments](agent-environments.md) for what the state and replay
return and do not establish.
## Build an environment bundle
`rea environment-build <capture> [dir] [--name <name>] [--template-rule <rule> ...]
[--format-rules-only] [--package-tarball <path>] [--capture-timeout-ms <ms>] [--dry-run]` builds the bundle
`environment-export` writes from one recorded capture: a `.har` file, which it
inspects as `inspect-web-network-capture` does, or capture Evidence JSON from
`inspect-web-network-capture` (HAR) or `capture-browser-scenario`. It projects
the environment manifest from the in-memory Evidence with the opt-in rules
`after-uid-literal`, `opaque-token`, `sibling-variance` and `prefix-variance`
unless `--template-rule` or `--format-rules-only` selects others, prepares the
state, and writes the bundle; every file is byte-identical to the three-step
path's when that path lists the same rules in the views' order and exports
under the build's name. A loopback or IP-literal capture is named after its file unless `--name`
is given. It prints one JSON report: the capture and manifest identities, the
state, a `coverage` summary of which tools answer from the state, which replay
and why each write is withheld with a hint, the files, warnings and the next
command. `--dry-run` writes nothing and may omit the directory.
```sh
rea environment-build grafana.har ./grafana --json
```
See [agent environments](agent-environments.md#build-a-bundle-in-one-step)
for the report, the hints and what the bundle ships.
## Export an environment bundle
`rea environment-export <manifest.json> --evidence <capture.json> <dir>
@@ -863,17 +1165,55 @@ Malformed format values and token windows fail with `INVALID_OUTPUT_VALUE` in th
selected structured format. Token limits and offsets must be nonnegative safe
integers; zero selects an empty window. These checks run before command execution. `--json` is indented on a terminal and compact when piped or
redirected, which keeps large results smaller for agents and scripts; pipe
through `jq .` for indented files. Output selection and formatting do not change
operation status.
through `jq .` for indented files. A formatting refusal exits nonzero even when
the underlying operation succeeded.
`--format md` escapes backslashes and pipes in table headers and cells, and
renders cell line breaks as `<br>`. These are presentation changes; retained
`--format md` escapes backslashes, pipes, and inline Markdown syntax in table
headers and cells so source text is displayed literally, and renders cell line
breaks as `<br>`. Underscores between letters or digits, as in `source_index`,
cannot form emphasis and stay unescaped. These are presentation changes; retained
Evidence and JSON output keep the original values. Nested objects and arrays
use JSON inside their owning Markdown cells, retaining the field names and
values instead of replacing them with an object label. Table widths are measured
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
Markdown refuses selected NUL characters and unpaired UTF-16 surrogates because
renderers or UTF-8 output would substitute them. TOON refuses selected unpaired
surrogates for the same UTF-8 reason. Use JSON or YAML to retain those values.
An authored replacement character and valid supplementary characters are not
treated as malformed.
`--format csv` and `--format tsv` export one selected row collection. Select an
exact array path with `--filter-output` and provide comma-separated direct
scalar columns with `--columns`; column order is the output column order:
```bash
rea inspect-analysis-view request.json --format csv \
--filter-output normalized_result.rows --columns name,address
```
The first line is a REA metadata record,
`# rea-delimited-v1 <JSON>`. It carries the selected path, view request, limitations and unknowns,
coverage, source coverage and completeness, command, and duration. A metadata
value of `null` means that the selected row collection did not provide that
fact; it does not mean complete coverage. The following line is the header.
CSV records use CRLF line endings; TSV records use LF. Cells containing the
delimiter, quotes, or line breaks are quoted and embedded quotes are doubled.
Values are emitted as UTF-8 without Unicode normalization. Missing and `null`
cells are empty; booleans and finite numbers use their ordinary text forms.
String cells and headers that begin with spreadsheet formula syntax are refused
with a structured error; unpaired surrogates in selected cells or headers are
also refused. JSON retains those values unchanged.
Delimited output does not flatten nested Evidence or nested field values.
It returns a structured error and a nonzero status if the filter does not
select exactly one row array, a row is not an object, or a selected cell is
nested or formula-leading. JSON remains the complete canonical output; use it
to retain fields outside the selected columns or preserve exact formula-leading
text. Token counting and token windows are not
available for CSV/TSV because slicing would corrupt records or metadata.
`--filter-output` selects fields from successful command data. Classified failures
retain their complete typed diagnostic projection even when the selector matches
a field such as `code` or `message`, or names a missing field. `--full-output`
@@ -892,10 +1232,10 @@ Cutter when available. This is separate from `--format json`, which selects the
CLI result representation; the global `--json` flag selects that same CLI
representation.
Cutter still accepts deprecated `--json=true` and `--json=false` compatibility
values. When both forms are present, `--json-output` takes precedence in either
argument order. Bare `--json` retains its global meaning of selecting JSON CLI
output formatting.
Cutter no longer accepts command-local `--json=true` or `--json=false`.
Replace these with `--json-output=true` or `--json-output=false`, respectively.
Combine `--json-output` with bare `--json` when both Cutter command output and
the CLI result should use JSON; either argument order is accepted.
| Status | Meaning |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -921,6 +1261,10 @@ server resources. It preserves a primary listener diagnostic, or a
tool count and Evidence identifiers. The successful `stopped` document is
returned only after cleanup is confirmed. The same in-process server owner
retains failed stages for retry; the CLI exposes no separate retry command.
`rea environment-build` is one-shot too: it prints its report and exits `0`,
or prints the refusing stage's structured error and exits `1`, writing nothing
when the refusal comes before the bundle is written. SIGINT cancels it with a
`cancelled` error; a cancellation before the bundle stage writes nothing.
`rea environment-export` is one-shot: it prints the written files and exits
`0`, or prints a structured `invalid_request` error and exits `1` for the same
pair refusals, a non-empty or file-occupied directory, a missing package
@@ -964,6 +1308,18 @@ new file. A publication that has committed can win a later cancellation; the
after commit, the `cleanup_incomplete` error reports the same file as
`details.published_path` and `details.bytes`.
When a failure retains a completed observation and no bundle committed,
`details.source_closure` states what that observation cites. `publication` is
`not_published`. `records` holds authentic source Evidence this invocation
captured, within 64 KiB. `omitted` lists up to 64 other identities and
`omitted_count` the total: `not_captured` means the invocation holds no such
record, so nothing is shown for it or its ancestors; `not_inlined` means it was
captured but is not in this response. `complete` is true only when every cited
record is in `records` or embedded in the observation. If examining the closure
itself fails, `collection_failure` names the error type and the closure state is
unknown; the original failure is still returned. Nothing is retained after exit,
and running the command again is a new observation.
When the reader of piped output closes early, as `rea ... | head` does, the
CLI stops writing, prints no error and exits `141` (`128 + 13`, the status of a
SIGPIPE-terminated writer). The operation itself may have completed; only its
@@ -997,3 +1353,30 @@ Counting retains the existing bounded formatted-string allocation after capacity
admission; omit the flag to stream large JSON documents without materializing
the complete formatted text. Exhausted token windows display an empty range at
the total token count and have no next offset.
### `inspect-devicetree`
Inspect an explicit local FDT v16/v17 blob without execution or an external tool.
Returns header, reservations, node/property tree, derived standard values and
selected phandle references. See [devicetree inspection](devicetree.md) for
limits and retained-view pagination.
```bash
rea inspect-devicetree /artifacts/board.dtb --json
rea inspect-devicetree /artifacts/board.dtb --detail summary --evidence_output /artifacts/board-evidence.json --json
```
### PE TLS and load configuration
```sh
rea inspect-pe-load-config /absolute/path/to/application.exe --json
```
Reports TLS callbacks and size-covered load-config fields without execution.
Use `--max-callbacks` and `--max-file-bytes` to set read budgets. See
[PE TLS and load configuration](pe-load-config.md).
Bun standalone executables use the existing `inspect-artifact` and
`extract-artifact` commands to inventory and materialize embedded source,
sourcemaps and bytecode without running the executable. See
[Bun standalone executables](bun-standalone.md) for layout coverage and limits.
+189 -26
View File
@@ -6,6 +6,60 @@ for the reviewed setup transaction and [client IDs](installation.md#supported-ag
The versioned verification notes below describe their stated runs; live service
acceptance is verified only where explicitly recorded.
## Launchers and PATH
A client launched from a desktop app, IDE or service can inherit a different
`PATH` from your terminal. An absolute path to `rea-agents` does not resolve its
interpreter: the npm launcher's `#!/usr/bin/env node` still searches `PATH` for
Node. Running that command successfully in a terminal does not establish that
the client can launch it.
For JavaScript installations, setup registers the selected runtime
executable and REA script by absolute path. On POSIX, it prefers executable
`node` or `bun` and REA aliases on setup's `PATH` only when they resolve to the
same selected files. This preserves manager-owned aliases across version updates;
otherwise setup uses the selected paths directly. This also applies when setup
runs through a package runner: retain the selected cache entry and runtime while
the registration uses them. Clearing either makes startup fail without resolving
another package from the client's project. Restore the same installation before
repairing it; an unidentified registration remains untouched. See
[installation and recovery](installation.md#package-runners-and-global-installation).
Setup does not copy your ambient environment into client
settings. To refresh an older registration, preview
`rea setup --client <client-id> --skill=false --dry-run`, then run the same command
without `--dry-run` and approve the displayed changes. Setup preserves unrelated
settings, backs up changed configuration and leaves an aligned registration
unchanged. Restart the client after applying the update.
If a runtime manager removes the registered executable and no surviving alias
was available, doctor reports its identity as unknown and setup retains the
registration. REA does not infer that an arbitrary missing executable was Node
from its script argument. Back up the client's configuration, verify the absolute
path and supported version of the runtime you intend to use, then update only
the runtime command, preserving the REA script arguments and other settings.
Rerun setup to review the registration and restart the client.
`rea mcp doctor --json` tests the production MCP adapter using the runtime
executable and environment of the terminal running doctor. It does not launch
the client's configured command or reproduce the client's environment. If doctor
passes but the client disconnects, inspect the client's startup error and registered
launcher; see [verification scope](client-verification.md#launch-environment-verification).
`rea doctor` has the same limit for provider checks, and reports a provider setting
the registration's file does not carry as a
[non-blocking unknown](installation.md#check-readiness-for-your-task).
## Runtime settings and client deadlines
`update_analysis_settings` changes curated defaults inside the running REA
server. It does not edit the client registration, extend the client's tool-call
deadline, or recompute timeout values previously derived by `rea setup`. When
raising a REA startup or operation deadline, separately ensure the client allows
enough time for the full request. Use `inspect_analysis_settings` for the running
server's defaults; `rea doctor` checks launch configuration and cannot observe
another process's runtime overrides. See the
[settings design](design/limits-and-recovery.md) for ownership and lifetime.
## Client tool names and discovery
REA advertises canonical names such as `binary_session`. The MCP host decides
@@ -29,6 +83,17 @@ Connect Fiddler Everywhere's MCP server separately for HTTP traffic debugging,
preserving your REA client entry. See the
[setup instructions and public skills](web-network-captures.md#fiddler-everywhere-mcp-and-public-skills).
## VS Code
`rea setup --client vscode` configures the default VS Code user profile's
`mcp.json`. Setup honors `VSCODE_PORTABLE` and `VSCODE_APPDATA` when they are
present in its environment. It cannot infer a running window's named profile
or `--user-data-dir`; configure that profile separately when needed.
`rea setup --client copilot_cli` instead configures Copilot's user
`mcp-config.json`. VS Code can forward eligible profile servers to Copilot
sessions; see the [VS Code MCP configuration reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
## GitHub Copilot CLI
Setup preserves an existing `tools` selection in the REA registration, including
@@ -159,14 +224,25 @@ and Claude Code reports that autocompact is thrashing. To avoid this:
`mcpServers.rea` entry in `~/.claude.json` (`$CLAUDE_CONFIG_DIR/.claude.json`
when that variable is set), then restart Claude Code. Setup keeps this
override.
- To remove entire tool families from the upfront catalog, run
`rea setup --client claude_code --tool-families <ids>` with the families the
workflow needs (see [the advertising scope contract](mcp-contracts.md#tool-family-advertising-scope)).
Out-of-scope tools are not advertised and their calls fail with the SDK's
disabled-tool error, so widen the selection when a task needs another
family. Setup writes the scope into the entry's `env` as
`REA_MCP_TOOL_FAMILIES`; rerun setup with a different selection to refresh
it, then restart Claude Code.
The sizes above come from REA's own `tools/list`. A live Claude Code session
against DeepSeek's API is unverified.
For a client whose stdio reader rejects the complete catalog frame, add
`"REA_MCP_OUTPUT_SCHEMA_PROFILE": "off"` to REA's `env` object and restart the
client. This omits only advertised output schemas; REA still validates complete
structured results against its canonical contracts. See [MCP output schema
For a client whose stdio reader rejects the complete catalog frame, set
`"REA_MCP_OUTPUT_SCHEMA_PROFILE": "compact"` to bound every advertised output
schema, or `"off"` to omit them, in REA's `env` object, then restart the client.
Both keep canonical result validation and complete structured results; `compact`
keeps a widened root shape and `off` advertises nothing. On catalogs large
enough that full input schemas alone approach the frame limit, also keep the
input profile at its `compact` default. See [MCP output schema
profiles](mcp-contracts.md#output-schemas).
## Kimi Code
@@ -199,6 +275,21 @@ registration but does not contribute to the derived tool timeout; raise
`toolTimeoutMs` to cover a longer selected JADX deadline.
This headroom is a practical default, not a guarantee for every analysis
duration. Setup preserves positive timeout values already in the `rea` entry.
Setup also forwards explicitly selected input/output schema profiles,
`REA_MCP_MAX_RESPONSE_BYTES`, `REA_MCP_MAX_INPUT_ELEMENTS`, provider paths and supported Ghidra, JADX and Kuna
settings. It also forwards explicit executable and resource selections for ADB,
apktool, packet capture, GDB/Rizin/objdump, Cutter, WABT, Wakaru, firmware and
signal tools, browser inspection, and pwntools/pwndbg. This includes their
supported prlimit overrides, Darwin Swift compiler/SDK/module-cache selections,
and compile timeout, logging level and explicitly configured reference-secret
patterns. A provider being unavailable does not discard its selected paths;
doctor reports its readiness separately. Explicit selections override discovered
defaults, including when setup runs again with a changed selection. Unrelated
environment variables, process ownership tokens and one-shot target paths are not
copied. For an explicitly configured startup target, see the
[initial-target settings](mcp-contracts.md#initial-target-at-startup). Existing
client environment overrides remain intact unless setup supplies the same key.
You can edit `startupTimeoutMs` and `toolTimeoutMs` there; doctor keeps positive
caller-selected values aligned and reports a non-blocking advisory when a tool
timeout is shorter than the known provider allowance.
@@ -256,6 +347,15 @@ Setup and doctor follow Qwen Code's tilde and working-directory-relative home
overrides. Qwen Code also discovers personal skills in `~/.agents/skills`,
where setup installs the bundled workflow.
If an existing direct-script registration fails with `env: node: No such file or
directory`, refresh it through setup as described under [Launchers and PATH](#launchers-and-path).
For an immediate Qwen-specific workaround, when Node is installed in
`/opt/homebrew/bin`, add `"PATH": "/opt/homebrew/bin:${PATH}"` to that server's
existing `env` object and restart Qwen. Qwen expands `${PATH}` from its parent
environment; other clients can use different substitution syntax. Qwen's stdio
launcher inherits the parent `PATH` and applies server overrides; a reduced
parent environment does not establish that Qwen removed Homebrew from it.
## Antigravity
Antigravity 2.0 discovers personal skills in `~/.gemini/config/skills`.
@@ -407,17 +507,51 @@ comments and unrelated settings. It resolves that file from `HERMES_HOME`, else
from the platform default Hermes itself uses — `%LOCALAPPDATA%\hermes` on
Windows, `~/.hermes` elsewhere — including any `HERMES_DATA_DIR_SUFFIX`. Hermes
connects an entry unless `enabled` is false, so setup states `enabled: true`.
Aliases whose anchor is defined in the `rea` entry retain their original resolved
values when setup refreshes or uninstall removes that entry. Setup does not
redirect another server's alias to REA's new launcher or policy.
This also covers an anchor on the removed registration key. References to
definitions outside the entry keep sharing the original value, including when
another definition later reuses the same anchor name.
Hermes personal skills install under the active Hermes home's `skills` directory.
Setup, doctor and uninstall follow the root's `active_profile` selection, as
plain Hermes CLI commands do. An explicit `HERMES_HOME` pointing to
`profiles/<name>` selects that named profile directly. An invalid, missing or
deleted sticky profile is reported instead of writing into the default home;
it also remains an unresolved skill selection, so setup cannot report a
successful empty installation. Overrides nested beneath the native Hermes home
use that native home's sticky selection and profile root together, matching
[Hermes's root rules](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_constants.py#L83).
Repair the profile or run `hermes profile use default` before retrying.
Setup, doctor and uninstall share REA's sticky-profile resolver.
A custom `HERMES_HOME` outside the platform default home
reads its own `active_profile`. An override physically inside the default home
uses that default home's sticky selection and profile root together, including
when the final override directory does not yet exist.
For example, with `HERMES_HOME=~/.hermes/sub` and `~/.hermes/active_profile` set
to `coder`, setup selects `~/.hermes/profiles/coder/config.yaml` and that
profile's skills directory. It does not read `~/.hermes/sub/active_profile`.
Without an outer named selection, this override uses `~/.hermes/sub/config.yaml`.
Use a home outside the platform default tree for an independent sticky selection.
This describes REA's current routing, not full Hermes CLI equivalence.
At Hermes commit `748c79a1`, the
[sticky lookup](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_cli/main.py#L612-L632)
also reads the outer selection, but
[`resolve_profile_env`](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_cli/profiles.py#L2431-L2461)
resolves it beneath the configured `HERMES_HOME`. For nested overrides, confirm
the profile Hermes actually uses and point `HERMES_HOME` directly at that
`profiles/<name>` directory before setup. Compatibility is tracked in
[#3939](https://github.com/morluto/rea/issues/3939).
REA resolves existing ancestor directories before checking physical containment,
including symlinks and junctions, even when the override directory does not exist.
An override beneath a symlinked default home therefore uses the default home's
sticky selection. If `~/.hermes/link` points outside the default home,
`HERMES_HOME=~/.hermes/link/new` instead uses that override root, whether or not
`new` exists. Previewed destinations retain the configured path spelling.
An unreadable path or dangling alias leaves the home unresolved; repair that path
before configuring Hermes. Inspect setup's previewed destinations before applying
changes.
An explicit `HERMES_HOME` pointing to `profiles/<name>` selects that named
profile directly, bypassing sticky selection. An invalid, missing or deleted
sticky profile is reported instead of writing into the default home; repair the
profile or run `hermes profile use default` before retrying. It also remains an
unresolved skill selection, so setup cannot report a successful empty installation.
REA follows `HERMES_HOME` whitespace trimming, environment-variable expansion,
and home expansion before resolving that profile. Setup previews the resolved
configuration and skill destinations; doctor and uninstall use those same paths.
@@ -434,10 +568,12 @@ installs its skill copy there.
## Factory Droid
For [Factory Droid](https://docs.factory.com/harness/mcp), setup writes
`mcpServers.rea` into the user-level `~/.factory/mcp.json`. Entries carry
`command`, `args`, and `env`. Droid discovers personal skills from
`~/.factory/skills` ([skills](https://docs.factory.com/harness/skills)), so
setup installs its skill copy there.
`mcpServers.rea` into the user-level `~/.factory/mcp.json` (the
`FACTORY_HOME_OVERRIDE` override replaces the home that contains `.factory`).
Entries carry `command`, `args`, and `env`. Droid discovers personal skills
from `~/.factory/skills` ([skills](https://docs.factory.com/harness/skills),
following `FACTORY_HOME_OVERRIDE` when set), so setup installs its skill copy
there.
## Trae
@@ -485,14 +621,43 @@ the fallback file untouched. Existing REA environment overrides are preserved
when refreshing its command. A malformed or unreadable fallback file fails setup instead of
being silently suppressed; repair or remove it and rerun. Uninstall removes an
REA-owned `rea` entry from both the native table and the fallback, backing up
each changed file, so an emptied native table cannot load a leftover fallback
registration; other fallback servers, settings and a foreign `rea` entry are
kept. ZCode disables a
each changed file. If removing the last native server would activate a retained
fallback server, uninstall refuses before changing either file; review the
fallback configuration before retrying. Other fallback servers, settings and a
foreign `rea` entry are kept. ZCode disables a
server with `enable: false` on its entry; doctor reports such a registration
as stale, and rerunning setup removes the flag to repair it. Native configuration
uses strict JSON; setup refuses comments, trailing commas and a BOM rather than
writing a registration ZCode cannot load.
## FreeBuff
For FreeBuff, the proposed integration writes `mcpServers.rea` into the user-scope
`~/.agents/mcp.json` shared-agent file. Entries carry `command`, `args`, and
`env`; stdio is the default transport. The proposed personal skill location is
`~/.agents/skills`, so setup installs its skill copy there. Because the same
file doubles as ZCode's user-scope fallback (see above), a `freebuff`
registration is also in the file ZCode uses until ZCode defines its own native server.
Setup and doctor detect FreeBuff from its own profile directory
(`~/.config/freebuff-desktop`), not from the shared `.agents` folder that
REA's skill installs create for every client.
The configuration write and personal skill locations are proposed integration
boundaries. FreeBuff MCP loading and skill discovery have not been verified
with an installed native FreeBuff client/version; filesystem tests cover REA's
editing, discovery, and ownership behavior only. Automatic applicability
requires the FreeBuff profile directory even when the shared MCP file exists.
When uninstall cannot establish FreeBuff or another shared-storage reader from
product discovery, a malformed or unreadable shared MCP file is retained
unchanged and does not stop other REA removals, including `--purge-data`. When
FreeBuff or ZCode product applicability is established or unresolved, the same
malformed shared file stops uninstall before removals. See
[Diagnose, update, and remove](installation.md#diagnose-update-and-remove).
FreeBuff setup support requires `[email protected]` or later. Older versions
reject `rea setup --client freebuff` as an unsupported client. See the
[installation guide](installation.md#supported-agents) for client availability.
## DeepSeek Harness
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
@@ -558,8 +723,10 @@ become active again. Malformed applicable files block setup before changes.
Grok Build loads `[mcp_servers.rea]` from `$GROK_HOME/config.toml`, or from
`~/.grok/config.toml` when `GROK_HOME` is unset or empty. Setup edits that server
table, `[mcp_servers.rea.env]`, and a root `disabled_mcp_servers` entry that
names `rea`. It sets `startup_timeout_sec = 30` and leaves every other name
in that list. The shared skill installed under `~/.agents/skills` is already
names `rea`. It defaults `startup_timeout_sec` to 30 seconds, preserves an
existing positive timeout, and leaves every other name in that list.
[Grok supports longer startup timeouts](https://docs.x.ai/build/features/mcp-servers)
for cold package-runner launches. The shared skill installed under `~/.agents/skills` is already
on Grok Build's skill path.
Grok Bot (`grok_bot`) is detected from `~/.grokbot`, or from `SAND_DATA_ROOT`
@@ -589,10 +756,6 @@ can override an inherited disable. Lower files and other server entries remain
unchanged; a legacy server named `servers` that conflicts with the native table
requires manual repair before setup can write.
When Hermes aliases refer to an anchored REA entry, refreshing or removing REA
retains the aliases' original resolved values. Setup does not redirect another
server's alias to REA's new launcher or policy.
With no client selected, the interactive skill choice targets the shared
`~/.agents/skills` copy. Selecting a client targets its personal destination
when supported. Use `rea setup --skill-client shared --skill` for the shared
+23
View File
@@ -1,11 +1,26 @@
# Client and agent verification
FreeBuff MCP loading and personal skill discovery are currently unverified with
an installed native client/version. Its temporary-filesystem coverage verifies
REA's configuration and applicability boundaries only; it does not establish
that FreeBuff consumes the written registration or skill.
Use these optional lanes to verify an installed client or evaluate an agent's
REA workflow. [Testing REA](testing.md) provides the normal development commands
and provider lanes. Each record below states its host, client version, model
fixture, and remaining coverage; a native-client run with a loopback model does
not establish live model-service acceptance.
## Launch environment verification
`rea mcp doctor --json` starts the production MCP adapter with the current runtime
executable and environment. A healthy result establishes that adapter's
initialization, discovery and target-free request flow; it does not verify a
client's configured launcher or its inherited environment. Test the generated
registration through the actual client, including a reduced `PATH` when checking
desktop or service startup. Record the client version and launch environment
separately from terminal doctor results. See [launcher troubleshooting](client-configuration.md#launchers-and-path).
## Published Pi code-mode renderer limit
A separate probe of the published `@earendil-works/pi-codemode` 1.1.0 and 1.2.0
@@ -48,6 +63,14 @@ skill setting; Hermes discovers it through the selected `HERMES_HOME`.
Set `REA_VERIFY_HERMES_STICKY_PROFILE=1` to exercise native Hermes selection of
a named sticky profile with `HERMES_HOME` still pointing to its root.
For Qwen on POSIX, set `REA_VERIFY_QWEN_REDUCED_PATH=1` and select its JavaScript
entry point with `REA_VERIFY_QWEN_COMMAND` to test a restricted client environment.
Setup retains the caller's `PATH`; the native Qwen `chat` or `call` run then uses
an isolated `PATH` containing only Bash for the shell-tool fixture. The lane
checks that `node`, `npm` and `npx` cannot be found and uses the generated REA
registration unchanged. Its receipt records `reducedPath`, `clientPath` and
`registrationCommand`; the option alone does not establish a passing native run.
The Qwen lane also supports native Windows (verified with Qwen Code 0.25.1 and
Node 24.19.0 for both `chat` and `call`). With Node 24.18.0, the same `call`
journey reached full Evidence readback but the client aborted during exit with
+334 -39
View File
@@ -1,35 +1,121 @@
# Admission limits and recovery
# Analysis settings, limits, and recovery
How to add a REA-owned admission ceiling, budget, or deadline, and what a caller
does when one is hit. This is a design rule, not an inventory. Ceilings that
should become settings are tracked in
This guide explains the runtime settings design for maintainers and developers:
who owns each value, when an update takes effect, how providers enforce it, and
how to extend the system. The public tool contract is in
[MCP contracts](../mcp-contracts.md#editable-analysis-settings); recorded runtime
checks and their limitations are in the
[verification report](../verification/runtime-analysis-settings.md).
## Problem and design boundary
A valid workload can exceed a conservative default. An oversized ASAR header
may prevent analysis of the entire archive; a slow Ghidra function may exceed
its decompile deadline. Requiring a new REA process to change either budget
interrupts the investigation and can discard reusable provider state.
REA exposes a curated set of analysis defaults through MCP so callers can
adjust a reported limit and retry the same workload in the running runtime.
Each setting has a typed value, resource bounds, an enforcement owner, and a
defined application point. The design applies across archive admission,
provider startup, operation deadlines, and worker memory requests.
The setter changes in-memory defaults. It does not edit arbitrary environment
variables, configuration files, MCP registrations, or provider installations.
Those mechanisms have different owners and lifetimes. It also cannot change a
client deadline, extend accepted work, release retained resources, or remove a
fixed representation limit. Exposing a setting requires a working enforcement
path, not just an entry in the tool schema.
Further ceilings that should become settings are tracked in
[#6176](https://github.com/morluto/rea/issues/6176). Naming honesty for limit
errors is [#5910](https://github.com/morluto/rea/issues/5910).
## Ownership and data flow
| Owner | Responsibility |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Domain definitions](https://github.com/morluto/rea/blob/main/src/domain/analysisSettingDefinitions.ts) and [types](https://github.com/morluto/rea/blob/main/src/domain/analysisSettings.ts) | Exact setting IDs, value types, shared bounds/defaults, units, provider/operation associations, application points, and structured limit metadata. Numeric constants stay with their domain owner. |
| [Launch parser](https://github.com/morluto/rea/blob/main/src/config/analysisSettings.ts) | Parse selected launch values once, preserving an invalid value as a typed configuration failure rather than substituting a default. |
| [AnalysisSettings](https://github.com/morluto/rea/blob/main/src/application/AnalysisSettings.ts) | Own one runtime's launch baseline, immutable override revisions, atomic updates, inspection, and accepted-operation snapshots. It does not own provider lifecycle or cleanup. |
| [Composition](https://github.com/morluto/rea/blob/main/src/main/eagerRuntime.ts) and [typed configuration views](https://github.com/morluto/rea/blob/main/src/config/runtimeAnalysisSettings.ts) | Inject the same settings reader into providers and workflows; translate overrides into the configuration each consumer understands. |
| Provider or resource owner | Resolve the effective budget, enforce it at the applicable boundary, and attach the actual value and known source to failures. |
| [MCP registration boundary](https://github.com/morluto/rea/blob/main/src/server/EvidenceMcpServerCore.ts) | Enter the operation's settings scope before invoking its handler. |
| [Settings contracts](https://github.com/morluto/rea/blob/main/src/contracts/analysisSettingsContracts.ts) and [tool registration](https://github.com/morluto/rea/blob/main/src/server/registerAnalysisSettingsTools.ts) | Validate named requests and deliver inspection/update results without requiring an open target or healthy analysis provider. |
The normal path is: composition creates the store, an MCP handler captures its
current revision, and providers read that snapshot when resolving budgets.
If an owner rejects work, it reports the effective limit through the shared
error projection. A subsequent settings update publishes a new revision for
later handlers. It does not reach into the failed operation or retry it.
### Accepted-operation snapshots
`AnalysisSettings.run()` installs the current immutable snapshot in
`AsyncLocalStorage`. Its `read` function returns that snapshot inside the
operation and the latest revision outside it. Providers depend on the reader
contract rather than on the mutable store or an ambient environment lookup.
Acceptance here is entry into the registered handler's settings scope, not
arrival of bytes at the MCP transport. Work queued or awaited within that scope
keeps its snapshot. Deferred server initialization must still dispatch through
the canonical scoped handler.
| Event | Revision used |
| --------------------------------------------------------- | ----------------------------------- |
| Operation A enters its handler, then waits for a provider | Current revision 0 |
| A settings update publishes revision 1 | Future handlers can read revision 1 |
| Operation A resumes and starts its worker | Its accepted revision 0 |
| Operation B enters its handler | Revision 1 |
This gives each operation stable budget semantics even if another connection
updates settings while it waits. `next_worker` and `next_startup` describe where
an accepted value is applied; they do not cause workers or startups belonging
to an older operation to adopt a newer revision.
Inspection deliberately reads the store's current defaults, rather than the
calling operation's snapshot. It reports `active_values.status: "not_observed"`:
an inspection result cannot establish the budget or remaining time of an
already-running worker.
### Composition and embedding
The MCP runtime creates one store and supplies its reader to provider factories.
`createServer` uses a supplied store or creates one, and wires its default
providers to that store. An embedder supplying a custom binary session or other
provider must also inject the same reader into that provider's composition and
pass the store to `createServer`. Merely exposing the settings tools cannot
make a custom provider consume updates.
Keep default factories, explicitly supplied providers, transport composition,
and deferred handler dispatch covered separately where their wiring differs.
Resolve settings at the operation boundary; copying a runtime value into a
provider constructor would freeze it at launch. For entrypoints outside the
MCP wrapper, an embedder needing the same snapshot guarantee must establish
the operation scope with `AnalysisSettings.run()`.
## Name a default, then a setting when operators need headroom
Put the number in a named constant with a unit. Do not leave it as a magic
number at the call site.
When a workload may need more room, add a setting whose unset value is that
constant. One module under
[`src/config/`](https://github.com/morluto/rea/blob/main/src/config) owns the
default, any floor, the maximum, the schema, the parser, and the recovery
text. Register that schema on `environmentSchema` in
[`environment.ts`](https://github.com/morluto/rea/blob/main/src/config/environment.ts).
`parseConfig` calls `parseEnvironment`. Doctor uses that same parser through
`nativeAnalysisConfigurationError`, so startup and `rea doctor` accept the
same values. Add the name to `SELECTED_REGISTRATION_SETTINGS` so `rea setup`
copies it into the client registration. Unset keeps today's behavior. An
invalid value is a `ConfigurationError` and projects as `configuration_invalid`.
Do not fall back to the default.
constant. Pure numeric contracts live in `src/domain/analysisResourceLimits.ts`
or their existing domain owner. `analysisSettingDefinitions.ts` binds the
curated runtime IDs to those same bounds, defaults and application points.
Environment parsers and recovery text stay under `src/config/`; register launch
schemas on `environmentSchema` so startup and doctor agree. Add launch settings
to `SELECTED_REGISTRATION_SETTINGS` when setup should forward them. Unset keeps
today's behavior. An invalid launch value remains a configuration failure,
never a silent fallback; its validation boundary determines whether MCP can
start (see [invalid launch configuration](#invalid-launch-configuration)). An
invalid runtime write is `invalid_request` and changes nothing.
[`REA_REGEX_MATCHING_DEADLINE_MS`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.ts)
([#6267](https://github.com/morluto/rea/pull/6267)) is the reference. Default
5000 ms, floor 1 ms, maximum 2147483647 ms, because Node schedules a longer
`setTimeout` delay as 1 ms. Setup copies the setting. A hit says:
> The effective REA_REGEX_MATCHING_DEADLINE_MS value is 5000 ms (source default; default 5000 ms, floor 1 ms, maximum 2147483647 ms). Set REA_REGEX_MATCHING_DEADLINE_MS in the environment that starts REA to a positive decimal integer from 1 to 2147483647, then restart REA. The worker old-generation heap does not raise this deadline.
For example, `regex.matching_deadline_ms` and its launch seed
`REA_REGEX_MATCHING_DEADLINE_MS` share a 5000 ms default, 1 ms floor and
2147483647 ms maximum. Node schedules a longer `setTimeout` delay as 1 ms.
Changing the runtime setting affects the next accepted search; changing the
launch environment affects the next REA process.
[`REA_MCP_MAX_RESPONSE_BYTES`](https://github.com/morluto/rea/blob/main/src/config/mcpResponseBudget.ts)
is the byte-budget shape: a decimal safe integer with a 10 MiB floor
@@ -39,7 +125,8 @@ raise REA's reserved string limit or Node's string ceiling. The setup
allowlist does not copy this setting. A new headroom setting should, following
the regex deadline.
A per-call budget is not an environment setting. JavaScript `max_heap_mb`
A per-call budget can have a runtime default without having an environment
setting. JavaScript `max_heap_mb`
(CLI `--max-heap-mb`) is an integer from 128 to 16384 MiB, default 1024. It
applies on the next analysis, which starts a new worker, and does not restart
REA. It does not raise the parent result budget, transfer ceilings, or fixed
@@ -51,8 +138,144 @@ does: its recovery says to set `NODE_OPTIONS` (for example
`--max-old-space-size=<MiB>`) on the process that starts REA, then restart
that process. `max_heap_mb` does not raise it.
## Runtime settings through MCP
`inspect_analysis_settings` lists the curated settings, their current and launch
values, source, bounds, application point and relevant operation names. Filters
by exact setting IDs, provider or operation intersect. Active worker values are explicitly `not_observed`; a configured default is
not a measurement of active work. Inspection also separates known server limits from unknown client deadlines and receive buffers.
`update_analysis_settings` accepts an atomic `changes` array. Each entry names
an exact `setting` and either `action: "set"` with a schema-validated `value`, or
`action: "reset"`. Reset restores the launch value. `expected_revision` is an
optional compare-and-set guard; conflicts and duplicate keys change nothing.
An unchanged write does not advance the revision.
The revision tracks override state, not only effective numeric values. Setting
an explicit override equal to the launch value still changes its source to
`runtime` and advances the revision. Reset removes that override, even if the
effective number stays the same. Validation, revision checks, and duplicate-key
checks all happen before publishing the replacement snapshot; a rejected batch
cannot partially apply. Clients coordinating writes can supply
`expected_revision`; without it, valid updates apply to the current state.
| Runtime setting | Application point |
| ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `asar.max_header_bytes` | Next archive operation, including full JavaScript application analysis |
| `ghidra.startup_timeout_ms`, `hopper.startup_timeout_ms` | Next provider startup attempt |
| `ghidra.decompile_timeout_seconds` | Next operation on the existing Ghidra Program; `null` means unbounded |
| `jadx.operation_timeout_ms` | Next JADX operation |
| `regex.matching_deadline_ms`, `regex.worker_heap_request_mib` | Next search / new regex worker |
| `capture.decoder_old_generation_mib` | Next owned capture decoder |
| `javascript.analysis_timeout_ms`, `javascript.max_heap_mb` | Next source analysis / new JavaScript worker |
| `wasm.command_timeout_ms` | Next WABT operation |
| `native.matching_representation_bytes` | Next `match_native_functions` request |
### Precedence and lifetime
Where a setting supports an explicit per-call argument, precedence is:
1. The caller's explicit argument.
2. The accepted operation's runtime override.
3. Valid launch configuration, where that setting has an environment seed.
4. The built-in default when launch configuration is absent.
An invalid launch value does not fall through to the built-in default. Preserve
omitted caller arguments in Evidence: resolving an internal default must not
rewrite the request as though the caller supplied it. `null` is the explicit
unbounded value for `ghidra.decompile_timeout_seconds`; it is not a substitute
for an unknown or omitted value elsewhere.
A write does not extend active work, relaunch a provider, clear retained
cleanup, or invalidate successful Evidence.
Overrides belong to one REA runtime, survive target close/open, and disappear
when REA exits. Connections sharing that runtime share its overrides. No files,
ambient environment, registrations or upstream software are edited. Overrides
are runtime-wide, not scoped to a target or an individual connection.
### Failure and retry
An ASAR admission error supplies `details.limit` with the exact setting,
effective value, units, bounds, scope, application point, restart requirement
and, when known, required bytes and value source. The agent can immediately call:
```json
{
"changes": [
{ "setting": "asar.max_header_bytes", "action": "set", "value": 33554432 }
]
}
```
Then retry the same full-archive request once work and cleanup have settled.
No preliminary inspection or source subset is required. The setter does not
retry automatically. If an explicit per-call value caused the failure, correct
that argument; raising a runtime default cannot override it.
The resource owner constructs `details.limit` from the registry plus the budget
it actually enforced. Metadata includes `agent_editable: true`,
`scope: "rea-runtime"`, and `rea_restart_required: false`. Optional
`required_value`, `source`, and `parameter` fields are supplied only where known
or applicable. Do not infer a source because a value happens to equal a default.
Error copying, attribution, redaction, and delivery must preserve these facts.
Human-readable remediation explains the same failure; clients should not need
to parse that sentence to identify an editable setting.
`scope` and `applies_at` describe a continuing caller, such as an MCP
connection. A one-shot invocation, such as a CLI command, has released its
provider session and exited its runtime before its caller reads the failure,
so its adapter projects with an explicit `"invocation"` caller lifetime.
`details.limit` then keeps every other field and reports
`scope: "rea-invocation"`, `applies_at: "next_invocation"` and the launch
`environment` seed when one exists. `agent_editable: true` still holds, because
the caller chooses that environment value or per-call `parameter` for the new
invocation; `rea_restart_required: false` means no running REA process needs a
restart. A producer keeps guidance that holds only while its caller retains the
session, such as "the Ghidra session remains usable" or a same-session runtime
update, in `continuingCallerGuidance`; the shared presentation adds it only for
a continuing caller. The producer's own message and remediation must stay true
after the session is released. A composed error records each embedded child
failure through `childFailureDiagnostics`, which snapshots it at composition
for both lifetimes when they differ, so cleanup history keeps its state and
the final projection selects the caller's form. Failures retained inside
Evidence keep the continuing form, so CLI and MCP Evidence stay identical.
Cache policy is part of enforcement. Successful reusable analysis can survive
a budget update. Transient Ghidra decompile failures remain uncached so a raised
deadline can recover on the same Program. Admission checks still apply to reused
data: lowering an ASAR limit must reject a cached oversized header. Neither
raising a limit nor clearing a transient analysis failure authorizes skipping
cleanup or changing the identity of the provider session.
### Invalid launch configuration
Launch validation has two relevant boundaries. Settings validated by global
configuration parsing can prevent REA from starting; an MCP setter cannot
repair a server that is not running. Correct the launch environment or
registration, use `rea doctor` for its configuration checks, and start REA again.
Provider-local validation can leave MCP available. For example, an invalid
`REA_JADX_OPERATION_TIMEOUT_MS` is inspectable with `source: "invalid"`, a
`configuration_error`, and omitted `value` and `launch_value`. A valid runtime
override repairs the effective JADX timeout without editing that launch seed.
Inspection then reports the runtime value while retaining the launch error.
Reset exposes the invalid launch state again; it does not repair or replace it
with the built-in default. Provider availability and execution must resolve the
same effective setting so readiness changes along with repair and reset.
`REA_NATIVE_MATCHING_REPRESENTATION_BYTES` is validated the same way, by
`match_native_functions` alone.
## Keep a fixed ceiling honest
Inspection's separate
[constraints](https://github.com/morluto/rea/blob/main/src/server/analysisSettingsConstraints.ts)
describe operator-owned, external, and fixed controls. REA can report its
configured response budget and parent heap limit; client deadlines and receive
buffers remain explicitly unknown. These are not entries accepted by the
runtime setter. Raising a worker heap request cannot raise the parent heap,
and raising a provider deadline cannot make the client wait longer.
If the ceiling must stay fixed, say so in the failure. Name the limit and the
effective value, say this release has no runtime override, and name what does
not raise it. Do not invent a setting.
@@ -68,20 +291,24 @@ The string the caller sees includes:
- the limit or setting name
- the effective value and unit
- the source (`default` or `setting`)
- the source when known (`default`, `setting`, `runtime`, or `request`)
- the default, the maximum, and the floor when the setting has one
- the next action, including where to set it ("in the environment that starts REA")
- whether a restart is required
- what the change does not raise
Invalid settings never take this path. They are `configuration_invalid`.
Doctor reports `runtime-configuration:<setting>` and says to correct it in the
shell or the MCP registration, then rerun.
Distinguish an invalid configuration from a valid budget exhausted by work.
Preserve the configuration failure and its setting identity; apply the
[launch validation boundary](#invalid-launch-configuration) when describing
whether live repair is possible.
## What agents do
Follow `remediation.action`. Change a request field or a `REA_*` setting only
when that text recommends the change. Restart only when it says the value is
Use `details.limit` for a direct runtime update when it names an editable
setting with `scope: "rea-runtime"`. For `scope: "rea-invocation"`, set its
`environment` or `parameter` for a new invocation instead.
Follow `remediation.action` for per-call, fixed and external controls.
Inspect settings only when bounds or application details are missing. Restart only when it says the value is
read at startup. A sentence that a control does not raise the limit is not a
request to change it.
@@ -103,22 +330,90 @@ request to change it.
- `resource_constraint` for capacity, when `retryable` is true: wait for the
active admission to finish, then retry within the reported limits. Do not
restart REA to free the slot.
- `configuration_invalid`: correct the named setting in the environment that
starts REA, then start REA again. Run `rea doctor` for the
- `configuration_invalid`: when MCP remains available and the failure belongs
to a curated provider-local setting, inspect and set a valid runtime override.
Otherwise correct the named setting in the environment that starts REA, then
start REA again. Run `rea doctor` for the
`runtime-configuration:` check. Re-run `rea setup` for that client when the
setting is on the registration allowlist, then reconnect so the new process
sees it.
- `invalid_request`: correct the reported fields from the current schema or
CLI help. Do not repeat the unchanged call.
- `invalid_request`: follow the specific recovery and reported issue. Inspect
the schema or CLI help when the input violates the contract or the response
asks for `inspect_input_contract`. A valid selector can instead require a
different choice, such as selecting a reported address; an inventory miss
does not establish that the target lacks the item. Do not repeat the
unchanged call when the response says it cannot succeed unchanged.
A restart is required for an environment setting and for a client still
running the previous registration. It is not the first step for a transient
A restart is required to adopt a changed launch environment or registration.
The curated runtime settings above require no REA restart. It is not the first step for a transient
MCP drop, a capacity wait, or retained cleanup.
## Tests
## Adding a runtime setting
Lock the unset default, one accepted override, rejected floor, maximum, and
syntax, and the recovery sentence through `parseEnvironment` and `parseConfig`.
1. Identify the enforcing owner and the real resource constraint. Decide whether
callers need a per-call argument, a runtime default, a launch-only control,
or a fixed limit. A registry entry must not advertise authority REA lacks.
2. Define the value type and named numeric bounds with the domain owner. Add the
ID and metadata to `analysisSettings.ts` and `analysisSettingDefinitions.ts`.
Reuse those bounds in runtime validation, launch parsing, and error metadata.
Choose the application point and provider/operation associations from actual
consumers; a shared control need not apply to every provider.
3. If there is a launch seed, add its parser and appropriate configuration/doctor
validation. Decide whether an invalid value blocks the runtime or only the
provider. Add it to setup's `SELECTED_REGISTRATION_SETTINGS` when it should
be forwarded; runtime editability does not automatically propagate it.
4. Thread the shared reader through every applicable composition path. Resolve
the effective value within the accepted operation, honor explicit arguments,
and preserve their omission in Evidence. Keep availability checks consistent
with execution. A worker heap request must not be described as a measured
process memory limit.
5. Enforce the value at its resource boundary and construct structured limit
facts there. Preserve those facts through shared error projection and copies.
Check cache reuse, lowered admission, transient failures, and retained cleanup
against the same ownership rules.
6. Update the public contracts and affected guides, then regenerate catalogs
and packaged skill metadata using the repository commands. Keep externally
owned and fixed controls in the separate inspection constraints, not in the
editable registry. Validate both store semantics and a public recovery path.
## Tests and verification
Cover the unset default, accepted overrides, rejected floor/maximum values,
and invalid syntax through the owning parser and configuration boundary.
[`regexMatchingDeadline.test.ts`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.test.ts)
is the pattern. Project the failure and assert `configuration_invalid` for a
bad value and the recovery wording for a hit.
is one example. Assert error codes, setting IDs, values, units, provenance,
application points, and resulting behavior. Check that adapters deliver the
canonical recovery guidance, with independent assertions for its underlying
facts. Avoid freezing prose or deriving all expected values from the same
registry used by the implementation; either can hide the wrong failure.
See [test design](../testing.md#test-design-and-pruning) for the assertion policy.
Runtime checks cover atomic validation, stale writes, reset, runtime isolation,
accepted-operation snapshots, and explicit argument precedence. Public MCP
acceptance must reject an oversized ASAR, raise admission, and successfully
retry the same complete archive on the same connection. Lowering admission must
also refuse previously cached headers. The real Ghidra timeout verifier checks
recovery after a live update without replacing its session. Also cover invalid
provider-local launch configuration, repair/reset availability, default and
custom composition paths, and preservation of structured limits through error
copies. Gates used to overlap work and updates need bounded acknowledgements
and release/join in `finally`.
The [verification report](../verification/runtime-analysis-settings.md) separates
public MCP checks, simulated provider coverage, real Ghidra execution, and
measured task costs. It does not establish real-provider behavior for every
editable setting or active worker telemetry.
## Bun standalone module graphs
`MAX_BUN_MODULE_COUNT` admits at most 100,000 module records.
`MAX_BUN_RETAINED_BYTES` admits at most 256 MiB of serialized graph payload and
separately bounds retained entry metadata. Physical metadata read work is also
limited to 256 MiB per preparation, including repeated uncached ranges. A
256 KiB / 1,024-entry exact-range cache avoids full-page amplification. The
combined logical path tree admits at most 250,000 distinct prefixes; this is
checked before adding nodes, with shared prefixes counted once. The shared
artifact expansion budget also bounds the sum of referenced member lengths. Refusals name the boundary.
There is no runtime override; additional host memory or `NODE_OPTIONS` does not
raise these ceilings. Use a smaller producer fixture or a separately reviewed
reader for larger graphs. No partial extraction is claimed on admission failure.
+8 -6
View File
@@ -53,7 +53,7 @@ Every collection supports `fields`, `offset` and `limit`; its stable row ID is
always included. Selection changes delivery, not source inspection. Pages keep
source coverage and parent Evidence links. Page exhaustion does not establish
absence in a partial source. Complete Evidence remains accessible through
`get_evidence` and Evidence bundle export.
`get_evidence_bundle` and Evidence bundle export.
The summary's `probe_issues` counts files with reported issues and their retained
issue entries. It previews at most two such files, including stable `file_id`,
@@ -135,11 +135,13 @@ observations, not an atomic directory snapshot. Unread bytes remain unknown.
## Resource bounds and reuse
Defaults are 20,000 examined entries and 8 MiB of total probe reads. Callers may
select up to 100,000 entries and 64 MiB. A separate 32 MiB conservative
representation budget bounds retained paths and facts before growth. Limits
and failed probes produce partial coverage. Traversal and reads are serial.
Each reader admits one inspection at a time; overlapping requests receive a
retryable capacity refusal without cancelling the admitted caller.
select up to 100,000 entries and 64 MiB. A separate 32 MiB representation
budget bounds retained rows, paths and facts before growth. Each retained path
is charged at the larger of its UTF-16 heap size and its escaped JSON size, so
typical macOS bundle paths fit about 20,000 rows, and control-character paths
fewer. Limits and failed probes produce partial coverage. Traversal and reads
are serial. Each reader admits one inspection at a time; overlapping requests
receive a retryable capacity refusal without cancelling the admitted caller.
Unity reference decoding stops after a witness in each `Data/Managed` directory;
other assemblies' references remain unexamined, while their headers are still
+105
View File
@@ -0,0 +1,105 @@
# Flattened devicetree inspection
`inspect_devicetree` / `rea inspect-devicetree` reads a local DTB without an
engine, target execution or external tools. It supports FDT versions 16 and 17;
other versions return `unsupported_target`. Corrupt recognized blobs return
`invalid_request`, and exhausted budgets return `resource_constraint` without
partial success.
```bash
rea inspect-devicetree /artifacts/board.dtb --json
rea inspect-devicetree /artifacts/board.dtb --detail summary --evidence_output /artifacts/board-evidence.json --json
```
The complete Evidence includes the artifact SHA-256, validated header fields,
64-bit memory reservations as exact hexadecimal strings, and a flat node tree
with explicit parent paths. Each property retains its file offset, byte length,
and up to 256 raw bytes as hex. `truncated` qualifies the preview only; parsing
coverage includes the entire declared tree. Version 16 has no declared structure
size, so `header.size_dt_struct` is null.
Derived fields carry `confidence: derived`: standard ASCII string lists, scalar
phandles and cell counts, and `reg` tuples using the parent's `#address-cells`
and `#size-cells` (defaults 2 and 1). Zero-cell components are null (omitted), distinct from an encoded zero. Cell
counts above four, invalid counts and incomplete tuples remain undecoded with a
limitation. Addresses are bus-local; no `ranges` translation is attempted.
Invalid or ambiguous strings remain raw, rather than being guessed.
The summary is labeled derived and includes root compatible/model, chosen
bootargs/stdout-path and aliases. An undecodable alias retains its name with a
null path; it is not silently treated as absent. Single-cell `interrupt-parent`, `remote-endpoint` and `msi-parent`
references resolve through phandle declarations. Missing or duplicate targets
have null target paths. A node whose `phandle` and `linux,phandle` disagree
adds a limitation, and references to either value stay unresolved. A present
`phandle` that is not one cell also adds a limitation; `linux,phandle` cannot
resolve that node when the canonical declaration is malformed. Binding-specific
reference arrays and overlays are not resolved; an empty reference list does not
prove absence of hardware links.
MCP `detail: summary` retains complete Evidence in the session. Follow its
`parent_evidence_id` with `inspect_analysis_view`:
```json
{
"source": { "kind": "retained-evidence", "evidence_id": "ev:<digest>" },
"view": {
"kind": "devicetree",
"collection": "properties",
"path": "/serial@1000",
"offset": 0,
"limit": 32
}
}
```
Collections are `nodes` (identity and property counts), `properties` (requires an
exact node path), `reservations`, and `references`. Pages use retained parsed
facts and do not reopen the file. Each page carries fixed-size context
(artifact, header, coverage and counts) rather than the summary strings, so a
large `bootargs` value does not repeat on every page. Source indices are scoped to the selected
collection and parent Evidence; property indices are scoped to their node.
| Input budget | Default | Ceiling |
| ---------------- | ------: | ------: |
| `max_file_bytes` | 4 MiB | 16 MiB |
| `max_nodes` | 8,192 | 65,536 |
| `max_properties` | 32,768 | 262,144 |
| `max_depth` | 64 | 256 |
Decoded names, paths, values and row overhead share a fixed 32 MiB representation
allowance, charged before expansion. It has no runtime override.
The reservation count also uses `max_properties`. CLI flags use kebab case,
for example `--max-nodes 16384`. View pages contain at most 256 rows.
## Verification
Synthetic tests cover v16/v17, malformed headers/blocks/names/tokens, resource
limits, derived values, and CLI/MCP delivery including retained pages after
removing the input. The optional real lane requires **dtc 1.8.1**:
```bash
npm run build:cached
node scripts/verify/firmware/devicetree.mjs
```
It compiles a DTS with reserved-memory, chosen, aliases and phandles, compares
REA's node paths against `dtc -I dtb -O dts`, and checks the derived report.
`REA_DTC_PATH` selects the verification executable; it is not runtime setup.
The dtc 1.8.1 lane ran on macOS arm64 with Node 26.5.0. Its seven node paths
matched the decompiled tree. The report included `model: "REA DTB verification"`,
`bootargs: "console=ttyS0"`, alias `serial0 -> /serial@1000`, reservation
`0x80000000 + 0x1000`, and a resolved reference from `/consumer` to
`/serial@1000`. The deliberately minimal remote-endpoint fixture produces dtc
graph-binding warnings; this lane verifies FDT structure and selected property
derivations, not hardware-binding validity.
A repeat run of that complete compile/decompile/CLI verification took 2.13 s
elapsed, 1.52 s user CPU and 0.27 s system CPU under `/usr/bin/time -l`. This is a
small fixture with warm filesystem caches, not a cold-start or large-board
benchmark. Peak process-family memory and whole-workflow token usage were not
measured; the sandbox denied the memory-reporting system query. No pre-feature
equivalent REA workflow exists for comparison. The pinned Node 24.22.1/npm
11.16.0 toolchain was unavailable; dependency installation used the local cache
with Node 26.5.0/npm 12.2.0 instead.
+110
View File
@@ -0,0 +1,110 @@
# .NET single-file bundles
Use `rea inspect-artifact /absolute/path/host --json` to inventory an ordinary
.NET single-file host and `rea extract-artifact /absolute/path/host --json` to
materialize its members into a fresh, REA-owned output directory. With a host
selected through `open_binary`, the corresponding MCP tools are
`inspect_artifact` and `extract_artifact`. No .NET installation is needed for
inspection or extraction.
For a compact inventory, call `inspect_artifact` with `detail: "summary"`,
then use `inspect_analysis_view` with the returned `parent_evidence_id`.
Inventory occurrence pages accept `logical_paths`, `occurrence_ids`, selected
`fields`, `offset`, and `limit`; node pages accept batches of `artifact_ids`
to retrieve expanded-byte digests. Include `format_metadata` and
`source_location` when member declarations and stored extents matter. Identity
fields survive projection, and the complete inspection Evidence stays retained.
REA detects the apphost marker and its preceding header offset from PE, ELF,
and thin Mach-O bytes, regardless of filename. An unbundled apphost's zero
offset remains an ordinary native artifact. Universal Mach-O slice inventory
keeps its existing behavior; bundled contents inside slices are not expanded.
When a native file has a recognized PyInstaller CArchive, apphost-marker
scanning stops before its payload. A .NET helper stored inside that archive
does not replace the outer container's format; an outer .NET host can still
contain a PyInstaller helper as a member.
## Supported layout
| Header | Producer generation | Member encoding |
| ------ | ---------------------------------- | --------------------------- |
| 1.0 | .NET Core 3.x | Uncompressed |
| 2.0 | .NET 5 | Uncompressed |
| 6.0 | .NET 6 and later using this layout | Uncompressed or raw DEFLATE |
Versions, minor versions, and flags outside this set are refused explicitly;
they do not silently become an ordinary PE or a successful empty inventory.
Flags 0 and the v2+ compatibility flag 1 are admitted. Unknown file-type bytes
are retained with a limitation, rather than treated as executable authority.
Encrypted/protected wrappers and recovering their payloads are outside this
reader's scope.
The host keeps its whole-file SHA-256. Each embedded file gets its expanded-byte
SHA-256, path, declared size, compressed size, and `format_metadata` declaration
(`major_version`, `minor_version`, `file_type`). Its `source_location` describes
the stored host extent, including the compressed length when applicable.
The root manifest's `root_format` is `dotnet-bundle`; the root content node
still records its native PE/ELF/Mach-O format. A file-type declaration or `.dll`
suffix does not establish a valid assembly. Inspect an extracted component
explicitly with the managed tools to establish PE/CLI facts.
## Admission and containment
Metadata is range-read with a 64 KiB cache. Policy budgets are 16 MiB of
manifest metadata, 100,000 files, 64 KiB per UTF-8 string, 512 MiB of stored or
expanded bytes per file, 256 path parts per member, 250,000 distinct in-memory
path-tree nodes, and 2 GiB of aggregate expanded bytes. The node budget counts
shared directory prefixes only once. These are REA
resource policies, not limits imposed by Microsoft's format. Marker scanning
streams 64 KiB chunks and reuses completed probe facts during extraction;
ordinary native files without a marker incur one extra
read pass before host hashing.
Extraction hashes the whole host again through the same descriptor used to read
members and compares it with the inventory's root digest before creating an
output directory. This adds a full-host read pass to extraction, including when
the embedded members themselves are small.
Malformed/truncated headers, unsafe or conflicting paths, overlapping member
ranges, members crossing the manifest, contradictory configuration extents,
invalid UTF-8, corrupt/trailing compressed data, and incorrect expanded sizes
fail before output publication. Source descriptor/path identity is checked
during reading; extraction verifies the host digest and member bytes against
the inventory. Existing output roots are never overwritten. Cancellation and
failures release owned streams/descriptors through the shared extraction
rollback scope.
## Reproduce producer fixtures
The source-owned generator uses the installed .NET 8 SDK's **Microsoft
HostModel** producer to emit x64 v1, v2, plain v6, and compressed v6 bundles
for Windows, Linux, or macOS. It does not run generated hosts or download runtime packs. Supply an
SDK apphost template from
`packs/Microsoft.NETCore.App.Host.<rid>/<version>/runtimes/<rid>/native/apphost`
(`apphost.exe` on Windows). Pass `windows` (the default), `linux`, or `osx`
as the optional third generator argument to match that template.
```bash
dotnet build scripts/fixtures/dotnet-bundle/BundleFixture.csproj -c Release --configfile scripts/fixtures/dotnet-bundle/NuGet.Config -o /absolute/path/producer -p:BaseIntermediateOutputPath=/absolute/path/producer-obj/
dotnet /absolute/path/producer/BundleFixture.dll /absolute/path/apphost.exe /absolute/path/absent-bundles-directory
rea inspect-artifact /absolute/path/absent-bundles-directory/net8-compressed/Probe.exe --json
rea extract-artifact /absolute/path/absent-bundles-directory/net8-compressed/Probe.exe --json
```
The generator writes `oracle.json` beside each host. An independent C#
`BinaryReader`/`DeflateStream` pass records version, member types and stored
extents, then compares every expanded member digest with its source file.
Compare those records with inspection Evidence and the extracted bytes.
This verifies Microsoft's compression writer separately from synthetic parser
fixtures; it is not evidence that the bundled host executes on a target runtime.
Deterministic boundary and public-entrypoint coverage:
```bash
npm run test:focused -- tests/boundary/filesystem/dotnetBundleArtifacts.test.ts tests/acceptance/dotnetBundleJourney.test.ts
```
Layout references: Microsoft's [manifest writer](https://github.com/dotnet/runtime/blob/v8.0.22/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs),
[file entry writer](https://github.com/dotnet/runtime/blob/v8.0.22/src/installer/managed/Microsoft.NET.HostModel/Bundle/FileEntry.cs),
[apphost marker handling](https://github.com/dotnet/runtime/blob/v8.0.22/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs),
and [compression producer](https://github.com/dotnet/runtime/blob/v8.0.22/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs).
+25 -2
View File
@@ -75,6 +75,13 @@ DOM node indices retain their snapshot positions. Invalid rows are omitted with
completeness diagnostics, so `total_nodes` can exceed the retained node count
and a parent index can refer to an omitted source node.
`dom.node_value_length_unit` is `utf8-bytes`: each node's `node_value_length`
counts the bytes needed to encode its value as UTF-8, not characters. Web
observations instead declare `utf16-code-units`. These existing measurements
are preserved; the unit is reported once for the whole DOM, without retaining
text values. Historical captures without the unit leave it unknown; reading
them does not infer a unit or convert their stored lengths.
Script metadata retains its execution-context frame ID when CDP supplies one and
records admission separately in `frame_scope`. An `authorized` scope refers to
the observed document generation; `unknown` retains the known frame ID without
@@ -140,6 +147,14 @@ window-targeted renderer reload/crash actions, and synthetic `open-url` or
WebContents identities, process metrics, and IPC channel and value-shape metadata.
Payload values are not retained.
For the exact Playwright launch sequence, the active hook removes only REA's
bootstrap arguments from the application-visible `process.argv`. The vector keeps
the selected entry point and every caller argument, in order, including repeated
hook paths, `.asar` filenames, and debug-like options. The private launch handoff
is consumed before application code runs. If the observed sequence cannot be
matched exactly, argv stays unchanged and capture coverage reports `hook_conflict`;
REA does not guess an entry point or remove arguments by filename patterns.
`window_index` selects from Playwright's current Page list at the start of each action. REA resolves that Page to its corresponding BrowserWindow once; the action's `target` uses the BrowserWindow's capture-scoped `window_id`, matching the window inventory. Reordering either enumeration after selection does not retarget the action. Handle cleanup failures retain the completed action records and remain owned for provider cleanup retry.
Run the real fixture verifier with an operator-provided Electron runtime:
@@ -186,6 +201,9 @@ neither result means the requested host operation occurred. The existing
`openExternal`, `openPath`, and `trashItem` blocks reject their promises.
The `trashItem` block prevents that call from reaching the OS trash operation;
it does not intercept other ways the application can remove files.
Process rejection and uncaught-exception events are observed at dispatch without
adding exception handlers. The runtime and application retain their warning,
handler, and fatal-exit policies, including for rejected shell-block promises.
CLI SIGINT/SIGTERM requests cancellation and awaits owned-process cleanup.
Window IDs, URLs, and cached titles come from one main-process Electron snapshot,
so a crashed renderer does not require a successful renderer-side title query.
@@ -305,8 +323,13 @@ capture provides them.
ASAR observation reads the header through one open file handle and validates its
declared lengths against the archive size. Header/index admission is the same
budget used by static ASAR reads: 16 MiB / 16777216 bytes by default, or
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB) when set. An oversized header is
budget used by static ASAR reads: 16 MiB / 16777216 bytes by default, seeded by
`REA_ASAR_MAX_HEADER_BYTES` (at most 256 MiB). In MCP,
`update_analysis_settings` can override `asar.max_header_bytes` for new
operations without restarting REA. Cached headers are checked against each
operation's admission, including after a decrease. See the
[settings design](design/limits-and-recovery.md#failure-and-retry).
An oversized header is
not parsed; selecting a member path does not bypass admission, and the page
stays unauthorized. The header cache separately retains at most that many bytes
of already-admitted encoded headers across eight archives. Evicting that cache does not raise admission.
+16 -3
View File
@@ -24,15 +24,26 @@ requires an absolute filesystem path and preserves that exact selection:
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`tables` lists each declared note program header or section, including the
section name when a string table is present. Zlib-compressed section-name
tables are decompressed within the selected file-size budget. An unsupported
section name when a string table is present. Zlib- and Zstandard-compressed
section-name tables are decompressed within the selected file-size budget. An unsupported
compression type leaves section names unknown and adds a limitation; note bytes
remain independently examined. Each `entries[]` item reports owner, type,
descriptor SHA-256, and a hex preview for descriptors of 64 bytes or less.
A section name also stays null when its offset is outside the declared string
table or its bytes lack a NUL terminator. A limitation explains that missing
name. Note coverage remains complete when all declared note tables and entries
were examined; an unavailable section name does not erase those note facts.
For GNU notes, `gnu.build_id_hex`, `gnu.abi_tag`, or `gnu.gold_version` are
filled when the descriptor matches that type. Other owners keep `gnu` null.
For Linux `CORE` notes, `core.prpsinfo`, `core.file`, `core.siginfo`, or
`core.auxv` are decoded for process arguments and status (`NT_PRPSINFO`), mapped
file memory regions and paths (`NT_FILE`), fault signal and code (`NT_SIGINFO`), and
auxiliary vectors (`NT_AUXV`) across 32-bit and 64-bit images. Other owners or unparsed
notes keep `core` null.
`PT_NOTE` and `SHT_NOTE` are examined independently. The same file range may
appear twice when both tables declare it.
@@ -43,7 +54,7 @@ the image never carried notes in another container or overlay.
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
32 note entries are examined by default across every table. Retained note
names, section names, and GNU descriptor text share a scan budget based on
names, section names, GNU descriptor text, and core dump string representations share a scan budget based on
the source bytes plus any decoded section-name table, capped at
`max_file_bytes`. The raw source and decoded table must also fit together
within that ceiling. If admission fails, the error reports the required
@@ -58,6 +69,8 @@ numbering (`PN_XNUM`, including a section count stored when `e_shnum` is 0) is
unsupported and returns an unsupported-target outcome rather than invalid-input
recovery.
Zstandard section-name tables also use a 128 MiB decoder window limit.
`SHF_COMPRESSED` note sections return `unsupported_target` before their payload
is interpreted or decompressed. Their compression header is storage metadata,
not a note header. The current result requires physical file offsets for each
+7
View File
@@ -91,6 +91,13 @@ nothing, and `fields: []` returns only `source_index`. Offset/limit page matchin
rows in original order (defaults 0/64). Unfiltered pages slice the retained
collection; queries do not decode the file again.
For `chunks`, `names` matches the four-character IFF chunk tag (for example
`"AtU8"`, `"Code"`, `"ExpT"`); available `fields` are `index`, `name`,
`location`, and `payload`. For `lambdas`, `names` matches the resolved function
name from the atom table; `null` names (unresolved atom indices) are never
selected by a name filter. For `atoms`, `names` matches the decoded atom name
string.
`beam` keeps the module, form/Code bounds, original source coverage, and table
counts. A null count means no table was decoded; zero means a decoded empty
table. Missing tables and unresolved atom names remain explicit, and an empty
+210 -8
View File
@@ -52,6 +52,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -115,6 +124,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -178,6 +196,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -241,6 +268,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -304,6 +340,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -367,6 +412,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -430,6 +484,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -493,6 +556,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -556,6 +628,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -619,6 +700,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -682,6 +772,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -691,12 +790,8 @@
"type": "object",
"properties": {
"logical_path": { "type": "string" },
"declared_sha256": {
"anyOf": [{ "type": "string" }, { "type": "null" }]
},
"calculated_sha256": {
"anyOf": [{ "type": "string" }, { "type": "null" }]
},
"declared_sha256": { "type": ["string", "null"] },
"calculated_sha256": { "type": ["string", "null"] },
"unpacked": { "type": "boolean" }
},
"required": [
@@ -705,7 +800,7 @@
"calculated_sha256",
"unpacked"
],
"additionalProperties": false
"additionalProperties": {}
},
{
"type": "object",
@@ -731,7 +826,33 @@
"calculated_md5",
"unpacked"
],
"additionalProperties": false
"additionalProperties": {}
},
{
"type": "object",
"properties": {
"logical_path": { "type": "string" },
"algorithm": { "type": "string", "const": "dfu-crc32" },
"declared_crc32": {
"type": "string",
"pattern": "^[a-f0-9]{8}$"
},
"calculated_crc32": {
"anyOf": [
{ "type": "string", "pattern": "^[a-f0-9]{8}$" },
{ "type": "null" }
]
},
"unpacked": { "type": "boolean" }
},
"required": [
"logical_path",
"algorithm",
"declared_crc32",
"calculated_crc32",
"unpacked"
],
"additionalProperties": {}
}
]
}
@@ -797,6 +918,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -860,6 +990,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -923,6 +1062,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -986,6 +1134,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1049,6 +1206,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1112,6 +1278,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1175,6 +1350,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1238,6 +1422,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
@@ -1301,6 +1494,15 @@
},
"required": ["kind", "operation", "timeout_ms"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "retry_cleanup" },
"operation": { "type": "string", "minLength": 1 }
},
"required": ["kind", "operation"],
"additionalProperties": false
}
]
},
+348
View File
@@ -0,0 +1,348 @@
# ESP flash images
REA reads a dump of an ESP32-family SPI flash, such as the output of
`esptool read-flash 0 ALL dump.bin` or `esptool merge-bin`, through
`inspect_artifact` and `extract_artifact`. The reader is static: no device,
esptool or ESP-IDF is needed, and no code is run.
The container format is `esp-flash-image`. Entries are the bootloader region,
the partition table sector and one file per partition. The bootloader and
each app partition are also decoded as [ESP images](esp-image.md), so their
headers, segments, integrity and descriptors appear in the root
`format_metadata`. Layout facts are cited to ESP-IDF v6.1
(`esp_flash_partitions.h`, `flash_partitions.c`, `gen_esp32part.py`,
`bootloader_common_loader.c`, `bootloader_utility.c` and each chip's
`esp_rom_caps.h`).
## Recognition
A file is a flash image when REA finds a partition table in the entry
structure ESP-IDF's bootloader verifies (`esp_partition_table_verify`):
32-byte `0x50AA` entries, at most one `0xEBEB` MD5 entry over the entries
before it, and a terminating entry within `0xC00` bytes. ESP-IDF accepts a
table with no partition entries, but REA requires one, so that neither
erased flash nor a lone MD5 entry is read as a table. A table whose MD5 does not match, or that has a second MD5 entry, is
still read, with `md5.status` `invalid` and a limitation, because the
bootloader would refuse it. The table is located in this order:
1. at the caller's `partition_table_offset`, when given; no scan follows;
2. at the default `0x8000`;
3. by scanning every `0x1000`-aligned flash sector for a table whose MD5
verifies. This location is derived, and the inventory says so.
A table never lies inside the located bootloader image, so the default
offset and the scan are tried only after the image's last segment. Without
a caller selection, the scan runs only when an ESP image sits at a ROM
bootloader offset, and a single app or bootloader image ends at its last
segment, so neither ordinary files nor single images are scanned. A C3 or
S3 dump begins with its bootloader, so flash layouts are detected before a
file is read as one ESP image.
Unless the caller names the table with `partition_table_offset`, REA
refuses two kinds of dump before looking for a table:
- an image of a known chip at another chip's ROM offset, such as an ESP32
bootloader at the start of a dump read from `0x1000`. The dump's origin
is not the flash address REA assumed, given or default, and a table found
there would place every partition at the wrong file offset; pass the
`flash_offset` where the dump starts. When a partition table sits where
the image's own ROM offset puts `0x8000`, after the image's segments, the
refusal names that offset.
Without a selected format such a dump opens as its bootloader image, whose
[ESP image](esp-image.md) facts say how to reopen it.
- an ESP8266 image at a ROM offset. ESP8266 dumps can hold the same table
format, and they keep the [ESP image](esp-image.md) reader's ESP8266
refusal.
When no table is found the layout is not recognized. This is an unknown
outcome, not a malformed file: flash encryption, which encrypts the
bootloader and the table, is one cause. When a table is found without a
located bootloader and `flash_offset` is omitted, a limitation says that
each partition's file offset assumes the dump starts at flash `0x0`.
## Caller inputs
| Input | Meaning |
| ------------------------- | ----------------------------------------------------------------------------- |
| `format: esp-flash-image` | Read the file as a flash dump; with no table found, refuse it |
| `flash_offset` | Flash address (`0x…`) of the dump's first byte; default 0 |
| `partition_table_offset` | Flash address (`0x…`, a multiple of `0x1000`) of the table; disables the scan |
MCP takes these on `open_binary`; the CLI takes `--target-format
esp-flash-image`, `--flash-offset` and `--partition-table-offset` on
`inspect-artifact` and `extract-artifact`. The offsets require the format, and
the format takes no `architecture` or `entry_point`. The inputs are part of
the target's identity, so reopening the same bytes with other inputs reads
them again instead of reusing the earlier reading. The `open_binary` summary
reports them as `flash_offset` and `partition_table_offset`, null when
omitted.
## Layout facts
The root `format_metadata` (`esp-flash-image`) records:
- `flash_offset`: its `value` and whether the `caller` gave it or it is the
`default`;
- `chip`: from the bootloader's `chip_id`, or null without a located
bootloader;
- `bootloader`: the region from the chip's ROM bootloader offset to the
table, with its ESP image facts;
- `partition_table`: its flash address, the `method` that found it
(`default`, `caller` or `scan`), the partition count and the MD5 `status`
(`valid`, `invalid` or `absent`) with the stored and computed digests;
- `apps`: ESP image facts for each app partition the dump holds; app
partitions declared over the same bytes share one decode;
- `otadata`: the otadata partition's records, or null without one;
- `boot_selection`: the app the bootloader would select, for every table;
- `partitions_outside_dump`: declared partitions that begin before the
dump's first byte, which have no entry;
- `undescribed`: ranges outside every region, each from a byte that is not
erased flash to the end of its gap.
The ROM loads the bootloader from `ESP_ROM_BOOTLOADER_OFFSET_FLASH`:
`0x1000` for ESP32 and ESP32-S2; `0x0` for ESP32-S3, C2, C3, C6, C61, H2 and
H21; and `0x2000` for ESP32-C5, H4, P4 and S31. A bootloader is located only
where its own `chip_id` says the ROM loads it.
### Partitions
Each partition entry is `partitions/<label>.bin` when the label is a safe
name of up to 16 letters, digits, `.`, `_` and `-` that starts with a
letter, digit or underscore and has at least one letter or underscore, is
not a Windows device name such as `NUL` or `COM1`, alone or before a dot,
and no other label repeats it, ignoring case as some file systems do; it is
`partitions/<index>.bin` otherwise. `source_path` keeps the label, the
table's own name for the partition. Its `format_metadata`
(`esp-flash-partition`) records the table `index`, `label` (null when not
UTF-8), `label_hex` (the label's bytes when it is not UTF-8, else null),
`type`, `subtype`, ESP-IDF's `type_name` and `subtype_name`,
`flash_address`, `size`, the `encrypted` and `readonly` flags and any
`unknown_flags`. The `encrypted` flag says how a device with flash
encryption stores the partition; it does not establish that the dump's
bytes are ciphertext.
Each partition entry also records where the bytes the dump holds are
written, relative to the partition's start: `written_offset`, the first
byte that is not erased flash (`0xFF`), `written_end`, just past the last
such byte, and `written_bytes`, how many bytes are not `0xFF`. A partition
erased throughout has null offsets and `written_bytes` `0`; a partition the
file holds none of has all three null. The range bounds the written
bytes and does not say whether erased flash lies between them; extract the
partition to read them. REA reads the bytes the partitions cover once, so
overlapping partitions share that read however many the table declares.
ESP-IDF 5.3 and later may declare primary `bootloader` and `partition_table`
entries over the bootloader region and the table sector. They are
partitions like any other, whose bytes are also `bootloader.bin` and
`partition-table.bin`, and they are exempt from the rule that partitions
follow the table, as in `gen_esp32part.py`.
Every partition from the dump's first byte on is an entry holding the bytes
the file has. One that the file holds only in part, as when an `esptool
merge-bin` dump ends inside its last partition, has an entry limitation that
gives the size the table declares; one that lies past the end of the file is
an entry with no bytes and a limitation, and has no image facts. A partition
that begins before the dump's first byte has no entry and is listed in
`partitions_outside_dump`. One unreadable partition does not hide the
others: partitions outside the dump or overlapping another are each reported
while the rest stay readable.
### App partitions and the bootloader
Each app partition and the bootloader region carry their entry's `path`
and ESP image `facts`:
`status` is `esp-image`, `erased` (the image header bytes are erased flash,
as in an empty OTA slot) or `unrecognized` (ciphertext or other data). For an
image they hold the [ESP image](esp-image.md) root facts, `segments`,
`signature_blocks`, `coverage` and `limitations`, with offsets relative to
the region. Extract a partition and inspect it as an ESP image for its
segments as entries.
The summary view (`inspect_artifact` with `detail: "summary"`, or the
inventory summary of `inspect_analysis_view`) reports the root facts under
their own kind, `esp-flash-image-summary`, and gives each of these images a
`summary` instead of its `facts`: `status`, `chip`, `entry_addr`,
`segment_count`, `load_ranges` (the load address, declared length and
classification of each segment that is mapped, loaded or unclassified),
`signature_block_count`, the `descriptor` kind with the fields that tell
builds apart (an app's `project_name`, `version`, `idf_ver`,
`secure_version` and `app_elf_sha256`; a bootloader's `version`, `idf_ver`
and `secure_version`), the `checksum` and `appended_sha256` statuses,
`coverage` and `limitations`. The default complete detail keeps the full
facts, every segment included, and the summary's `inventory_evidence_ids`
name the Evidence that holds them.
`erased` is the bootloader's view: with no image header at the start of the
slot, it finds no app there. An erased slot can still hold data further in,
even a complete app image. When it does, its entry's `written_offset` and
`written_end` say where, and a limitation says that the slot holds bytes the
bootloader does not read; REA does not look for images in them.
### OTA data
The otadata partition holds two `esp_ota_select_entry_t` records, one at the
start of each 4 KiB sector. Like the bootloader, REA reads the last otadata
partition the table declares, and the last factory app and OTA app of each
subtype; a limitation names any repeated declaration. REA reports each
record the partition and the dump hold: its `ota_seq`, `seq_label`,
`ota_state` and its name, `crc`, whether the CRC matches (CRC-32 of
`ota_seq` seeded with `0xFFFFFFFF`) and whether the bootloader would treat
it as valid.
### Boot selection
`boot_selection` is present for every table. It is derived as
`bootloader_utility_get_selected_boot_partition` does without app rollback
or anti-rollback, and `basis` names its branch:
- `otadata`: the valid record with the higher `ota_seq` (record 0 on a tie)
selects slot `(ota_seq - 1) % ota_app_count`, indexed by OTA subtype;
- `factory-fallback`: no record is usable, or the table has no OTA app, and
a factory app is declared; or records are set but none is valid, as after
a CRC mismatch, which selects the factory index whether or not a factory
app is declared;
- `ota_0-fallback`: no record is usable, or the table has no OTA app, and no
factory app is declared, so the bootloader selects boot index 0;
- `no-otadata`: the table declares no otadata partition, or one at offset 0,
which the bootloader takes for none, so it selects the factory index, as in
the common factory-only layout;
- `none`: the bootloader rejects the table, for an MD5 mismatch or a second
MD5 entry, or cannot read an otadata partition smaller than two 4 KiB
sectors, and resets without selecting an app;
- `unknown`: the dump lacks an otadata record the bootloader reads.
Without an OTA app the records do not matter, so a dump that lacks them
still selects a fallback. REA does not compare the partitions with the
flash size in the bootloader header, which the bootloader also checks.
`active_record` and `slot` are set for `otadata` and `slot` for
`ota_0-fallback`. `partition_index` is null when the table declares no
partition for the selected slot or fallback; the bootloader then tries the
other apps. `assumptions` list what the derivation leaves out: records used
as written (for the branches that read them), anti-rollback, load fallback,
and factory-reset and test-app GPIO holds and a wake from deep sleep; they
are empty for `none` and `unknown`. `running_partition` is always
`unknown`: at boot the bootloader may first mark a pending-verify record
aborted, check the app's secure version against eFuse, and fall back to
another partition when an app fails to load.
## Coverage and extraction
Erased flash (`0xFF`) between regions is described. In a gap between
regions, the first byte that is not erased starts an `undescribed` range to
the end of the gap, and makes coverage `unknown`; the rest of that range is
not examined. Coverage is also `unknown` when no bootloader is located, or
when a declared partition begins before the dump's first byte, since it has
no entry. A partition cut short or left out by the end of the file does not
make it `unknown`: its entry holds the bytes the file has, none for one past
the end, and its limitation says what the file lacks. Coverage describes the
inventory of the file, not the observation of every declared flash byte.
So artifact comparison classifies every partition entry of two dumps. Where
both lack the same bytes, as two `merge-bin` dumps of the same layout do,
the entries are equal; a dump against a longer one reports each partition
it cuts short or lacks as `changed`, and that entry's limitation says the
file did not hold the bytes.
`extract_artifact` writes every entry as an ordinary file, `nvs_keys`
partitions included. On a device without flash encryption an `nvs_keys`
partition holds the NVS encryption keys in plain text, and a dump of it
contains them. REA decrypts nothing, recovers no keys and does not interpret
NVS.
## Comparison
Artifact comparison uses each partition's `label`, `label_hex`, `type`,
`subtype`, `flash_address`, `size` and flags as its declaration, so a
partition that moves, changes type or flags, or is renamed with unchanged
bytes is `changed` in `metadata`. Partitions pair by path. The root facts
are not compared as a declaration: they derive from the dump's bytes, which
content comparison covers, and from the caller's `flash_offset` and
`partition_table_offset`, which are part of the target's identity and
decide each partition's byte range.
## Limits
- A flash dump inside another container, such as a ZIP, is classified from
its first bytes and so reads as an ESP image, not as a flash layout; extract
it first.
- ESP8266 dumps are refused; Secure Boot v1 bootloader digests and
flash-encrypted layouts are not read.
- Partitions are not checked against the flash chip size, which the
bootloader also verifies.
- NVS, FAT, SPIFFS and other data partitions are extracted, not decoded.
- An image nested in a partition is decoded for its facts; its segments are
not inventory entries.
## Verification
Synthetic fixtures with computed MD5s and CRCs, and one CRC otatool wrote,
cover the table rules, every way the table is located, the scan's chunk and
sector edges, the misplaced-origin guard with the origin omitted or given,
the ESP8266 refusal, the `0x0`, `0x1000` and `0x2000` ROM bootloader
offsets, single images that are not scanned, overlaps, primary entries,
truncation and partitions outside the dump, index names for unsafe labels,
repeated declarations, the otadata states and records, and each branch of
the bootloader's selection. CLI and stdio MCP tests exercise the inputs and
their identity and extraction. MCP `compare_artifacts` tests compare
partition declarations, labels that are not UTF-8 included, two dumps that
end inside their last partition, and such a dump against a full one.
The optional real lane builds its dumps with esptool and ESP-IDF's own tools:
```sh
REA_RISCV_CLANG=/path/to/clang REA_RISCV_LLD=/path/to/ld.lld \
REA_ESPTOOL_PYTHON=/path/to/python-with-esptool \
REA_ESP_IDF_PATH=/path/to/esp-idf \
npm run verify:esp-flash
```
It converts the conformance ELFs with esptool, builds a default, a
relocated and a primary-entry partition table with `gen_esp32part.py`,
assembles dumps with `esptool merge-bin`, one of them written from flash
`0x1000` with `--target-offset`, and writes OTA records with `otatool`
through `tests/conformance/esp/flash_file_tool.py`. That script replaces
parttool's esptool calls with reads and writes of the dump file, so
otatool's own OTA logic runs unchanged. The lane then checks:
- the bootloader and app facts against `esptool image-info`;
- every partition against `gen_esp32part.py`'s decoding of the table REA
extracted;
- both OTA records against `otatool read_otadata` after switching to slot 0,
then to slot 1, then erasing the otadata, and the derived selection after
each: slot 0, slot 1, then the factory fallback;
- that each extracted file is the dump's bytes at its range and that the
source is unchanged;
- a table with the primary `bootloader` and `partition_table` entries that
`--primary-bootloader-offset` fills, read with complete coverage and no
overlap or misplacement limitation; it has no otadata, so its boot
selection is `no-otadata` with the factory app;
- the relocated table, found by scanning and named with
`partition_table_offset`, and the dump written from `0x1000`, read with
`flash_offset` and, without it, left an ESP image whose `flash_dump`
names `flash_offset` `0x1000`;
- an app with a flipped byte beside valid partitions, and a truncated dump,
read with complete coverage and its missing partitions as entries with no
bytes.
The lane ran on macOS arm64 with Node 24.22.1, Homebrew Clang and LLD 21.1.8
and ESP-IDF v5.5.2's `gen_esp32part.py`, `otatool.py` and `parttool.py`,
once with esptool v5.6.0 and once with 4.12.0. Its `gen_esp32part.py` is
identical to v6.1's, and its otatool and parttool differ only in import style
and esptool command spelling. All checks passed in both runs. With v5.6.0 and
the build already cached, the run took 24.1 s elapsed, 16.9 s user CPU and
6.3 s system CPU under `/usr/bin/time -l`; the largest single process peaked
at a 278 MiB resident set, and the combined peak of all processes was not
measured.
The lane does not establish:
- dumps read from a device: its dumps come from `merge-bin`;
- chips other than ESP32-C3 and ESP32;
- `ota_state` and record validity, which `read_otadata` does not print, or
OTA states the bootloader writes itself, app rollback or anti-rollback;
- selection with gaps or repeated OTA subtypes, which otatool does not
write;
- flash-encrypted dumps, which are not recognized by design.
It does not run in CI.
+333
View File
@@ -0,0 +1,333 @@
# ESP images
REA reads ESP32-family firmware images through `inspect-artifact` /
`inspect_artifact` and `extract-artifact` / `extract_artifact`. OTA downloads,
app partitions and second-stage bootloaders are all ESP images. Reading is
static: no esptool, device or flash access is needed, and no code is run or
disassembled.
```sh
rea inspect-artifact ./firmware.bin --json
rea inspect-artifact ./ota.bin --detail summary --evidence_output evidence.json --json
rea extract-artifact ./firmware.bin --json
```
For MCP, open the file with `open_binary`, then call `inspect_artifact`. The
summary's `root_format_metadata` holds the image facts. Use its
`parent_evidence_id` with `inspect_analysis_view` to select segment rows.
`extract_artifact` writes one file per segment and per signature block.
For agent sessions, advertise the `session,artifact,application` tool families
and consider setting both `REA_MCP_INPUT_SCHEMA_PROFILE` and
`REA_MCP_OUTPUT_SCHEMA_PROFILE` to `compact`. The output profile bounds each
advertised output schema to 4,096 bytes by relaxing nested client validation;
REA still validates complete results against the canonical contracts. Pair
this with retained summaries and selected inventory views to limit the context
needed for one question. See [MCP contracts](mcp-contracts.md) for the profiles
and their client compatibility tradeoffs.
The layout facts follow ESP-IDF v6.1 (`esp_app_format.h`, `esp_app_desc.h`,
`esp_bootloader_desc.h`, `esp_image_format.c`, the ROM `secure_boot.h`
headers and `docs/en/security/secure-boot-v2.rst`) and esptool v5.6.0
(`bin_image.py`, `cmds.py`, `loader.py`, espsecure).
## Recognition
The container format is `esp-image`. A file is an ESP image when its bytes
show all of these; the file name does not matter:
- magic `0xE9` at offset 0 and 1 to 16 segments. A count of 0 is valid but
loads nothing, and accepting it would admit x86 programs that begin
`E9 00`;
- a flash mode and flash speed code that ESP-IDF's enums define;
- a `hash_appended` byte of 0 or 1, as esptool requires;
- when the file holds the first segment header, a segment length that is a
multiple of 4.
Later segments, the checksum and the digest are integrity facts. A truncated
download is still an ESP image, with its integrity reported as unavailable.
| Input | Outcome |
| --------------------------------------------------- | ---------------------------------------------------------------- |
| ESP8266 image (v1 or v3): same magic, 8-byte header | Refused: "ESP8266 firmware images are not supported…" |
| Flash-encrypted image (ciphertext) | Not recognized: the bytes carry no magic. Unknown, not malformed |
| ESP8266 v2 image (magic `0xEA`) | Not recognized |
An ESP8266 image has its first segment header at offset 8, and that segment
loads into ESP8266 DRAM, IRAM (Arduino's eboot links at `0x4010F000`) or the
IROM flash window, where v3 and Arduino app images begin. In an ESP32-family
header the same bytes hold `wp_pin` and the drive settings, which esptool and
ESP-IDF write as `0xEE` and zeros. Inside another container, an ESP8266 image
is classified as a plain file.
Recognition cannot exclude every file that starts with an x86 near jump
(`0xE9`), such as a DOS `.com` program. An explicit `--target-format dos-com`
or `raw-binary` selection skips ESP recognition.
## Image facts
Root `format_metadata` has `kind: "esp-image"`:
| Field | Label | Meaning |
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `header` | observed | Every `esp_image_header_t` field, with ESP-IDF's names, for example `entry_addr`, `chip_id`, `min_chip_rev_full` |
| `chip` | derived | `target`, `isa` and the table's `source` (`ESP-IDF v6.1`) from `chip_id`; `null` for an ID the table lacks |
| `checksum` | derived | `offset`, `stored`, `computed` and `status`: `valid`, `invalid` or `unavailable` |
| `appended_sha256` | derived | The digest `hash_appended` adds: `offset`, `stored`, `computed` and `status`, including `not-appended` |
| `image_end` | derived | End of the checksum block and any appended digest; `null` when the file ends before it |
| `signature_sector_offset` | derived | The Secure Boot v2 signature sector, when it holds at least one valid block |
| `undescribed_trailing` | derived | Byte ranges after the image that nothing describes; empty when every byte is described |
| `descriptor` | observed | `esp_app_desc_t` or `esp_bootloader_desc_t` fields, with ESP-IDF's names, and the descriptor's file `offset` |
The descriptor's `kind`, derived from its magic, classifies the image as an
`app` or a `bootloader`. Without a descriptor the role is unknown.
The ISA comes from `chip_id`, never from the series letter: ESP32-S31 is
RISC-V. The chip table covers every `esp_chip_id_t` in ESP-IDF v6.1:
| `chip_id` | Target | ISA | `chip_id` | Target | ISA |
| --------- | --------- | ------ | --------- | ---------- | ----- |
| `0x0000` | `esp32` | xtensa | `0x0012` | `esp32p4` | riscv |
| `0x0002` | `esp32s2` | xtensa | `0x0014` | `esp32c61` | riscv |
| `0x0005` | `esp32c3` | riscv | `0x0017` | `esp32c5` | riscv |
| `0x0009` | `esp32s3` | xtensa | `0x0019` | `esp32h21` | riscv |
| `0x000C` | `esp32c2` | riscv | `0x001C` | `esp32h4` | riscv |
| `0x000D` | `esp32c6` | riscv | `0x0020` | `esp32s31` | riscv |
| `0x0010` | `esp32h2` | riscv | | | |
Other IDs, for example esptool v5.6.0's ESP32-E22 (`0x1F`), have `chip: null`
and unclassified segments.
**Integrity.** The checksum is the XOR of every segment data byte, seeded with
`0xEF`, in the last byte of the next 16-byte block. The appended SHA-256 covers
every byte through the checksum. Both are facts, not admission rules: a
mismatch is reported with a limitation and the segments stay readable.
**Descriptors.** Both descriptors begin segment 0: ESP-IDF's bootloader reads
the app descriptor there, and esptool places the bootloader descriptor there.
Strings stop at the first NUL and are omitted, with a limitation, when they
are not valid UTF-8. The bootloader descriptor's magic is a single byte
(`0x50`), so its reserved bytes, which ESP-IDF zero-fills, must also be zero.
A `--ram-only-header` image puts RAM segments first, so its app descriptor is
not in segment 0 and its role is unknown. `mmu_page_size` keeps ESP-IDF's
name and value: the MMU page size as a base-2 logarithm, as
`esp_app_desc_t` declares it, so `16` means 64 KiB. It is `0` in images built
before ESP-IDF v5.4. `spi_flash_mode` is the next byte, which ESP-IDF v6.1
names, kept as stored and not interpreted; releases that reserve it leave it
`0`. A file can end inside the app descriptor: every field it holds in full
is kept, a field it cuts off is `null`, and `observed_length` gives the
descriptor bytes the file holds. The segment's truncation is reported as usual.
## Segments
Each header-declared segment is one entry named `segment-<index>.bin`, for
example `segment-00.bin`, so two builds pair segments by position. Its
`source_location` is the segment data in the image file. `format_metadata`
(`esp-image-segment`) records `index`, `load_addr`, `data_len`,
`header_offset` and `classification`.
`classification` follows ESP-IDF's bootloader (`should_map` and
`should_load` in `esp_image_format.c`) on a cold boot:
| Value | Meaning |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `mapped` | Inside the chip's IROM, DROM or, where the MMU is per memory type, PSRAM range (`soc.h`) |
| `loaded` | `should_load` selects it: not mapped, at or above `0x10000000`. RAM ranges are not checked |
| `not-loaded` | Below `0x10000000`: the padding segments esptool writes at address 0, including `--secure-pad-v2` |
| `unknown` | The chip is unknown, or the mapping depends on the bootloader's MMU page size |
`loaded` does not mean the bootloader accepts the segment. Before loading,
ESP-IDF's `verify_load_addresses` rejects the image when a loaded segment is
not entirely in the chip's DRAM, IRAM, RTC memory or SPM, or overlaps the
bootloader's own stack, data or loader code. For example, an ESP32-C6
segment at `0x43000000` is `loaded` here and rejected there. REA models no
RAM ranges.
Padding segments are inventoried and extractable, but are never load regions.
When waking from deep sleep, ESP-IDF also skips RTC-memory segments; that
runtime case is not modelled. ESP32-C6 and ESP32-H2 map flash from
`0x42000000` through 256 pages of the bootloader's configured MMU page size,
which no image records. The app descriptor's page size describes the app's
build, not the bootloader's. With page sizes from 8 KiB (which the hardware
supports and esptool builds for) to 64 KiB, segments from `0x42200000` to
`0x43000000` are `unknown`. Ranges follow ESP-IDF's `should_map`, not esptool's
wider placement ranges.
A segment cut short by the end of the file is the bytes the file holds, with
an entry limitation that gives the declared length.
Artifact comparison uses each segment's `load_addr` and `data_len` as its
declaration, so a segment that moves with unchanged bytes is `changed` in
`metadata`. Segments pair by index: inserting or removing one shifts the
paths of the segments after it. Header offsets and `classification` are not
compared; the image's own change covers the header and chip. The image's and
the signature blocks' `format_metadata` carry no declaration: their fields
come from their own bytes, which content comparison covers, and a block's
`image_digest_matches` follows the image's content.
## Secure Boot v2 signatures
A signature sector starts at the next 4 KiB boundary after the image and
holds up to three 1216-byte slots. A slot holds a block when it has the `0xE7`
magic and a matching CRC32 at offset 1196, the rule ESP-IDF and espsecure
apply. Each slot is examined independently, so an erased or damaged slot does
not hide a later block. Each block is an entry, `signature-block-<slot>.bin`,
with `esp-image-signature-block` metadata:
| Field | Meaning |
| ---------------------------------- | ------------------------------------------------------------------------- |
| `index`, `version`, `scheme` | Slot 0 to 2, version byte, and `rsa` (`0x02`) or `ecdsa` (`0x03`) |
| `sha_version`, `curve_id` | For ECDSA, the third byte (0 is SHA-256, 1 SHA-384) and the curve ID byte |
| `digest_algorithm`, `image_digest` | The digest the block records |
| `image_digest_matches` | Whether it equals the digest of every byte before the sector |
| `public_key_sha256` | SHA-256 of the block's public key, the value eFuse key digests hold |
| `signature_status` | Always `unverified` |
Signatures are observed, not verified: REA checks no public key, signature or
eFuse. `public_key_sha256` is the digest espsecure `signature-info-v2`
prints, so it can be compared with `espefuse summary`. An unknown version or
`sha_version` leaves the layout-dependent fields `null`. A slot with the magic
but no matching CRC32 is not a block: it is reported in a limitation, whether
or not another slot holds a block, and its bytes are undescribed. Secure Boot
v1 signatures are not decoded.
## Coverage and limitations
Bytes after the image are described when they are erased flash (`0xFF`) or a
signature sector's blocks; erased slots and slack in that sector count as
erased flash. Every other byte is in `undescribed_trailing`.
| Situation | Outcome |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Segment data or a segment header beyond the end of the file | Truncation limitation; checksum and SHA-256 `unavailable`; coverage `unknown` |
| File ends before the checksum or the appended digest | That status is `unavailable`, with a limitation; coverage `unknown`, since a signature sector could follow |
| Checksum or SHA-256 mismatch | `invalid`, with a limitation; coverage is unchanged |
| Erased flash after the image that runs past the three slots | Described; no limitation |
| Erased flash that ends before the three slots are complete | Limitation; coverage `unknown`, since a signed image cut there would have lost its sector |
| A signature sector the file cuts short | Limitation; coverage `unknown` |
| Any other bytes after the image | Ranges in `undescribed_trailing`, a limitation, and coverage `unknown` |
| Segment length that is not a multiple of 4 | Limitation: ESP-IDF rejects such an image |
| Segment of 16 MiB or more, except on chips with 32-bit cache addresses | Limitation: ESP-IDF rejects such an image |
ESP32-S3, ESP32-P4, ESP32-C5, ESP32-C61, ESP32-H4 and ESP32-S31 map flash
through 32-bit cache addresses (`SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP`), so
ESP-IDF sets no 16 MiB segment limit for them. For an unknown chip the limit
is not applied.
Undescribed bytes include other flash contents such as an app partition's
stale data, a Secure Boot v1 signature, and the flash segments that esptool
`--ram-only-header` writes after the checksum without counting them. REA
reports their ranges and does not guess their structure. In the padding, in a
sector without a valid block, in the sector's slack and after the sector, the
first byte that is not erased starts a range to the end of that region. A
slot that is neither a block nor erased is reported whole. Adjacent ranges
are merged.
A whole-flash dump whose partition table REA finds is read as an
[ESP flash image](esp-flash-image.md). Without one, a dump of a chip whose
bootloader is at offset 0, such as ESP32-C3 or ESP32-S3, is recognized as that
bootloader image, and the partition table, apps and data follow as
undescribed bytes.
A dump read from a later flash address, such as an ESP32 dump read from
`0x1000` with `read-flash 0x1000 …` or `merge-bin --target-offset 0x1000`,
opens as its bootloader image. REA says so when the file shows it: the image
at file offset 0 is not an app, its chip's ROM loads the bootloader from a
later address (`0x1000` for ESP32 and S2, `0x2000` for C5, H4, P4 and S31),
and a partition table sits where that origin puts `0x8000` (file offset
`0x7000` for `0x1000`), after the image ends. Then `flash_dump` records
`flash_offset` and
`table_file_offset`, a limitation says to reopen the file with
`format: "esp-flash-image"` and that `flash_offset`, and `inspect_artifact`
lists the same `open_binary` call as a next probe. `flash_dump` is null for
every other image. Evidence that REA recorded before it reported the field
omits it; REA still reads and compares such Evidence, and the omission says
nothing about where the file was read from.
## Limits
An image has at most 16 segments and 3 signature blocks. Decoding reads each
byte once and retains only fixed-size fields. It reads through erased flash to
find the first byte that is not erased, and stops there. There is no
file-size limit.
## Analyzing a segment
Inventory does not disassemble code or build a memory map. `open_binary`
admits neither RISC-V nor Xtensa as a raw-binary architecture, so a segment
cannot yet be opened as a raw image. Each segment's `source_location` and
`load_addr` give the file offset and address to map it at by hand, for
example with the Rizin tool's `om` command.
## Verification
Synthetic fixtures with computed checksums, digests and CRCs cover
recognition, ESP8266 layouts, the ESP32, ESP32-C3, ESP32-C6 and ESP32-P4
memory maps, truncation at each structure, integrity mismatches, erased and
undescribed trailing bytes, signature slots including damaged and unknown
layouts, and the refusals. CLI and stdio MCP tests exercise the same artifact
provider and advertised schemas.
The optional real lane builds ELF files from source, converts them with
esptool and compares REA's facts with `esptool image-info`:
```sh
REA_RISCV_CLANG=/path/to/clang REA_RISCV_LLD=/path/to/ld.lld \
REA_ESPTOOL_PYTHON=/path/to/python-with-esptool \
npm run verify:esp-images
```
It needs a Clang with the RISC-V target, `ld.lld` and a Python that can run
`esptool` and `espsecure`. It checks each tool before building anything and
names the variable to set when one is missing, and it warns when esptool is
not v5.6.0, the version whose sources the layout facts cite. It builds an
ESP32-C3 app, the same app with `--dont-append-digest`, `--secure-pad-v2`,
`--ram-only-header` and `--pad-to-size`, a bootloader, and ESP8266 v1 and v3
images. It then checks:
- entry point, chip ID, each segment's length, address and header offset,
checksum, digest and descriptor fields against `image-info`;
- each segment's classification in the app, its variants and the
bootloader, whose linker scripts fix every section's region, including the
`not-loaded` padding that `--secure-pad-v2` adds;
- after signing with RSA-3072 and appending ECDSA P-256 and P-384 blocks,
each block's `public_key_sha256` against espsecure `signature-info-v2`,
and that its recorded digest matches;
- extraction of the app and the signed image: each segment file equals the
bytes after esptool's segment header offset, each signature block equals
its slot in the sector espsecure appended, and the source's SHA-256 is
unchanged and equals the reported `root_sha256`;
- the `--pad-to-size` erased tail is described; the `--ram-only-header`
flash segments are undescribed;
- a flipped checksum, a flipped digest byte, a truncated segment (where
`image-info` stops with "End of file reading segment"), and both ESP8266
refusals.
`REA_XTENSA_ESP32_GCC` adds an ESP32 (Xtensa) app built with Espressif's
`xtensa-esp32-elf-gcc`. Without it the report marks that image as skipped.
Each command runs as a process tree owned by the shared verifier command
owner, so a timeout also stops the linker or assembler a compiler driver
starts. The lane removes its workspace only after every tree has stopped and
keeps it when that cleanup cannot be confirmed.
The lane ran on macOS arm64 with Node 24.22.1, Homebrew Clang and LLD 21.1.8,
and `xtensa-esp32-elf-gcc` 14.2.0 (crosstool-NG esp-14.2.0_20251107), once
with esptool and espsecure v5.6.0 and once with 4.12.0. All checks passed for
all 13 images in both runs. With v5.6.0 the complete run, including the builds
and every esptool call, took 21.7 s elapsed, 16.9 s user CPU and 6.1 s system
CPU under `/usr/bin/time -l` with warm caches. The largest single process
peaked at a 253 MiB resident set; the combined peak of all processes was not
measured.
The lane does not establish:
- the other chips: only ESP32-C3 (RISC-V) and ESP32 (Xtensa) images come
from a real producer; the other rows of the chip table are checked against
the ESP-IDF v6.1 headers by reading them;
- images from a real ESP-IDF project build, an OTA download or a device's
flash, none of which it inspects;
- that a signature is valid: it compares digests, and nothing verifies a
signature.
It does not run in CI.
+13 -2
View File
@@ -28,6 +28,15 @@ The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
no system tools and changes no user configuration. [Upstream provenance and
license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md) are packaged.
Limiter discovery validates the selected path without launching it. A relative
path or directory is reported as `not_configured`, a missing file as
`executable_missing`, and denied access or another filesystem-probe failure as
`runtime_unavailable`. Recovery names the selected path and
`REA_EVM_PRLIMIT_COMMAND`; denied access asks for execute permission and
filesystem access. The original admission or system code stays in diagnostics.
An available filesystem probe does not verify the selected tool's version or
util-linux compatibility, and REA does not substitute another command.
## Evidence semantics
- `encoding` is required: raw bytes or UTF-8 hex, with optional `0x` prefix and
@@ -80,8 +89,10 @@ without bytecode text or duplicated raw stdout:
}
```
The result is distinct derived Evidence linked to its complete parent. Its
`normalized_result.summary` preserves every producer-returned selector, decoded
The result is distinct view Evidence linked to its complete parent. Like the
parent, it has `confidence: inferred`; projection never strengthens inferred
candidates. Its `normalized_result.summary` preserves every producer-returned
selector, decoded
byte offset, dispatch, argument/mutability string including null, carrier/decoded
identities, original limitations and explicit unknowns. Payload `evidence_kind`
remains `inferred`. It preserves stderr/truncation and records stdout text length.
+75 -1
View File
@@ -26,6 +26,9 @@ REA does not install these tools, their dependencies or system packages. It
checks the tool's `--version` banner when executing a request. Binwalk must
print `binwalk <version>` and Unblob must print the version alone. Inspection and
extraction have independent prerequisites; one does not require the other.
Capability discovery recommends `REA_BINWALK_COMMAND` for missing inspection
and `REA_UNBLOB_COMMAND` for missing extraction. Configuring the other engine
does not enable the unavailable operation.
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed. `rea doctor` does
not currently inspect firmware readiness. Engine selection/version checks run
@@ -82,6 +85,11 @@ Published file `relative_path` preserves producer spelling for lineage; `path`
uses the writer's actual normalized filename. An extracted filename that cannot
be represented losslessly as UTF-8 fails publication explicitly with its raw
name bytes in hexadecimal instead of silently omitting the entry.
Unpublished symlinks retain the extracted tree's target bytes in lowercase
`link_target_hex`; `link_target` is null when those bytes are not valid UTF-8.
Older retained Evidence may omit the hexadecimal field. Unblob may have rewritten
the target before REA observed it, so these bytes are not necessarily those stored
in the firmware image.
## CLI and MCP
@@ -275,7 +283,7 @@ Additional lanes are explicit, so the base lane stays small:
REA_FIRMWARE_VERIFY_EXT4=1 npm run fixtures:firmware
REA_FIRMWARE_VERIFY_EXT4=1 npm run verify:firmware
# Existing Ghidra 12.1.x and its declared JDK required.
# Existing Ghidra 12.x (>=12.1) and its declared JDK required.
REA_FIRMWARE_VERIFY_GHIDRA=1 npm run verify:firmware
```
@@ -285,3 +293,69 @@ REA_FIRMWARE_VERIFY_GHIDRA=1 npm run verify:firmware
`scripts/rea.mjs` to verify the packaged CLI/MCP workflow. Real coverage here is
generated gzip/USTAR/ext4 with a Linux x64 ELF child; SquashFS, UBIFS, proprietary
firmware, other hosts and architectures remain unverified.
## Inspect ESP images
Use `inspect-artifact` / `inspect_artifact` on an ESP32-family app or
bootloader image, such as an OTA download or app partition, for its chip,
classified segments, checksum and SHA-256 results, descriptor and Secure Boot
v2 signature blocks. `extract-artifact` / `extract_artifact` writes one file
per segment and signature block. Reading is portable and needs no external
tool. See [ESP images](esp-image.md).
## Inspect ESP flash dumps
Use the same tools on a whole-flash dump, such as `esptool read-flash 0 ALL`
output. The inventory reports the bootloader, the partition table and how it
was found, each partition with its declared type, flags and digest, ESP image
facts for the bootloader and each app partition, and the OTA records with a
derived selection; the running partition stays unknown. Extraction writes
each partition, `nvs_keys` included, as an ordinary file. For a dump that
does not start at flash address 0, select `esp-flash-image` with
`flash_offset`. See [ESP flash images](esp-flash-image.md).
## Inspect extracted devicetrees
Use [`inspect_devicetree`](devicetree.md) / `rea inspect-devicetree` on an
extracted DTB for its node/property tree, reservations, hardware summary and
selected phandle relationships. FDT v16/v17 inspection is portable and requires
no external tool or disassembler.
## Inspect Intel HEX and S-record images
Use `inspect-artifact` / `inspect_artifact` on an Intel HEX or Motorola S-record
file for its address regions, declared start address and record facts, then
`extract-artifact` / `extract_artifact` for one raw file per region. Decoding is
portable and requires no external tool. See
[Intel HEX and S-record images](hex-records.md).
## Inspect U-Boot legacy uImages
`inspect-artifact` / `extract-artifact` recognize a legacy uImage by its
`0x27051956` magic and inventory it without running or decompressing it.
The root facts record the 64-byte header (architecture, OS, type, and
compression as raw values and names, load and entry addresses, image name,
and timestamp) with the header and data CRC32 verified; the stored payload
is extractable as `payload.bin`, and multi-file and script images additionally
expose each sub-image as `image-<n>.bin` from the stored size table. Declared
addresses are facts from the header, not proven entry points, and compressed
payloads stay as stored. Intel HEX/S-record and FIT images are separate
readers and are not covered here. See [U-Boot legacy uImage](uimage.md) for
component padding, preserved names, resource policies and verification scope.
## Inspect UF2 and DfuSe images
Use `inspect-artifact` / `inspect_artifact` on a UF2 file for each familyID's
address regions and its block numbering, or on a DfuSe file for its targets,
elements and DFU suffix, then `extract-artifact` / `extract_artifact` for one
raw file per region or element. A DfuSe suffix CRC mismatch follows
`integrity_policy`. Decoding is portable and requires no external tool. See
[UF2 and DfuSe images](uf2-dfuse.md).
## Inspect U-Boot FIT images
Use `inspect-artifact` / `inspect_artifact` on a U-Boot FIT (`.itb`) for its
images, declared load and entry addresses, configurations and verified hashes,
then `extract-artifact` / `extract_artifact` for one raw file per image.
Decoding is portable and requires no external tool. See
[U-Boot FIT images](fit-images.md).
+172
View File
@@ -0,0 +1,172 @@
# U-Boot FIT images
REA inventories U-Boot Flattened Image Tree (FIT, `.itb`) files through
`inspect-artifact` / `inspect_artifact`, and materializes the stored bytes of
each image through `extract-artifact` / `extract_artifact`. Decoding is static
and needs no external tool.
```sh
rea inspect-artifact ./boot.itb --json
rea inspect-artifact ./boot.itb --detail summary --evidence_output evidence.json --json
rea extract-artifact ./boot.itb --json
```
For MCP, open the file with `open_binary`, then call `inspect_artifact`. Summary
delivery retains the complete Evidence; use `parent_evidence_id` with
`inspect_analysis_view` to read selected images and fields. `extract_artifact`
returns a fresh `output_root` containing one raw file per image. FIT summaries
use the current projection identity; there is no pre-coverage FIT summary variant.
## Recognition
A FIT is a flattened devicetree (FDT v16 or v17) with a top-level `/images`
node. Recognition walks the structure block within the first 4 KiB of the file
only; the file name does not matter. A devicetree whose `/images` node begins
after 4 KiB, a devicetree without `/images`, and any other FDT version stay on
the plain devicetree route: `open_binary` refuses them and
[`inspect_devicetree`](devicetree.md) still parses them. A U-Boot control DTB
that contains `/images` is treated as a FIT. The container format is `fit`.
Recognition is a probe. The complete devicetree parse decides, so a file whose
prefix declares `/images` but whose structure is malformed fails as a
malformed FIT rather than falling back to plain classification.
## Images and metadata
Each child of `/images` is one inventory entry named `images/<unit-name>.bin`,
listed in devicetree order. Its bytes come from exactly one data source:
| Source | Stored bytes |
| --------------- | --------------------------------------------------------------------------------------- |
| `data` | The embedded property value |
| `data-offset` | `data-size` bytes at `data-offset` past the devicetree end, aligned to 4 (`mkimage -E`) |
| `data-position` | `data-size` bytes at `data-position` from the start of the file (`mkimage -p`) |
`data-position` is read as U-Boot reads it: an offset from the start of the
FIT, not a machine address.
Image `format_metadata` (`fit-image`) records `index`, `name`, `description`,
`type`, `arch`, `os`, `compression`, `load` and `entry`, the `data_source`,
the raw `declared_offset` for external data, `image_data` (the shared-data
link), `cipher_present`, `cipher_algo`, `hash_algorithms`, `hashes_verified`,
`hashes_unverified`, `signature_count`, `signatures_unreadable` and
`signature_algorithms`. Algorithm lists are arrays. Nullable declaration fields
are absent unless named in `unreadable_fields`, which records parsing failures
per field. Hash or signature warnings do not make unrelated declarations unknown.
`load` and `entry` fold one or two 32-bit cells into a lowercase
`0x…` value, as U-Boot does, independent of `#address-cells`.
Root `format_metadata` (`fit`) records `fdt_version`, `fdt_totalsize`,
`external_data_base` (the aligned devicetree end), the root `description`,
`timestamp` (seconds since 1970) and `address_cells`, plus
`default_configuration` and `configurations`. A missing description or
timestamp is `null`.
Image bytes are reported as stored. Compressed images are not decompressed and
ciphered images are not decrypted: entries report `encrypted: false` and the
cipher declaration in `cipher_present` and algorithm in `cipher_algo`. A missing
or malformed algorithm stays `null`; it does not establish unciphered bytes.
Embedded `image-data` links require byte-identical stored properties; external
links require matching physical ranges.
The extracted file holds the stored bytes. Direct-analysis suggestions require
`compression: "none"` and `cipher_present: false`.
## Hashes and signatures
Hash subnodes are children whose name starts with `hash`. Inventory verifies
`crc32` (4 bytes, big-endian), `md5`, `sha1`, `sha256`, `sha384` and `sha512`
over the stored bytes, the same bytes mkimage hashed after any ciphering. All
hashes of an image are computed in one streamed pass while the inventory is
prepared, so a mismatch fails the whole inventory, naming the image, hash
node, algorithm, and the declared and computed values. A supported algorithm
whose value has the wrong length also fails.
Other algorithms such as `crc16-ccitt`, `crc8` or `sm3_256`, a hash without a
readable `algo`, and a hash without a `value` are not verified; each is
counted in `hashes_unverified` and reported as an image limitation.
Signature subnodes (names starting with `signature`) on images and
configurations retain the total declaration count, readable algorithms and
`signatures_unreadable`. Missing or malformed algorithms carry source limitations
so unknown declarations remain distinguishable from absence. Signatures are never
verified.
## Configurations
`/configurations` is optional. Each child records its `name`, `description`,
and the image references `kernel`, `firmware`, `fdt` (base plus overlays),
`ramdisk` and `loadables`, always as lists, plus its signature algorithms.
`compatible`, `fpga`, `script`, `cmdline` and `load-only` are not reported. A
reference to a name that is not an `/images` child, and a `default` that names
no configuration, are reported as limitations and kept as declared. Each
configuration carries `unresolved_reference_count`; dangling references share one
configuration-level diagnostic while every original reference remains available.
## Facts, limitations and failures
| Situation | Outcome |
| ---------------------------------------------------------------------------------------------- | ------------------------------------ |
| Malformed devicetree, truncated header, `totalsize` past the end of the file | Fails as malformed |
| No data source, more than one data source, external data without `data-size` | Fails as malformed |
| `data-offset`, `data-position` or `data-size` not one 32-bit cell, `image-data` not one string | Fails as malformed |
| External data outside the file, overlapping the devicetree or another image | Fails as malformed |
| Two images with an identical external range linked by `image-data` | Accepted as shared data |
| A dangling, chained or mismatched `image-data` link | Fails as malformed |
| Hash mismatch, or a supported hash value with the wrong length | Fails integrity |
| Unsupported, algorithm-less or value-less hashes | Unverified, reported as a limitation |
| Malformed `description`, `type`, `arch`, `os`, `compression`, `load`, `entry` or `timestamp` | `null`, reported as a limitation |
| Dangling configuration reference, missing default configuration | Reported as a limitation |
Failures apply to the whole file and return no partial inventory.
## Limits
The devicetree, including any embedded image data, must be at most 16 MiB; a
larger `totalsize` fails as a memory limit. Store large images externally
(`mkimage -E`): external data of any size is streamed from the file and never
loaded into memory. A FIT may declare at most 1,024 images and 1,024
configurations, and an image at most 8 hash nodes; the devicetree parser's
node, property and depth bounds also apply. The sum of declared image sizes
is bounded by the shared archive expansion budget. These are fixed resource
bounds with no runtime override. Configurable ceilings are tracked in
[#6176](https://github.com/morluto/rea/issues/6176).
## Analyzing an image
Inventory does not execute anything, and `load` and `entry` are declarations.
After extraction:
- inspect `flat_dt` images with [`inspect_devicetree`](devicetree.md);
- open an uncompressed image with `open_binary` using format `raw-binary`, an
architecture matching its declared `arch`, and `entry_point` set to its
declared `load` address; raw-binary maps file offset 0 and seeds analysis at
`entry_point`. Automatic suggestions require nonempty bytes, an admitted
architecture/address/extent, and a declared entry that is absent or equal to
load. Distinct or unreadable entry declarations remain in Evidence without
a raw-loader suggestion, since this loader cannot represent separate mapping
and analysis entry addresses.
## Comparison
Artifact comparison reports changes to an image's payload interpretation,
including load and entry declarations. Equivalent external offset forms and
inventory positions leave those declarations unchanged. Unavailable
interpretations remain unknown, while independently known changes survive.
## Verification
Synthetic fixtures with computed hashes cover embedded, `data-offset` and
`data-position` layouts, shared data, overlap and bounds failures, every
verified hash algorithm, configurations, resource limits, inventory,
extraction and reader lifecycle. CLI and stdio MCP tests exercise the same
artifact provider and advertised schemas. Real U-Boot `mkimage` 2023.01 output
has also been verified for embedded, `-E -B 0x200`, and
`-E -p 0x2000 -B 0x200` layouts, with byte-exact extraction checked against
`dumpimage`. The OpenWrt 24.10.3 ABT ASR3000 recovery FIT was checked against
`mkimage`, `fdtget` and `dumpimage`, including its kernel, initrd and devicetree.
No image was executed.
For small containers, complete delivery avoids an additional view request.
Choose summary and selected views when the omitted facts justify that extra
request; summary delivery does not reduce the underlying inventory work.
+14 -7
View File
@@ -109,9 +109,13 @@ server; older servers keep the complete request after schema discovery.
header check does not establish valid table contents or exported symbols.
- **`libflutter` facts** are per-ABI observations: byte size, SHA-256
digest, GNU build IDs from ELF-declared `PT_NOTE` / `SHT_NOTE` ranges,
and any _labeled_ strings the build
carries. Release engine builds often strip the `Dart VM version` string —
absence is reported as `null`, not guessed. Android clang/LLVM toolchain
and Dart/toolchain strings the build carries. `dart_version` accepts the
labeled `Dart VM version:` form and the unlabeled Dart
`Version::String()` form (`<version> (<channel>) (<commit time>)` with an
optional ` on "<os>_<arch>"` suffix) that release engines embed. An
unlabeled match is a candidate based on its shape; it is not bound to a
Dart symbol and another string can match. `null` means no supported pattern
was found, not proof that no version exists. Android clang/LLVM toolchain
lines are reported verbatim as build provenance.
`build_id_candidates` preserves distinct IDs. `build_id` is populated only
when all declared note ranges were examined and one ID was found;
@@ -122,10 +126,13 @@ server; older servers keep the complete request after schema discovery.
Compressed `SHT_NOTE` sections are skipped with an unsupported-compression
limitation and partial coverage; IDs from other note ranges remain candidates.
Encoded note storage is never interpreted as note headers or inflated.
- Labeled strings must fit their complete grammar. `dart_version_capped`
and `toolchain_capped` independently report labels omitted by grammar or
projection bounds; either makes identification coverage partial. Values
split across read chunks are retained without publishing truncated prefixes.
- Version and toolchain strings must fit their complete grammar.
`dart_version_capped` and `toolchain_capped` independently report labels
omitted by grammar or projection bounds; either makes identification
coverage partial. Values split across read chunks are retained without
publishing truncated prefixes. Unlabeled runs that miss the
`Version::String()` grammar are ignored and do not set
`dart_version_capped`.
- **`coverage: "partial"`** also appears when an ABI carries `libapp.so`
without `libflutter.so` (or vice versa).
- The APK itself is digested before parsing, so `target.sha256` binds the
+129
View File
@@ -0,0 +1,129 @@
# GameCube DOL analysis through Ghidra
This lane imports 32-bit big-endian PowerPC GameCube/Wii DOL images through
Ghidra. It does not execute the image. DOL is a headerless format with no magic
number, so REA never detects it: the caller must select it explicitly.
Only the cached MEM1 window `0x80000000..0x81800000` is admitted. Wii MEM2
and uncached aliases are outside this lane.
Not covered: small-data anchors (`r2`/`r13`), REL, RPX, and IDA or Hopper
support. See [Not supported yet](#not-supported-yet).
## Select the format
CLI:
```bash
rea inspect /absolute/path/to/main.dol --target-format gamecube-dol --provider ghidra --json
rea function /absolute/path/to/main.dol 0x80003130 --target-format gamecube-dol --provider ghidra --json
```
MCP, with the existing `open_binary` tool:
```json
{
"path": "/absolute/path/to/main.dol",
"provider_id": "ghidra",
"format": "gamecube-dol"
}
```
The architecture comes from the DOL header (`powerpc`). Do not pass
`architecture` with `gamecube-dol`; it is rejected. `entry_point` is optional,
and if given it must equal the header entry.
Omitting `format` never imports a file as a DOL. Detection does not run for DOL.
## What the import does
REA reads the 256-byte DOL header at admission. It checks every section's file
range and load address. It records BSS/data overlaps and computes the exact
uninitialized BSS gaps once. Malformed headers are refused before Ghidra starts.
Ghidra then runs in two steps:
1. The standard `BinaryLoader` maps the whole file at base `0x0`, with
`-processor` and `-cspec` taken from the language selection below.
2. The pre-script `bridge/ghidra/ReaGhidraPrepareDol.java` reads a section file
that REA wrote from the parsed header, with versioned collection counts. It creates one initialized block per
used text and data section at its load address, with file bytes from the
section's file offset. The whole-file block is removed before the section
blocks are created. Text is read-execute and data is read-write. It sets the
entry point and labels the entry
`__start`.
Sections that the header marks unused are not mapped. The 256-byte header and
any bytes between sections are not mapped either.
## BSS
A DOL has one BSS range, and it is not always disjoint from the data sections.
The domain parser computes the parts of BSS that no data section covers. Both
the pre-script and load-image attestation consume those parsed ranges. The
pre-script creates uninitialized read-write blocks only in those gaps. Where BSS overlaps a data slot, that slot
keeps its file bytes. Overlapping slot indexes are reported by the parser.
## Bound language
The default language is `PowerPC:BE:32:default` with compiler spec `default`,
which ships with Ghidra. The load-image attestation checks the language Ghidra
reports, not only the requested one.
### Optional Gekko/Broadway override
`REA_GHIDRA_LANGUAGE_ID=PowerPC:BE:32:Gekko_Broadway` selects the Gekko/Broadway
variant. The language is not in stock Ghidra. It comes from the GameCubeLoader
extension, by Cuyler, licensed under Apache-2.0. The extension ships
`ppc_gekko_broadway.ldefs` under its `data/languages` directory and must be
installed in the Ghidra installation's `Ghidra/Extensions` directory. The stock
lane never requires it.
Any other language id must start with `PowerPC:BE:32:`. Other languages are
refused with a specific constraint.
## Paired-single limitation
The stock `PowerPC:BE:32:default` language does not decode the paired-single
instructions that Gekko and Broadway add. Those words decode as bad or undefined
instructions. Use the Gekko override to decode them.
## Verification
The source-owned fixture is built from `tests/conformance/c/ppc.c`,
`tests/conformance/c/dol.c`, `tests/conformance/ghidra/ppc-probe.S` and the
linker script `tests/conformance/ghidra/dol-fixture.ld`. It is converted to a DOL
by `tests/conformance/ghidra/dol-fixture.mjs`. Its text is at `0x80003100`, and
its single BSS range overlaps data slot 1 and leaves a gap. The fixture is
never executed. The real GameCube or Wii game images are never used.
```bash
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:dol
```
The lane needs the same toolchain as the PowerPC ELF lane: `REA_PPC_CLANG`,
`REA_PPC_READELF` and LLD. An independent DOL header reader and GNU readelf
(run on the source ELF) check the section table, file bytes, entry and BSS gap
before Ghidra starts. The checks never use REA's parser.
After import, the lane checks the load-image attestation (`verified`), the
bound language, the `__start` entry procedure, a direct call to the leaf
function, decompilation, and bytes at the data, tail, marker and BSS-gap
addresses. It also checks CLI identity.
The Gekko pass runs only when `PowerPC:BE:32:Gekko_Broadway` is in the Ghidra
installation's language catalog, the same catalog REA uses to admit
`REA_GHIDRA_LANGUAGE_ID`. A language that exists only in a user extension under
`~/Library/ghidra` is not admitted, so the lane prints
`skipped: PowerPC:BE:32:Gekko_Broadway (not in the Ghidra installation catalog that REA admits); stock lane still verified`
and still succeeds on the stock checks.
The ELF lane (`npm run verify:ghidra:ppc`) is separate. See
[PowerPC ELF analysis through Ghidra](ghidra-ppc.md).
## Not supported yet
- Small-data anchors: `r2` and `r13` are not seeded from the DOL header.
- REL (relocatable modules) and RPX (Wii U) are not admitted.
- IDA and Hopper refuse `gamecube-dol`. Ghidra is the only provider.
- Disc images (ISO/GCM) are not supported. Pass an extracted DOL file.
+13 -8
View File
@@ -3,7 +3,7 @@
REA can analyze DOS MZ executables and explicitly selected COM images through the bring-your-own Ghidra adapter.
The Linux x64 and macOS arm64 verification lanes exercise actual 16-bit disassembly and
decompilation through the CLI and stdio MCP. Provider admission accepts Ghidra
12.1.x and that installation's declared JDK range. The verification lane uses
12.x (>=12.1) and that installation's declared JDK range. The verification lane uses
Ghidra 12.1.4 and JDK 21; no DOS emulator or cross-compiler is required.
## Open and inspect
@@ -29,13 +29,18 @@ point using the available structural bytes and actual file length. It reports
those DOS facts separately from Windows-header evidence. A recognized PE
signature remains PE; recognized NE, LE, and LX headers are explicitly
unsupported. A nonzero Windows-header pointer without a recognized signature
remains an unresolved candidate, even when the DOS structure validates:
automatic target selection fails closed and reports both observations.
REA does not infer DOS intent from padding or infer a damaged Windows header
from the pointer alone. Inventory classification is a bounded-prefix format
hint; it returns `unknown` for that candidate or when its prefix cannot
establish the format. That result is distinct from complete DOS validation and
does not decide target admission.
usually remains an unresolved candidate, even when the DOS structure validates:
automatic target selection fails closed and reports both observations. When a
nonempty relocation table ends inside a complete DOS header, the remaining
header bytes belong to DOS padding; a nonzero dword there is not a Windows
declaration. An unread signature beyond the current probe is requested before
that padding rule applies. This avoids manufacturing a Windows header from
original DOS padding while preserving rejection of damaged Windows
declarations.
Inventory classification is a bounded-prefix format hint; it returns `unknown`
for an unresolved candidate or when its prefix cannot establish the format.
That result is distinct from complete DOS validation and does not decide target
admission.
Ghidra imports a private target snapshot with `MzLoader`, language
`x86:LE:16:Real Mode`, and compiler specification `default`. These settings,
+32 -18
View File
@@ -24,7 +24,8 @@ specific unsupported constraint rather than guessing a compatible profile.
A second admission covers recordless legacy firmware images
([#1620](https://github.com/morluto/rea/issues/1620)): ELF32 `ET_EXEC` headers
declaring `EF_MIPS_ARCH_32` or `EF_MIPS_ARCH_32R2` with an unspecified ABI
declaring `EF_MIPS_ARCH_1` (MIPS-I, a zero ISA field), `EF_MIPS_ARCH_32` or
`EF_MIPS_ARCH_32R2` with an unspecified ABI
field, `EF_MIPS_ABI2` clear, and an _inspected_ absence of any ABI flags
record (a target whose record was merely not observed stays refused). For
these images o32 is an analysis assumption derived from the ELF32/ET_EXEC
@@ -38,11 +39,19 @@ bridge-reported `language_id` after import. A caller-selected
pin; combining the pinned profile with an environment override is a typed
refusal. A header that declares an ABI field but carries no record is still
refused. Two further boundaries apply: an inspection that could not examine
every ABI declaration source (for example a carved ELF whose section table
extends past the captured bytes without a program-header record) does not
establish the recordless admission, and an `EF_MIPS_ARCH_32` header that
_declares_ an ABI is refused rather than relabeled, because the declared-ABI
lane is verified for `EF_MIPS_ARCH_32R2` headers only.
every ABI declaration source does not establish the recordless admission
(target opening already refuses a carved ELF whose section table extends past
the captured bytes, as described below), and an `EF_MIPS_ARCH_1` or
`EF_MIPS_ARCH_32` header that _declares_ an ABI is refused rather than
relabeled, because the declared-ABI lane is verified for
`EF_MIPS_ARCH_32R2` headers only. The recordless MIPS-I lane pins the same
generic 32-bit language, which also decodes later MIPS32 instructions; the
session states that conformance to the MIPS-I subset is not established from
the header alone, and names the committed language when the caller selected a
variant instead of the pin. A committed profile is bound to the current
target's lane and captured ELF metadata before startup; reusing a profile
resolved for the other recordless lane is refused rather than exporting
attribution for a target it did not describe.
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
@@ -68,13 +77,16 @@ argument ABI and unknown nonzero MSA values remain unsupported. Without a
separate ABI flags record, FP attribute values are admitted only when absent,
`ANY` (0), double precision (1), or FPXX (5), the values covered by the
recordless profile. Parsed values are retained in the MIPS analysis profile.
For carved ELFs whose section table extends
beyond the captured file, REA examines every complete captured section header
and records the unavailable headers as a limitation. The provider refuses an
incomplete ABI declaration inspection even when a program-header ABI record is
valid, because an unavailable section header could carry an unsupported or
conflicting declaration. A missing program-header record does not make ABI
declarations in uncaptured sections absent or supply a default ABI.
A carved ELF whose section header table extends beyond the captured file is
refused when the target is opened, before provider selection, as is every ELF
declaration beyond EOF ([installation](installation.md#ghidra)). The refusal
names the declared range and the file size. It applies even when a
program-header ABI record was captured: an uncaptured section header could
carry an unsupported or conflicting declaration, so the captured bytes are not
analyzed as a complete smaller program. The provider also refuses any ABI
declaration inspection that reports unexamined headers. A missing
program-header record does not make ABI declarations in uncaptured sections
absent or supply a default ABI.
The field layout follows
[`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h).
@@ -248,11 +260,13 @@ observations and does not relabel the upstream result as verified.
CLI/MCP discovery and decompilation, direct calls, target/profile/Evidence
identity, snapshot round-trip and final process cleanup remain covered.
For both byte orders, the lane also carves away the section table while retaining
all program-header payloads. It checks the actual Ghidra language, entry-address
decompilation, distinct artifact digest and missing-section limitations through
CLI Evidence and an MCP-produced snapshot. Caller-selected Ghidra heap limits
are passed to these child workflows.
For both byte orders, the lane also cuts the image one byte before its section
header table, after checking that every program header and payload stays
captured. `open_binary` and the CLI must both refuse that copy before provider
selection as `target_unavailable` with constraint `file_format_rejected`,
naming the declared section-table range and the captured size, and leave it
unchanged. Caller-selected Ghidra heap limits are passed to the lane's CLI and
MCP child workflows.
For soft-float fixtures the lane also checks, through `inspect_native_api`, the
exact parameter and return types and storage of float, double, mixed (pair
alignment and stack spill) and struct-returning functions; that a caller's
+2 -1
View File
@@ -3,7 +3,8 @@
This lane extends native ELF analysis to 32-bit big-endian PowerPC; it does
not execute PowerPC code. It is the first milestone of
[#1741](https://github.com/morluto/rea/issues/1741). GameCube/Wii DOL
executables, PPC64 and other PowerPC variants are separate follow-ups.
executables are covered by [GameCube DOL analysis](ghidra-dol.md). PPC64 and other
PowerPC variants remain separate follow-ups.
## Admission boundary
+76 -3
View File
@@ -15,7 +15,7 @@ snapshot parser and a separate optional annotation extension. Check the
[release boundary](installation.md#released-package-and-main) when using npm.
See [import language and analysis seeds](installation.md#ghidra-import-language-and-analysis-seeds)
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS or RISC-V import
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS, RISC-V or GameCube DOL import
is a configuration error naming the conflicting settings. It does not make an
otherwise admitted target unsupported; REA preserves the requested settings.
An inaccessible language
@@ -30,12 +30,36 @@ signal. Confirm resource causes from process output or host/container records;
operation-scoped JVM `OutOfMemoryError` observations retain their separate resource
classification. Automatic kernel or cgroup OOM attribution is not implemented.
After the authenticated startup handshake, rejected inputs (`invalid_request`,
`not_found`, `ambiguous`, an invalid function name, or an address without one
exact file offset) use
`diagnostics.diagnostic_scope: "request"` when their active output checkpoint is
available. They preserve the active request's
`failure_output`, target/profile identity and runtime/log coordinates, while
omitting cumulative launcher stdout/stderr and launch-support output. Startup,
process, protocol and other remote failures retain full session diagnostics.
The Ghidra client still owns its bounded cumulative output; this projection does
not clear it or alter session readiness. Log paths refer to the owned ephemeral
runtime and are removed by normal cleanup.
Provider selection and profile identity follow
[MCP contracts](mcp-contracts.md#identity-and-discovery). One deep
provider stays bound to the target; runtime failure never selects another
provider automatically. The contracts below describe Ghidra observations,
not equivalence with another engine's pseudocode or analysis database.
## Request payload ceiling
Ghidra requests use `REA_BRIDGE_MAX_REQUEST_BYTES`: default 16 MiB, with a
configurable range of 7,464–536,870,887 bytes. REA measures UTF-8 JSON, including
escaping, before allocating the serialized request or sending it. The framing
newline is excluded. An oversized request returns `resource_constraint` with
the effective setting, its source, and the exact payload byte count.
Set the value in the environment that starts REA, then restart REA to apply it.
This setting is enforced by the REA client; the Java bridge has no corresponding
request ceiling.
## Admitted inventory semantics
| Concern | Ghidra contract |
@@ -148,6 +172,48 @@ have no file backing. ELF mappings are observed
without independent format-specific load-image verification, as the result's
status and limitations state.
Relocation rows retain Ghidra's `symbol` value, including an empty string or
null when returned by Ghidra. It is a provider observation, not proof of the
runtime-resolved address. Older Evidence may not have recorded this field;
a relocation page requesting it reports that uncertainty.
For large relocation inventories, select MCP `inspect_native_load_image` with
`detail: "summary"`. It returns verification state, observed identity and counts,
while retaining complete Evidence. Use `normalized_result.parent_evidence_id`
with `inspect_analysis_view` and a view such as:
```json
{
"kind": "native-load-image",
"collection": "relocations",
"offset": 0,
"limit": 32,
"fields": ["address", "status", "type"]
}
```
Collections are `source_files`, `mappings`, `relocations`, `entry_points`,
`entry_context`, `checks` and DOL `sections`. Optional
`indices` select original row indices before paging; empty indices select none.
Fields are collection-specific. Relocation defaults omit byte payloads; check defaults omit potentially large `expected`/`observed` inventories. Request those fields explicitly when needed. Other collections default to all fields. Metadata recovery remains counted and retained in complete parent Evidence.
Parent Evidence, collection and `source_index` identify a row, including same-address
relocation alternatives. Null counts or rows mean unrecorded or inapplicable;
they never establish an empty observation. Exhausting a page does not upgrade
unsupported verification or runtime coverage. Pages query immutable retained
facts without reopening the target or repeating Ghidra work.
Each page carries the source's structured `verification_status` independently
of its page coverage.
CLI summary delivery requires a complete Evidence bundle:
```sh
rea inspect-native-load-image <path> --provider ghidra --detail summary --evidence_output /new/load-image.json --json
```
Read selected pages later through `inspect-analysis-view` using the returned
parent ID and an `evidence-file` source pointing to that bundle. Complete
delivery remains the default in both adapters.
An unsuccessful numeric procedure lookup reports the current Ghidra image base
and gives the CLI commands above alongside the corresponding MCP operations.
Repeat any target-format, architecture, and entry-point options used to open the
@@ -175,7 +241,7 @@ profile stages before retrying their retained descriptors.
| Concern | Ghidra contract |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline unless `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` sets one; a decompile that reaches it fails only that request with `remote_code` `decompile_timed_out`, the Program and session stay open, and the next decompile uses a fresh decompiler process. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
| Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline unless the launch setting `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` or runtime override `ghidra.decompile_timeout_seconds` sets one; a decompile that reaches it fails only that request with `remote_code` `decompile_timed_out`, the Program and session stay open, and the next decompile uses a fresh decompiler process. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. |
| Serialization | A FIFO sends one Program request at a time. Caller cancellation removes queued work promptly and is passed to active socket waits. This is an adapter safety commitment, not a claim that every Ghidra API is thread-safe. |
| Function identity | Every function result carries the entry address and Ghidra FunctionManager classification for external, thunk, and resolved thunk target. These are observations, not proof that unresolved targetless calls have been recovered. |
| Assembly and pseudocode | Assembly is complete Ghidra Listing text; pseudocode is Ghidra decompiler output. Neither is original source, and cross-provider comparison never treats Hopper and Ghidra text as equal or unequal semantic facts. |
@@ -185,6 +251,13 @@ profile stages before retrying their retained descriptors.
| P-code value flow | Function dossiers include up to 3,000 high-p-code operations, at most 64 inputs per operation, and 12,000 def-use edges. Results identify memory reads/writes, branches, and calls by p-code opcode, and mark decompiler-dead operations. Large results report truncation, an omitted-operation lower bound, and known omitted-input/edge counts. The dossier is an intra-function graph; `trace_native_values` composes derived parameter/argument and return/output dependencies across resolved calls. It does not resolve memory aliasing, call side effects, runtime behavior, persistent state, or business meaning. |
| Result extent | Function instruction scans and native API boundary observations remain complete. P-code flow is the explicit bounded exception; its count fields distinguish exact retained counts from lower-bound or known-omitted counts. Caller cancellation and provider failures remain distinct. |
Runtime updates apply to newly accepted operations on the existing Program.
They preserve accepted budgets and do not clear cleanup blockers or change the
client deadline. Transient decompile timeout failures remain uncached so the
same function can be retried after raising the limit. See the
[settings design](design/limits-and-recovery.md) for snapshots, precedence,
and provider integration.
Function extents and instruction completeness refer to Ghidra's analyzed body.
An inferred no-return call can terminate that body before subsequent bytes.
Pseudocode, assembly, procedure info, instruction windows, incoming and outgoing
@@ -232,7 +305,7 @@ host/provider acceptance run with only the host compiler.
`--dll`. These static fixtures contain source-owned encoded instructions; they
are not compiler-produced programs. See the [Windows verification scope](windows-ghidra-p0.md#native-authority-and-target-identity)
for real-provider coverage and exclusions.
These lanes check all 26 read-only operations, target/snapshot/import SHA-256 linkage,
These lanes check all 27 read-only operations, target/snapshot/import SHA-256 linkage,
and project, endpoint, process, and runtime cleanup. The independent native
lane checks DACLs, handle admission, cancellation, and Job Object lifecycle.
`npm run verify:ghidra:windows:package` packs and installs REA into an isolated
+3 -3
View File
@@ -85,9 +85,9 @@ node scripts/verify-real-ghidra-mips.mjs --psp-missing-extension
npm run verify:ghidra:mips
```
The generic verifier also exercises a section-truncated firmware fixture. That
check uses generic MIPS symbols and language expectations and runs only in generic
mode; the PSP report explicitly records carved-PSP coverage as not run.
The generic verifier also checks that a section-truncated firmware copy is
refused when the target is opened. That check runs only in generic mode; the
PSP report explicitly records carved-PSP coverage as not run.
The maintained PSP fixture builds from `tests/conformance/psp/`, never executes,
and is independent of commercial game data. PSP GNU tools supply ELF/ABI,
+264 -40
View File
@@ -9,29 +9,43 @@ lane shape as [generic MIPS support](ghidra-mips.md).
ELF `EM_RISCV` (243) is retained as the provider-neutral `riscv` family. The
target also retains the raw ELF declarations that decide a lane: class, byte
order, `e_type`, and `e_flags`. Recognition does not mean every provider or
RISC-V variant is supported. Existing PE, managed and Mach-O architecture sets
are unchanged. Headerless inputs use the separate [raw-binary admission
order, `e_type`, `e_flags`, and the ISA string that `.riscv.attributes`
declares. Recognition does not mean every provider or RISC-V variant is
supported. Existing PE, managed and Mach-O architecture sets are unchanged.
Headerless inputs use the separate [raw-binary admission
boundary](raw-binary.md); raw RISC-V images are not supported.
The Ghidra lane admits little-endian ELF32 and ELF64 `ET_EXEC` images that
declare `EF_RISCV_FLOAT_ABI_DOUBLE` in `e_flags`, with no `EF_RISCV_RVE`,
`EF_RISCV_TSO`, or any bit outside `EF_RISCV_RVC | EF_RISCV_FLOAT_ABI`. These are
header declarations, not proof that every instruction conforms to that ISA.
The Ghidra lane admits little-endian `ET_EXEC` images with no `EF_RISCV_RVE`,
`EF_RISCV_TSO`, or any bit outside `EF_RISCV_RVC | EF_RISCV_FLOAT_ABI`, in one
of three lanes:
- ELF64 or ELF32 images that declare `EF_RISCV_FLOAT_ABI_DOUBLE` use Ghidra's
stock double-float model unchanged. Their `.riscv.attributes` is not read for
admission.
- ELF32 images that declare the soft-float ABI (`e_flags` float-ABI bits `0`,
ilp32) use REA's [ilp32 calling-convention policy](#ilp32). They must also
declare an RV32I base and only [decoded extensions](#isa-declarations) in
`Tag_RISCV_arch`.
These are header declarations, not proof that every instruction conforms to
that ISA.
Provider selection returns the specific unsupported constraint rather than
guessing a compatible profile:
| Declaration | Outcome |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Little-endian ELF64 `ET_EXEC`, double-float ABI | admitted, `RISCV:LE:64:default` + `gcc` |
| Little-endian ELF32 `ET_EXEC`, double-float ABI | admitted, `RISCV:LE:32:default` + `gcc` |
| Big-endian | refused; Ghidra 12.1.4 ships no big-endian RISC-V language |
| `ET_REL`, `ET_DYN`, other `e_type` | refused with the declared value |
| `EF_RISCV_RVE` | refused; the embedded 16-register profile (x0–x15) needs its own processor specification |
| `EF_RISCV_TSO` | refused; the total-store-order memory model is unverified here |
| Soft, single, or quad float ABI | refused; the pinned generic compiler specification models double-float |
| Reserved or newer `e_flags` bits | refused with the offending bits named |
| Declaration | Outcome |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Little-endian ELF64 `ET_EXEC`, double-float ABI | admitted, `RISCV:LE:64:default` + `gcc`, lane `elf64-exec-le-double-float-generic-v1` |
| Little-endian ELF32 `ET_EXEC`, double-float ABI | admitted, `RISCV:LE:32:default` + `gcc`, lane `elf32-exec-le-double-float-generic-v1` |
| Little-endian ELF32 `ET_EXEC`, soft-float ABI, decoded extensions only | admitted, `RISCV:LE:32:default` + `gcc` with the `__rea_ilp32` prototype, lane `elf32-exec-le-soft-float-generic-v1` |
| ELF32 soft-float ABI without `Tag_RISCV_arch`, or with any other extension | refused; the reason names the extensions outside the decoded set |
| ELF32 single-float ABI (ilp32f) | refused until its [written policy](#ilp32f) is implemented and verified |
| ELF64 soft- or single-float ABI (lp64, lp64f), or the quad-float ABI | refused; no verified calling-convention policy |
| Big-endian | refused; Ghidra 12.1.4 ships no big-endian RISC-V language |
| `ET_REL`, `ET_DYN`, other `e_type` | refused with the declared value |
| `EF_RISCV_RVE` | refused; the embedded 16-register profile (x0–x15) needs its own processor specification |
| `EF_RISCV_TSO` | refused; the total-store-order memory model is unverified here |
| Reserved or newer `e_flags` bits | refused with the offending bits named |
`e_type` is the only load-shape declaration this lane reads. A fixed-address
`ET_EXEC` declaration does **not** establish static linkage: `PT_INTERP` and
@@ -58,15 +72,193 @@ with the `gcc` compiler specification per class, records
pin without changing the requested override. An override that matches the pin is
accepted and recorded as `configured-v1`.
The soft-float lane pins the same `RISCV:LE:32:default` language. Every RV32
language in Ghidra 12.1.4, including the deprecated soft-float `RV32I`, `RV32IC`
and `RV32IMC` variants in `old/riscv_deprecated.ldefs`, uses the same
`riscv.ilp32d.sla` decoder; only the compiler specification differs. REA keeps
the generic language and corrects the calling convention instead.
## Calling conventions
Ghidra's generic RV32 compiler specification, `riscv32-fp.cspec`, models the
ilp32d ABI. Its default prototype passes `float` and `double` values in
`fa0`–`fa7` and returns them in `fa0`, and it passes a structure of one or two
floating-point members in floating-point registers. An ilp32 image passes all
of these in integer registers. Measured on Ghidra 12.1.4 with an ilp32 build of
the fixture in `tests/conformance/c/riscv-float-abi.c`, the stock model
decompiles `rea_riscv_abi_pick(1.5f, 2.5f, sel)` as
`rea_riscv_abi_pick(in_fa0,in_fa1,0x3fc00000)`.
### Decision record: D3, RISC-V float ABIs
Decided in [#7477](https://github.com/morluto/rea/issues/7477).
- **Choice.**
- Admit little-endian ELF32 `ET_EXEC` images that declare the soft-float ABI
(ilp32) through a new lane, `elf32-exec-le-soft-float-generic-v1`. Lane ids
are built from their fields: `elf<class>-exec-le-<float ABI>-float-generic-v1`.
- Keep the pinned `RISCV:LE:32:default` language and `gcc` compiler
specification. After analysis, `ReaGhidraRiscvCallingConvention.java`
derives the program-specific `__rea_ilp32` prototype from the program's
default prototype as the [ilp32 policy](#ilp32) states. It installs the
prototype through a Ghidra specification extension and assigns it, with
dynamic storage, to every non-thunk function, every external function and
every function definition. Thunks follow their targets. The script fails,
and writes no report, if the derived prototype or any function still
places a value in a floating-point register, or if the prototype does not
pass a 16-byte value by reference.
- One derivation serves every policy. A policy row says how it changes the
default prototype's floating-point entries and preserved registers; every
policy passes values of 9 bytes or more by reference.
- The profile commits `riscv_calling_convention: ilp32-prototype-v1`. The
bridge reports the policy and its function and definition counts, and REA
requires that report exactly when the profile commits the policy.
- Admission requires a `Tag_RISCV_arch` declaration with an RV32I base and
only extensions that Ghidra 12.1.4's generic RV32 language implements with
p-code ([ISA declarations](#isa-declarations)).
- Soft-float profiles add `float_abi` and `arch` to `riscv_elf`. Double-float
profiles keep their exact parameters, so their digests, sessions and
snapshots are unchanged.
- ilp32f keeps its [written policy](#ilp32f) and stays refused until its
prototype is implemented and verified. Quad-float, RVE, TSO, and ELF64
soft- or single-float images stay refused.
- **Alternatives** (and what switching to each would take):
- Pin the deprecated soft-float languages (`RISCV:LE:32:RV32IMC` and others,
`riscv32.cspec`): REA's language catalog would have to read `old/`, the
variants cover only I, IC and IMC, Ghidra marks them deprecated, and no
stock specification models ilp32f.
- Assign `__rea_ilp32` only to callables whose types use floating-point
values, as the MIPS soft-float lane does: typed storage is the same, but
prototypes Ghidra infers for untyped code would keep the ilp32d model. In
an ilp32 image that contains F or D instructions, that model can still
claim `fa0`. Switching is one predicate in the Java script and a new
policy id.
- Admit with a limitation and no correction: a reduced-capability contract
that needs its own review, because arguments, returns and aggregates
become unverified.
- Keep refusing: the lane rows become refusals again.
- Admit images without `Tag_RISCV_arch`, or refuse a list of known
extensions instead of admitting a list: a declaration that REA cannot
check would be treated as compatible. Widening the decoded set later is
additive.
- Add `float_abi` and `arch` to double-float profiles as well: one shape for
every lane, but every existing RISC-V session and snapshot would be
invalidated.
- Ship ilp32 and ilp32f together (the question in #7477): ilp32f needs a
float-and-integer structure rule that Ghidra's model lacks, so it ships
separately with its own fixtures.
- **Semantic owner and consumers.**
- Owners:
- `src/domain/riscvIsaDeclaration.ts` owns the float-ABI vocabulary of
`e_flags` and the parsing of `.riscv.attributes` and `Tag_RISCV_arch`.
- `GHIDRA_RISCV_LANES`, `GHIDRA_RISCV_CALLING_CONVENTIONS` and
`GHIDRA_RISCV32_DECODED_EXTENSIONS` (`src/ghidra/GhidraRiscvProfile.ts`)
own admission, lane ids and the policy table.
- `bridge/ghidra/ReaGhidraRiscvCallingConvention.java` owns how each policy
becomes a Ghidra prototype.
- Consumers: target resolution, Ghidra provider admission, the analysis
profile, the provider client and launcher, the session handshake, project
history, the real-Ghidra lane and this page. Later work reuses the table:
ilp32f adds a row, and RISC-V ESP images (F6) select a row through D5.
- **Costs of changing it.**
- **Code:** S. A policy or lane is one table row plus the Java derivation.
- **Compatibility:** the soft-float lane id, `riscv_elf.float_abi`,
`riscv_elf.arch` and `riscv_calling_convention` are persisted in sessions,
snapshots and Evidence once released. A changed derivation ships as a new
policy id, such as `ilp32-prototype-v2`, which invalidates soft-float
snapshots only.
- **Migration:** none for admitted targets; double-float profiles are pinned
by tests. Previously refused soft-float targets become admitted.
- **Verification:** rerun `npm run verify:ghidra:riscv`.
### ilp32
Policy `ilp32-prototype-v1`, prototype `__rea_ilp32`. XLEN is 32 bits and no
floating-point register passes a value.
| Topic | psABI rule | `__rea_ilp32` |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Integer and pointer values | `a0`–`a7`, then the stack in 4-byte-aligned slots | The default integer and stack entries, unchanged |
| `float` | One integer register, or a stack slot | The `fa0`–`fa7` entries and the `float` rule are removed, so a `float` takes the next integer register |
| `double` and 8-byte values | A register pair with the low word in the lower register. Named arguments need no aligned pair. With one register left, the low word goes in `a7` and the high word on the stack | The default `join` rule with stack spill: Ghidra writes `a2:4,a1:4` for a pair and `Stack[0x0]:4,a7:4` for the split |
| Aggregates up to 8 bytes | One or two integer registers, laid out as in memory, also when every member is floating-point | The homogeneous-float-aggregate rule is removed; the `join` rule applies |
| Aggregates over 8 bytes | By reference: the caller passes the address of a copy | The default rule that converts structures and unions of 9 bytes or more to pointers |
| Return values | As the first argument of the same type: `a0`, or `a0` and `a1`. Larger values go to a caller buffer whose address is an implicit first argument in `a0` | The `fa0`/`fa1` return entries and floating-point rules are removed; Ghidra's hidden-return fallback supplies the pointer in `a0` |
| Variadic arguments | The integer convention. A variadic argument with 8-byte alignment and size takes an aligned (even-numbered) register pair, or the stack; after the first stack argument, the rest go on the stack | The default variadic rule (`join` with alignment), unchanged |
| Preserved registers | `s0`–`s11` and `sp`; `gp` and `tp` are not allocated. No floating-point register is preserved | The default preserved list without `fs0`–`fs11`, which join the killed-by-call list |
| Scalars over 8 bytes | By reference (`long double` is 16 bytes), and returned through a caller buffer | A rule before the final join converts any value of 9 bytes or more to a pointer; a return that size already goes to a caller buffer |
Ghidra recovers the arguments of a variadic call from the registers the caller
writes, not from their types. A padding register that still holds an earlier
value can appear as an argument, and arguments after an unwritten register are
omitted.
### ilp32f
Written policy for `EF_RISCV_FLOAT_ABI_SINGLE` on ELF32. REA does not admit it
yet. ABI_FLEN is 32 bits.
| Topic | psABI rule |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `float` | `fa0`–`fa7` while one is free, then the integer convention |
| `double` and other values wider than 32 bits | The integer convention, as in ilp32 |
| Structure of one or two floats (after flattening) | One floating-point register per member when enough are free, otherwise the integer convention |
| Structure of one float and one integer, in either order | One floating-point and one integer register when both are free, otherwise the integer convention |
| Unions | Never flattened; the integer convention |
| Return values | As the first argument: `fa0`, `fa0` and `fa1`, or `fa0` and `a0` |
| Variadic arguments | The integer convention, as in ilp32 |
| Preserved registers | `s0`–`s11`, `sp`, and `fs0`–`fs11` for values of 32 bits or fewer |
The planned prototype limits the default floating-point entries to 4 bytes and
keeps the shared rule that passes values of 9 bytes or more by reference.
Observed on Ghidra 12.1.4, a `double` then takes an integer pair and a structure
of two floats takes `fa0` and `fa1`. Ghidra's model has no rule for a structure
of one float and one integer: it joins `struct { float; int; }` into `a1:a0`
instead of `fa0` and `a0`. The ilp32f policy needs that rule and its own
fixtures before it is admitted.
### ilp32d and lp64d
The double-float lanes use Ghidra's stock `riscv32-fp.cspec` and
`riscv64-fp.cspec` models unchanged. This page does not verify them. They share
the float-and-integer structure gap above.
## ISA declarations
The target reader finds the one `SHT_RISCV_ATTRIBUTES` section and reads
`Tag_RISCV_arch`, such as `rv32i2p1_m2p0_c2p0_zicsr2p0_zifencei2p0`. It
retains the string, the base and the extension names, or an unavailable reason
when the section is malformed, declares other vendors or scopes, or uses a
non-canonical string. A missing section or tag is recorded as absent.
Calling-convention lanes admit an image only when it declares an RV32I base
and only extensions that Ghidra 12.1.4's generic RV32 language implements with
p-code: `m`, `a`, `f`, `d`, `c`, `zicsr`, `zifencei`, and the subset names
toolchains record, `zmmul`, `zaamo`, `zalrsc`, `zca`, `zcf` and `zcd`. Any
other extension is refused. In particular:
- Ghidra 12.1.4 decodes no Zcb, Zcmp or Zcmt instruction.
- Its bit-manipulation, cryptography and packed-SIMD files follow draft
encodings and mark nearly every instruction unimplemented.
- Vendor extensions, such as Espressif's `xesploop`, `xespv` and `xespdsp`,
decode only as custom-opcode placeholders.
ESP-IDF builds ESP32-C2 and C3 images as `rv32imc_zicsr_zifencei` and ESP32-C5,
C6, C61, H2 and H21 images as `rv32imac_zicsr_zifencei_zaamo_zalrsc`, all ilp32.
Those base strings are admitted. Builds that add `_zba_zbb_zbs`,
`_zcb_zcmp_zcmt` or an `xesp` extension are refused.
## Evidence
The lane identity is `elf32-exec-le-double-float-generic-v1` or
`elf64-exec-le-double-float-generic-v1`, and `riscv_elf` retains `elf_class`,
`byte_order`, `type`, and the raw `flags` alongside the read-only booleans
`float_abi_double`, `rvc_declared`, `rve_declared`, and `tso_declared`. Those
fields report declarations the header makes; none of them is evidence of the
original compiler, of instruction conformance, or of a floating-point calling
convention beyond the ABI the pinned language models.
The lane identity is one of the lane ids above, and `riscv_elf` retains
`elf_class`, `byte_order`, `type`, and the raw `flags` alongside the read-only
booleans `float_abi_double`, `rvc_declared`, `rve_declared`, and
`tso_declared`. Soft-float profiles also retain `float_abi: soft`, the declared
`arch` string, and `riscv_calling_convention: ilp32-prototype-v1`. Those fields
report declarations the header and attributes make, and the policy REA applied;
none of them is evidence of the original compiler or of instruction
conformance. Session Evidence states the prototype and how many functions and
definitions use it.
## Verification and limits
@@ -76,9 +268,9 @@ tree ships no host decompiler binary, so `Decompiler/os/<platform>/decompile` an
[installation](installation.md#ghidra).
Run `npm run verify:ghidra:riscv` with an existing Ghidra 12.1.4 installation,
JDK 21+, and a RISC-V-capable Clang, LLD and LLVM readelf. The verifier acquires
no tools and never executes target code. Select installed tools explicitly when
they are not on `PATH`:
JDK 21+, and a RISC-V-capable Clang, LLD, LLVM readelf and LLVM objdump. The
verifier acquires no tools and never executes target code. Select installed
tools explicitly when they are not on `PATH`:
```sh
GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC \
@@ -86,20 +278,52 @@ JAVA_HOME=/path/to/jdk-21 \
REA_RISCV_CLANG=/path/to/llvm/bin/clang \
REA_RISCV_LLD=/path/to/lld/bin/ld.lld \
REA_RISCV_READELF=/path/to/llvm/bin/llvm-readelf \
REA_RISCV_OBJDUMP=/path/to/llvm/bin/llvm-objdump \
npm run verify:ghidra:riscv
```
The lane builds the committed freestanding C and assembly sources into RV32 and
RV64 `ET_EXEC` fixtures. Independent LLVM readelf observations establish ELF
declarations and symbol addresses. Public CLI and stdio MCP workflows verify
function enumeration, decompilation, pinned language/compiler identity, decoded
probe bytes, global data, and direct calls. It validates advertised MCP output
schemas and both JSON representations, saves and reopens snapshots, rejects
header-mutated soft-float and unsupported high-bit declarations, and checks
unchanged source bytes and owned cleanup. Failures retain the diagnostic
workspace; successful runs remove it and report the observed process lineage.
The lane builds four `ET_EXEC` fixtures from committed freestanding C and
assembly sources:
Module tests cover admission and malformed headers. Call-graph completeness,
string inventories, dynamic linkage, Andes or RVE/TSO images, and big-endian
targets are **not** established by this lane. `.riscv.attributes` ISA extensions
are not inspected; the generic language pin remains an analysis convention.
- RV32 and RV64 double-float builds (`rv{32,64}imafdc`) of `riscv.c`.
- An RV32 soft-float build of `riscv-float-abi.c` with ESP32-C3's
`-march=rv32imc_zicsr_zifencei -mabi=ilp32`, typed with DWARF.
- The same objects linked with `--strip-all`. Every allocated section keeps
its address and bytes, so the typed build's symbols locate its functions.
Independent LLVM readelf observations establish ELF declarations,
`Tag_RISCV_arch` and symbol addresses. Public CLI and stdio MCP workflows verify
function enumeration, decompilation, pinned language/compiler identity, the
committed lane and policy, decoded probe bytes, global data, and direct calls.
They validate advertised MCP output schemas and both JSON representations, save
and reopen snapshots, and check unchanged source bytes and owned cleanup.
For the soft-float builds the lane also checks:
- `fence.i` and `csrr` decode, as ESP32-C3's Zifencei and Zicsr declare.
- An LLVM objdump reading of `rea_riscv_abi_caller` finds each call-site word
where the [ilp32 policy](#ilp32) puts it: an unaligned `double` in `a1`/`a2`,
the `a7`/stack split, two floats in an integer pair, a large structure and a
`long double` passed by reference (the pointed-to copies hold the source
values), aligned variadic pairs, and an indirect call.
- `inspect_native_api` reports `__rea_ilp32` with exactly that storage for the
typed build. The stripped build has one integer word per parameter location
and returns in the typed build's registers, or in none when the result goes
through caller memory.
- The caller's pseudocode passes the source constants through that storage and
names no floating-point register.
- Retained Evidence states the prototype outcome; the stripped build has no
function definitions.
Header-mutated copies check admission. The RV32 double-float image relabelled as
soft-float (`e_flags` `0x1`), refused before this lane, is admitted through the
soft-float lane over CLI and MCP. The soft-float image relabelled as
single-float, RV64 soft-float, and unsupported high bits are refused with their
reasons. Failures retain the diagnostic workspace; successful runs remove it and
report the observed process lineage.
Module tests cover admission, malformed headers and attribute sections, the
handshake, and the conformance checks against recorded LLVM and Ghidra output.
Call-graph completeness, string inventories, dynamic linkage, Andes or RVE/TSO
images, big-endian targets, ilp32f images, and decoding beyond the probes are
**not** established by this lane.
+15 -3
View File
@@ -1,12 +1,24 @@
# Godot PCK archives
`inspect-artifact` and `extract-artifact` read ordinary Godot PCK formats 2, 3
`inspect-artifact` and `extract-artifact` read ordinary Godot PCK formats 1, 2, 3
and 4 without running the game or requiring Godot. Official Godot 4.4.1, 4.6.1
and 4.7.1 produced these respective formats in verification; other engine
releases have not been verified. Format 1 is unsupported.
and 4.7.1 produced formats 2, 3 and 4 respectively in verification; other engine
releases have not been verified. Format 1 compatibility follows the
[Godot 3.5 loader](https://github.com/godotengine/godot/blob/3.5/core/io/file_access_pack.cpp#L201-L242)
and [exporter](https://github.com/godotengine/godot/blob/3.5/editor/editor_export.cpp#L1094-L1164)
layout and is covered by synthetic standalone and embedded fixtures; an actual
Godot 3 export has not been exercised.
Format 1 uses an 84-byte header, no pack or member flags, and absolute member
offsets in the complete source file, including an embedded executable carrier.
Its reported flags and file base are zero because those fields are absent.
Zero-offset removal records remain unsupported; REA does not apply patches.
Inspection retains the original header and directory facts, including engine
version, flags, declared file count, source paths, offsets and declared MD5.
For a member declared as `res://scenes/main.gd`, the occurrence's
`source_path` retains that exact declaration while `logical_path` is
`scenes/main.gd`, the relative path used for lookup and extraction.
Observed plaintext bytes receive content-addressed SHA-256 identities. MD5 is a
container integrity claim, separate from the observed SHA-256. A mismatch fails
by default; `--integrity-policy record-and-continue` retains the observed bytes
+4 -1
View File
@@ -36,7 +36,7 @@ The MCP tool `project_harmony_application_graph` takes the same
- exact component paths, artifact IDs, and hashes (manifests, Ark bytecode,
resources, native libraries, JavaScript, signing)
- a packaging-model hint: Stage is claimed only from a `module.json` entry
- a packaging-model hint: Stage is claimed only from an exactly cased `module.json` entry
outside a classified resource path, FA only from `config.json` outside a
classified resource path, otherwise `unknown`
- FA resources under `assets/<module>/resources/` as well as Stage `resources/`
@@ -46,6 +46,9 @@ The MCP tool `project_harmony_application_graph` takes the same
- path-based N-API bridge hypotheses between bytecode and `libs/<abi>` native
libraries
An unavailable manifest can still supply a path-based model hint; its content
identity remains unknown. Manifest names follow case-sensitive ZIP member names.
File and slice occurrences supply component and child-package paths; directory
trees and symlinks retain their identities in the source inventory without
being classified as code files. Ark and JavaScript runtime hints summarize the
+129
View File
@@ -0,0 +1,129 @@
# Intel HEX and S-record images
REA inventories Intel HEX (I8HEX, I16HEX, I32HEX) and Motorola S-record (S19,
S28, S37) programming files through `inspect-artifact` / `inspect_artifact`, and
materializes their decoded bytes through `extract-artifact` /
`extract_artifact`. Decoding is static and needs no external tool.
```sh
rea inspect-artifact ./firmware.hex --json
rea inspect-artifact ./firmware.s37 --detail summary --evidence_output evidence.json --json
rea extract-artifact ./firmware.hex --json
```
For MCP, open the file with `open_binary`, then call `inspect_artifact`. Summary
delivery retains the complete Evidence; use `parent_evidence_id` with
`inspect_analysis_view` to read selected regions and fields. `extract_artifact`
returns a fresh `output_root` containing one raw file per region.
## Recognition
A file is recognized only when its first line, starting at byte 0, is a fully
valid record: correct start character, hex digits, byte count, checksum, known
record type and fixed length. The file name does not matter. A byte-order mark,
a leading blank line or an invalid first record leaves the file unrecognized,
so it falls through to plain `file` classification. Recognition never searches
for a record later in the file. The container formats are `intel-hex` and
`srec`.
## Regions and metadata
Each inventory entry is one address-contiguous region of decoded bytes, named
`0x<start>-0x<end>.bin` with an inclusive end address of eight lowercase hex
digits, for example `0x08000000-0x0800001f.bin`. Regions are listed in ascending
address order. Out-of-order records are sorted into regions; gaps separate
regions and are never filled.
Region `format_metadata` (`hex-record-region`) records `start_address`,
`end_address`, `length`, `record_count`, and `first_line`/`last_line`: the
1-based line span of the contributing data records.
Root `format_metadata` (`hex-records`) records:
| Field | Meaning |
| ------------------------------------------------- | --------------------------------------------------------------------------------- |
| `encoding`, `variant` | `intel-hex` or `srec`; the variant derived from the record types seen, or `mixed` |
| `line_count`, `record_count`, `data_record_count` | Lines read, non-blank records, and data records |
| `data_bytes`, `duplicate_bytes`, `region_count` | Unique decoded bytes, bytes rewritten with identical values, and regions |
| `lowest_address`, `highest_address` | Address bounds of the decoded bytes, or `null` without data |
| `termination` | Whether Intel `01` or S-record `S7`/`S8`/`S9` was present |
| `start_address`, `start_address_record` | The Intel `05` EIP or the `S7`/`S8`/`S9` address, with its record type |
| `start_segment_cs`, `start_segment_ip` | The Intel `03` CS:IP pair |
| `header_hex`, `header_text` | S-record `S0` data as hex, and as text only when every byte is printable ASCII |
| `declared_data_record_count` | The last `S5`/`S6` count, validated against the data records before it |
`variant` is `null` for an S-record file in which no data or termination
record establishes an address width. Start addresses are declared facts, not
proven entry points.
## Facts, limitations and failures
| Situation | Outcome |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Gaps between regions | Separate regions; never zero-filled |
| Out-of-order records | Sorted into regions |
| An address written twice with the same value | Accepted and counted in `duplicate_bytes` |
| An address written twice with different values | Fails, naming the address and the line spans of both writes |
| Checksum mismatch, odd hex length, non-hex character, byte count disagreeing with the line | Fails with the line, declared and computed values, and record type |
| Line longer than 521 (Intel) or 514 (S-record) characters, excluding the line ending | Fails before the line is decoded |
| Intel type `06` or higher, `S4`, wrong fixed length for Intel `01`–`05` or `S5`–`S9` | Fails |
| Missing Intel `01` or `S7`–`S9` | Regions are returned; coverage is unknown and the file may be truncated |
| A non-blank record after termination, or a second `S0` | Fails; blank lines are allowed anywhere |
| `S5`/`S6` count different from the data records before it | Fails |
| Termination type not matching the data address width | Reported as a limitation; the declared fact is kept |
| Conflicting repeated `03`/`05` start records | Fails; identical repeats are allowed |
| An Intel data record whose 16-bit offset wraps past `0xFFFF` | Fails: producers disagree on whether the address wraps or continues linearly |
| I16HEX data above `0xFFFFF`, or data crossing the S1/S2/S3 address width | Fails |
Lowercase hex, LF and CRLF line endings, and trailing spaces or tabs are
accepted. A CR not followed by LF is an invalid character. Failures apply to
the whole file and return no partial regions.
## Limits
The file must be at most 128 MiB. Every data byte needs two hex digits, so the
decoder reserves at most half the file size for decoded bytes before reading,
and stores only written addresses. The decoder retains at most 65,536
address-discontiguous record runs before merging them into regions. A run is a
sequence of consecutive records whose addresses continue each other. Both are
fixed resource bounds, not format limits, and fail as limits (`file-size` and
`capacity`) with no runtime override. Configurable ceilings are tracked in
[#6176](https://github.com/morluto/rea/issues/6176). The line-length limits are
the one-byte record count: Intel HEX allows 255 data bytes, while S-record
counts include address and checksum bytes.
Decoded regions are cached only for the reader's lifetime. Each open checks
its admitted carrier identity again. Region streams copy at most 64 KiB per
chunk, so consumers cannot mutate cached bytes and opening a large region does
not copy it in full. Caller cancellation keeps its exact reason, and closing
the reader waits for active region streams to close.
## Analyzing a region
Inventory does not establish the CPU, byte order or memory map, or that a
declared start address is executable. To disassemble a region, extract it and
open the region file with `open_binary` using format `raw-binary`, an
architecture you choose, and `entry_point` set to the region start address;
raw-binary maps file offset 0 at `entry_point`. Each region is opened
separately, so references between regions are not resolved. A caller-selected
Ghidra language and base address for raw-binary imports is tracked in
[#4246](https://github.com/morluto/rea/issues/4246); Thumb-only Cortex-M images
may be refused or decoded in ARM state until
[#5744](https://github.com/morluto/rea/issues/5744) is resolved.
## Verification
Fixtures with computed checksums cover supported record types, checksums,
termination, address gaps and overlaps, resource refusals, inventory, extraction
and reader lifecycle. CLI and stdio MCP tests exercise the same artifact
provider and advertised schemas, including retained Evidence views.
GNU objcopy 2.40 output was also checked on Linux: the system `true` ELF was
converted to Intel HEX and S-record, with additional address-rebased I32HEX
and S37 cases. Each yielded 13 regions. A 19,124,817-byte Intel HEX conversion
of the system Python 3.11 ELF yielded 11 regions containing 6,799,261 decoded
bytes. Public CLI inspection and extraction matched GNU BFD address ranges
and independently exported section bytes. Adjacent GNU sections were joined
without filling address gaps. These are real producer outputs from system
applications, not vendor-firmware or target-execution verification; srec_cat
and other producers remain unverified. No general speedup is claimed.
+6 -1
View File
@@ -40,7 +40,12 @@ Record the image digest, REA source revision and these dependency versions:
- OpenSSL and cURL advertising HTTP3 and HTTP2 in `curl --version`.
The verified guest used Python 3.14.7, cURL 8.22.0, ngtcp2 1.25.0 and nghttp3
1.18.0. Other dependency profiles need verification. Back up the guest's
1.18.0. The current requirements update certifi from 2026.7.22 to 2026.10.19
to remove the compromised emSign trust root described in
[GHSA-6q48-6c7w-9gw2](https://github.com/certifi/python-certifi/security/advisories/GHSA-6q48-6c7w-9gw2).
The revised dependency graph has not been rerun in that sealed guest; the
historical verification does not cover this update. Other dependency profiles
need verification. Back up the guest's
`/etc/hosts` before adding `127.0.0.1 lab.test`. Reserve guest TCP/UDP loopback
ports 18443 and 19443. These changes belong only to the disposable guest.
+74 -2
View File
@@ -43,7 +43,8 @@ rea decompile /absolute/path/to/program main --json
If the registration cannot be read or validated, provider diagnostics retain its
selected `registration_path` and the configuration owner's setting/constraint
facts in `configuration_details.settings`, plus `configuration_remediation`.
facts in `configuration_details.settings`. The availability's `remediation`, and
each operation it makes unavailable, carry the configuration owner's repair.
`configured` means a path was selected, not that it is valid or an IDA connection
was established; failed registrations remain unavailable. Correct
`REA_IDA_MCP_CONFIG` in the next CLI invocation's environment or the MCP server's
@@ -80,6 +81,71 @@ Native MCP 1.1.2 can navigate in the previously focused pseudocode tab when swit
Native tool observations retain actual tool names and complete JSON text. Upstream `structuredContent` can be a shortened preview even while text contains the full result. REA reads the full native representation and rejects missing/malformed text instead of treating previews as complete observations.
### Migrate a legacy attached registration
Both attached profiles remain supported. Migrating replaces the upstream server;
adding `"protocol": "native"` to a legacy Python proxy registration is insufficient.
The native plugin is separate from mrexodia/ida-pro-mcp, and upgrading mrexodia to
its modern tool catalog does not select this profile.
1. Back up the working registration file. Install and start the native plugin
using its [upstream instructions](https://github.com/0xshlomil/ida-free-mcp).
IDA and the plugin are bring-your-own; REA does not install or build them.
Use a build with the navigation correction described above, and restrict its
listener to loopback or apply the documented host firewall restriction.
Stop the legacy endpoint first if both servers would use the same port.
2. Open the same original input binary in IDA and wait for analysis. Keep that
file readable by REA. The native metadata resource must report its recorded
input SHA-256; the resource's `path` identifies the database, not an input
file to substitute. Universal Mach-O inputs still require a thin extraction.
3. Replace the registration, using the native plugin's actual local endpoint.
For example, this legacy registration:
```json
{
"mode": "attached",
"command": "python",
"args": [
"/absolute/path/to/ida_pro_mcp/server.py",
"--ida-rpc",
"http://127.0.0.1:13337"
]
}
```
becomes:
```json
{
"mode": "attached",
"protocol": "native",
"url": "http://127.0.0.1:13337/mcp"
}
```
Keep `REA_IDA_MCP_CONFIG` pointing at the replacement file and close/reopen
the REA binary session to connect with it. If changing the environment's
configuration path, restart the MCP server as described above. Do not reuse
the legacy RPC URL as an MCP HTTP endpoint.
4. Check `binary_session` availability before resuming workflows. Direct
`procedure_callers` becomes unavailable and dossier callers remain unknown;
incoming address xrefs do not establish direct call edges. Native strings
use C-string rendering. Assembly and instruction reads no longer navigate
the GUI; native pseudocode still changes focus and closes existing
pseudocode tabs. IDA Free decompilation may require cloud access and consent.
5. Run the [real-provider verification](#verification) with two distinct
functions and the required second-function marker. Supply independently
known string and xref oracles for those checks. Existing Windows evidence
covers IDA Pro 9.3 with the documented source build and listener change;
it does not establish stock-plugin, Windows Free, or native macOS behavior.
A valid registration or successful doctor check alone does not verify the
connection, target identity, or function switching.
REA continues to verify identity and image base around requests and closes only
its connection, preserving the attached database. Restore the backed-up legacy
registration and its upstream endpoint to return to the legacy profile.
The legacy producer moves the active GUI as a side effect: assembly and raw function instructions jump to the function, and successful pseudocode opens or reuses a pseudocode view. A function dossier does both. Native GUI decompilation also changes the active view. `binary_session` reports these operations with `effects.may_show_ui: true`; headless operations and the other attached operations report `false`.
## Give REA a target for headless analysis
@@ -155,6 +221,12 @@ For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <cl
## Coverage and evidence
REA admits x86, x86_64, arm and arm64 executables through IDA. Every other CPU
family, including families that REA recognizes later, is refused until REA
admits it explicitly. Raw-binary and GameCube DOL targets are refused; open
them with Ghidra. Admission is not verification: the real workflows under
[Verification](#verification) cover x86_64 and arm64 only.
All profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. In the modern headless profile, targets that are not proven x86-only use processor-neutral `callgraph` edges instead of the x86 instruction checks in upstream `callees` (mrexodia/ida-pro-mcp#549). REA requests depth zero: the pinned upstream emits the root's outgoing edges without scanning child functions, then REA batches function-name lookup for those addresses. The graph covers recognized function destinations only; imports and other non-function targets remain unknown. A server without `callgraph` returns an empty relationship list with an explicit coverage limitation. Graph and path workflows retain those limitations alongside observed edges. A failed or inconsistent graph reply remains an error. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
All profiles expose `list_documents` as the single target admitted for the session. Attached mode verifies its digest against IDA metadata before returning it; headless mode binds the digest-checked private input. `inspect_address_context`, `trace_feature`, and `binary_overview` can therefore resolve that identity without upstream document-listing tools. Segment enumeration is optional: when the selected profile cannot provide it, `binary_overview` still returns document and procedure/string counts and omits `segments` and `segment_count`; omission means unobserved, not empty. The modern headless profile also supports `resolve_containing_procedure`. An address resolves through upstream `lookup_funcs` to the canonical entry of the function that contains it; the body stays unavailable like other IDA identities. When no function contains it, REA reads the database-scoped minimal `survey_binary` and reports `not_in_procedure` for an address inside one of its segments (ends are exclusive) and `outside_segments` otherwise. A missing, truncated or inconsistent survey fails the read instead of guessing a reason. `trace_feature` marks a trace truncated, with a residual unknown for that address, when upstream caps the references it returned. Attached profiles do not declare address containment resolution.
@@ -175,7 +247,7 @@ The verifier requires the resolved procedure in both inventory and procedure sea
The initial real workflows use stdio registrations for the legacy Windows GUI profile and a Windows x64 headless supervisor with IDA 9.3. The headless workflow has also passed on macOS arm64 with IDA Professional 9.1 (build 9.1.250226) against the same upstream commit `c133c3853faa111a9b00ee615c013b720d0c4acd`, using a source-built arm64 Mach-O fixture; Mach-O C symbols carry a leading underscore, so the procedure name is `_rea_fixture_add`. Streamable HTTP is tested against a loopback MCP fixture with the pinned client SDK; a real IDA HTTP-supervisor workflow remains unverified. Linux headless engine operation, other IDA releases, architectures, and modern attached GUI tools require their corresponding real workflows before support is claimed.
Native Windows verification uses IDA Pro 9.3 with the public two-function fixture and Windows `whoami.exe`, using upstream `c9d15d98e8a143fc74f6e14037c48f63889b6d6c` with a loopback-listener change. It covers production CLI/MCP parity, distinct-function switching, identity and connection cleanup. See the [correctness and performance measurements](https://github.com/morluto/rea/blob/main/experiments/analysis-efficiency/ida-native-windows.md). This follow-up did not run Windows IDA Free or native macOS.
Native Windows verification uses IDA Pro 9.3 with the public two-function fixture and Windows `whoami.exe`, using upstream `c9d15d98e8a143fc74f6e14037c48f63889b6d6c` with a loopback-listener change. It covers production CLI/MCP parity, distinct-function switching, identity and connection cleanup. See the [correctness and performance measurements](https://github.com/morluto/rea/blob/a2e2c29b59e8775b0e6aedb09a9eb16adf32646b/experiments/analysis-efficiency/ida-native-windows.md). This follow-up did not run Windows IDA Free or native macOS.
Headless readiness distinguishes an explicit `auto_analysis_ready: false` from
unknown readiness (`null`). The former returns a retryable capability refusal
+40 -8
View File
@@ -62,10 +62,11 @@ Collections are `sections`, `images`, `types`, `methods`, `fields`,
`generic_method_instances` and `generic_method_issues`. Every returned row includes
its source `index`, scoped by artifact SHA-256 and collection. All collections
accept batch `indices` and `fields`. Exact `names` filtering is supported for
`sections`, `images`, `types`, `methods`, `fields`, `properties`, `events` and
`parameters`; native `return_types`, `field_types` and `parameter_types` rows
are selected by source indices (or `method_indices` for `parameter_types`),
not rendered names. Types also accept `image_indices`, `namespaces` and
`sections`, `images`, `types`, `methods`, `fields`, `properties`, `events`,
`parameters`, `return_types`, `field_types`, `parameter_types` and
`generic_method_instances`. Native type names match the retained rendered display value exactly; every same-name row
keeps its source index, and an unavailable (`null`) name never matches a string.
Types also accept `image_indices`, `namespaces` and
`declaring_types`; methods, fields, properties and events accept
`declaring_types`. Parameters accept exact `method_indices`, so a batch of
method indices retrieves their parameters without reading the entire parameter
@@ -240,7 +241,7 @@ record addresses remain distinct candidates, and conflicting file mappings
remain malformed.
Module records establish names through their declared NUL-terminated string pointers. NUL/0xFF padding, spaces, Unicode and suffix-shared storage are supported. A pointer to a longer string does not match just its suffix; a pointer directly into a shared suffix may name it. Exact reversed UTF-8 matching avoids rescanning the file for every name and retains duplicate-name owners until module records resolve them. A contributor investigated a Linux
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64, the reader searches executable ranges for bytes consistent with RIP-relative LEA/MOV references to module-array candidates. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`.
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64 or arm64, the reader searches executable ranges for bytes consistent with a reference to a module-array candidate: RIP-relative LEA/MOV on x86-64, an adjacent `adrp`/`add` pair on arm64. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`. Cpp2IL's StrippedCodeRegSupport plugin also recovers the module list of an inlined CodeRegistration from the module names; this fallback additionally requires the exact located module set and a single code reference. A Unity 2022.3.62f3 macOS Master build checked by a contributor keeps neither registration symbol; its type table is then inferred as described below.
The binary/metadata pair is caller-selected. Name/count/token agreement is structural evidence, not a cryptographic same-build proof. File-offset locations refer to the primary metadata artifact; linked virtual addresses refer to `binary.artifact`. Both SHA-256 identities remain available in compact native pages as `artifact` and `binary_source`.
@@ -298,6 +299,30 @@ The result adds:
`Il2CppMetadataRegistration.typesCount` and its value. That record is found as
the single place whose `fieldOffsetsCount` and `typeDefinitionsSizesCount` both
equal the metadata type-definition count and whose type table is file mapped.
Linking reads only that table. When no record on x86-64 or arm64 has both of
those counts, the reader collects maximal runs of pointers to Il2CppType-shaped
records (an assigned Il2CppTypeEnum kind from 0x01 to 0x1e; class and
value-type data below the type-definition count) that are long enough for every
type index the metadata references. Exactly one such run may exist in the
image, it must start and end inside its mapping (a run touching either end may
continue in a neighbouring mapping), and its start must be loaded by the same
instruction forms as the module-array fallback. `types_field` is then `null`,
`types_count` is that run's length rather than an observed `typesCount`, and
Evidence is **inferred**. The run length is at least every referenced type
index but can differ from `typesCount`: a type-shaped pointer after the table
would lengthen it, and an unreadable slot (such as an unresolved relocation)
would end it early. Only the type indices the metadata references are read. A
second long-enough run anywhere, a run that a type-shaped neighbour extends
before the loaded address, a run touching a mapping end or a slot the loader
binds or leaves unresolved (its stored word is only a placeholder), and a covered
128-byte record whose two counts match but whose other fields are unusable all
leave `metadata-registration-not-found` rather than a guess; the search stops
at the second run. The length requirement only separates the type table from
other pointer runs when the metadata references most of it: on the one Master
build checked, the highest referenced index is 6,031 of 6,576 entries. A
metadata referencing few type indices usually
leaves several short runs and fails closed.
Counts use their declared 32-bit width independently of alignment padding.
The complete 128-byte registration must have unambiguous loader coverage. Its
used fields through byte 112 must be file-backed; a trailing declared zero-fill
@@ -353,7 +378,8 @@ the result has no rows, and `generic_method_pointers_field`,
`generic_method_pointers_count` and `generic_method_table_stride` are `null`.
A table of a few rows can still leave two layouts agreeing, which fails as
ambiguous rather than picking one. Metadata versions 24 and 39 and builds
without a static CodeRegistration (`code_gen_modules_field` `null`) fail as
without a static CodeRegistration (`code_gen_modules_field` `null`) or
MetadataRegistration (`types_field` `null`) fail as
`generic-methods-unsupported`: for version 24 the type-definition generic
container offset and the generic class layout have not been checked against a
real build, so it is refused rather than guessed.
@@ -418,10 +444,10 @@ The builds checked are listed after the module search rules above.
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
| `invalid-method-token` | input error | A token is outside its module's method-pointer rows, or a MethodDef row is reused within one image. |
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
| `metadata-registration-not-found` | input error | No single MetadataRegistration matches the type-definition count. |
| `metadata-registration-not-found` | input error | No single MetadataRegistration or code-loaded type-table candidate was resolved. |
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
| `generic-methods-unsupported` | unsupported | Generic instances were requested for metadata version 24 or 39 or a build without a static CodeRegistration. |
| `generic-methods-unsupported` | unsupported | Generic instances were requested for metadata version 24 or 39, or a build lacks either static registration. |
| `generic-method-registration-not-found` | input error | No CodeRegistration layout bounds every genericMethodTable row, or the generic tables are unreadable. |
| `ambiguous-generic-method-registration` | input error | More than one CodeRegistration layout bounds every genericMethodTable row. |
@@ -502,6 +528,12 @@ message names one reason:
| `image-range-overlap` | Two images claim the same type definitions. |
| `invalid-string` | A name index is outside the strings section, its string is unterminated, or it is not valid UTF-8. |
Record extents are checked for the decoded images, types, methods, fields,
properties, events, parameters, string literals, nested types, generic
containers, generic parameters and interface offsets. Other header sections
are checked for file bounds and overlap; their internal record layouts are not
decoded or validated by this inventory.
A version field that disagrees with the file's layout, for example a version
39 file relabeled as 29, usually surfaces as `unexpected-header-size` or
`record-size-mismatch`.
+13 -13
View File
@@ -47,19 +47,19 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
## Choose a guide
| Task | Guides |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [.NET](managed-code-analysis.md), [Python packages](python-packaged-applications.md), [Android](android-analysis.md), [APK signatures](apk-signature.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
| Task | Guides |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [UnityFS](unityfs.md), [U-Boot uImage](uimage.md), [.NET](managed-code-analysis.md), [Python packages](python-packaged-applications.md), [Android](android-analysis.md), [APK signatures](apk-signature.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [ESP images](esp-image.md), [ESP flash images](esp-flash-image.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
For development, use [testing](testing.md), [client verification](client-verification.md),
[tool design](tool-design.md), [admission limits and recovery](design/limits-and-recovery.md),
+72
View File
@@ -0,0 +1,72 @@
# Classic iNES container inspection
REA recognizes classic iNES from the `NES` + `0x1a` byte signature, independently
of the file extension. This read-only container inspection uses portable
Node.js file reads; it needs neither Ghidra nor an emulator.
```bash
rea inspect-artifact ./cartridge.nes --json
rea extract-artifact ./cartridge.nes --json
```
Through MCP, call `open_binary` with the local path, then `inspect_artifact`.
Use `extract_artifact` to materialize the regions in the fresh directory named
by its result. The caller owns that successful extraction directory.
## Header and file regions
Inspection returns `container-header` and `reconstruction` observations inline.
The single inventory Evidence under `substeps[].evidence.normalized_result`
contains the complete graph and an `ines` object:
- `ines.header`: original 16-byte `header_hex`, PRG/CHR file-unit counts, mapper,
raw flags 6/7, trainer and battery flags, and the declared mirroring mode.
- `occurrences[]`: each region's path, `source_location.offset` and
`source_location.length` in the original container. Follow `artifact_id` to
the node's observed SHA-256 and byte count.
- `ines.reconstruction`: ordered region paths, concatenated byte count,
SHA-256 and `verified: true`. REA accumulates the concatenation digest during
the same physical-order region scan that produces node hashes, then compares
it with the original whole-file digest. Final source freshness is also checked.
No reconstructed file is written during inspection.
Regions are `header.bin`, optional `trainer.bin` (512 bytes),
`prg/000.bin` onward (16 KiB each), `chr/000.bin` onward (8 KiB each), and
optional `trailing.bin`. Every input byte is retained, including unused header
fields and uninterpreted trailing data. Zero CHR-ROM units create no CHR region;
REA does not fabricate CHR-RAM contents.
After extraction, concatenate the materialized files in
`ines.reconstruction.region_order` from the inspection inventory. Their bytes
reproduce the input exactly. Graph path order is lexical and is not physical
file order; use the explicit region order or source offsets.
## Supported boundary
This increment supports classic iNES with a complete 16-byte header, nonzero
PRG count, complete declared trainer/PRG/CHR payload, and zero reserved bytes
12–15. Dirty legacy headers are refused because their upper mapper bits are
ambiguous; REA does not silently repair them. NES 2.0 and UNIF signatures,
unsupported header variants and truncated payloads are explicitly refused.
An arbitrary file named `.nes` is not admitted as iNES.
The PRG/CHR units describe file storage, not mapper bank sizes or CPU windows.
The mapper number and flags are header declarations, not proof of cartridge
hardware or runtime behavior. Bank switching, 6502/65816 disassembly, emulator
sessions and interpretation of console-specific trailing data remain outside
this lane. Raw-binary Ghidra analysis is a separate workflow. This container
increment addresses the first milestone of [#2072](https://github.com/morluto/rea/issues/2072);
the related [#1641](https://github.com/morluto/rea/issues/1641) tracks 6502/65816
native analysis with explicit load addresses and provider limitations. Extracted
PRG units still need caller-selected CPU windows before static code analysis;
their physical file offsets do not establish those addresses.
Source-owned byte fixtures verify original offsets and hashes, extraction and
byte-exact reconstruction, CLI/stdio MCP parity, malformed/unsupported inputs,
failed target-switch recovery, cancellation and source identity. These checks
do not establish emulator behavior or compatibility with every historical ROM.
The portable CLI/stdio MCP acceptance and source-identity checks run in Linux
and macOS CI. The same workflows have also been executed on native Windows x64.
Format references: [iNES](https://www.nesdev.org/wiki/INES) and
[NES 2.0](https://www.nesdev.org/wiki/NES_2.0).
+421 -87
View File
@@ -3,13 +3,14 @@
Run REA, connect your selected agents, and check the tools needed for your task.
Setup previews configuration changes and asks for approval before applying them.
| Task | Section |
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), the [IDA guide](ida-provider.md), or [Kuna](kuna-provider.md) |
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
| Task | Section |
| ---------------------------------- | --------------------------------------------------------------------------------------------------- |
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
| Install a Claude Code/Codex plugin | [Agent plugins and marketplaces](#agent-plugins-and-marketplaces) |
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), the [IDA guide](ida-provider.md), or [Kuna](kuna-provider.md) |
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
## Start setup
@@ -84,9 +85,16 @@ For an existing configuration, setup and uninstall recovery refuse a detected
replacement or edit and name the recovery backup. Quit clients before editing their configuration;
these checks do not lock out another writer modifying the same file in place,
or exclude pathname changes between the final check and publication.
Setup refreshes an existing `rea` registration only when its launcher proves REA
ownership. Foreign or unresolved launcher identity blocks setup before any
backup or write, including an OpenCode legacy entry that migration would remove.
Review the existing launcher and manually rename or remove that registration to
approve replacing it. A different existing installation path is foreign to the
current entrypoint; a path that cannot be resolved remains unknown. Cordis uses
its stable REA entry ID instead of launcher identity.
Setup also retains valid environment overrides in REA's existing server entry,
such as `REA_MCP_INPUT_SCHEMA_PROFILE`, `REA_MCP_OUTPUT_SCHEMA_PROFILE`, and
`REA_MCP_MAX_RESPONSE_BYTES`.
`REA_MCP_MAX_RESPONSE_BYTES`, and `REA_MCP_TOOL_FAMILIES`.
Detected provider settings replace only matching environment keys.
Setup manages REA's launcher, selected provider settings, and recommended timeout
defaults. Codex approval policies, tool selections, and other caller-owned server
@@ -162,7 +170,9 @@ Setup leaves existing skill copies in other locations untouched.
Setup refuses symbolic links at the selected skill directory or its managed
`references` directory, matching uninstall and update's ownership rules. Use a
regular skill directory, or select `--skill=false` to configure the client
registration without installing the bundled skill.
registration without installing the bundled skill. A non-directory ancestor is
reported as `ENOTDIR`; preserve the blocking file and correct the directory path
before rerunning setup.
The final selected directories are checked before approval and again before
setup changes any client or Hopper installation. Unselected client destinations
do not block other setup choices. Doctor reports linked or non-directory skill
@@ -204,7 +214,8 @@ identity remains available at `identity.catalog`.
Setup pins package-runner MCP registrations to the exact installed REA version,
installs the matching skill and on-demand references in the same plan, and adds
`startup_timeout_sec = 30` for Codex and Grok Build. Codex also receives a
`startup_timeout_sec = 30` for Codex and Grok Build when no positive timeout
is already configured. Codex also receives a
`tool_timeout_sec` derived from known provider startup plus operation headroom.
## Limit REA to one project or agent profile
@@ -264,7 +275,7 @@ After reviewing an update plan, rerun that same prefixed setup command without
`--dry-run` and approve its exact changes. Profile removal likewise needs the
prefix on `claude mcp remove --scope user rea` and any `rea uninstall` command;
keep the broader client scope of uninstall in mind. Restart the selected
profile after changing its pinned registration.
profile after changing its registration.
`claude mcp remove` removes only the registration. To remove REA from this
profile without running the broader uninstall flow, first back up the profile's
@@ -314,34 +325,30 @@ commands. Record an existing selection first if you intend to restore it after
finishing. The configuration path below is the default profile's
`~/.claude.json`; a different active profile uses its own config root instead.
Register with the variable unset:
<!-- x-release-please-start-version -->
Register with the variable unset, substituting the absolute runtime and entry
script from your existing installation or setup dry-run:
```bash
claude mcp add --scope local rea -- npx -y rea-agents@6.7.0 mcp
claude mcp add --scope local rea -- "/absolute/path/to/node" "/absolute/path/to/rea/scripts/rea.mjs" mcp
```
On native Windows, use the same project directory and wrap the package-runner
shim so the stdio client can launch it:
On native Windows, use the same project directory and absolute paths:
```powershell
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.7.0 mcp
claude mcp add --scope local rea -- "C:\Program Files\nodejs\node.exe" "C:\tools\rea\scripts\rea.mjs" mcp
```
<!-- x-release-please-end -->
Local scope stores the entry under that directory's path in `~/.claude.json`,
so other projects don't load it. This route doesn't install the bundled skill.
Avoid `--scope project`, which writes `.mcp.json` into the repository where it
can be committed or copied. `rea doctor` and `rea uninstall` read only the
user-level entry, so they don't report or remove a local-scope registration;
remove it with `claude mcp remove --scope local rea` from the same directory.
`rea update` doesn't refresh it either, so it stays on the version it names.
`rea update` doesn't refresh it either, so it continues using the selected installation.
To move it to a newer release, first back up the project entry in
`~/.claude.json` and record its environment and other metadata. In particular,
preserve caller-selected overrides such as `REA_MCP_INPUT_SCHEMA_PROFILE` and
`REA_MCP_MAX_RESPONSE_BYTES`. Run `claude mcp remove --scope local rea` from the
`REA_MCP_MAX_RESPONSE_BYTES`, and `REA_MCP_TOOL_FAMILIES`. Run `claude mcp remove --scope local rea` from the
same directory, then recreate it with the platform-appropriate command above
and the new version. Reapply every recorded environment value with Claude
Code's `mcp add --env NAME=value` options and restore any other retained entry
@@ -374,7 +381,7 @@ Select the part you want to check:
| Task | Readiness check |
| ----------------------------------- | ------------------------------------------------------------------------ |
| Static JavaScript/Electron analysis | Run `rea analyze-javascript-application PATH --json` directly. |
| One analysis engine | `rea doctor --provider ghidra --json` (or `hopper`, `ida`) |
| One analysis engine | `rea doctor --provider ghidra --json` (or `hopper`, `ida`, `kuna`) |
| One agent registration | `rea doctor --client codex --json` (see [client IDs](#supported-agents)) |
| Installed workflow instructions | `rea doctor --skill --json` |
@@ -383,6 +390,20 @@ A scoped report has `scope.mode: "explicit"`. Its `scope_checks` determine
`environment_healthy` summarizes the full audit. `--target` adds a target check,
but the report remains audit-wide unless a scope option is also supplied.
Provider checks read the terminal running doctor. A client-started server may
receive a different environment, and doctor cannot observe it. When an available
provider relies on a setting that a registration's file does not record, or
records with other text, doctor adds a non-blocking
`registration_environment:<client>` check with `classification: "unknown"`. The
setting names appear in that registration's `provider_settings`. Selecting the
client or an affected provider places the check in `scope_checks`; it never
changes `healthy`, because the client may still pass the setting on. Preview
`rea setup --client <client-id> --skill=false --dry-run` from that terminal and
approve it to record the settings, then restart the client. To see what the
running server can use, call `binary_session` in the client and read
`result.tool_availability`. For Hopper, only an explicit `HOPPER_LAUNCHER_PATH`
is compared.
An invalid `REA_LOG_LEVEL` fails every scope, including one client or the
installed skill, because the executable rejects it before any command. A
selected client also fails for every other setting that MCP startup rejects,
@@ -412,6 +433,13 @@ The public error code varies by caller: `capability_unavailable`,
only a generic startup error. Check this prerequisite before following
provider-specific recovery advice.
`binary_session` still returns status on such a host. Providers whose
readiness check reaches the owned-child launch, such as Ghidra's Java probe,
JADX and Apktool, report themselves unavailable with a reason naming the
missing `ps`. A provider that fails an earlier configuration check, such as
JADX without `REA_JADX_MCP_JAR`, keeps that reason. The other providers keep
their own status. `open_binary` keeps the refusal described above.
## Diagnose, update, and remove
`rea doctor --json` is strictly read-only. `rea update` updates only the npm or Bun global installation that owns the running CLI. Source checkouts and package-runner copies must be updated through the mechanism that owns them; a fresh package-runner invocation can use `npx rea-agents@latest` or `bunx rea-agents@latest`. `rea uninstall` removes only REA-owned agent registrations and skill files; `--purge-data` additionally removes REA cache and state paths.
@@ -429,11 +457,19 @@ and other MCP servers. Purging removes REA's legacy cache and state under
archives under `${XDG_CACHE_HOME:-~/.cache}/rea/hopper-releases`. Other cache
namespaces remain intact. A caller-selected `REA_KUNA_CACHE_DIR` is retained
and reported because REA cannot establish ownership of its entire contents.
Lexically equivalent spellings of that default directory, including a trailing
separator, a `.` segment, or a repeated separator, share its purge result.
The comparison preserves POSIX `..` traversal and literal backslashes.
A client configuration that is malformed, unreadable, or at an unsafe
path stops the operation before anything is removed, and a client that fails
while being updated stops the remaining removals. Uninstall reports each
candidate `configuration_path` with its outcome. A client with multiple files
keeps their individual `actions` alongside its aggregate status and detail,
path stops the operation before anything is removed when that client's
product applicability is established or unresolved. Shared registration
storage such as `~/.agents/mcp.json` whose product cannot be established
(for example when FreeBuff and ZCode discovery directories are absent) is
retained unchanged and does not block other REA removals, including cache
and state deletion with `--purge-data`. A client that fails while being
updated stops the remaining removals. Uninstall reports each candidate
`configuration_path` with its outcome. A client with multiple files keeps
their individual `actions` alongside its aggregate status and detail,
including earlier removals when a later file fails. The paths name
the discovered configuration candidates, not resolved physical file identities.
For JSON clients, uninstall removes every repeated `rea` member in the effective
@@ -495,6 +531,10 @@ Do not rename `index.cjs` to `index.mjs` or disable protection to make startup p
### Update the owning installation
Read-only metadata and version checks time out after 10 seconds. This does not
interrupt a package installation already in progress. If a probe cannot be
stopped, the updater reports a cleanup failure.
`rea update` installs the exact resolved
release into the npm prefix or Bun global directory that owns the running
package, then checks the new executable's version before reporting success.
@@ -525,8 +565,8 @@ non-TTY, and JSON modes without applying configuration changes.
## Released package and main
Repository main documents its current code and generated catalog. `@latest`
selects the npm release, and persistent MCP registrations are pinned to the
version that performed setup. Installing newer instructions does not update a
selects the npm release, and managed MCP registrations bind the installation
that performed setup. Installing newer instructions does not update a
running server or its registration.
The npm release checked on **2026-10-11** was **6.4.0**, published from
@@ -616,9 +656,12 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
| WorkBuddy | `workbuddy` |
| ZCode | `zcode` |
| DeepSeek Harness | `dsh` |
| FreeBuff | `freebuff` |
`droid`, `dsh`, `kimi`, `qoder`, `trae`, `workbuddy`, and `zcode` require
`[email protected]` or later. The 6.4.0 registry check above does not cover them.
`freebuff` requires `[email protected]` or later. Older releases reject
`rea setup --client freebuff` as an unsupported client.
See [client configuration and troubleshooting](client-configuration.md) for
configuration paths, profiles, trust policies, and schema-limit workarounds.
@@ -637,7 +680,8 @@ Hopper is separate commercial software with its own license. Its free demo has
vendor-defined limits, and a paid license is optional. REA reuses any detected
installation and preserves Hopper during uninstall.
The automated Hopper installer supports macOS 12+ and x86_64 hosts running
The automated Hopper installer supports macOS 12+, subject to the minimum
macOS version of the current Hopper release, and x86_64 hosts running
Ubuntu 24.04+, Fedora 41+, Nobara 44+, Arch Linux, CachyOS, or Omarchy. The
official Linux packages are x86_64-only, so setup does not offer Hopper on other
Linux architectures; configure [Ghidra](#ghidra) there instead. REA itself runs on any
@@ -646,8 +690,33 @@ an existing Hopper launcher is selected through `HOPPER_LAUNCHER_PATH`. Ghidra
and IDA have their own provider-specific host requirements; Windows Ghidra uses
the [experimental P0 boundary](windows-ghidra-p0.md).
Bazzite uses RPM packages, but REA does not install Hopper automatically on it.
Setup skips the Hopper offer because Bazzite is outside the supported host list,
regardless of whether Xvfb is installed. Bazzite documents
[containers for installing Linux packages](https://docs.bazzite.gg/Installing_and_Managing_Software/Containers/).
If using a container, check its distribution and architecture against the list
above. Install REA and Hopper in that container, give it access to the files you
want to analyze, and configure your agent to launch REA there. Verify installation
and analysis before relying on that workflow.
`rea doctor` checks health; it does not offer to install Hopper. Run `rea setup`
for installation. Setup cannot prompt when its output is redirected or during a
container image build. To approve Hopper installation without prompts, run
`rea setup --yes --install-hopper`.
If a Hopper request fails on macOS or Linux, the setup result's `download`
field names the request (`release_feed`, `package` or `public_key`), its `url`,
and the observed outcome `kind`: `http_status` with `httpStatus`, `timeout`,
`network`, `response_too_large`, or `url_refused`. The remediation names the
same URL and outcome. HTTP 404 or 410 means the file is missing, so an
immediate retry is unlikely to help; 5xx, 408 and 429 indicate a temporary
service failure. The status does not establish the underlying cause or guarantee
that retrying will succeed. Doctor and Linux launches report the same request
and status when publisher verification cannot download the current package.
Hopper metadata and package downloads require HTTPS throughout the redirect
chain; a redirect to HTTP is refused before that destination is requested.
chain; a redirect to HTTP or to a URL carrying credentials is refused before
that destination is requested, and those credentials are not reported.
On macOS, approved setup downloads the official DMG, checks its published size
and digest, validates the application bundle, and atomically installs it to
@@ -655,6 +724,15 @@ and digest, validates the application bundle, and atomically installs it to
operator can choose its demo mode or activate an existing license. Homebrew and
administrator access are not used.
Each Hopper release declares its own minimum macOS version, so the setup plan
cannot confirm support before approval. Setup compares this Mac's version with
the release metadata's `deployment_target` before downloading the DMG, and with
the signed application's `LSMinimumSystemVersion` before copying it. If this Mac
is older, setup fails with `incompatible_host_version`, names both versions, and
installs nothing. Update macOS, or install a Hopper release that supports this
macOS version yourself and set `HOPPER_LAUNCHER_PATH`. An existing Hopper
installation is not checked against the current release's minimum.
The Hopper launcher action used by REA creates a document from an executable;
its supported command-line interface does not attach to an already-open
document. While another live REA session owns the same target and loader
@@ -681,6 +759,12 @@ search permissions (`0700`) on a directory you own before retrying.
On supported Linux distributions, approved setup verifies Hopper's official
`.deb`, `.rpm`, or Arch package before invoking the native package manager.
On apt systems, Hopper's runtime dependencies are installed with
`--no-upgrade` in their own transaction, so setup no longer names an
already-installed dependency as an upgrade target. apt can still upgrade a
package that the Hopper package or the Python library repair requires at a
newer version. Arch's `pacman -S --needed` still updates an outdated
dependency.
The official release feed selects the current public demo without a version
pin. REA verifies the package's detached PGP signature against Hopper's stable
primary fingerprint `7C2D73BAFAF5B9A710A69DA1A26DF815099B1BCD`, then checks
@@ -689,6 +773,12 @@ Rotating signing subkeys remain bound to that primary identity.
The feed row itself is not PGP-signed; doctor labels its display version as
`reported_release_version`, separately from the authenticated launcher identity.
Even a correctly signed package can contain a launcher whose hash disagrees with
the feed. REA stops before installing that package. When reporting this error,
include the release version, package format and both hashes from the diagnostic.
Each format needs its own check: a matching Ubuntu DEB does not establish that
the Fedora RPM matches.
The approval plan includes the caller-selected cache location. Approved setup
retains the signed archive, public key and release metadata under
`${XDG_CACHE_HOME:-$HOME/.cache}/rea/hopper-releases/<launcher-sha256>`.
@@ -722,6 +812,16 @@ host mount and the rest of `/tmp` remain unchanged; this fallback never invokes
`sudo`. `rea doctor --provider hopper --json` reports the selected
strategy and both host and effective mount facts.
You do not need to start a separate `Xvfb :99` server. REA starts and manages its
own display. After installation, run `rea doctor --provider hopper --json` and
check `hopper-demo-runtime` for display and Python problems. Setup also runs these
health checks: installing the package is only one step toward a working provider.
If an image build ends with `exit status 1`, read the setup output immediately
before that summary to find the failing step. On minimal Ubuntu images, run
`apt-get update` before setup if an earlier layer deleted the package indexes.
REA's package installation commands do not refresh those indexes.
Hopper also embeds Python; the Python executable and standard library alone
are insufficient on Ubuntu. Approved setup checks the system interpreter
at `/usr/bin/python3` and, when necessary, installs its matching
@@ -803,7 +903,10 @@ Hopper startup allows 120,000 ms for launch, initial analysis, and authenticated
bridge readiness. For large targets, set `REA_HOPPER_STARTUP_TIMEOUT_MS` to a
decimal integer from 1 to 2,147,483,647 milliseconds in the environment that
starts REA. CLI and MCP use the same setting; invalid values fail configuration
validation. An MCP client's own request timeout must also allow enough time.
validation. In MCP, `update_analysis_settings` can set
`hopper.startup_timeout_ms` for the next startup attempt without restarting REA;
accepted work keeps its budget and retained cleanup must still complete. An
MCP client's own request timeout must also allow enough time.
`open_binary` validates and selects a target without waiting for native provider
readiness. During Hopper startup, `binary_session.provider_operation_health`
reports `busy` at stage `launch`; `analysis_activity` describes bridge requests
@@ -813,9 +916,13 @@ stage `connection`.
If macOS denies Apple Events, allow the application that starts REA under
**System Settings → Privacy & Security → Automation**, then retry. REA releases
the target reservation when the native helper reports its complete authorization
rejection and its arguments cannot alter the helper's generated AppleScript.
Arguments containing quotes, backslashes, or line breaks remain unchanged but
cannot establish that a failed handoff opened no document. Other failed handoffs
rejection after literal argument admission. Before application handoff, REA refuses
double quotes, backslashes, or line breaks in the final launcher path or arguments,
with `details.launch_rejection.boundary: "argument_encoding"` and the affected source/index.
Select literal-safe launcher/runtime paths and loader arguments, or another
compatible provider. REA preserves original file names and evidence: a quoted
executable name can still work when its prepared image path is literal-safe;
database paths must themselves be representable. Other failed handoffs
retain ownership until document closure is confirmed. If a timed-out bridge
becomes ready later, `close_binary` connects to that run, authenticates its
identity, and requests document shutdown. Once its listener is ready, the bridge
@@ -882,7 +989,12 @@ selected only when it is the arm64 family's only candidate. REA checks the
entire source's SHA-256 while copying the slice; source identity and reported
offsets still refer to the original container. Any other ambiguous architecture
subtype requires an explicitly extracted thin image; REA does not guess.
Configured loader arguments remain explicit overrides.
Set `HOPPER_LOADER_ARGS_JSON` in REA's launch environment to a JSON array of
strings to override the derived Hopper loader arguments, for example
`["-l","Mach-O"]`. Empty string elements are preserved; NUL bytes are not
accepted. An omitted setting or empty array uses the derived arguments. `rea
setup` forwards an explicitly selected setting to supported registrations;
restart the MCP server to apply a change. These arguments are Hopper-specific.
FAT64 is distinct from the CPU architecture: FAT32 containers can contain 64-bit
executable images. This preparation avoids the Raw Binary loader dialog observed
with native FAT64 loading on Hopper 6.1.0-demo.
@@ -920,7 +1032,9 @@ including while a shutdown request is in flight. A helper exit alone is
insufficient, and an application that never authenticated has no verified
process identity. If no matching bridge becomes available, inspect and close
the reported document in Hopper before ending the owning REA connection.
Export the Evidence bundle before `close_binary` clears session records.
`close_binary` preserves Evidence and complete Unknown history in memory on the
same connection. Export the bundle before ending the REA process to resume later;
`reset_investigation(save_path)` checkpoints and explicitly clears those records.
`binary_session.cleanup_pending` remains true while provider cleanup is retained,
and provider health remains visible even with `open: false`; that flag alone
does not establish successful cleanup.
@@ -942,6 +1056,14 @@ When Hopper cannot start in CI, run
`rea doctor --provider hopper --json` in the failing runner.
Structured failures distinguish private-display dependencies, an unsupported
demo dialog or build, process-ownership conflicts, and an early lifecycle exit.
Once launcher authentication succeeds, an absent first-run dialog is reported as
`demo_dialog_absent` rather than a build or publisher rejection. The watcher
waits 10 seconds by default. Set `REA_HOPPER_DEMO_DIALOG_TIMEOUT_MS` in the
environment that starts REA to a positive decimal integer of milliseconds from
1 to 2147483647 when a slower host needs a longer window. An invalid value is
`invalid_dialog_timeout`: the error names that setting, shows the rejected
value and its source, lists the allowed range, and asks you to correct the
setting and restart REA.
If a private-display probe times out or is cancelled and its process cleanup
fails, REA retains that exact probe owner and reports `cleanup_incomplete`.
Session close retries the probe before releasing its cleanup marker; doctor
@@ -954,7 +1076,7 @@ copying license secrets into logs, or killing unrelated Hopper processes.
REA connects to an existing Ghidra installation on Linux x64/arm64, macOS x64/arm64,
or experimental Windows x64 P0.
It accepts Ghidra 12.1.x and the 64-bit full JDK declared by that installation's
It accepts Ghidra 12.x (>=12.1) and the 64-bit full JDK declared by that installation's
`application.java.min` and `application.java.max`. The JDK's reported platform must also
map to the same native decompiler directory as the host: a JDK running under
emulation (for example x86_64 under Rosetta on Apple silicon) or reporting a
@@ -962,7 +1084,8 @@ non-host `os.name` fails the doctor
even when it is an otherwise accepted 64-bit full JDK. Select a matching JDK
through `JAVA_HOME`; adding another decompiler in the JVM-selected directory
does not satisfy this host-validated admission rule. Current 12.1 releases require
JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4
JDK 21 or newer; Ghidra 12.2 declares JDK 25 or newer. Neither sets a maximum.
The bridge is verified with Ghidra 12.1.4
and JDK 21. Each installation must include the native decompiler for the host
architecture in `Ghidra/Features/Decompiler/os/<platform>/` or the corresponding
`build/os/<platform>/` directory. Linux ARM64 uses `linux_arm_64`; official
@@ -973,7 +1096,7 @@ reported as incompatible instead of being admitted from its directory name, and
a header that cannot be read or recognized is reported as unknown. REA does not
build or install native tools, or change Gatekeeper quarantine settings.
The adapter exposes 26 read-only operations: fourteen inventory/name/search
The adapter exposes 27 read-only operations: fifteen inventory/name/search
operations and twelve function-analysis operations. These cover metadata,
decompilation, assembly, resolved calls, typed references, xrefs, function
dossiers, instructions, recovered data types, measured load mappings, loaded
@@ -997,9 +1120,13 @@ Target admission preserves recognized PE/NE/LE/LX signatures separately from
reader layout support. A Windows header overlapping the first 64 DOS-header
bytes remains an unsupported layout in this release; it is not reported as an
invalid offset. Tiny PE files with short optional headers have additional
unverified loader requirements. Unconfirmed nonzero DOS padding remains an
unresolved Windows-header candidate, with independently validated DOS facts in
the target error diagnostics; those facts do not prove a Windows header absent.
unverified loader requirements. A nonzero Windows-header pointer without a
recognized signature remains an unresolved candidate, with independently
validated DOS facts in the target error diagnostics; those facts do not prove a
Windows header absent. When a nonempty relocation table ends inside a complete
DOS header, the remaining header bytes are DOS padding, so a nonzero dword
there is not a Windows declaration. An unread signature beyond the current
probe is requested before that padding rule applies.
On Linux and macOS, `annotate_native_function` also edits a function name and/or
entry comments atomically and returns refreshed analysis, and `set_address_name`
and `set_addresses_names` name a function entry or any other mapped address
@@ -1048,14 +1175,15 @@ rea providers --json
```
`rea setup` can configure supported Windows agents and install the bundled
REA skill after approval. Direct registrations use Node to launch REA's entry
script; package-runner registrations use the pinned `npx` or `bunx` command. Hopper
REA skill after approval. Managed registrations use the absolute selected Node or
Bun runtime and REA entry script, including when setup ran through a package runner. Hopper
installation remains unavailable on Windows. Setup never installs Ghidra,
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
The separate `rea mcp add` builtin currently uses Incur's package-runner child
launcher, which can fail on Windows with `MCP_ADD_FAILED: spawn npx ENOENT`
The separate `rea mcp add` builtin delegates most clients to Incur's package-runner
child launcher. Run that acquisition from a trusted directory; prefer managed
`rea setup` when supported. That child launcher can fail on Windows with `MCP_ADD_FAILED: spawn npx ENOENT`
before registering a client. REA's setup path is unaffected. For a client supported
by setup, review `rea setup --client opencode --skill=false --dry-run --json`
(substitute its client ID), then use the same scoped command without `--dry-run`
@@ -1073,7 +1201,7 @@ the application metadata and headless launcher. An explicitly supplied
REA reports that error instead of silently selecting another installation.
This discovery is read-only and does not execute the Ghidra launcher.
Doctor validates the platform, architecture, Ghidra 12.1.x application version,
Doctor validates the platform, architecture, Ghidra 12.x (>=12.1) application version,
`support/analyzeHeadless` or `support/analyzeHeadless.bat`, the installation's
Java major range, 64-bit JDK bitness, and the presence of `javac`/`javac.exe`.
It also checks that the reported JVM `os.arch` and `os.name` map to the same native decompiler
@@ -1129,9 +1257,10 @@ This coordinate selects only REA's session-private configuration directory, not
the installation or an existing user configuration directory.
Each verified session uses an ephemeral temporary project and isolated
home/cache/config/temp paths. REA passes `-readOnly`, `-deleteProject`, uses
Ghidra's default analysis settings, and loads its packaged Java
bridge via `-scriptPath`; it never opens an existing user project. Linux and
home/cache/config/temp paths. By default REA passes `-readOnly`, `-deleteProject`,
uses Ghidra's default analysis settings, and loads its packaged Java
bridge via `-scriptPath` into a new ephemeral project; opt-in
[existing-project reuse](#reuse-an-existing-ghidra-project) opens only an owned copy. Linux and
macOS use a current-user-only local bridge socket and descriptor. The
project remains under the selected temporary directory. If its Unix socket
pathname would exceed the host's byte limit, REA allocates a separate owner-only
@@ -1150,7 +1279,7 @@ original process owner for a later cleanup retry.
The experimental Windows transport uses authenticated IPv4 loopback with a
private native-owned bearer descriptor and Job Object process ownership.
Operations begin only after default auto-analysis completes. `open_binary`
In the default fresh-import mode, operations begin only after default auto-analysis completes. `open_binary`
selects and validates the target/provider binding; it does not wait for Ghidra
import and auto-analysis. The first Ghidra-backed query starts that work lazily.
The provider startup deadline is 330,000 ms by default for import, analysis,
@@ -1159,8 +1288,10 @@ bridge, and health readiness. Large binaries can need more: set
milliseconds in the server environment. An absent setting uses the default;
empty, invalid or out-of-range supplied values fail configuration validation.
When the deadline elapses, the `provider_timeout` error names
`REA_GHIDRA_STARTUP_TIMEOUT_MS` with the deadline that was applied and suggests
`REA_GHIDRA_DISABLED_ANALYZERS` for analyzers that dominate the time.
`REA_GHIDRA_STARTUP_TIMEOUT_MS` with the deadline that was applied. In
fresh-import mode it also suggests `REA_GHIDRA_DISABLED_ANALYZERS` for analyzers
that dominate the time and `REA_GHIDRA_PROJECT_PATH` for reusing a completed
analysis; in existing-project mode it omits that import and analyzer advice.
REA parses the supplied configuration environment and
passes the deadline to each provider client; a startup failure is returned by the query that triggered it,
without exposing partial analysis. This deadline is separate from MCP transport
@@ -1169,19 +1300,25 @@ can time out earlier even when Ghidra would complete within its startup deadline
See [Ghidra first-query deadlines and recovery](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery)
for client options and the close/reopen recovery flow.
The 330-second default is configurable. Configuration is read when REA starts:
changing an environment variable in another shell, or closing and reopening a
binary in the same MCP server, does not change that server's startup allowance.
The 330-second default can be changed on a running MCP server by setting
`ghidra.startup_timeout_ms` through `update_analysis_settings`. The next accepted
operation that starts a provider uses that value; an accepted startup keeps its
original budget. Changing an environment variable in another shell, or closing
and reopening a binary, does not change the launch configuration already read
by that server.
For a CLI retry with an example 30-minute allowance:
```sh
REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 rea analyze /absolute/path/to/app.exe --provider ghidra --json
```
For MCP, export useful Evidence before restarting, close the binary and resolve
any cleanup failure, update the environment in the client's REA server launch
configuration, then restart REA and reconnect before reopening the target. Allow
startup plus the query in the client's own tool-call deadline. Every retry uses
For MCP, update the runtime setting and resolve any retained cleanup before the
next startup attempt. Follow the error's close/reopen guidance where needed;
the settings update does not clear cleanup blockers. To change the launch
environment instead, export useful Evidence, close the binary and resolve any
cleanup failure, update the client's REA server launch configuration, then
restart REA and reconnect. Allow startup plus the query in the client's own
tool-call deadline; runtime updates do not change that deadline. Each new startup attempt uses
a fresh ephemeral import; smaller result requests do not reduce the prerequisite
auto-analysis. A larger time allowance does not raise the JVM heap limit or
guarantee completion. Keep [heap and host CPU controls](#ghidra-heap-and-cpu-controls)
@@ -1208,9 +1345,15 @@ fails with `provider_timeout` and that message; other Ghidra tools stay
available. If the caller also cancels while the timeout settles, the session
still closes and the message says so. Session reuse concerns an active MCP or
library session; a standalone CLI command still closes its own session when it
finishes. The limit is read when a Ghidra session
starts, and Codex/Kimi registrations derived by `rea setup` add it to the tool
deadline as a single-decompile estimate. A bulk call can need that limit for
finishes, so its timeout names `REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS` for a new
invocation instead. In MCP, `ghidra.decompile_timeout_seconds` can override the launch
value for newly accepted operations on the existing Program; explicit `null`
removes the deadline. A transient timeout remains uncached so the same function
can be retried after an update. See the
[settings design](design/limits-and-recovery.md#runtime-settings-through-mcp).
Codex/Kimi registrations derived by `rea setup` add the selected launch value
to the tool deadline as a single-decompile estimate; a runtime update does not
change that registration. A bulk call can need that limit for
every attempted function plus listing and file work; select the client deadline
for the workload, split batches, use `decompile_all` limit/resume, or reduce
`trace_native_values` `max_functions` to select fewer serial decompiles. Ghidra's own interactive default is 30 seconds. Unresolved computed calls
@@ -1270,6 +1413,63 @@ not needed for the make step, only for the Ghidra provider. If your toolchain
rejects the inherited `-mmacosx-version-min=10.6` flag, retry the make with
`ADDITIONAL_FLAGS="-w"`.
### Reuse an existing Ghidra project
For repeated investigations, select an expanded local project marker with
`REA_GHIDRA_PROJECT_PATH=/absolute/projects/app-analysis.gpr`. Both CLI and MCP
use this shared selection. `REA_GHIDRA_PROGRAM_NAME` selects one exact program
in the project root; when omitted it defaults to the selected target's filename.
Paths, globs and command flags are not program names. The matching `.rep`
directory must be present; restore `.grez` archives with Ghidra first.
```bash
export REA_ANALYSIS_PROVIDER=ghidra
export REA_GHIDRA_PROJECT_PATH=/absolute/projects/app-analysis.gpr
export REA_GHIDRA_PROGRAM_NAME=app
rea decompile /absolute/target/app --procedure entry --provider ghidra --json
```
Set the same variables in the MCP server environment and restart/reconnect;
`open_binary` still selects the original target file. `rea setup` retains these
explicit settings in a saved registration. Fresh import remains the default
when the project setting is absent. This route supports native ELF, PE and
Mach-O programs on POSIX without provider-pinned import preparation; Windows,
raw/DOS/DOL and special pinned-language import profiles require fresh import.
REA fingerprints only the selected `.gpr`/`.rep` pair, copies it into its owned
runtime, verifies the copied content against the committed analysis profile,
and runs `-process <program> -noanalysis -readOnly`. Ghidra never opens or saves
the original database; REA reads selected files only for fingerprinting and copying.
This mode does not copy unrelated sibling projects, rerun repair scripts, or
admit bridge database edits. Cleanup removes the owned copy.
The bridge checks the program's recorded import SHA-256 against the admitted
target digest; mismatches and project changes fail explicitly. Reopen the target
to resolve a new profile after intentionally changing the source project.
The profile records the caller path, resolved project/name, program and content
fingerprint. Original analysis settings and completeness remain **unknown**;
prior annotations or edits are caller-project state, not fresh REA discoveries.
A matching recorded import digest does not claim that the database is unedited.
Saved REA seed/analyzer reports remain historical facts in retained source Evidence;
they are not interpreted as overrides applied by this session.
Project content participates in snapshot identity, so different project states
cannot reuse the same profile's cached observations.
To prepare a new project explicitly, use your existing Ghidra installation in a
new directory you own (this writes that project; REA does not save it for you):
```bash
mkdir /absolute/new-projects
GHIDRA_HEADLESS_MAXMEM=1536M JAVA_TOOL_OPTIONS=-XX:ActiveProcessorCount=2 \
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /absolute/new-projects app-analysis \
-import /absolute/target/app -analysisTimeoutPerFile 600 -max-cpu 2
```
Check that preparation completed successfully before selecting the project.
Preparation time, disk copy/fingerprinting, engine startup and client deadlines
are separate costs; reusing a project avoids import/analysis, not all startup.
Use the [heap/CPU controls](#ghidra-heap-and-cpu-controls) appropriate to the target.
### Ghidra import language and analysis seeds
Ghidra detects the language from the executable header. To set it (for example, Borland C++ PE images):
@@ -1283,6 +1483,33 @@ REA passes `-processor`/`-cspec`, checks both against the installation's `.ldefs
Language definitions must be well-formed XML encoded as UTF-8 or BOM-marked UTF-16, with any encoding declaration matching the bytes. Literal Unicode replacement characters (U+FFFD) are accepted; malformed source bytes are rejected.
Ghidra imports little-endian ARM ELF files, including Cortex-M
(`arm-none-eabi`) firmware, as `ARM:LE:32:v8`. That language defaults to ARM
state, and its ELF loader marks code as Thumb only where `$t` mapping symbols
or odd function-symbol addresses say so. Cortex-M cores run only Thumb code, so
the gap matters mainly for code without those markers, such as stripped images
and handlers reached only through the vector table. REA has no M-profile ARM
lane. As an unverified interim, select an M-profile language, which decodes in
Thumb state by default. For ARMv6-M, ARMv7-M and ARMv7E-M cores:
```bash
export REA_GHIDRA_LANGUAGE_ID="ARM:LE:32:Cortex"
```
For ARMv8-M cores, use `ARM:LE:32:v8-m` instead. REA has not verified either
language for Cortex-M analysis. While the variable is set, it applies to every
Ghidra import in the REA server:
- raw-binary, DOS, GameCube DOL and PSP opens are refused, as are opens in
lanes that reject a different language, such as RISC-V;
- REA asks Ghidra to import everything else in the selected ARM language,
including x86, x86-64, arm64, PowerPC and Hexagon ELF files and PE and
Mach-O files, because it does not check that the override's processor
matches the target.
Restart REA without the variable before you analyze other targets. A future
M-profile lane may refuse it; remove the setting then.
To seed code that auto-analysis cannot reach (computed jumps, pointers in data):
```bash
@@ -1324,6 +1551,21 @@ current setting. For Windows PE resource analysis, the exact name is
`WindowsResourceReference`. Disabling an analyzer skips the facts it would have
produced.
Ghidra's Objective-C Message Analyzer can log
`Required Objective-C data type not found in data type manager` /
`Try adding libobjc.dylib` when a Mach-O links Objective-C runtime symbols but
defines no class metadata (thin Cocoa glue in a C++ binary is a common case).
That text is non-fatal Ghidra analyzer output: auto-analysis continues, and the
analyzer simply skips `objc_msgSend` call-target recovery. REA's headless import
does not accept a "add this library" option, and on modern macOS
`/usr/lib/libobjc.A.dylib` exists only in the dyld shared cache, so there is no
on-disk library to add. Treat the line as a coverage limitation, not an REA
failure; check the session's `failure_kind` (and related provider errors) for
any real abort. To silence that analyzer, set
`REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer"`. For large
executables that miss the startup deadline instead, raise
`REA_GHIDRA_STARTUP_TIMEOUT_MS` and the [Ghidra heap](#ghidra-heap-and-cpu-controls).
### Ghidra heap and CPU controls
Set resource controls in the environment that launches the CLI or MCP server,
@@ -1419,12 +1661,93 @@ On a controlled Windows x64 runner, use
`npm run verify:ghidra:windows`. The verifier generates a deterministic native
PE fixture from source bytes and requires the Windows native authority before
opening the provider. The controls are implemented on main; see the
[release boundary](#released-package-and-main). The lane verifies all 26 admitted
[release boundary](#released-package-and-main). The lane verifies all 27 admitted
read-only operations, digest identity, and runtime cleanup. The separate
`verify:ghidra:windows:package` lane checks the packed CLI/MCP boundary with an
ordinary user. It requires the built Windows native bundle and an existing
Ghidra/JDK installation; neither is inferred from startup alone.
## Agent plugins and marketplaces
REA's npm package is the shared Claude Code and Codex plugin artifact. It
contains `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and the
canonical generated `skills/` tree. Both manifests select `./skills` and run a plain Node bootstrap pinned to
that exact package version. The client must have a supported standalone `node`
on its PATH. Claude expands its documented `CLAUDE_PLUGIN_ROOT` in the script
argument; Codex resolves `cwd: "."` from the installed plugin root before
launching the relative script. The bootstrap verifies the package version and runs its bundled runtime in
the same process, preserving stdio and REA's provider/worker cleanup. It does
not start another package runner or download a different runtime. The existing release skill
generator also pins CLI recovery commands to the same version.
The repository's single-plugin marketplaces are declared in
`.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`.
Both select an exact `rea-agents` npm version instead of mutable source-checkout
contents. Release Please updates their selectors alongside the plugin manifests.
No plugin-only publication or generated skill files committed to Git are needed.
The qualified release archive contains a runtime-only manifest and
`npm-shrinkwrap.json` plus bundled runtime dependencies, including PTY packages
for each supported OS and CPU; development dependencies and development-only overrides
stay in the source manifest and lockfile. Both clients receive this same archive.
This distribution becomes installable after the first qualified npm release
containing these plugin manifests. Earlier published packages are not changed
by this source proposal. For local source development, run `npm ci && npm run build:cached` to generate
the runtime and packaged skill before loading the repository as a plugin.
A source plugin uses the locally built runtime and generated skill; rebuild after
source changes. Missing builds and mismatched version pins fail with an actionable
error. The authored `.agents/skills/` tree is not the plugin's shipped skill layout.
Windows source workflows that need native controls still require the
[Windows source-checkout build](https://github.com/morluto/rea/blob/main/native/windows/README.md#build).
In Claude Code:
```text
/plugin marketplace add morluto/rea
/plugin install rea@rea
```
In Codex CLI, add the marketplace and install REA:
```bash
codex plugin marketplace add morluto/rea
codex plugin add rea@rea
```
Each release updates both manifest versions and their pinned server version
together, which is when installed copies refresh the bundled skill and move to
the new server. A plugin does not run `rea setup`, so provider
configuration still follows the sections above. Use the plugin or a
`rea setup` registration for a client, not both, to avoid two `rea` servers.
`rea doctor` does not see plugin-owned state. Its client and skill checks read
only the registrations and skill locations that `rea setup` manages, so for a
plugin-only install `rea doctor --client <id>` and `rea doctor --skill` report
`missing` and recommend `rea setup`. Do not follow that remediation. Check the
plugin with the client instead (`claude plugin list` and `/mcp` in Claude
Code, `codex plugin list` in Codex).
For analysis engines, ask the connected server: have the agent call
`binary_session` and read `result.tool_availability`, which reports what the
server the plugin started can use, with reasons and remediation. See
[MCP contracts](mcp-contracts.md).
A plugin does not put `rea` on your `PATH`. To check an engine from a
terminal, use the package runner at the version the plugin pins:
<!-- x-release-please-start-version -->
```bash
npx -y rea-agents@6.8.0 doctor --provider ghidra --json
```
<!-- x-release-please-end -->
If your installed plugin is older, use the version in its manifest instead.
That report describes the terminal's environment, not the plugin server's.
The server inherits the client's environment, so a setting such as
`GHIDRA_INSTALL_DIR` that exists only in your terminal does not reach it.
## MCP Registry
REA is published in the official MCP Registry as `io.github.morluto/rea`. Registry
@@ -1435,33 +1758,45 @@ For a client that supports Registry discovery, search for `io.github.morluto/rea
and select the npm package. The published metadata launches the existing stdio
server through `npx` with the `mcp` command.
For a client that requires manual configuration, use:
For supported clients, prefer `rea setup`: it records the absolute JavaScript
runtime and entry script of the REA installation performing setup. Client starts
then use that installation directly, keeping the client's working directory
without running npm or Bun package resolution there. Registry clients control
their own package acquisition; Registry metadata cannot name local absolute paths.
<!-- x-release-please-start-version -->
For manual stdio configuration, substitute the absolute paths of an existing
installation (use the command shown in a setup dry-run):
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.7.0", "mcp"]
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/rea/scripts/rea.mjs", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
Run current setup to migrate recognized older `npx`/`bunx` registrations, with
backups, then restart the client. Unrelated or unidentified registrations remain
untouched. Setup does not prewarm a separate package-runner command.
Persistent registrations should use one exact package version. `rea setup`
writes the same package-runner shape, or `bunx rea-agents@<version> mcp` when
setup runs through `bunx`, and pins it to the exact version that performed
setup. Run current setup to refresh an older registration, then restart the
client.
If setup itself runs from a package-runner cache, the registration depends on
that cache entry and the selected runtime remaining installed. Removing either
makes startup fail; it does not download or resolve another package. Restore the
same installation and rerun setup. Changing package requests, cache roots, or
runtimes can create a different installation identity. If the old entry cannot be
identified, restore it first or manually remove the confirmed REA registration
before running setup again. A global installation provides a more durable path.
## Package runners and global installation
A package-runner invocation can use a REA version installed in the current project. To
request the latest release explicitly, use:
Package runners can select project-local packages and read project configuration,
even for explicit version requests. Acquire REA from a trusted working directory,
outside the target project, and pass absolute target paths to analysis commands.
REA cannot validate package acquisition that happens before its entrypoint starts.
To request the latest release explicitly, use:
```bash
npx rea-agents@latest setup
@@ -1478,16 +1813,14 @@ For an intentional rollback, make the package request explicit:
npm exec --yes --package=rea-agents@2.4.0 -- rea setup
```
Setup continues to pin persistent MCP registrations to the exact version that
performed setup. Running current setup later migrates unversioned or older
managed registrations through the normal reviewed setup transaction.
Setup binds persistent MCP registrations to the installation that performed setup.
Running current setup later migrates recognized legacy package-runner registrations
through the normal reviewed setup transaction.
With Bun, `bunx rea-agents setup` (or `bunx rea-agents@latest setup`) is the
equivalent entry point. Setup run through `npx` registers
`npx -y rea-agents@<version> mcp`; setup run through `bunx` or `bun x` registers
`bunx rea-agents@<version> mcp`. On Windows, setup wraps either command in
`cmd.exe /d /c` so clients can start the runner's command shim. Neither
registration points into the runner's temporary package cache.
equivalent acquisition entry point. Managed registrations use the selected Bun
executable and REA entry script directly. Windows registrations likewise use
absolute runtime and script paths, without a package-runner command shim.
REA supports Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), and 26.12.1+. Node.js 23, 25, earlier 26.x releases, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
@@ -1521,8 +1854,9 @@ Review the plan, approve the selected client changes, then restart those clients
This registers the installed entrypoint directly; installing the global package
alone does not replace existing `npx` registrations. Each client still has its own
REA process and analysis state. Direct registrations follow the installed CLI;
use `rea update` to update it. Running setup through `npx` again selects the pinned
package-runner command. Do not register paths inside npm's temporary package cache.
use `rea update` to update it. Running setup through `npx` again selects the
installation that executes setup, potentially in npm's temporary package cache;
retain that cache entry for as long as the registration uses it.
### npm workspace errors before REA starts
Loaded 100 of 4622 files, more files were not shown because too many files have changed in this diff. Show more