observeSettlement classified a group as alive_at_deadline when the settlement
budget expired after one empty observation, though no live member was ever
seen. Track observed liveness and return unverifiable when the deadline ends
observation with only empty rounds. The two-consecutive-empty rule for
quiesced is unchanged, and the cleanup_outcome projection for non-quiesced
settlements stays cleaned.
Fixes#2170.
* feat(ghidra): admit RISC-V ELF analysis through a pinned generic lane
Refs #2063.
Recognize `EM_RISCV` (243) as a provider-neutral family that carries its own ELF
facts, and bound the first Ghidra lane to little-endian `ET_EXEC` images that
declare the double-float ABI, pinned to `RISCV:LE:32:default` or
`RISCV:LE:64:default` with the `gcc` compiler specification.
Ghidra 12.1.4 is not reliable without the pin: its `riscv.opinion` pins
`variant="default"` for the 64-bit constraint and leaves the 32-bit constraint
with the comment that the loader "must choose between 'default' and 'AndeStar'
variants". Measured on the installed release tree, a source-owned generic RV32
ELF with no Andes declaration imports as `RISCV:LE:32:AndeStar_v5:gcc`, while
RV64 resolves to `RISCV:LE:64:default:gcc`. The lane therefore states the
language per width and refuses any caller-selected processor outside it instead
of inheriting the header guess.
Refusals name the declaration rather than falling back to a guessed profile:
big-endian images (Ghidra 12.1.4 ships no big-endian RISC-V language),
non-`ET_EXEC` images, `EF_RISCV_RVE`, `EF_RISCV_TSO`, and every float ABI other
than double, which the pinned generic compiler specifications model. The raw
e_flags bits stay in Evidence as declarations, not as instruction semantics.
Family recognition now has one owner for the ELF-metadata families:
`ELF_METADATA_ARCHITECTURES`, `isElfMetadataArchitecture`, and
`HeaderArchitecture` replace ten `Exclude<BinaryArchitecture, "mips">` sites that
would each have needed a second hand-edited member. `ghidraPinnedLanguage` is
that owner for the import commitment: the session readback previously re-derived
only the MIPS pin, so a profile committing any second pinned language failed with
a language-mismatch error before the bridge started.
Hopper and IDA refuse RISC-V by name, matching the MIPS wording. Caller-supplied
raw-binary entry keeps its existing four-family list; this lane is ELF-header
admission only.
Verification on macOS 15.7.7 arm64, Node 24.18.0, Ghidra 12.1.4 PUBLIC and JDK
21.0.12.1, with the host decompiler built from the release sources for
`os/mac_arm_64`: `npm run check` passes; `npm run test:fast` passes 396 test
files; `npm run verify:test-discovery` discovers all 917 test files exactly once.
Source-owned RV32 and RV64 double-float `ET_EXEC` fixtures complete
`rea analyze --provider ghidra` and `rea decompile rea_riscv_entry`, committing
`RISCV:LE:32:default` and `RISCV:LE:64:default` with `language_selection:
provider-pinned-v1`; the soft-float RV64 shape is refused with the named
float-ABI reason, and the full refusal matrix including the RV32 and
width-mismatch shapes is covered by unit tests. No `verify:ghidra:riscv` lane
script is included in this change; the runs above were performed manually against
the installed engine. Fork pull requests receive no CI check runs, so the
real-provider scope above is the evidence available for review.
* fix(ghidra): bound the RISC-V lane by declared flags and document it
Address review feedback on #2151.
`e_flags` bits outside `EF_RISCV_RVC | EF_RISCV_FLOAT_ABI` are now refused with
the offending value named, so a reserved or newer producer declaration cannot
enter the verified generic lane by falling through the RVE, TSO and float-ABI
checks.
Admission reads `e_type` only, so the refusal text no longer calls `ET_EXEC`
static: a fixed-address declaration does not establish static linkage, and
`PT_INTERP` and dynamic dependencies are not inspected. The session records that
boundary as a limitation rather than inferring a load model.
Add `docs/ghidra-riscv.md` in the shape of the MIPS lane guide (admission
boundary, refusal table, why the language is pinned per class, Evidence fields,
and what the fixtures do not establish), and correct
`docs/firmware-analysis.md`, which still told users that MIPS and RISC-V
decompilation had not been added.
Verification after the change: `npm run check` and `npm run docs:check` pass; the
RISC-V module tests pass (17 lane cases plus 9 parser cases); on Ghidra 12.1.4
with JDK 21.0.12.1 the RV32 and RV64 double-float fixtures still analyze and
decompile with `RISCV:LE:32:default` / `RISCV:LE:64:default` and
`language_selection: provider-pinned-v1`, the soft-float fixture is still refused
by name, and the new linkage limitation appears in the emitted Evidence.
* fix(ghidra): align RISC-V pinning with admission
Reuse the complete lane admission boundary when selecting a language, retain unsigned unsupported flag bits in diagnostics, and derive float ABI metadata from the source declaration.
* test(ghidra): verify RISC-V CLI and MCP workflows
Build source-owned RV32/RV64 fixtures with Clang and LLD, establish independent ELF and symbol facts with LLVM readelf, and verify real provider evidence, instruction bytes, calls, decompilation, schema projections, snapshot replay, refusal diagnostics, source immutability and cleanup.
Verified on macOS ARM64 with Ghidra 12.1.4, JDK 21 and LLVM/LLD 23.1.2. Dynamic linkage, big-endian, Andes and RVE/TSO workflows remain unverified; ISA attributes are not inspected.
---------
Co-authored-by: morluto <[email protected]>
Report malformed Hermes and DeepSeek Harness documents as YAML. Carry the
compiled setup/uninstall diagnostics coverage from #2153 onto the existing
validation from #2157, including unchanged files and no backup writes.
Add malformed YAML scenarios for both clients.
Validation: 174 focused tests, npm run check, test discovery, package
verification, and npm pack --dry-run.
Co-authored-by: Jax367 <[email protected]>
* fix(setup): name invalid client config shape errors clearly
Setup was dumping raw Zod issues for non-object roots or server tables,
and uninstall always claimed the file was not valid JSON. Report the
structural problem instead, and reserve parse failures for real syntax errors.
Fixes#2152
Co-authored-by: Cursor <[email protected]>
* fix(setup): preserve client configuration record validation
Keep descriptive shape errors while retaining Zod record validation and its own-property copy. Reject tagged YAML objects before setup or uninstall writes and prevent inherited JSONC properties from fabricating client registrations.
Add production-boundary regressions for tagged YAML roots and server tables, plus inherited Cursor and OpenCode settings. All 91 focused tests, npm run check, verify:test-discovery, verify:package, and npm pack --dry-run passed.
---------
Co-authored-by: Beqa Ksovreli <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: morluto <[email protected]>
* fix(apple): preflight XML plist nesting before keyed archive decoding
inspect_keyed_archive documented a 128-level nesting limit, but the XML
plist path reached recursive decoding before any depth check: plist and
the prototype-key traversal in parseXmlPropertyList recurse once per
nesting level, so a ~75 KiB, 3000-level XML plist exhausted the call
stack and reported Maximum call stack size exceeded instead of the
bounded-resource limit that shallower over-limit documents (129, 200)
already produced. The CLI classified both as truncated, so no crash or
classification bypass was involved; the diagnostic was the defect.
Reject excessive nesting in parseXmlPropertyList before any recursive
pass, using the projection's own limit (MAX_PLIST_DEPTH) so the error is
identical to the one projectPlistValue raises: callers keep the
limit/truncated classification with no error-handler translation. Depth
counts values the way the projection does - container members nest one
level deeper, dictionary keys name members rather than nesting, and a
self-closing element is the value at its own level - so the guard
rejects exactly the documents the projection rejected, at the same
boundary, and every in-limit document decodes unchanged. The guard sits
at the shared decoder boundary, so Interface Builder, app Info.plist
resolution and native inspect_plist gain the same deterministic limit;
the binary plist path keeps its existing expansion preflight.
Binary plist behavior, ordinary XML decode, __proto__ omission counting,
and archive digest/Evidence provenance are unchanged. OpenStep
autodetection is untouched by this guard, as the existing Interface
Builder preflight also scopes its depth check to XML.
Regression coverage: decoder unit tests at and just past the limit for
dictionary and array nesting with self-closing and opaque markup,
module tests through decodeKeyedArchiveBytes including a valid
NSKeyedArchiver XML envelope at the limit, a production-boundary test
through inspectBundleKeyedArchive, and the compiled public CLI
reporting code truncated / reason limit for a 3000-level plist.
* fix(apple): check XML nesting against parsed element structure
Reuse the iterative XML decoder document for depth preflight and prototype-key lookup. Bound recursive plist wrappers alongside dictionaries and arrays without interpreting markup inside attributes or DOCTYPE declarations as values.
Validated with 104 focused tests including compiled CLI, MCP prototype-key, and Interface Builder coverage; npm run check and verify:test-discovery passed. No real-provider verification was run.
---------
Co-authored-by: Zburgers <[email protected]>
Co-authored-by: morluto <[email protected]>
* fix(frida): reject an unreadable script file before starting a session
The shared instrument_with_frida workflow started the Frida session, which
attaches or spawns the target, before the provider read a file script
source. A missing script file then failed loadScript with a bare
invalid_request, and session cleanup resumed the paused spawn, so the
target ran without instrumentation. Check that the selected file is a
readable regular file first and report the path, or access_denied for
EACCES/EPERM, before any device effect.
Fixes#2209
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(frida): let cancellation win over the script file check
An already-aborted request with an unreadable file source returned the
input or access-denied error instead of AnalysisCancelledError. Check the
signal before the script file preflight and again after it, so file
sources keep the existing cancellation semantics.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* refactor(frida): keep the pre-start checks out of instrument
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Frida session lookups, script lookups, unreadable script files and the
instrument-with-frida PID and script checks returned invalid_request with
no issues, while the remediation told callers to correct the listed
arguments. Attach the session_id, script_id, path, target or script issue
with the reason so CLI and MCP callers can see what to fix.
Fixes#2213
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(clients): register Qoder CLI during setup
- SupportedClients: qoder definition (~/.qoder/settings.json, format
json, QODER_CONFIG_DIR override honored)
- Qoder CLI documents mcpServers as the user-scope server table with
command/args/env and stdio as the default transport; no personal
skills directory is documented, so setup skips the skill copy
- tests: qoderClientConfiguration.test.ts (path resolution and
QODER_CONFIG_DIR override, upsert preserving unrelated settings,
fresh/empty configs, uninstall)
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/qoderClientConfiguration.test.ts 6/6.
* fix(clients): install Qoder CLI's personal REA skill
Review follow-up (#1572): the Qoder CLI Skills documentation lists
~/.qoder/skills/{skill-name}/SKILL.md with automatic discovery, so the
definition now carries skillPath following Qoder's configuration
location (QODER_CONFIG_DIR honored) and setup installs the bundled
skill copy there. The installation docs no longer claim Qoder has no
personal skills directory, and the bundled REA skill's supported
client list includes qoder.
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/qoderClientConfiguration.test.ts 7/7.
---------
Co-authored-by: morluto <[email protected]>
* feat(clients): register Trae during setup
- SupportedClients: trae definition (VS Code-style user directory,
User/mcp.json, format json)
- Trae documents mcpServers as the top-level server table with
command/args/env and stdio as the default transport; it has no
personal skills directory, so setup skips the skill copy
- tests: traeClientConfiguration.test.ts (per-platform user directory
resolution, upsert preserving unrelated settings, fresh/empty
configs, uninstall)
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/traeClientConfiguration.test.ts 6/6.
* fix(clients): install Trae's personal REA skill
Review follow-up (#1573): the Trae Skills documentation lists global
skills under ~/.trae/skills on macOS/Linux and %userprofile%/.trae/skills
on Windows, so the definition now carries that skillPath and setup
installs the bundled skill copy there. The installation docs no longer
claim Trae has no personal skills directory, and the bundled REA
skill's supported client list includes trae.
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/traeClientConfiguration.test.ts 7/7.
* docs(clients): qualify Trae configuration path evidence
---------
Co-authored-by: morluto <[email protected]>
* feat(clients): register ZCode during setup
- SupportedClients: zcode definition (~/.zcode/cli/config.json, format
zcode, personal skills at ~/.zcode/skills)
- ClientConfigurationDocument: zcode dialect reads/writes the nested
mcp.servers table; withClientServers now tolerates a missing outer
table for fresh documents
- docs: add ZCode to the supported-clients table with configuration notes
- tests: zcodeClientConfiguration.test.ts (path resolution, nested
upsert, fresh/empty configs, uninstall)
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/zcodeClientConfiguration.test.ts 6/6.
* fix(clients): preserve ZCode fallback servers and honor enable flag
Review follow-up (#1564), two integration gaps:
- ZCode reads the user-scope ~/.agents/mcp.json fallback only while the
native .zcode config defines no servers at all, with no merging. Setup
now copies the fallback's mcpServers entries into the native table when
writing the first native server, so registrations that only live in
the fallback keep loading; the fallback file is left untouched. A
malformed fallback fails setup closed (configure and preflight)
instead of being silently suppressed.
- ZCode disables a server with "enable": false on its entry, which the
generic status parsing (disabled/enabled) did not recognize: doctor
reported such a registration as aligned although ZCode never starts
it. The ZCode dialect now treats enable: false as disabled (state
stale) and setup rewrites the entry on rerun.
Docs describe both behaviors; the bundled REA skill's supported client
list includes zcode.
Verified: tsc --noEmit clean; vitest run
tests/boundary/filesystem/zcodeClientConfiguration.test.ts 10/10;
npm run check:fast green; npm run verify:test-discovery 733 files.
* fix(clients): reject non-regular ZCode fallback files before reading
Resolve the fallback through the client-config transaction path and the
regular-file admission before parsing it, so a FIFO or other non-regular
file at ~/.agents/mcp.json fails setup closed instead of blocking the
read indefinitely. Directory and FIFO fallback cases are covered in
tests, and the preflight remediation names unreadable fallbacks too.
---------
Co-authored-by: morluto <[email protected]>
* feat(flutter): identify Dart snapshot builds by pure APK parsing
Add a flutter-provider family with one operation, identify_flutter_build,
that reads an APK's lib/<abi>/libapp.so and libflutter.so through the
hardened ZIP reader and reports build facts as observations:
- The Dart VM snapshot hash, read 20 bytes after every snapshot magic
(f5f5dcdc). The vm and isolate sections are cross-checked: agreeing
sections collapse to one snapshot_hash, disagreeing sections keep every
candidate with snapshot_hash null and partial coverage.
- Per-ABI library digests and byte sizes, plus the engine library's GNU
build-id note and any labeled 'Dart VM version' or Android clang/LLVM
toolchain strings, verbatim; release builds without a version string
report null rather than a guess.
- The APK is digested before parsing so Evidence binds the observation to
the exact input, and a target with no libapp.so reports not-detected
with complete coverage.
The scanner is streaming with explicit pending-magic and rescan
bookkeeping, so chunk boundaries cannot double-count or drop sections, and
magic-free streams never accumulate. No engine, no external lookups: the
hash-to-SDK mapping stays with external datasets by design.
Verified against Flutter Gallery 2.9.2 (112 MB, three ABIs): every ABI
reports the same agreeing hash 65817c30a78bb44c3dc3771876b6010a from two
sections, per-ABI build-ids, and the clang toolchain line; a plain
aapt2-built APK reports not-detected with complete coverage. Scanner
goldens come from the same libraries. READMEs carry the new 'Flutter apps'
row in every supported language.
* feat(flutter): inspect Dart AOT snapshots through the ELF symbol table
Add inspect_dart_aot to the flutter family: for one ABI's libapp.so it
locates the four _kDart*Snapshot* symbols through the ELF dynamic symbol
table (ELF32 and ELF64 little-endian, bounds-checked reads; a payload
without exported symbols is refused), reports each section's offset and
size, validates data-section headers (magic, kind, length; instructions
sections honestly report nulls for the fields their layout does not
carry), and projects a bounded string pool from the isolate data section:
distinct package: source URIs (the app's dependency inventory), dart: SDK
URIs, and identifier-shaped tokens, each with counts, bounds, and partial
coverage when truncated.
The string pool is labeled as a byte-pattern projection, not deserialized
cluster semantics: typed class and function recovery remains an
SDK-specific parser, which the snapshot hash now unlocks. The ABI is
caller-selected with a deterministic default (arm64-v8a, x86_64, then
first available).
Verified against Flutter Gallery 2.9.2: all four symbols on both ELF64
(arm64-v8a) and ELF32 (armeabi-v7a) images, header validation, hash
agreement with identify_flutter_build, and a 756 package: / 201 dart:
URI pool from the isolate data section; goldens in DartElf.test.ts come
from the same libraries.
* fix(flutter): own snapshots, bound image reads, and preserve cancellation
Mirror the ownership bar the maintainers applied across the merged JEB,
ADB, and Apktool PRs:
- Copy the admitted APK into a provider-owned, read-only snapshot (EXCL
create, chmod 0400) before digesting and parsing; every later read uses
the snapshot, so a concurrently modified original cannot mix into one
observation. The result reports the snapshot's byte size and digest.
- Retain failed snapshot-root cleanup in a provider set and retry it on
close(), throwing an aggregate error when a root is still owned; root
removal is injectable so the retention is testable.
- Allocate the AOT image buffer at exactly the declared library size
before reading (bounded before allocation, not concat-after-stream),
keep the 256 MiB ceiling, and check cancellation between digest and
stream chunks through a captured abort reason.
- Accept APK paths containing spaces (covered by a fixture test) and
escape the ABI pattern's dash for v-mode regex compatibility.
Also add tests for cleanup retention with close retry and for
cancellation before any snapshot root is created.
* docs(flutter): cover the Italian README with the Flutter row
The Italian translation landed between the merged feature PRs; its table
now carries every merged row plus this one, keeping its documentation-link
set identical to README.md.
* fix(flutter): make the ABI pattern portable for v-mode validators
Use a hex-escaped hyphen in the ABI character class, matching the form
the ADB portability fix uses, so clients compiling advertised schemas in
unicodeSets mode accept the inspect_dart_aot input schema.
* refactor(flutter): reuse shared harnesses and deduplicate the vertical
The duplicate-detection gate (jscpd, 2.5% threshold) tipped to 2.51% with
the flutter files added. Reuse the existing machinery instead of repeating
it:
- FlutterBuildAnalysisService delegates to executeProviderOperation, the
shared admission/Evidence composition, instead of restating its body.
- The CLI registers both commands from one table-driven spec.
- Boundary tests use the shared serveCli and assertAdvertisedMcpContracts
harnesses rather than hand-rolled equivalents.
- The provider test consolidates the four payload-shape cases into one
table-driven case; the service test uses one admission table and
distinct phrasings.
Duplicate lines fall from 2.51% to 2.48%, back under the gate.
* fix(flutter): address review findings from real-app verification
CassieFox23 verified the PR against three real F-Droid Flutter apps and
a control app, and found five gaps. All are fixed and re-verified against
cone (info.tangential.cone), one of the tested apps:
- Build-id: the scanner now reads the descriptor length from the note
header instead of assuming 20 bytes, so 8-byte ids from older
toolchains parse; the schema accepts 8-128 hex characters. Verified
against cone's readelf-reported ids on all three ABIs.
- Compiler line: the toolchain capture bound rises from 200 to 500
characters, keeping cone's 262-character clang line verbatim.
- Zero-byte libapp.so: the extraction target is created before streaming,
so an empty entry yields a clean unsupported_target refusal for
inspect_dart_aot and elf_header_valid: false with partial coverage for
identify_flutter_build, instead of a raw ENOENT with code UNKNOWN.
- Truncated library: identify_flutter_build marks fewer than two snapshot
sections as partial coverage with a limitation naming the count, and a
missing ELF magic gets its own limitation.
- Docs: the default ABI preference now matches the code exactly
(arm64-v8a, x86_64, x86, armeabi-v7a, then the rest alphabetically).
Also: every test that read the author's /tmp/opencode goldens now builds
a self-contained synthetic ELF64 image with dynsym, four snapshot
symbols, two snapshot sections, and a string pool, so the suite runs
anywhere; the verify lane now requires a present hex build-id per ABI
instead of accepting null.
* fix(flutter): address the Codex review findings
Eight findings, all applied:
- P1: SnapshotHashScanner keeps candidates in a Set (no quadratic
Array.includes) with a 64-distinct-hash cap, so a hostile payload of
magic+hash records cannot stall the scan or bloat the response.
- P1: projectStringPool stops admitting new distinct entries once each
category reaches twice its projection bound and reports
collectionCapped; allocation and sorting now track the response, not
the payload.
- P2: ZIP reader rejections during entry iteration or entry streaming
map into the typed error algebra (cancellation vs input refusal)
instead of escaping as unhandled exceptions in both operations.
- P2: magic_valid now marks coverage only for the *Data symbols; a
complete four-symbol image with valid data magics reports complete
coverage (the instruction symbols intentionally lack the magic).
- P2: the MCP server closes the Flutter provider during shutdown, so a
retained snapshot root is retried and reported, not leaked.
- P2: LabeledStringScanner commits matches only when a printable run is
delimiter-terminated; the carry grows to 600 bytes so a 500-character
toolchain line survives any chunk boundary untruncated.
- P2: both Flutter tool effects declare writesFilesystem: true; the
owned APK snapshot and extracted libapp.so are temporary writes.
- P2: the verify lane asserts every ABI agrees on exactly one snapshot
hash instead of discarding the computed set.
* fix(flutter): address the second Codex review round
Nine findings on the first-round fixes:
- P1: identify_flutter_build enforces a cumulative extraction budget
(768 MiB) across every library entry, not just per library, so an APK
with many ABIs cannot stream an unbounded total.
- P1: a single NUL-free printable run stops materializing at 1024 bytes
and marks the pool capped, so an adversarial one-run section cannot
allocate unbounded strings.
- P1: ELF cString bounds the NUL-terminator scan to the name maximum
plus one, so malformed symbol tables cannot scan the whole image per
entry.
- P2: mapReaderFailure distinguishes ArtifactReaderFailure texts —
budget/limit rejections map to resource constraints, integrity
failures (CRC/overlap/signature) to their own input refusal — instead
of collapsing all reader failures into one message.
- P2: the labeled-string carry is cleared when a delimiter commits a
run, so consecutive toolchain lines are never concatenated.
- P2: string-pool counts are renamed *_count_at_least and documented as
lower bounds, because capped collection discards values beyond twice
the projection bound.
- P2: inspect_dart_aot marks hash disagreements, non-pair source counts,
and candidate-cap saturation as partial coverage.
- P2: only valid lowercase-hex hash records count as snapshot sources;
complete magic records with non-hex fields do not inflate agreement.
- P2: the per-entry size refusal in inspect_dart_aot moves after ABI
selection, so an oversized unselected ABI no longer refuses a small
request.
* fix(flutter): address the third Codex review round
Five findings:
- P2: the owned snapshot revalidates the source after the copy (size,
mtime, inode), so an in-place replacement or swap mid-copy refuses
the observation instead of publishing mixed bytes.
- P2: a failed ZipArtifactReader close retains the snapshot root for
close() retry instead of silently swallowing the descriptor leak,
in both operations.
- P2: snapshot headers are parsed within their symbol bounds and a
symbol smaller than the 16-byte header reports magic_valid false,
so trailing-image bytes can no longer fake a valid data section.
- P2: the verify lane counts provider.close() cleanup failures as
lane failures instead of certifying a run with retained roots.
- P2: LabeledStringScanner reports toolchain_capped when further
distinct matching lines are observed past the 4-line projection;
identify marks such results partial with a naming limitation.
* fix(flutter): address rounds three and four of the Codex review
Round three (one missed finding) plus round four (five findings):
- P2: the GNU build-id note descriptor size is decoded from all four
bytes of the little-endian field, rejecting nonzero high bytes
instead of masking them to 16 bits.
- P1: the Flutter MCP handlers route through createContractToolHandler
and the session's analysisAdmission, so close_binary and snapshot
operations drain in-flight Flutter calls instead of racing them.
- P2: the owned-snapshot copy is a streamed, abort-checked write (no
copyFile), so cancellation stops the write promptly while the
source-identity revalidation (size, mtime, inode) still guards
against mid-copy replacement.
- P2: identify marks any snapshot_hash_sources value other than exactly
two as partial, matching the inspection path's pair requirement.
- P2: the verify lane refuses to run without --apk (exit 2) instead of
printing a false-green pass, and the engine check requires every
libapp-carrying ABI to have a present libflutter with a valid hex
build-id rather than filtering missing engines away vacuously.
* fix(flutter): address the fifth Codex review round
Six findings plus the upstream duplication baseline:
- P2: the flutter-provider kind reports available in the capability
inventory, so binary_session no longer lists always-registered
Flutter tools as provider_missing.
- P2: printable runs that exceed the 1024-byte cap are discarded at
their delimiter instead of emitting their truncated prefix as a
fabricated observation.
- P2: ELF symbols must fit inside the file-backed portion of their
PT_LOAD segment (p_filesz), so addresses in gaps or BSS are refused
instead of accepting unrelated bytes.
- P2: the build-id carry spans the complete 80-byte maximum note
(16-byte header + 64-byte descriptor) so chunk-boundary splits
cannot drop the first byte.
- P2: the labeled-string carry is sized for the toolchain pattern's
true 605-byte maximum, not the previous 600.
- P2: the AOT completeness predicate requires exactly two sources and
one candidate unconditionally, so zero-source images with valid data
magics report partial instead of complete.
Also: the shared ELF fixture builder and snapshot-section helper moved
to a dedicated FlutterTestFixtures module, eliminating cross-file test
duplication; jscpd now passes at 2.50% while main sits at 2.52%.
* fix(flutter): address the sixth Codex review round
Five findings:
- P2: snapshot-hash scanning is bounded to the exported data symbols'
byte ranges, so decoy or stale magic/hash sequences elsewhere in the
image cannot inflate source counts or contribute candidates.
- P2: labeled-string runs longer than the carry bound are rejected
instead of truncated, preventing a regex prefix from being published
as a verbatim source string.
- P2: the AOT image reread checks cancellation between file reads and
before the synchronous parsing stage, so a cancelled caller cannot
receive a success from a completed 256 MiB read.
- P2: the verify lane requires exactly two snapshot sources per ABI,
matching the provider's pair requirement, instead of accepting any
count >= 2.
- P2: ELF section and program header entry sizes are validated against
the class-defined minimum widths before walking either table, so
undersized entries cannot cause field reads to cross into
neighboring bytes.
* fix(flutter): address the seventh Codex review round
Six findings:
- P2: data-symbol ranges are deduplicated by offset and size, so aliased
symbols mapping to the same bytes are scanned once and the result
marks partial coverage with a limitation naming the shared range.
- P2: openEntrySafely forwards return/throw to the underlying Readable's
iterator, so an early for-await exit closes the ZIP entry stream and
releases its producer instead of leaking a backpressured pipe.
- P2: inspect_dart_aot now reports snapshot_hash_candidates and
snapshot_hash_sources alongside snapshot_hash, so a null hash from
conflicting evidence is distinguishable from missing hash data.
- P2: overlong printable runs only set toolchain_capped when they begin
with a recognized label prefix (Dart VM version: or Android (); other
long strings such as license text are skipped without marking partial.
- P2: readElfSymbol falls back to PT_DYNAMIC/DT_SYMTAB when section
headers are absent (e_shnum 0, ET_DYN), so stripped Flutter libraries
with exported snapshot symbols are no longer refused; the dynamic
table is bounded by DT_STRSZ, DT_SYMENT, and the image end.
- P2: the verify lane exercises inspect_dart_aot for every ABI carrying
libapp.so, not just the preferred default.
* fix(flutter): address the eighth Codex review round
Five findings:
- P2: each data-symbol range gets a fresh SnapshotHashScanner so the
carry from one noncontiguous slice cannot fabricate a record spanning
into the next; results are aggregated after scanning.
- P2: dynsym name lookups are bounded to the linked string table —
st_name offsets outside the table and terminators beyond it are
rejected instead of reading unrelated payload bytes as symbol names,
in both the section-header and PT_DYNAMIC paths.
- P2: identify_flutter_build routes its reader.entries and reader.open
calls through the same collectEntriesSafely and openEntrySafely
wrappers as inspect_dart_aot, so malformed ZIPs surface as input
refusals instead of AnalysisProtocolError.
- P2: mapReaderFailure recognizes ArtifactReaderFailure texts carrying
a cancelled reason, mapping them to AnalysisCancelledError instead
of AnalysisInputError.
- P2: the cumulative extraction cap is enforced inside the chunk loop,
not after each library completes, so refusal happens before the
remaining bytes are streamed and hashed.
* fix(flutter): harden streaming, sizes, and cancellation after self-review
Four issues found in a systematic self-audit across all bug classes
from eight Codex rounds:
- createReadStream in both the digest function and the owned-snapshot
copy now receives the AbortSignal directly, so cancellation aborts
the filesystem read at the source instead of waiting for the next
chunk to arrive on a slow filesystem.
- The owned snapshot enforces a 2 GiB APK-level size cap before any
copy I/O, so a path pointing at an enormous file is refused before
disk usage grows.
- identify_flutter_build preflights the sum of declared entry sizes
against the 768 MiB cumulative budget, refusing before any ZIP
extraction when the declared total already exceeds it.
- The LabeledStringScanner triple result() call is collapsed to a
single invocation (the previous code called result() three times in
the emission, relying on end() being idempotent).
No behavior change for valid inputs; all 36 tests, check:fast, and
the cone verify lane pass unchanged.
* fix(flutter): require structurally parseable ELF headers for complete coverage
CassieFox23's third corrupt-header case: an ELF with valid magic but
zeroed section/program header offsets passes the 4-byte magic check
and the byte scanner still finds hash patterns in the data sections,
so identify_flutter_build reports complete coverage for a structurally
broken file.
The elf_header_valid check now also verifies beyond the magic:
e_type must be a known value (ET_REL/ET_EXEC/ET_DYN), at least one
of e_shoff/e_phoff must be non-zero, and at least one of
e_shnum/e_phnum must be non-zero. A file with valid magic but zeroed
structure reports partial coverage with the existing ELF-magic
limitation.
* fix(flutter): validate ELF and snapshot coverage
Share bounded ELF header parsing between identification and inspection. Accumulate headers across ZIP chunks and validate table entry widths and extents before claiming complete identification coverage.
Bind AOT hashes to the exported VM and isolate headers, validate stored snapshot lengths including the magic bytes, and preserve partial evidence for missing, malformed, aliased, or overlapping data headers.
Preserve native stream cancellations and APK resource constraints across both operations. Bound snapshot copies while streaming and retain useful input-failure diagnostics and lower-bound count wording.
Validation: 72 focused tests; check:fast; check including formatting; docs:check; test discovery; jscpd; real cone APK and non-Flutter control on Linux. Verified CLI/MCP parity for valid and corrupted ELF headers and AOT inspection across three ABIs. Native Windows and macOS were not run.
* fix(flutter): unify artifact ownership and bound ELF evidence
* fix(flutter): preserve ELF provenance and partial coverage
Read GNU build IDs only from bounded ELF note ranges and retain conflicting candidates. Preserve source versus owned storage failures and honor cancellation through parsing and final cleanup.
Separate snapshot structure from string-pool coverage. Verify every Gallery ABI with digest-bound engine labels and URI counts, and document the supported snapshot-kind layout.
---------
Co-authored-by: REA Contributor <[email protected]>
Co-authored-by: morluto <[email protected]>
Put setup, developer commands, MCP replies, and investigation recipes before
client details and implementation references. Preserve existing guide anchors,
examples, provenance, partial-result recovery, and provider restrictions.
Correct verified documentation drift, synchronize the Ghidra capability
wording, and regenerate metadata for skill version 40. Keep uncertain platform
and lifecycle findings unchanged. Refs #2156.
Validation: 56 focused tests, check:fast, docs:check, VitePress production build,
format and link/example checks. Real isolated CLI/MCP probes cover Ghidra,
NativeAOT, HAR, cancellation, Chrome, large ELF delivery, native type limits,
and the new JS/managed/process/setup recipes. Heavy work uses two CPUs;
a resource-heavy DOM probe was stopped and replaced by a smaller real CDP
payload-limit control. Windows, macOS GUI, ADB, and the full CI matrix were
not executed.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Universal Mach-O admission reported the selected slice architecture while the IDA provider's private input copy handed the whole FAT container to upstream idalib, which then picked its own slice (x86_64 for /bin/cat) while Evidence labeled the session arm64. FAT parsing now exports the selected slice's byte range as universalSlice, and the private input copy writes exactly those bytes: the whole source digest still anchors the copy against admission-time changes, and the thinned result anchors the post-operation identity check.
Thin targets keep the whole-file copy, and mach-o-universal remains the container format across Evidence, snapshots, and provider routing.
Validation: typecheck, oxlint (no new warnings), oxfmt, and targeted vitest runs — ida adapters 12 files/116 tests, domain and services projects 60 files/736 tests, ghidra/hopper behavior 2 files/50 tests, filesystem and universal-extraction boundary 2 files/49 tests, native signature MCP 1 file/2 tests, plus a new thinning suite (5 tests, red before the fix: 3 failed). The thinned product was verified byte-identical to the `clang -arch arm64` input of a `lipo -create` universal fixture. Controlled fixtures only; no live IDA analysis was performed for this change.
Closes#2139
startMcpTransport loads optional observation providers once per process
and passes them to every server instance the SDK constructs. Each
instance's closeProviders also closed the shared browser scenario
provider, so when the SDK discarded a server/discover probe instance for a
legacy initialize, every later capture_browser_scenario call failed with
"Browser scenario provider is closing." Server instances no longer close
the caller-supplied provider; the transport returns
closeOptionalProviders and process shutdown closes it once.
Fixes#2195
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(setup): remove emptied skill directories on uninstall
Uninstall removes only the managed skill files so user files survive,
but it left every emptied reverse-engineer-anything directory and its
references/ subdirectory behind. After the ownership manifest is removed,
remove the managed directories deepest first with a non-recursive rmdir,
which keeps any directory that still holds user files.
Fixes#2159
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(setup): report emptied skill directories that cannot be removed
Only a directory that still holds user files, is already absent, or is no
longer a directory is kept silently. Any other rmdir failure, such as a
parent that denies deletion, now fails the skill item with the directory
path instead of reporting the skill as removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
A client configuration that parses but whose root or MCP server table is
not an object (for example {"mcpServers": null} or []) made setup print a
raw Zod issue dump and made uninstall claim the file was not valid JSON.
parseClientConfiguration now raises a ClientConfigurationShapeError naming
the root or the servers key, which setup reports as the malformed detail
and uninstall reports instead of a syntax error.
Fixes#2152
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(managed): report malformed PE layouts as invalid input
readManagedPeLayout raises ManagedReaderFailure for DOS, COFF and section
header failures such as section data past EOF. Those escaped the managed
inspectors, so ManagedStaticProvider wrapped them as ProviderAdapterError
and the CLI and MCP reported execution_failure with advice to run rea
doctor. Map format failures to an invalid_format input issue carrying the
reader's detail, as inspect-pe-resources and inspect-go-binary do.
Fixes#2191
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(managed): route unmappable PE layouts through one input error
inspect-managed-native-boundaries caught a layout-level ManagedReaderFailure
itself and returned an empty malformed inspection with exit 0, while the
other managed operations now report the same bytes as invalid input. Let
the layout failure reach the provider's invalid_format mapping; metadata
failures after a readable layout still return malformed coverage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
JebProvider throws when REA_JEB_MCP_URL fails endpoint validation, and the
composition built it eagerly for the CLI and every MCP connection. A
malformed value, or http://localhost:8425/mcp, therefore crashed every CLI
command with a stack trace and failed every MCP connection with an
internal server error. The composition now parses the endpoint first and,
when it is unusable, returns a JEB port that reports not_configured
availability and a ConfigurationError naming REA_JEB_MCP_URL.
Fixes#2143
Co-authored-by: Claude Opus 5.5 <[email protected]>
Add opt-in retained inventory and WASM Evidence views with exact nested output contracts and a small static WASM descriptor. Preserve old complete CLI/MCP workflows, producer choices, original authenticated Evidence and cleanup ownership.
Validated with real public applications, genuine old Evidence migration, 123 focused assertions, 11 synchronized description checks and 42 installed-package WABT cases. Detailed generated experiment JSON stays outside the final tree; concise findings and reproduction inputs remain. Main-existing completion/package CI failures are separately attributed and left unchanged.
Refs #2016
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
createCli parsed REA_LOG_LEVEL with a throwing schema, so an invalid or
uppercase value crashed every command, including doctor and --help, with
a ZodError stack trace. Parse it into a ConfigurationError result and have
the executable print the same configuration message as the MCP adapter
and exit 1 before building the CLI.
Fixes#2135
Co-authored-by: Claude Opus 5.5 <[email protected]>
Uninstall checks every skill destination but labeled each result only as
"skill" (or "Claude Code skill"), so a retained or failed destination
could not be located. Label each result with the directory it refers to.
Fixes#2111
Co-authored-by: Claude Opus 5.5 <[email protected]>
`inspect-managed-members --type` accepted repeated and empty names that
the MCP contract rejects. The provider's contract parse then threw, and
the CLI reported execution_failure with advice to run `rea doctor`.
Reuse the contract's type_names schema so the CLI rejects them as a
validation error on --type.
Fixes#2121
Co-authored-by: Claude Opus 5.5 <[email protected]>
Target resolution admitted a property list only when its name ended in
.plist, so keyed archives with other extensions (for example .archive)
were rejected as an unsupported binary format before any plist tool ran.
Classify bplist00 bytes as a plist, as the artifact inventory already does.
Fixes#2129
Co-authored-by: Claude Opus 5.5 <[email protected]>
Use POSIX mkdtemp owner-only allocation directly instead of adding a fallible permission rewrite and rollback before returning the cleanup owner. Respect restrictive umasks, assert privacy and real scratch access, and align provider documentation.
Avoid lazy provider startup for document and procedure completion. Propagate MCP cancellation through completers, cancel lookups during session transitions, and share provider result projection with ordinary operations. Cover cold, starting, warm, queued, cancelled, and closed sessions through MCP composition and provider boundaries.
Retain exact empty and computed literal keys in JavaScript observations. Bind CDP response bodies to request occurrences, account for late responses, and validate the target after final progress callbacks.
* fix(ghidra): retain launcher command outcome in launch failure diagnostics
Ghidra headless launch failures surfaced only the GhidraLaunchError name
and a generic message, dropping the launcher command's exit code/signal
and JVM stderr. Retain the failed command outcome on GhidraLaunchError in
the shared captured-output envelope and project it as launch_outcome in
the session failure_cause, mirroring the compiler_outcome diagnostics.
Fixes#1992
* test(ghidra): skip POSIX JVM fixture tests on Windows
The bin/java fixture is an extensionless POSIX shell script handed to
execFile, which Windows cannot execute. Follow the repo pattern
(test.skipIf) so the suite is a named Windows skip.
Reuse managed snapshot admission and hashing in sequential path comparisons, retaining caller paths and completed inspection Evidence on later failure. Preserve exact failed-close owners alongside typed read and cancellation failures, and retry them through caller-owned resource scopes. Require canonical Hopper lease identities, remove signed offset coercion, and repair stale setup cleanup expectations that failed CI.
Distinguish an unresolved goal from an exhausted call graph and preserve provider failures through cancellation. Retain completed IDA replies and post-operation identity observations as partial Evidence without claiming a verified final binding. Remove source-wide bundler cache-name heuristics and stale expectations.
Keep failed JSON read handles in the existing server resource scope and fail admission until cleanup succeeds. Thread cancellation through every process CLI input read and replace timer-driven cancellation checks with a shared first-read gate.
Classify authenticated standalone Evidence as wrong-kind input while preserving tampered-record integrity failures and actionable messages across ledger, CLI, and MCP consumers. Record unrelated CS_RESTRICT process neighbors as unverified without weakening related-process ownership checks. Update stale ADB fixtures for the actual shell protocol.
Validated focused CLI, MCP, filesystem, ownership, and error projection tests; static, docs, and duplication gates pass. The host-level CS_RESTRICT load reproduction was not run. Refs #2050, #2035, #2064.
Share loader selection across static findings and module relationships: CTS static imports and re-exports use CommonJS lookup while dynamic imports retain ESM lookup. Retain runtime import-equals dependencies, bindings, and exported names; do not resolve require file-URL strings as local artifacts. Restore import-map descriptions in CLI help.
Validated resolver and semantic tests, application reconstruction boundaries, and CLI help. Installed TypeScript NodeNext emission and Node require behavior were checked independently. Refs #2056, #2057, #2058, #2053.
Label new captured response bytes as Playwright body API results and retain the possibility of a credentialed follow-up load. Preserve legacy representations and source limitations through export without treating body-origin uncertainty as missing event coverage.
Validated source and filesystem boundary tests plus real Chrome CLI and MCP network workflows covering binary, compressed, empty, unfinished, and same-URL responses and profile cleanup.
Authenticate residual unknown IDs against their immutable identity fields. Permit valid empty ZIP extraction manifests and join firmware child provenance to parent-owned chunk and multi-file reports without inventing missing source ranges.
Validated with focused identity, archive, cleanup, and firmware normalization tests. Firmware joins were checked against the pinned Unblob producer; no real Unblob extraction was run.
* fix(ghidra): recover stale clients on same-target reopen
* test(ghidra): allow supervised shutdown in health regression
* fix(ghidra): read process exit without diagnostic snapshots
Health and startup exit checks called the supervisor snapshot, which
concatenates retained launcher output only to read the exit code.
* test(adb): align fixtures with literal output and directory commands
---------
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: morluto <[email protected]>
Use Android 8-compatible batched find/stat with NUL-framed names and raw symlink targets. Preserve malformed or invalid UTF-8 records as partial evidence with exact source bytes. List hidden direct children without synthetic navigation entries.
Share process byte capture with UTF-8 callers and screenshots, removing duplicate timeout and cancellation supervision. Verified 35 focused process, ADB, CLI and MCP tests; compatibility checked against AOSP Oreo sources. Live Android device/emulator verification was not run.
Validate managed reconstruction through the shared artifact and MVID scope boundary, including known/unknown identity contradictions. Forward MCP comparison cancellation into both bundle reads and consolidate pre-cancel coverage.
Verified managed graph/reconstruction and filesystem/MCP cancellation workflows. A controlled missing-signal mutation reads 130 chunks instead of the expected one; cancellation leaves the session unchanged and retry succeeds.
Keep omitted discovery attachment state as null through Node, browser, Electron, and reconciliation evidence. Report the uncertainty while retaining Node observation unless discovery explicitly marks the target attached. Reject worker TargetInfo records missing the required boolean with partial coverage.
Validate omitted and explicit states through provider, CLI, and MCP boundaries and real Node 24 Inspector workflows. Consolidate Electron regression coverage into the existing list-and-inspect workflow.
BREAKING CHANGE: attached fields in JavaScript runtime, browser, and Electron target discovery and inspection results may be null when discovery does not report attachment state.
Derive extraction limits and web module fields from the shared contracts. Reject empty or NUL-containing local paths before CLI path resolution, preserve firmware relative-path handling, and apply the advertised extraction defaults.
Validate advertised JSON Schemas with Ajv and check the registered parser defaults; prune redundant schema-shape and description assertions.
Reconcile Mach-O UUID observations without hiding malformed or conflicting sources. Require source-map identity digests to be supported by consistent observations. Keep truncated Android manifests as partial raw evidence, validate WABT section framing, and reject missing JEB inventories or error-marked success replies.
Consolidate native UUID parsing and simplify capacity and malformed-input fixtures. Focused source and public workflow checks pass, including native arm64e inspection and real WABT framing probes; a successful real JEB GUI project opening remains unverified.
Stop cached metadata replay before a provider write starts, including while its reply is pending or fails after applying the mutation. Keep snapshot import and export disabled until the target is reopened.
Validate failed replies, conflicting provider identity, and overlapping reads through the shared session boundary.
Separate stable registration ownership from setup editability. Keep group-owned rows removable, retain expression-backed structures without edits, and reject malformed literal patches before setup writes. Preserve foreign rows and backups during uninstall.
Correct stale alias fixtures to use valid Harness patch rows and remove redundant alias coverage. Validation: 78 configuration boundary tests, 6 public setup/uninstall CLI tests, full static checks, and docs checks passed. Harness behavior was verified from pinned source; no real Harness runtime verification is claimed.
Distinguish absent CLI directories from unreadable directory counts and truncated slots while preserving known PE headers and readable CLI data.
Retain environment-entry coverage in the Darwin parser and classify restricted empty, unavailable and ambiguous environments at the native boundary. Keep cleanup fail-closed; code-signing flags do not establish ownership.
Validation: 116 managed source tests, 13 ownership tests, native Swift parser probe and two live Darwin run-token tests; build, static checks and docs checks pass. Restricted-flag cases use parser fixtures; no real restricted capture is claimed. Related to #2035.
* feat(go): inspect embedded binary build metadata
* fix(go): preserve byte metadata and validate container mappings
* fix(go): support moduleless legacy build metadata
Recognize optional module string headers in declared zero-filled memory while rejecting ambiguous and unmapped pointers. Preserve the original module-pointer field as the zero-byte source anchor.
Classify initial symlink-loop and overlong-path failures as invalid input, with CLI and MCP regressions.
* fix(go): reject undersized PE optional headers
* fix(native): stabilize ELF memory checks and clean up exec wrappers
Use existing parent/group/run-token ownership for caller-selected Python launchers, which can exec through a shell or prlimit. Add cancellation regressions for the valid wrapper and wrong-parent/token cases.
Replace the timing-dependent 30,000-section memory workload with a valid 32 MiB padded ELF under a 64 MiB address-space limit. Check the same image successfully through CLI and MCP before requiring a typed memory failure, preserving the production timeout and all error assertions.
Validation: npm run check and check:fast passed; 61 focused tests passed including Go CLI/MCP and session snapshots. Real decoder probes with pinned packages passed on Python 3.12.3 and 3.12.14. The full local ELF verifier is blocked by this container's procps/process-ownership boundary; the exact pushed head requires GitHub's real-binary-layout lane.
* test: repair Electron fixtures and stabilize catalog startup checks
Declare Electron imports in option, preload override, and path-resolution fixtures after upstream began requiring proven API provenance. Retain all option and preload assertions and adjust exact source ranges for the added import line.
Use the existing bounded acceptance timeout for the cold catalog lifecycle test instead of a separate 15-second ceiling. This retains the coverage allowance and all catalog, error, and cleanup assertions without retries.
Validation: npm run check, final check:fast, formatting and diff checks passed. Source path regression and Go boundary/MCP/service checks passed. All 20 Go CLI cases and both supported-host runtime cases passed on rerun; two macOS-only cases skipped on Linux. A prior contended local run timed out once in a Go CLI diagnostic; that case passed on rerun. Electron process-boundary verification is blocked locally by procps namespace inspection and requires fresh CI.
* test(mcp): isolate nested schema references between tool contracts
Clear Ajv's non-meta schema registry after each root, including compilation failures. Disabling root registration alone leaves nested IDs available to later tools and can let a dangling reference pass the catalog gate.
Add regressions for leakage after successful and failed compilation while retaining shared meta-schema validators. The regression failed before the fix; all 11 schema cases and full npm run check pass. Reviewed independently.
* test(ci): align Go effects and source-map verification contracts
Audit the Go inspector wording as static metadata inspection. Assert retained source declaration indexes in BOM reconstruction and the compiler-derived nested-map oracle. Keep a 45,000-map admission control below the 32 MiB output budget and the 50,000-map overflow case above it with the expanded representation.
Validation: reproduced all four failing assertions, then passed 120 focused tests and the final admission suite. npm run check and real Chrome 154 / esbuild 0.25.10 source-map verification passed, including all four nested-map scenarios and verified process lineage.
---------
Co-authored-by: morluto <[email protected]>
Require a package directory for bare ESM imports. Continue CommonJS root and subpath lookup through ancestor packages after ordinary misses, while keeping exports authority, unavailable metadata, and failed nonempty main entrypoints terminal.
Carry failed-main ownership in the internal resolution result and retain the nearest local miss after exhausting ancestors. Consolidate package selection at its owning boundary and qualify loader differences with real Node 24, CLI, and MCP regressions.
Accept absent map-level sourcesContent and preserve null source-name slots alongside known originals and aligned content. Represent unnamed local declarations explicitly, retain leaf-local and flattened indices, and preserve declared indexed-section order across consumers.
Account for added indices before browser output allocation. Accept legacy retained rows without indices. Consolidate public CLI/MCP source-map fixtures and update the stale named-source-only assertion.
BREAKING CHANGE: Browser source-map original and mapping rows expose null for unavailable source names instead of inventing an unknown-source string. New index fields are optional when reading older retained evidence.
Refs #2023, #2024