Compare commits

...
Author SHA1 Message Date
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 <76467478+morluto@users.noreply.github.com>
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 <noreply@anthropic.com>

* docs(environment): record the write-views verification

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

* fix(environment): reconcile write views without hiding contradictions

* fix(environment): reject incompatible slug-only saves

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* test(binary-layout): compare ELF32 symbols and relocations with readelf

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
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 <76467478+morluto@users.noreply.github.com>
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 <noreply@anthropic.com>

* 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 <noreply@codebuff.com>

* 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 <noreply@codebuff.com>

* 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 <noreply@codebuff.com>

* 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 <noreply@codebuff.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@codebuff.com>

* 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 <noreply@codebuff.com>

* 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 <76467478+morluto@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
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 <76467478+morluto@users.noreply.github.com>
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 <94890352+Adolanium@users.noreply.github.com>
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* docs(environment): note the create-calls census on current main

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

* 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 <noreply@anthropic.com>

* test(environment): split the derived-values view tests by rule

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

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* fix(environment): preserve composed parts and unique generator families

* fix(environment): reject contradictory create references

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
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 <76467478+morluto@users.noreply.github.com>
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* test(environment): split the derived-values view tests by rule

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

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* fix(environment): preserve composed parts and unique generator families

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
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 <76467478+morluto@users.noreply.github.com>
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 <76467478+morluto@users.noreply.github.com>
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 <76467478+morluto@users.noreply.github.com>
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 <Abelo9996@users.noreply.github.com>
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
998 changed files with 77608 additions and 6516 deletions

No files matched your search

+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
@@ -384,8 +384,9 @@ 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
@@ -28,8 +28,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
@@ -128,7 +128,7 @@ 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.
@@ -175,6 +175,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:
@@ -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`
@@ -342,14 +342,18 @@ providers. Top-level session capabilities describe the current route, and
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
+7
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.
+1
View File
@@ -858,6 +858,7 @@ jobs:
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
+6
View File
@@ -55,6 +55,12 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- name: Install ELF32 cross toolchains for source-owned fixtures
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
gcc-i686-linux-gnu gcc-arm-linux-gnueabi gcc-mips-linux-gnu gcc-mipsel-linux-gnu \
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: |
+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"
}
+6 -5
View File
@@ -80,7 +80,7 @@ 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 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,10 +108,11 @@ 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
owner to complete. Never attest to human review on their behalf. Draft status
is optional; preserve the PR's current draft status unless the user asks to change
it. Later pushes do not invalidate the readiness declaration or require another
human sign-off. Keep the PR body and affected validation current as work changes.
The readiness check validates
the filled declaration; CI and automated review results do not establish that
human review occurred, maintainer scope approval, or merge permission.
+102
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
+30 -4
View File
@@ -25,6 +25,18 @@ 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 +47,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
@@ -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
@@ -368,9 +386,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
+68 -10
View File
@@ -3,6 +3,8 @@ import com.google.gson.*;
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;
@@ -40,7 +42,8 @@ class ReaJadxBridge {
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 static final class InputFailure extends IllegalArgumentException {
@@ -142,6 +145,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;
@@ -263,17 +274,38 @@ class ReaJadxBridge {
"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) {
Map<String, Object> entry = Map.of("name", field.getAlias(),
"type", field.getType().toString(), "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();
Map<String, Object> summary = Map.of(
"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);
ClassSnapshot snapshot = new ClassSnapshot(cls, methods, summary);
"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.
@@ -293,6 +325,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();
@@ -443,7 +497,8 @@ class ReaJadxBridge {
"items", items));
}
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);
@@ -526,7 +581,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", "6", null, null, null),
Server server = new Server(new Implementation("rea-jadx-bridge", "7", null, null, null),
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
SessionToolsKt.registerSessionTools(server, holder);
ClassToolsKt.registerClassTools(server, holder);
@@ -568,9 +623,12 @@ 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"))));
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.";
};
+17 -4
View File
@@ -404,6 +404,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) {
@@ -3381,7 +3383,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()) {
@@ -4771,7 +4773,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
}
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");
@@ -4780,7 +4793,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) {
@@ -4974,7 +4987,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
JsonArray analysisExtensions,
int decompileTimeoutSeconds
) {}
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;
+18 -10
View File
@@ -15,6 +15,11 @@ import sys
PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
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", (3, 32, "little"): "i386", (40, 32, "little"): "arm",
(8, 32, "little"): "mips", (8, 32, "big"): "mips"}
PROFILE_SCOPE = ("Layout profile requires ELF64 x86-64, 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]
+21 -5
View File
@@ -683,7 +683,12 @@ 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. Members a view names in
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
@@ -721,7 +726,7 @@ 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, nested clock, increment, slug, stamp, derived, unique and renewed changes the plan models. |
| `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`), with any sent members; it is served only when a recorded call found those fields already set, so a toggle never is. |
| `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}`). |
@@ -923,6 +928,9 @@ Known limits of served writes:
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
@@ -1057,9 +1065,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
@@ -1071,6 +1081,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.
+26 -5
View File
@@ -194,6 +194,21 @@ 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.
`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
@@ -400,11 +415,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**
+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`.
+2 -2
View File
@@ -23,7 +23,7 @@ graph TD
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
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"]
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
@@ -75,7 +75,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, Evidence, comparisons, application graphs"]
end
subgraph "Configuration"
+6 -3
View File
@@ -8,9 +8,12 @@ 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, ELF32 i386, ELF32 little-endian ARM and ELF32
little- or big-endian MIPS. Other machines, classes and byte orders, including
AArch64 and big-endian ARM, return `unsupported_target`. 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 \
+38
View File
@@ -434,6 +434,17 @@ 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`.
@@ -598,6 +609,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`
@@ -605,6 +632,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;
+12 -3
View File
@@ -465,7 +465,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.
@@ -917,8 +924,10 @@ 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.
`--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
+33
View File
@@ -6,6 +6,17 @@ 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.
## 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
@@ -493,6 +504,28 @@ as stale, and rerunning setup removes the flag to repair it. Native configuratio
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.
FreeBuff is main-only and is not in a published release yet; npm 6.7.0 is the
latest. `rea setup --client freebuff` against `@latest` reports an unsupported
client until a release containing it is published.
## DeepSeek Harness
DeepSeek Harness registers MCP clients through a Cordis YAML patch sequence
+5
View File
@@ -1,5 +1,10 @@
# 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
+290 -37
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,122 @@ 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 |
### 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.
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.
## 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 +269,22 @@ 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. 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 +306,72 @@ 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.
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.
+7 -2
View File
@@ -305,8 +305,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.
+4 -2
View File
@@ -24,8 +24,8 @@ 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.
@@ -63,6 +63,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
+9 -2
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
@@ -187,7 +187,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. |
@@ -197,6 +197,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
+3
View File
@@ -7,6 +7,9 @@ releases have not been verified. Format 1 is unsupported.
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
+29 -4
View File
@@ -241,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`.
@@ -299,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
@@ -354,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.
@@ -419,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. |
+44 -14
View File
@@ -319,14 +319,14 @@ Register with the variable unset:
<!-- x-release-please-start-version -->
```bash
claude mcp add --scope local rea -- npx -y rea-agents@6.7.0 mcp
claude mcp add --scope local rea -- npx -y rea-agents@6.8.0 mcp
```
On native Windows, use the same project directory and wrap the package-runner
shim so the stdio client can launch it:
```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 -- cmd.exe /d /c npx -y rea-agents@6.8.0 mcp
```
<!-- x-release-please-end -->
@@ -616,9 +616,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
`rea-agents@6.5.0` or later. The 6.4.0 registry check above does not cover them.
`freebuff` requires `rea-agents@6.8.0` 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.
@@ -681,6 +684,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
@@ -803,7 +812,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
@@ -1173,19 +1185,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)
@@ -1212,9 +1230,14 @@ 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. 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
@@ -1448,7 +1471,7 @@ For a client that requires manual configuration, use:
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.7.0", "mcp"]
"args": ["-y", "rea-agents@6.8.0", "mcp"]
}
}
}
@@ -1462,6 +1485,13 @@ 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.
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
registration. Setup therefore materializes the pinned entry once, best-effort
with a bounded timeout, before finishing. When that step cannot run, for
example offline, the first client start performs the one-time install inside
the client's MCP startup deadline and may need one more restart.
## Package runners and global installation
A package-runner invocation can use a REA version installed in the current project. To
+40 -8
View File
@@ -140,17 +140,24 @@ returns an unavailable error with the retained inventory, rather than claiming
the selected source does not exist. Observed exact file candidates keep their
identity; unknown traversal cannot establish a unique application root.
ASAR header/index admission defaults to 16 MiB / 16777216 bytes. Set
`REA_ASAR_MAX_HEADER_BYTES` in the environment that starts REA to a decimal
byte count of at most 268435456 (256 MiB) to raise or lower it; restart REA
after changing it. Invalid values are configuration errors, never a silent
fallback. The archive index is read before any member can be selected, so
ASAR header/index admission defaults to 16 MiB / 16777216 bytes. In MCP, use
`update_analysis_settings` to set `asar.max_header_bytes` to an integer byte
count from 1 to 268435456 (256 MiB), then retry the same full-archive request.
The update applies to new operations without restarting REA; cached headers
must also satisfy the new admission. For CLI or future MCP processes, set
`REA_ASAR_MAX_HEADER_BYTES` in the launch environment. Changing that environment
requires a new REA process. Invalid launch values are configuration errors,
never a silent fallback. See the
[settings design](design/limits-and-recovery.md#failure-and-retry) for recovery
metadata, precedence, and reset behavior.
The archive index is read before any member can be selected, so
`source_paths`, `--source-path`, `detail: "summary"`, `max_heap_mb`, and
available host memory do not bypass or raise it. When the header exceeds the
effective allowance, the failure names it, whether it came from the default or
the setting, and the setting's limits. Alternatively, analyze an extracted
effective allowance, the failure reports the value, its known source, and the
setting's limits. Alternatively, analyze an extracted
application directory, which does not admit an ASAR header. Repeating the unchanged
request fails the same way.
request without changing admission fails the same way.
## Read the analysis
@@ -195,6 +202,27 @@ For supplied passive browser or Electron observations, use
[runtime reconciliation](javascript-runtime-reconciliation.md). It compares
existing Evidence without rerunning static analysis or contacting CDP.
Each newly analyzed JavaScript file retains `analysis_coverage` in its graph
observation. `syntax` distinguishes complete parsing, recovered syntax errors,
failed parsing and unexamined source. `semantics` records the number of observed
unknown findings and typed uncertainty, including returned-reference widening
and unavailable type-only compiler options. `examination` reports static semantic
resource limits and Electron wrapper expansion limits independently. A completed
pass does not establish complete runtime semantics. Route examination and result
capacity remain in the separate route inventory.
Electron applicability is `observed` when a static Electron module reference is
retained, and otherwise `unknown`: a source without such a reference does not
prove that the application is inapplicable. Sources excluded by `source_paths`
retain `not-examined` with a reason and null semantic counts. Historical Evidence
without this facet remains unmeasured. The existing `parse_status` remains its
conservative overall summary; finer facts do not relabel earlier partial results.
Summary delivery counts these facts under `summary.coverage.static_sources`.
Read exact per-file measurements with `inspect_analysis_view`, selecting
`kind: "javascript-facts"` and `property_keys: ["analysis_coverage"]`, or inspect
the module item. Both views use retained Evidence and preserve source provenance.
## ASAR integrity
ASAR inventory checks Electron integrity metadata for embedded archive entries
@@ -716,6 +744,10 @@ deadline per source, including startup and fact transfer. Select
`max_heap_mb` and `analysis_timeout_ms` through MCP, or `--max-heap-mb` and
`--analysis-timeout-ms` on the dedicated CLI command. These choices control the
analyzer; the caller's `NODE_OPTIONS` still controls the CLI/MCP process heap.
For defaults across new MCP operations, use `update_analysis_settings` with
`javascript.max_heap_mb` or `javascript.analysis_timeout_ms`. Explicit per-call
arguments take precedence, and accepted work keeps its original settings. See
the [settings design](design/limits-and-recovery.md#precedence-and-lifetime).
V8 old-space limits do not impose a process RSS limit. When REA runs on Bun,
the analyzer ignores the V8 heap flags; resource failures then report
`worker_heap_limit_bytes` as null instead of a requested or V8-reported limit.
+47 -5
View File
@@ -932,6 +932,25 @@ of a later null value. Inspect the slot's `xrefs` and the writer's assembly or
pseudocode, retaining uncertainty about execution order and runtime values.
These operations inspect static evidence and do not execute the target.
## Editable analysis settings
Agents can call `update_analysis_settings` to raise a curated admission,
timeout or worker heap default on a connected server, then retry the same
request. Errors carry `details.limit` with the setting and bounds when the
failing owner supports runtime recovery; `inspect_analysis_settings` is optional
when those facts are supplied. Updates are atomic, apply to new work, survive
target close/open, and vanish when REA exits. Explicit per-call arguments take
precedence. Neither tool changes host deadlines or resolves retained cleanup.
See [runtime settings and recovery](design/limits-and-recovery.md#runtime-settings-through-mcp)
for the full setting list, reset/revision semantics and ASAR retry example.
For implementation ownership, accepted-operation snapshots, provider wiring,
and extension guidance, see the
[maintainer design](design/limits-and-recovery.md#ownership-and-data-flow).
Ghidra and Hopper startup updates apply to the next startup attempt without
restarting REA. Ghidra decompile timeout updates apply on the existing Program.
An operation already accepted retains its original budgets.
## Ghidra first-query deadlines and recovery
A successful MCP initialize handshake establishes the REA connection.
@@ -1033,11 +1052,13 @@ the current client, so it is not a fresh-start recovery. On the same connection:
`provider_id: "ghidra"`, then retry the first query with an appropriate client
deadline. The connection and selected provider do not need to change.
If REA's provider startup allowance also needs raising, preserve Evidence and
close as above, then update `REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch
environment and restart the server. Reconnect the MCP client before reopening
the target. Configuration is read at process startup; close/reopen on the same
connection cannot apply a changed environment. See the
If REA's provider startup allowance also needs raising, set
`ghidra.startup_timeout_ms` through `update_analysis_settings` before the next
startup attempt. This needs no REA restart and does not change accepted work,
clear cleanup blockers, or extend the client's deadline. Alternatively, change
`REA_GHIDRA_STARTUP_TIMEOUT_MS` in REA's launch environment and restart the
server, preserving Evidence and closing as above. Close/reopen on the same
connection cannot apply a changed launch environment. See the
[CLI retry example](installation.md#ghidra).
This close/reopen flow was exercised on one real Linux stdio connection after a
@@ -1232,6 +1253,22 @@ Kimi-K3 backend, not the WorkBuddy MCP client. Set the profile on REA's
registration in the actual MCP host; the host's name alone does not identify
the downstream model or its schema constraints.
The compact profile also protects strict Anthropic-compatible gateways from
known schema-translation failures reported in [#3800](https://github.com/morluto/rea/issues/3800):
primitive `enum` properties without a sibling `type`, and `oneOf`/`allOf`
inside a property listed in `required`. Affected schemas use the same reduced
first-level projection described below, so the gateway receives a plain object
shape while REA continues to validate the complete canonical contract locally.
This is a compatibility projection, not a claim that the gateway reproduces
the full union semantics.
For array items expressed as an untyped `oneOf`/`anyOf`, the compact profile
adds a type when every branch has the same type. Mixed-type item unions use the
reduced projection; selecting one branch's type would exclude valid calls.
This addresses the emitted shape reported through a Gemini-backed gateway in
[#5201](https://github.com/morluto/rea/issues/5201). Gateway conversion and
live provider acceptance have not been verified for this projection.
The compact profile renders the same tool set, output schemas, annotations,
and canonical server-side validation. It changes only the advertised input
JSON Schema:
@@ -1263,6 +1300,11 @@ Boundary tests pin every advertised compact schema to the budget, keep them
valid JSON Schema that still accepts every canonical example. Client verification
also checks provider-facing sizes and canonical examples after conversion by
the actual Kimi Code CLI; local captures do not establish Moonshot acceptance.
The SDK catalog test checks that full-profile schemas retain nested ADB extra,
managed selection, and WASM timeout and detail restrictions. Path-contract tests
check full-profile local-path rules. Compact MCP workflow tests verify canonical
rejection of invalid inputs before provider work, independent of how much detail
the selected advertisement retains.
### Output schemas
+12 -7
View File
@@ -284,17 +284,22 @@ stops matching while leaving the Hopper API available. The deadline is
2147483647 ms. Unset uses 5000 ms. A larger value is refused because Node
schedules a delay past that signed 32-bit bound as 1 ms. Invalid values are
configuration errors and do not fall back to the default. The setting is read
from the environment that starts REA, so a change needs a restart. A deadline
failure names the effective milliseconds, whether they came from the default
or the setting, the default, and the maximum. The worker old-generation heap
does not raise it. Kuna regex search uses the same deadline. The worker
from the environment that starts REA; changing that environment needs a restart.
In MCP, `regex.matching_deadline_ms` overrides the deadline for newly accepted
searches without restarting REA. A deadline failure names the effective
milliseconds, known source, default, and maximum. The worker old-generation heap
does not raise it. IDA and Kuna use these same regex controls; Ghidra's Java
matcher does not. The worker
heap request is `REA_REGEX_WORKER_HEAP_REQUEST_MIB`, a decimal integer from
7 to 16384 MiB. Unset uses 128 MiB. A request below 7 MiB aborts the worker
during startup, and a larger value is refused. Invalid values are
configuration errors and do not fall back to 128. The setting is read from
the environment that starts REA, so a change needs a restart. A heap failure
names the effective request, whether it came from the default or the setting,
the default, the floor, and the maximum. A positive Node.js
the environment that starts REA, while MCP `regex.worker_heap_request_mib`
can override it for workers belonging to newly accepted searches. Use
`update_analysis_settings` for either runtime control; accepted work keeps its
budget. See the [settings design](design/limits-and-recovery.md).
A heap failure names the effective request, known source, default, floor, and
maximum. A positive Node.js
`--max-old-space-size` on the process that starts
REA, including `NODE_OPTIONS`, overrides that request and sets the effective
heap. `--max-old-space-size-percentage` takes precedence over that flag in
+18 -8
View File
@@ -17,7 +17,7 @@ Summary delivery retains the complete Evidence: use `parent_evidence_id` with
Extraction returns a fresh `output_root`; analyze that extracted directory with
`analyze_javascript_application`. Snapshot-only applications have no recovered
main script and receive no JavaScript next probe. Neither does a main script
whose code path lacks a JavaScript source suffix, such as `main`, because the
whose logical path lacks a JavaScript source suffix, such as `main`, because the
analyzer selects extracted files by suffix.
## Recognition and evidence
@@ -35,11 +35,18 @@ inventoried once. Native carrier bytes keep their content hash and native
classification while the root occurrence represents the SEA container.
Root `format_metadata` preserves raw flags, decoded flags, code path, blob
location, exec arguments and fuse observations. Children retain their role and
original `source_path`. The main script uses its declared code path; snapshots
and code caches use `__node_sea__/snapshot` and `__node_sea__/code-cache`.
Assets retain their names. Extraction applies the shared path-containment and
collision rules, rejects unsafe names and never overwrites existing files.
location, exec arguments and fuse observations. Main and asset children retain
their role and exact declared name in `source_path`. Safe, noncolliding names
become logical paths after the shared NFC path normalization. Names that are
unsafe as relative paths, have an exact or file-prefix collision with another
entry, or occupy the reserved `__node_sea__`
namespace receive deterministic logical paths: `__node_sea__/main.cjs` for the
main CommonJS source and `__node_sea__/assets/<ordinal>` for assets, using their
zero-based order in the blob. The original declared name remains
in `source_path`, including an empty asset key. Snapshots and code caches use
`__node_sea__/snapshot` and `__node_sea__/code-cache`. Extraction materializes
the logical paths through the shared path-containment and collision rules and
never overwrites existing files.
The fuse scan independently records `set`, `unset`, `absent`, or `conflicting`.
An unset fuse with a present blob is reported as a limitation. These are static
@@ -56,7 +63,7 @@ including the exec-argument extension byte. Older layouts without that byte,
Carrier byte order is independent of blob byte order.
Malformed magic, truncation, unknown flags/extensions, invalid UTF-8, duplicate
asset names and conflicting blob declarations fail closed with named reasons.
raw asset keys and conflicting blob declarations fail closed with named reasons.
Zero padding after a complete blob is allowed. The blob read ceiling is 256 MiB;
PE carriers also use the shared 512 MiB input, 65,536 directory-entry and 8 MiB
resource-metadata ceilings. The decoder admits at most 100,000 assets whose
@@ -74,7 +81,10 @@ code-cache bytes remain opaque.
Synthetic fixtures cover Mach-O, ELF and PE inventory/extraction, malformed
blobs, collisions and fuse contradictions. CLI and MCP tests exercise the same
artifact provider and advertised schemas.
artifact provider and advertised schemas. A CLI regression also generates a blob
with the pinned local Node version, an absolute main path and an empty asset
key, then inventories it inside a synthetic ELF carrier. This verifies the real
blob producer, not a real injected executable.
The optional real ELF lane uses Docker's official Node v24.10.0 image and pinned
postject to build a fixture, then verifies inventory and extracted script/asset
+6 -2
View File
@@ -25,8 +25,12 @@ metadata retains the original name, table index, type, compression flag,
relative offset and known directory record extent. Logical paths follow the
carrier's path dialect: backslashes separate components for PE carriers, while
they remain literal filename characters for POSIX carriers. The source name
always retains its stored spelling. If the carrier dialect is unconfirmed, REA
preserves backslashes verbatim instead of inferring POSIX semantics.
always retains its stored spelling in occurrence `source_path` and
`format_metadata.source_name`. For PYZ rows, that source is the exact module or
data key in the PYZ table, not a reconstructed filesystem name;
`format_metadata.container_name` identifies the containing PYZ image. If the
carrier dialect is unconfirmed, REA preserves backslashes verbatim instead of
inferring POSIX semantics.
`relative_offset` is relative to the CArchive start for CArchive rows, or to
the decoded PYZ image for PYZ rows. A CArchive directory record starts at
`archive_start + toc_offset + directory_record_offset`; its size is
+3 -13
View File
@@ -325,9 +325,7 @@ REA の利用、バグ報告、機能の要望、ビルドのテスト、修正
REA は、法令に従った研究、教育、相互運用性の確保、ソフトウェアの保存、および所有するソフトウェアや調査を許可されたソフトウェアの解析を目的としています。
The project's position on illegal or unauthorized use covers all REA
versions, distribution channels, and platforms; each copy retains its
applicable licenses.
違法または無許可の利用に関するプロジェクトの立場は、REA のすべてのバージョン、配布チャネル、およびプラットフォームに適用されます。各コピーには、それぞれに適用されるライセンスが引き続き適用されます。
## 貢献する
@@ -339,14 +337,6 @@ REA への貢献を歓迎します![issue を作成](https://github.com/morlut
[MIT](../../LICENSE)
REA-authored code is licensed under MIT. Third-party material retains its own
licenses and copyright notices; see [Third-party notices](../../THIRD_PARTY_NOTICES.md)
for the dependency inventory, preserved license texts, and external-tool terms.
REA が作成したコードは MIT ライセンスの下で提供されます。サードパーティの素材には、それぞれのライセンスと著作権表示が引き続き適用されます。依存関係の一覧、保存されたライセンス本文、および外部ツールの利用条件については、[サードパーティ通知](../../THIRD_PARTY_NOTICES.md)を参照してください。
**You must legally obtain and be authorized to use commercial tools**, including
IDA Pro / Hex-Rays decompilers, Hopper, and JEB, under their applicable licenses.
REA grants no tool license, key, or activation entitlement. Trials and demos
remain subject to vendor terms, including user, feature, automation, and output
sharing restrictions. Cracked copies, stolen keys, unauthorized key sharing,
and bypassing license restrictions are not authorized. This guidance does not
amend MIT or third-party licenses.
**IDA Pro / Hex-Rays デコンパイラ、Hopper、JEB を含む商用ツールは、適用されるライセンスに従って合法的に入手し、使用する権限を得ている必要があります。** REA はツールのライセンス、キー、またはアクティベーションの権利を一切付与しません。試用版やデモ版も、ユーザー、機能、自動化、および出力の共有に関する制限を含め、ベンダーの利用条件に従います。クラックされたコピー、盗まれたキー、無許可のキー共有、およびライセンス制限の回避は認められていません。このガイダンスは、MIT ライセンスやサードパーティのライセンスを変更するものではありません。
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -83,7 +83,8 @@ empty dependency set. Retained views describe the captured artifact digest;
they do not assert that the current file or running application is unchanged.
ELF section names are resolved through the section-header string table.
Zlib-compressed name tables are decoded within the native structural budget.
Zlib- and Zstandard-compressed name tables are decoded within the native structural
budget. Zstandard decoding also uses a 128 MiB window limit.
If a name table uses unsupported compression, the result reports that
limitation and leaves `.dep-v0` presence unknown rather than reporting the
section absent.
@@ -0,0 +1,12 @@
# Implementation plan
1. **Defaults.** `WriteBody.defaults` and its schema; a create's echo of a member a call leaves out takes its default.
2. **Merging.** Explain a later create on its own and merge it: agreeing rules, references one call shows alone, joined
bodies and defaults, the plan's rule order.
3. **Sorted echoes.** Learn `sorted` for creates and updates, store sorted strings when serving, and agree plain and
sorted echoes in update and create plans.
4. **References.** A references module that learns and resolves them; created and updated fields resolve them against
the replayed or live state; the members they read are accepted.
5. **Nested view keys.** The gate's view spelling learner, moved to its own module, notes object keys; rendering
applies them.
6. **Schema, tests and docs.**
@@ -0,0 +1,48 @@
# Learn creates from every call, sorted arrays and referenced entities
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
After the derived-values change, the creates prompt-built environments still withheld failed for four reasons the
write model did not cover:
- A create plan was learned from the tool's first call alone, so a later call that sent a member the first did not
(Linkding's `shared`), or left one out, was refused even when it agreed with the plan everywhere else.
- Linkding stores `tag_names` sorted, so a call that sent its tags in another order did not echo them, and its create
was withheld or contradicted.
- Homebox's item create answers its parent and type as whole objects (`parent`, `location`, `entityType`) beside the
`parentId` and `entityTypeId` it sent. Kept as constants, they pinned those members to their recorded values.
- Lists show some objects with fewer keys than an entity holds: Homebox lists an item's parent without the parent's
own type, and Keycloak lists a user's `access` with one key. A created entity's object rendered whole, so the list
check failed.
## Behaviour
- **Every call.** A later create is explained on its own and merged into the plan when both calls show the same fields,
but for a reference whose member a call without it did not send (one an earlier create added stays), 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
(`body.defaults`). A clock widens to `trimmed` only when no held entity or earlier call showed it at a fixed width. A
later create that disagrees leaves the plan as it was and is judged as before; one that left out a member the plan
fills while showing that field otherwise is `unexplained-change`. Merged rules keep the plan's order.
- **Sorted arrays.** An echo is `sorted` when the application stored the sent strings in code-point order rather than
as sent; serving stores them sorted. A plain echo and a sorted one agree, and the plan keeps the sorted one, since a
call whose array was already in order cannot show it; once a call stored strings out of order as sent, they disagree.
- **References.** A created or updated object field that holds fields of the entity a sent member names, as the state
holds it, is a `reference` (member, type, fields). Several members naming such entities resolve to the one named like
the field. The member is accepted with any value. Replay and serving look the entity up in the state they hold;
serving refuses a call naming one the environment does not hold or holds in more than one collection, and the field
is absent, from the entity and the create's answer, when the call does not send the member. An update that sends the
member naming the entity the field already repeats keeps a reference an earlier call showed.
- **Nested view keys.** A view that always shows a field's objects with the same keys, in any order, and fewer than
an entity stores, names them in `nested_fields` in the first recording's order, and a stored object it never
recorded renders with those keys.
Plans and views that need none of this are unchanged.
## Acceptance
- Linkding's prompt-recorded bookmark create whose read-back lists tags sorted is served, and Homebox's item create is
explained without pinning its parent or type.
- No write tool of the 69 recorded captures goes from served to withheld.
@@ -0,0 +1,10 @@
# Tasks
- [x] Merge later create calls into the plan, with defaults for members a call leaves out.
- [x] Learn and serve sorted echoes.
- [x] Learn, replay and serve references to held entities.
- [x] Learn the keys views show of nested objects and render written objects with them.
- [x] Add view, serving and unit tests.
- [x] Document the rules and view member.
- [x] Measure the recorded captures and served write tasks.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,61 @@
# Verification
Executed on macOS with Node v24.22.1 at `043053d1` (main `79845063`, the derived-values branch `8ce1127b` and this
branch). Recorded captures and served write tasks were measured with a local, unpushed build of this branch merged with
the scenario-timing branch (#6967), as the derived-values branch was; its baseline is the derived-values branch with the
same merge.
## Gates
- `npm run -s typecheck`, `lint`, `format:check`, `docs:check`, `mcp-catalog:check`, `verify:test-discovery`,
`scan:todos` and `npx knip`: passed.
- `npx vitest run --project domain --project services --project adapters`: 17,258 passed, 25 skipped, 1 failed ("stops
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
- `npm run test:focused --` the environment build, export and serve CLI boundaries, the analysis-view CLI and MCP
boundaries, the environment serve, served-writes and bundle MCP boundaries, MCP schema validation and output schema
constraints: 10 files, 82 passed.
## Tests
- View: a later create sending a member the first did not, or leaving one out, widens the plan with the member's default;
a later create storing a member otherwise than sent stays unexplained; sent tags stored sorted give a sorted echo; a
create embedding the shelf its `shelf_id` names gives a reference, and the list that shows the shelf by id and name
learns `nested_fields`.
- Serving: a create leaving the member out stores its default and one sending it stores it; a create embeds the named
shelf as the environment holds it, lists it with the listed keys, and is refused for a shelf it does not hold; sent
tags are stored sorted.
- Unit: references (the member named like the field among several, too few keys, a disagreeing value, an unheld entity)
and merges (a constant that differs, a member one fixes and the other accepts, a reference one call shows alone).
## Write plans over recorded captures
The 69 captures the derived-values branch measured: 50 published, 18 Claude Code recorded from a prompt, and one timed
Grafana recording. After merging main `ed79bf93` (which bounds create learning), the census was run again against main
with the scenario-timing branch: 124 served there, 132 with both branches, no write going from served to withheld; the
columns below are the measurements before that merge, whose totals it reproduces.
| Captures | Write plans | Served before both branches | Derived values | This branch |
| ------------------------- | ----------- | --------------------------- | -------------- | ----------- |
| 50 published | 187 | 68 | 68 | 70 |
| 19 recorded from a prompt | 120 | 56 | 61 | 62 |
- No write goes from served to withheld.
- Gains: Keycloak's published user create and update, whose list shows each user's `access` with one key, so a created
user now lists as recorded and its plan's sorted insert checks; and the round-4 Linkding bookmark create, whose tags
the application stores sorted.
- Explained but still withheld: Homebox's item create now merges with the location create, with references for
`parent`, `location` and `entityType` and `parentId` and `entityTypeId` accepted with any value. Its read-back list
answers items only, sorted by name, from a line with no parameter saying so, which the list model does not cover.
- Linkding's create whose later call sends `shared` merges too, and is withheld as `side-effect`: the recording also
added a bookmark through the UI that no recorded write explains.
## Served write tasks
The same captures rebuilt by the baseline and by this branch, with `fireworks_ai/gpt-oss-120b` and
`fireworks_ai/glm-5p3-flash`. The tasks are new; each checks the result from the environment's state, including that
nothing else changed.
| Capture | Tasks | Baseline (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
| --------------------------- | ------------------------------------------ | ----------------------- | -------------------------- |
| Linkding, recorded round 4 | 2 bookmark creates, one with two tags | 0/2, 0/2 | 2/2, 2/2 |
| Keycloak, published capture | create a user, change another user's email | 0/2, 0/2 | 2/2, 2/2 |
@@ -0,0 +1,12 @@
# Implementation plan
1. **Clock formats.** A `trimmed` ISO format, `fixedWidth` on a clock reading, fitting a format to another value, and
calendar digits, in a clock formats module.
2. **Composed strings.** A module that indexes a create's candidate parts once, segments a string, checks the type's
other entities and an update's before value, and spells the string from written fields.
3. **Explaining.** Create explanation composes unexplained strings after templates, learns trimmed clocks from the
type and generated integers; update explanation composes unsent changes after the existing rules.
4. **Later calls.** A later create or update widens the plan's clocks to a trimmed spelling it shows; a later create's
unique value may be a generated integer.
5. **Serving.** Derived fields are rebuilt after other rules; unique integers are seeded by length and kind.
6. **Schema, tests and docs.** The plan schema, view, serving and unit tests, and the write-plan docs.
@@ -0,0 +1,59 @@
# Explain values an application derives from other fields
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
Environments an agent built from a prompt withheld creates in three of five applications, each for a value the write
model could not explain, though the application derives it deterministically:
- Linkding's `web_archive_snapshot_url` is `https://web.archive.org/web/<creation time as YYYYMMDDhhmmss>/<sent url>`.
The plan kept it as a constant, which pinned `url` to its recorded value, so a later create with another URL was
refused.
- Grafana's folder `url` is `/dashboards/f/<uid>/<slug of the title>`, which pinned `uid` the same way, and its numeric
`id` is a large time-based integer the plan copied from the first create, which the second create's read-back
contradicted.
- Homebox, written in Go, spells nanosecond clocks with trailing fraction zeros dropped (`…34.38812218Z`), so a later
create's clock did not follow the format learned from the first.
## Behaviour
- **Composed strings.** A created field no other rule explains may be a `composed` string: literal text and parts read
from the fields other rules write, each a field's value, its slug, or a clock's calendar digits (`YYYYMMDDhhmmss`,
`YYYYMMDD`, `YYYY-MM-DD`, `YYYY/MM/DD`, in the clock's zone). Parts are found left to right, longest first, delimited
by non-alphanumerics; generated values may be short, other values need eight characters, as a copy does. A create
composes a string only when at least one other entity of the type spells it with the same parts and none spells it
otherwise, and never when one text comes from two sources serving could tell apart (two clocks the write stamps are
one source when they spell the same zone), and never when another entity holds the field as something other than the
parts spell, a non-string value included. An update's unsent change may be composed from members it stored as sent and
fields it left as they were, when the same parts also spell the value before the call and no other entity spells it
otherwise. Serving rebuilds the string from the written fields after every other rule and refuses a call when a part's
field holds nothing to spell, and a composed field no longer pins the members it reads. A composition learned on a
later call is checked against every earlier call, and a later create whose response spells a composed field otherwise
is withheld.
- **Trimmed clocks.** A clock format may be `trimmed`: its values differ only in how many fraction digits they show and
none ends a fraction in a zero. It is learned from the type's held values of the field, from a later create, and from
a later update; other clocks the plan stamps take the spelling too, and no clock takes it once a held entity, the
exemplar or any call the plan accepted showed the field at a fixed width; a later call that then shows it shorter is
withheld. `fraction` is the most digits shown, and serving drops trailing zeros and the point with them.
- **Generated integers.** A created integer of 10 to 20 digits that no entity of the type holds, where every held entity
holds a distinct one of the same length and JSON kind and none is a clock, is `unique`. Serving draws a seeded integer
of that length and kind that no held entity has. One that another field of the created entity also holds, as a value,
a number at any depth or a delimited part of a nested string, is not `unique`, and the create is withheld as
`unexplained-change`, since seeding cannot keep the two equal. A create is also withheld when another string holds its
digits and no `template` or `composed` rule rebuilds that string from it, or when a later create answers the field
with anything but an integer of the same length and kind. Compare treats two such integers of one JSON kind and length
under a `unique` rule as generated values.
Plans that need none of this are unchanged.
Later creates must retain the exemplar's unique-value family (UUID, integer with
the same digit count and JSON kind, or opaque token). Their recorded composed
fields must equal the values the plan rebuilds; simulation cannot replace a
contradictory captured value and then count that replacement as confirmation.
## Acceptance
- The prompt-built Linkding, Homebox and Grafana captures serve the creates and renames they withheld, and write
tasks that use them pass.
- No write tool of the 50 published captures or the earlier prompt-built captures goes from served to withheld.
@@ -0,0 +1,10 @@
# Tasks
- [x] Read and fit trimmed clock formats, and spell calendar digits.
- [x] Compose created strings and unsent update changes from other fields.
- [x] Learn generated integers as unique values and serve them by length and kind.
- [x] Widen plan clocks on later creates and updates, and spread a trimmed spelling to the plan's other clocks.
- [x] Add view, serving and unit tests.
- [x] Document the rules.
- [x] Measure the prompt-built captures, the 50 published captures and the served write tasks.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,91 @@
# Verification
Executed on macOS with Node v24.22.1 at `a1edeafd` (main `79845063` plus this branch). The prompt-built recordings and
evaluations used a local, unpushed build of this branch merged with the scenario-timing branch (#6967), whose request
times the clock rules need from browser scenarios, on Ubuntu 24.04 VMs with snap Chromium 155. The baseline is the same
merge without this branch.
## Gates
Follow-up on 2026-10-25, after merging main `a5bfd0440`, with Node 24.22.1 and npm 11.16.0 on macOS:
- Environment write/compare source tests: 26 files, 839 passed.
- Analysis-view CLI/MCP, environment served-write MCP, environment-compare CLI, managed bundle read guards, and setup
registration prewarm boundaries: 6 distinct files, 77 passed. The managed low-headroom failure is fixed by main's
Evidence ownership change (#7036); the setup tests share host preparation to satisfy lint without removing cases.
- `check:fast`, `format:check`, and `docs:check`: passed.
- New capture-to-plan regressions reproduce missing composition parts on earlier updates (null and structured sources)
and fixed-width clock evidence from either the selected entity or another held entity. Rule tests exercise several
separator/zone groups in both orders, choosing each group's widest trimmed peer while preserving fixed-width fields.
- Compare uses the domain clock matcher on parsed readings. Tests cover shorter fractions, absent fractions, maximum
width, trailing zeros, separators, zones, and response-entity scope; the compiled CLI accepts a Go-style create whose
live clocks have eight digits and served clocks have none, preserving all input Evidence files.
- The original application's recordings and model evaluations below were not rerun in this follow-up.
Original author validation:
- `npm run -s typecheck`, `lint`, `format:check`, `docs:check`, `mcp-catalog:check`, `verify:test-discovery`,
`scan:todos` and `npx knip`: passed.
- `npx vitest run --project domain --project services --project adapters`: 17,246 passed, 25 skipped, 1 failed ("stops
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
- `npm run test:prepare`, then `npm run test:focused --` the environment build, export and serve CLI boundaries, the
analysis-view CLI and MCP boundaries, the environment serve, served-writes and bundle MCP boundaries, MCP schema
validation and output schema constraints: 10 files, 82 passed.
## Tests
- View: Go-spelled tag creates and renames are served with a trimmed clock; bookmark creates are served with a composed
snapshot address and accept any URL; folder creates and renames are served with a composed address and a generated
integer id.
- Serving: a served tag create spells its clocks without trailing zeros; a served bookmark create composes its snapshot
address from the served clock and the sent URL; a served folder create gets a fresh sixteen-digit id and an address
that follows its uid and title, and a rename moves the address.
- Unit: fitting clock formats (trimmed, fixed width, separator and zone), calendar digits, seeded integers, generated
integer detection, composition and its refusals (one call alone, a held entity spelling otherwise, two sources with
one text, a member stored otherwise than sent, a value the parts do not spell before the call), and a wide body
composed within the existing time limit.
## Write plans over recorded captures
Every write plan of 69 captures, built by the baseline and by this branch: the 50 published captures, 18 captures
Claude Code recorded from a prompt for the prompt branch's checks, and one new timed Grafana recording.
| Captures | Write plans | Served on the baseline | Served on this branch |
| ------------------------- | ----------- | ---------------------- | --------------------- |
| 50 published | 187 | 68 | 68 |
| 19 recorded from a prompt | 120 | 56 | 61 |
- No write goes from served to withheld. The five gains are Linkding's bookmark create, Homebox's tag create, and
Grafana's folder create in two recordings and folder rename in the timed one.
- Published plans change only where a value is now composed: Linkding's bookmark create and BookStack's book create and
update (`description_html` as `<p>` around the description) now accept the member they read.
- Unchanged by design: Grafana's published folder, whose uid and title slug are the same text, so its address names two
sources; Ghost's post update, whose `lexical` keeps the `derived` rule that explained it before; BookStack's page HTML
and Jellyfin's playlist path, which other pages and playlists of the capture spell otherwise.
- What still withholds a create the composed value now explains: a later Linkding create that sends a member the first
did not (`shared`), Homebox item creates that differ from the first location create, and a Linkding create whose
read-back lists `tag_names` sorted.
## Served write tasks
The same timed captures, rebuilt by the baseline and by this branch, with the models `fireworks_ai/gpt-oss-120b` and
`fireworks_ai/glm-5p3-flash`. Create tasks are new; recorded write tasks are the applications' existing ones with entity
counts taken relative to each bundle's state.
| Application | Tasks | Baseline (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
| ----------- | ---------------------------------- | ----------------------- | -------------------------- |
| Linkding | 2 bookmark creates | 0/2, 0/2 | 2/2, 2/2 |
| Linkding | 3 recorded (update, notes, delete) | 3/3, 3/3 | 3/3, 3/3 |
| Homebox | 2 tag creates | 0/2, 0/2 | 2/2, 2/2 |
| Homebox | 2 recorded deletes | 0/2, 0/2 | 0/2, 0/2 |
| Grafana | folder create and rename | not served | 1/2, 2/2 |
| Grafana | 2 recorded deletes | | 2/2, 2/2 |
- Smoke and retrieval match on both builds (Linkding 9/9 and 8/9, Homebox 3/3 and 3/3); Grafana's new recording passes
its smoke task and 9/9 retrieval with both models.
- gpt-oss missed the Grafana rename by sending the update without the title it was asked to change, ten times; the
served environment refused each call with the members it accepts.
- Homebox's recorded deletes fail on both builds: this recording's served entity list holds its locations only, so the
models never find the item to delete.
- The created bookmarks' snapshot addresses and the created and renamed folders' addresses are checked from the
environment's state.
@@ -0,0 +1,8 @@
# Implementation plan
1. **Read-backs.** A create's observation looks for the first read of its entity before the next write; an item read's
fields and clock spellings are taken when it agrees otherwise, and the read observes; a list read adds fields only. The plan's observation records the
read and its shape, and serving merges the same objects.
2. **Sent members.** An update's sent member stored as a fresh clock after it sent back the held one is a `clock`; one
stored as the slug of another member stored as sent is a `slug`.
3. **Schema, tests and docs.**
@@ -0,0 +1,47 @@
# Writes whose answers show the entity otherwise than reads, and whole-object updates
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
Agents built environments from a prompt for five applications the prompt flow had never seen (Vikunja, Ghost, EspoCRM,
Paperless-ngx and Miniflux). Two write-model gaps withheld most of their creates and edits:
- **A create's answer is another view of the entity than its reads.** Paperless answers a tag create without the
`document_count` its item read shows. EspoCRM answers a contact create without nine fields its item read shows.
Vikunja answers a label create with a partial `created_by` and clocks to the nanosecond, where reads show the full
user and whole seconds. Ghost answers a tag create without the `count` its list shows. The state built the entity from
the answer alone, so the read-back contradicted the create.
- **A whole-object update sends back what the application derives.** Paperless's UI sends the tag's old `slug` with a
new name, and the application slugs the name anew. Ghost's UI sends the `updated_at` it read, and the application
stamps a new one. The update was withheld as `unexplained-change`, or its first call's value was pinned so that later
calls conflicted.
## Behaviour
- **Read-backs.** When the first read of a created entity before the next write shows fields the create's answer lacks,
or spells its clocks to another precision, the observed entity takes them, and the plan records the read
(`observation.read`, with its shape).
- An item read that agrees with the answer otherwise lends its fields and its clock spellings (whole seconds of a
nanosecond clock, or microseconds of one), and no longer confirms the create. One that disagrees on another value,
or omits an answer field its item view renders, still checks the create, which it contradicts.
- A list read, which may show fewer keys of an object, only adds fields the answer lacks and still checks the create.
- A read that shows nothing new leaves the observation as it was and still confirms the create.
- Serving merges the same two recorded objects, so a served create answers in its answer's shape and stores the
fields of both. Generated clocks in the create answer retain that answer's recorded precision independently of
the stored read-back precision.
- **Restamped clocks.** An update member sent back as the clock the entity held, and stored as a fresh clock near the
call, is a `clock` rule, accepted with any value.
- **Re-derived slugs.** An update member stored as the slug of another member the call stored as sent is a `slug` rule
from that member, accepted with any value.
- **Compatible saves.** A call that echoes an already-derived slug or holds a clock in its own recorded second does
not conflict with a demonstrated derivation, in either call order. A slug derivation must reproduce earlier saves
as well; an explicit custom slug that disagrees still withholds the update.
Plans and views that need none of this are unchanged.
## Acceptance
- The held-out recordings serve EspoCRM's contact create and Paperless's tag edit.
- A create whose item read-back disagrees with its answer on another value is still contradicted.
- No write tool of the 69 recorded captures goes from served to withheld.
@@ -0,0 +1,8 @@
# Tasks
- [x] Take a create's read-back fields into its observation, and serve the same merge.
- [x] Learn restamped clocks and re-derived slugs for sent update members.
- [x] Add view and serving tests that fail without the change.
- [x] Document the observation member and the rules.
- [x] Measure the held-out and recorded captures.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,48 @@
# Verification
Executed on macOS with Node v24.22.1 at `2f807efb` (main `8e6a9578` and this branch).
## Gates
- `npm run check` (build, typecheck, format, lint, typed lint, module boundaries, architecture guards) and
`npm run docs:check`: passed.
- `npx vitest run --project domain --project services --project adapters`: 17,624 passed, 25 skipped, 1 failed ("stops
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
- `npm run test:focused --` the analysis-view, environment build, compare, export and serve CLI boundaries, the
analysis-view, environment bundle, serve and served-writes MCP boundaries, tool schema validity, output schema profile
and MCP schema validation: 12 files, 106 passed.
- The eight new view and serving tests fail on main and pass here.
## Recorded captures
The 69 recorded captures and the five held-out agent recordings, built as environment-state views on main and on this
branch:
| Captures | Served on main | Served here | Served to withheld |
| ----------------------------------------- | -------------- | ----------- | ------------------ |
| 69 recorded | 132 | 132 | none |
| 5 held-out (20 write tools with a target) | 8 | 10 | none |
- **Now served:** EspoCRM's contact create (its read-back adds nine fields the answer lacks) and Paperless's tag edit
(the stale slug is slugged anew from the name).
- **Past the read-back, withheld by other gaps:**
- Paperless's tag create: its list's `all` member lists every id, which state rendering does not model.
- Ghost's tag create and edit: the state does not list the created tags in Ghost's tag lists.
- **Still contradicted:** Vikunja's label create, whose item read-back shows a fully loaded `created_by` where the answer
showed a partial one.
- Three plans of the recorded captures (Airbyte, BookStack, PhotoPrism) now record a read-back; their served state is
unchanged.
## Served write tasks
The held-out round's write tasks, rebuilt from the same agent recordings with this branch, with
`fireworks_ai/gpt-oss-120b` and `fireworks_ai/glm-5p3-flash`:
| Application | Main (gpt-oss, GLM) | This branch (gpt-oss, GLM) |
| ---------------------------------------------- | ------------------- | -------------------------- |
| EspoCRM contacts: create, update email, delete | 0/3, 0/3 | 1/3, 1/3 (create) |
| Paperless tags: create, rename, delete | 1/3, 1/3 | 1/3, 2/3 (rename with GLM) |
- gpt-oss's rename did not apply. Its trajectory was not kept, so the cause is unconfirmed. A served `PUT` requires
every member the recorded call sent (about 14 here), and GLM sent them all.
- EspoCRM's served update accepts only the members its recording edited, so the email change is refused, as on main.
+169 -12
View File
@@ -140,6 +140,72 @@ static tasks. Both lint caches include the website, documentation configuration,
and experiment code scanned by the checker; a regression compares the actual
lint inventory with Turbo's resolved inputs.
Static checks locate candidates; they do not establish semantic ownership.
`jscpd` finds token clones, and Knip counts test references as uses. Module
boundaries and architecture guards enforce their declared import and source
rules. Passing them does not prove that two parsers agree, a wrapper is still
needed at runtime, or cleanup has one owner.
The lint gate treats `no-async-promise-executor`, `no-unreachable`, and
`no-unsafe-optional-chaining` as errors. These catch lost async executor failures,
dead code, and unsafe uses of optional-chain results. `no-unsafe-finally` remains
a warning because cleanup paths can deliberately throw aggregated failures;
review whether they preserve the primary failure. Two local rules
cover narrower observed failures: `rea/no-buffer-deep-equality` flags Vitest
deep comparisons of Buffers from recognized constructors and file reads,
including simple `const` aliases; `rea/cancellation-cause` requires an explicit
cause when a directly checked caller `AbortSignal` is converted to an
`AnalysisCancelledError`. The rules do not infer arbitrary Buffer types,
follow indirect cancellation helpers, or prove that an explicit cause equals
the caller's reason. Keep focused boundary and lifecycle tests for those facts.
`rea/no-evidence-json-preadmission` rejects direct generic JSON parses in
`createEvidence` payload expressions; it does not analyze intermediate values.
`rea/no-positional-tool-contract` rejects catalog indices used to select
operation names, schemas or types; display-only selection remains allowed.
`rea/no-fixture-root-cleanup` follows `const` fixture-root aliases into teardown
deletion; mutable bindings and mixed root arrays remain outside its scope.
`rea/no-shared-module-mock` resolves Vitest import bindings before rejecting
module mocks in projects without per-file isolation. The test-project definitions
own isolation for Vitest and lint; the executable gate checks the resolved policy
and runs the same collector. Aliases and namespace imports count; comments,
shadowed locals, ordinary methods and `spyOn` do not. Negative CLI probes use an
external temporary project so concurrent repository checks cannot discover them.
Focused `.only` declarations, broken assertion promise chains, and unused disable
directives are errors. `vitest/valid-expect` warns (`maxArgs: 2`) because valid stored and
collected assertions can be awaited later. `import/no-duplicates` warns with
`preferInline: false`.
`no-useless-escape` keeps intentional `\-` regex escapes because advertised
patterns must also compile in `v` mode; the catalog regression checks every
advertised pattern.
Fast lint stays syntax-based. `npm run lint:typed` runs serially with pinned
`oxlint-tsgolint` 7.0.2005 and records native plus typed diagnostics in
`.cache/static-checks/typed-oxlint.json`. Production `src` treats
`no-misused-promises` as an error; tests/fixtures warn. `no-floating-promises` is
an error throughout the lint inventory. `return-await` is an error in
error-handling scopes so asynchronous completion stays inside its catch or
cleanup boundary. Exhaustiveness is an error when a union member has neither a
case nor a default. Intentional defaults count as handling the remaining union;
new members covered by those defaults therefore require behavioral review.
Two HTTP/3 source-test files allow the runner-owned `node:test` registration API;
ordinary unowned promises in those files still fail. The authored code-split
recovery input is exempt from floating-promise policy because it is analysis
input, rather than application orchestration. Preserve framework-owned callbacks
such as an awaited `Promise.finally`; inspect the actual completion owner before
changing a callback solely to satisfy its declared `void` return type.
`npm run knip:production` writes advisory candidates to
`.cache/static-checks/production-reachability.json`; execution/report errors fail.
Ordinary Knip remains. `check`, `check:pr`, and `check:ci:deep` run reachability
then typed lint sequentially after cached work. Typed lint builds the project
type graph and stays separate from fast feedback. Its default Go settings are
`GOMAXPROCS=2` and `GOMEMLIMIT=1024MiB`; caller-selected settings take precedence.
Routine iterations and rebases need focused regressions and relevant checks.
Before handing off a PR, run `npm run check` and generated-document checks when
applicable; CI owns the full suite and coverage. Use the full local gate for
@@ -169,6 +235,14 @@ with the consumer preloaded as well as alone. For simulated protocol traces,
use an acknowledged event barrier before asserting complete observations;
a zero-duration capture does not guarantee queued events have arrived.
Place faults at the semantic boundary under test: the selected read, EOF, hash
completion, or cleanup attempt. Global call ordinals can shift during setup or
refactoring without reaching that phase. For controlled asynchronous gates, use
`awaitAcknowledgement` from `tests/fixtures/lifecycleAcknowledgement.ts` to race
the acknowledgement against triggering-operation settlement and a bounded
deadline. Register release gates before launch, then release and join launched
work in `finally` before restoring mocks, even when the barrier fails.
Focused contract tests should import the named contract family they exercise;
reserve the full tool catalog for catalog-wide assertions. Resolve contracts by
operation name rather than array position.
@@ -200,6 +274,10 @@ work from fixture generation, schema compilation, subprocess startup and cleanup
Choose elapsed, CPU and process-family memory limits before the run; a worker
count or test timeout alone does not enforce those limits.
Keep capacity fixtures intact when diagnosing a test failure. For byte equality
of large buffers, use `Buffer.equals` or `Buffer.compare`; a generic structural
diff can allocate far more than the production operation.
Remove demonstrated repeated work while preserving distinct assertions and
resource ownership. Share immutable fixture preparation where safe; keep fresh
sessions when lifecycle or concurrency is the behavior under test. Compare the
@@ -308,6 +386,23 @@ Trivial helper assertions need no replacement. Remove abandoned production
scaffolding only after checking runtime and verifier consumers, including
compiled imports.
Assert error codes, structured limit facts, source provenance, and observed
recovery behavior instead of freezing explanatory sentences or banning obsolete
phrases. Keep exact checks for protocol values, setting and parameter names,
caller data, and diagnostic provenance. To verify guidance delivery, compare the
consumer with the owning formatter or constant, alongside independent assertions
for the behavior and facts; shared text alone is not a correctness oracle.
Documentation wording belongs in review, not substring tests of source files.
When replacing prose assertions, verify that a harmless wording change passes
and an incorrect budget or lost fact still fails.
After an owner changes, move fault injection and event spies to the path the
production consumer actually uses, and assert that the intended boundary was
reached. Keep independent expectations from authored input, producer output, or
an external oracle; comparing two paths through the same replacement algorithm
does not preserve the old oracle. A negative probe inside a conditional or loop
must assert that it ran.
Use representative scenarios rather than Cartesian matrices of equivalent
cases. Share setup without hiding inputs or expected evidence. For successful
`Result` values, throw the typed error before asserting the value; a preceding
@@ -335,6 +430,34 @@ that a real loader accepts the artifact. If multiple producer revisions share a
format identifier but differ in behavior, record that ambiguity rather than
using one installed version as an oracle for all of them.
### Boundary regressions
For parser, inventory, snapshot, or projection fixes, select the applicable
cases below. Assert the producing boundary's facts and their meaning in affected
consumers. Add [lifecycle regressions](#lifecycle-regressions) when resource
ownership or finalization changes; a single malformed fixture cannot establish
the whole contract.
| Failure class | Regression that catches it |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Format assumptions | Pair valid padding, offsets, or names containing delimiters with malformed input. Identify the producer revision when behavior varies by version; an unrelated runtime API's range is not an on-disk format limit. |
| Raw identity and normalization | Exercise allowed empty or unusual source names/metadata keys and values that normalize to one lookup or display value. Preserve admitted raw spelling under the existing redactions for transport credentials and explicitly marked sensitive values. Distinguish local paths, file URLs and HTTP paths; assert source identity through consumers. |
| Missing, empty, and unknown values | Distinguish omitted, null, empty, zero, and false where the format admits them. Pair a malformed optional field with usable sibling facts; assert uncertainty without inventing absence or discarding the row. |
| Completed work followed by failure | Complete one input, then fail or cancel a later input. Assert earlier digests, metadata, and parsed facts survive with the later failure reason and accurate processed/unprocessed coverage. For cancellation, assert the exact caller abort reason; where a primary failure was already produced, assert it survives. |
| Independent limits and failures | Trigger a sample/output limit alongside a read or parse failure. Assert both dimensions survive; reaching a limit does not erase sampling failures or prove complete coverage. |
| Representation expansion and capacity | Keep a valid set near the limit and a large malformed input followed by a valid input. Cover JSON escaping, expanded paths or decoded rows as applicable. Assert serialized bounds, retained identities, later valid results, and unknown omissions without charging discarded interpretations as retained output. |
| Snapshots, caches, and comparison | Vary metadata that affects the result while keeping payload bytes equal. Exercise historical records with newly introduced fields omitted, current caller inputs on replay, and reuse across differing selections. Assert correct invalidation or unknown coverage, current request provenance, and unchanged saved Evidence and IDs. |
| Public success and error delivery | Exercise affected public CLI/MCP paths, including partial Evidence on failure. Assert retained facts and named contracts; retrieve partial Evidence when MCP returns a reference. If SDK cancellation suppresses the reply, check server settlement and any retained Evidence. An error-code assertion alone misses lost observations. |
For partial Evidence delivery, verify that same-session readback preserves
identity and provenance without rerunning the provider. Include a no-writer
control that keeps completed Evidence inline and preserves the original failure.
When producer order is unspecified, synchronize fixtures on the observed request
or completion event. A filename, generated key, or fixed delay does not prove
which item completed before cancellation. Preserve real producer ordering and
use a controlled seam for the phase that needs deterministic failure.
Packed bridge validation uses Python's standard-library AST and tar readers
with `-I -S`: caller-local modules, `PYTHONPATH`, and site startup hooks must
not replace those readers or execute during validation. Controlled `ast.py`
@@ -352,6 +475,19 @@ pass that definition through the SDK's `toolDefinition` call option. This keeps
the selected output-schema validation without eagerly compiling unrelated
catalog schemas. Re-read definitions if the server advertises a changed list.
For exact nested input constraints, select the full profile explicitly or use
the full-SDK conformance suite that owns the field. Compact workflow tests
should assert canonical failure categories and provider-call controls where
rejection before provider work matters. Do not make today's omission of a
constraint a compact-profile invariant.
When a file repeatedly validates the same MCP schemas, use
`createMcpSchemaValidation` for file-owned preparation and clear it in
`afterAll`. Its content keys include the complete schema, and the SDK provider
keeps dialect and format validation. Keep sessions and connections scoped to
each test; share immutable schema preparation through the fixture's validator
dependency.
Artifact verification uses `artifactMcpCall` for both lifecycle and analysis
calls. A fresh SDK client has no held tool listing, so a bare `callTool` can
skip output-schema validation. The helper discovers actual advertised
@@ -384,15 +520,16 @@ the affected owners and applicable scenarios below. Include temporary snapshots
and one-shot commands. Use public CLI/MCP workflows where practical and a
controlled production seam for interleavings that cannot be reproduced reliably.
| Scenario | Assertion and control |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Target admitted; startup still pending | Status preserves the readiness distinction and observed startup activity; readiness requires the provider's documented proof. |
| First handshake is late, absent, invalid, or recovered during close | Initial and recovery paths retain the same authenticated identity facts. Foreign run/token controls cannot authorize cleanup; authenticated idle/native work survives the unauthenticated listener deadline. |
| Launch returns an error with a valid process handle | Cover already-exited and still-live owned processes. Verify observed exit separately from creation; failed termination or unavailable identity remains unresolved. |
| Cancellation arrives after workload exit, during result reads or temporary-root removal | Return typed cancellation with available partial observations and actual cleanup status. The non-aborted control succeeds; existing primary/cleanup failures keep their precedence. |
| Children have mixed health or cleanup outcomes | Missing peer health does not erase an observed failure or activity. Idle plus unknown stays unknown. Retry a failed child without closing a successful peer again, including after a close-option change. |
| Cleanup fails after success or primary failure | Public output retains the completed observation or primary diagnostic, remaining resources, and owner recovery action. Restore the denied resource and verify the owning cleanup retry. |
| Target exits without an acknowledgement, or transport disappears while it stays alive | Positive owned exit proof resolves target lifetime; other resources still need cleanup. Lost transport alone does not prove exit. Verify unrelated/attached targets survive and same-connection recovery works when supported. |
| Scenario | Assertion and control |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Target admitted; startup still pending | Status preserves the readiness distinction and observed startup activity; readiness requires the provider's documented proof. |
| First handshake is late, absent, invalid, or recovered during close | Initial and recovery paths retain the same authenticated identity facts. Foreign run/token controls cannot authorize cleanup; authenticated idle/native work survives the unauthenticated listener deadline. |
| Launch returns an error with a valid process handle | Cover already-exited and still-live owned processes. Verify observed exit separately from creation; failed termination or unavailable identity remains unresolved. |
| Cancellation arrives after workload exit, during result reads or temporary-root removal | Return typed cancellation with available partial observations and actual cleanup status. The non-aborted control succeeds; existing primary/cleanup failures keep their precedence. |
| Children have mixed health or cleanup outcomes | Missing peer health does not erase an observed failure or activity. Idle plus unknown stays unknown. Retry a failed child without closing a successful peer again, including after a close-option change. |
| Cleanup fails after success or primary failure | Public output retains the completed observation or primary diagnostic, remaining resources, and owner recovery action. Restore the denied resource and verify the owning cleanup retry. |
| Cleanup owners recover after an error was returned | Retain the original child and aggregate errors and project them after each owner releases. A successful peer stays released; the aggregate remains incomplete until all owners release. Preserve the first cause even when rejection is `undefined`. |
| Target exits without an acknowledgement, or transport disappears while it stays alive | Positive owned exit proof resolves target lifetime; other resources still need cleanup. Lost transport alone does not prove exit. Verify unrelated/attached targets survive and same-connection recovery works when supported. |
Shared process-supervision controls separate leader exit from output closure.
Cover output during held or failed cleanup, bounded retention and drain timeout,
@@ -406,6 +543,11 @@ owned cleanup seams while preserving real cleanup where feasible. Check server
settlement and retained resources even if the MCP SDK rejects the client promise
before the handler finishes. A cancellation during active execution does not
cover cancellation during final cleanup.
For ZIP extraction, replace the pathname after inventory reader close while the
admitted root descriptor remains owned. Verify that identical member bytes in a
different carrier, a directory, or a missing pathname cannot switch the
materialized source. Exercise descriptor close failure and retry before output
publication.
Use explicit abort reasons and assert their identity at admission and after
provider completion, alongside retained observations and cleanup state.
@@ -554,7 +696,11 @@ creation outside the workspace seam and its narrowly documented boundary/package
exceptions.
The shared temporary-directory and workspace helpers preserve caller labels
under an owned `rea-` prefix when needed. `npm run verify:test-temp-hygiene`
under an owned `rea-` prefix when needed. Their
[cleanup ownership differs](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#temporary-files-and-diagnostics):
`createTestTempDirectory` registers `onTestFinished` deletion, while
`createTestWorkspace` requires caller cleanup. Close resources before removing
their directory. `npm run verify:test-temp-hygiene`
uses the same ownership rule to find retained fixtures under a private temporary
root. Third-party caches remain outside that ownership rule.
@@ -1862,12 +2008,16 @@ coverage limits.
### Offline binary layout
`npm run verify:binary:layout` requires Linux x64, GCC/binutils, absolute
`npm run verify:binary:layout` requires Linux x64, GCC/binutils, the
`i686-linux-gnu`, `arm-linux-gnueabi`, `mips-linux-gnu` and `mipsel-linux-gnu`
cross `gcc`/`readelf` toolchains, absolute
`REA_PWNTOOLS_PYTHON` with pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2 and
absolute `REA_VERIFY_STRACE_COMMAND`. It compiles ephemeral source-owned ELF
fixtures and checks public CLI/MCP, lossless addresses/names, file ranges,
mitigation inferences, malformed/unsupported input, original file hashes and
released process ownership. Exec syscall tracing must identify only the declared
released process ownership. Cross-compiled ELF32 executables and relocatable
objects check the reported class, machine and byte order, and every section
name, address, offset and size against GNU `readelf`. Exec syscall tracing must identify only the declared
Node/Python launchers; no target binary is executed. Core/debugger claims need
separate verification lanes. Pass an installed package entrypoint as the script's
first argument to verify packaging independently of the checkout.
@@ -1975,6 +2125,13 @@ Codex CLI. The lane runs sequentially; select scenarios with
[agent evaluation reference](client-verification.md#agent-evaluation-and-conformance-records)
contains isolation, authentication cleanup, grading, and conformance boundaries.
The Notes evaluation packages `website/public/examples/notes-electron/`. When
changing those example bytes, run `tests/boundary/mcp/agentKnownAnswerMcp.test.ts`
and check its rubric in `tests/evaluation/model-evals/AgentEvaluationFixtures.ts`.
Preserve exact source facts and the coverage reported by the analysis. If a
source change crosses an unresolved static-analysis boundary, keep that
uncertainty and a negative control rejecting claims of complete coverage.
## Frida instrumentation
`npm run verify:frida` builds REA, spawns a short-lived local Node.js fixture,
+70 -12
View File
@@ -53,11 +53,11 @@ satisfy. A plan without execution does not establish runtime behavior.
- Use strict inputs with explicit target identity and required fields. Preserve
meaningful target, action, capture, and output choices; remove ignored options
and repeated permission declarations.
One-shot CLI provider inspections retain the caller's exact target spelling
in Evidence `parameters.input_path`; the subject keeps the canonical inspected
file identity. Keep request provenance separate from provider arguments.
Snapshot entries bind Evidence parameters to provider queries, so changes to
snapshot-backed request provenance must preserve that cache contract.
One-shot binary CLI analysis retains the caller's verbatim target spelling in
Evidence `caller_input_path`, including a result loaded from a snapshot. The
subject keeps the inspected file identity. Provider inspections also retain
the established `parameters.input_path` field for existing consumers. Keep
request provenance separate from provider arguments and snapshot queries.
- Return the requested facts inline with artifact/provider identity, source
locations, Evidence references, coverage, and relevant confidence and
limitations. Use supported selection, summaries or views when they answer
@@ -85,6 +85,21 @@ satisfy. A plan without execution does not establish runtime behavior.
### Interpret boundary facts once
The format or protocol reader owns interpretation. Pass its parsed facts and
explicit uncertainty to consumers; do not make adapters reparse display text,
reconstruct omitted values, or infer success from missing diagnostics.
Client registration inspections, for example, carry command identity and
activation independently of readiness. An owned registration may be disabled
or stale; unresolved ownership remains unknown. Consumers use those facts
instead of reclassifying the raw command.
A completed read can establish byte identity even when decoding or format
validation fails. State which representation a digest identifies, such as file
bytes, Fetch response-body bytes, or UTF-8 text. Preserve admitted source values
separately from normalized lookup and display values. When the retention
contract keeps only a digest and size, state that the bytes or text are
unavailable; the digest does not establish format validity.
Comparison needs separate proofs for lookup, correspondence, content, and
coverage. A hash cannot supply the proof that two retained values belong to
corresponding objects.
@@ -103,10 +118,6 @@ Complete traversal or inventory does not make unavailable values equal. A
partial aggregate fingerprint difference cannot establish modification when
omissions alone explain it; independently proven differences still survive.
The format or protocol reader owns interpretation. Pass its parsed facts and
explicit uncertainty to consumers; do not make adapters reparse display text,
reconstruct omitted values, or infer success from missing diagnostics.
Keep format recognition separate from provider admission. A recognizable header
can supply useful observations while later structure remains unresolved or a
provider cannot accept the artifact. Report the reason at the affected boundary
@@ -133,6 +144,12 @@ establish each transition. Include temporary files and one-shot commands, as
well as persistent engine sessions. Use the existing provider contracts;
providers need not expose identical state machines.
In multi-stage work, retain each completed, admitted observation before awaiting
the next stage. Later reads, validation, cancellation, or finalization can fail
without erasing those facts. Carry them through typed failure outcomes with
explicit coverage of unperformed work; do not construct a successful result for
stages that never ran.
- Keep target admission, provider readiness, active work, observed process exit,
and resource release separate. A valid process handle, successful launcher,
or selected target establishes only its own fact. Publish available identity
@@ -207,6 +224,12 @@ providers need not expose identical state machines.
applies rather than duplicate close/finally handling.
- Give descriptor-backed reads an owner before opening the file. A rejected
close must retain the exact handle and preserve any earlier failure.
Carry [`openRegularFile`](https://github.com/morluto/rea/blob/main/src/filesystem/RegularFile.ts)'s
admitted handle and initial metadata rather than restatting only to rebuild
those facts. Keep fresh descriptor and selected-path identity checks after
work, comparing against the original baseline instead of replacing it with
a later stat. Preserve explicit bigint reads when ordinary admission metadata
cannot supply the required size or timestamp precision.
Stable file reads expose completed values separately from cancellation so
consumers retain acquired identity facts after successful close or cleanup retry.
If an abortable caller stops waiting, keep the underlying work in that owner's
@@ -237,9 +260,38 @@ through the producing owner and affected CLI/MCP consumers.
## Implement and evaluate
Put provider protocols/parsing in adapters and shared CLI/MCP workflows in the
application layer. Share Evidence provenance, unknown projection, and session
retention semantics; matching payloads alone do not establish parity.
Put provider wire-protocol parsing in adapters and shared CLI/MCP workflows in
the application layer. Reusable format declarations may live in pure domain
owners: [`peOptionalHeader.ts`](https://github.com/morluto/rea/blob/main/src/domain/peOptionalHeader.ts)
decodes PE structure, while consumers retain machine admission, directory-address
interpretation, and file/mapping bounds. Share Evidence provenance, unknown
projection, and session retention semantics; matching payloads alone do not
establish parity.
Let `createEvidence` own JSON validation and snapshotting of ordinary observation
payloads. `EvidenceObservationInput` accepts semantically parsed parameters,
results, and raw results whose JSON validity still needs admission. Keep format,
selection, and identity validation at their producing boundary. Invalid JSON
issues are rooted at `parameters`, `normalized_result`, or `raw_result` in the
Evidence envelope.
`EvidenceObservation` retains the stronger JSON contract used by producer
helpers and `createImmutableEvidenceSteps`. The cooperative constructor also
requires an authenticated immutable result; preserve that ownership proof.
For objects, proof comes from completed sealing by
[`immutableJson.ts`](https://github.com/morluto/rea/blob/main/src/domain/immutableJson.ts);
`readonly` or `Object.freeze` alone does not establish it. JSON primitives are
directly eligible. Seal owned data and complete cooperative sealing before
reusing its result.
When a failed or cancelled operation retains completed, valid partial Evidence,
use the shared [`ToolResultDelivery`](https://github.com/morluto/rea/blob/main/src/server/ToolResultDelivery.ts)
path with the session's Evidence writer. Keep the original failure and the
named contract's target-content marking; replace the inline partial with a
retained reference only after recording succeeds. An absent writer or a writer
returning a failure `Result` leaves the partial inline, as does a partial that
is not valid Evidence. Unexpected recorder exceptions propagate. An `evidence_id`
member alone does not establish Evidence validity or retention.
Measure the resource before choosing a budget and enforce it before the costly
step. Account for value alternatives, trace-frame products, retained records,
@@ -248,6 +300,12 @@ completeness from examined records and exhaustiveness, rather than diagnostic
prose or an empty failure list. Preserve observations independently of cleanup
success.
Price mutually exclusive success and failure representations separately.
Replace a reserved failure envelope when pricing success, and release discarded
decoding state from retained-output accounting. Retaining additional failed-body
text changes the capacity available to later results; treat that as a contract
change and cover its effect on later valid items.
For example, if ten applicable records are discovered, eight are examined, and
five are returned, the five returned records do not establish complete coverage
of the ten. Keep discovered, applicable, examined, and retained scope distinct.
@@ -0,0 +1,83 @@
# Runtime analysis settings verification
Verified on macOS with Node 24.22.1, Java 21.0.12.1 and Ghidra 12.1.4.
This change adds two MCP tools and 11 runtime defaults without a new dependency.
## Behavioral checks
- Built stdio MCP: a valid ASAR with an index above 16 MiB is refused. Updating
`asar.max_header_bytes` to 32 MiB allows the unchanged full-archive inventory
and JavaScript analysis on the same connection. Both source files are parsed.
Reset refuses the same archive again; target close preserves the revision.
- An Electron header cached under a higher admission remains denied after a
decrease. No bulk extraction or source selection bypasses admission.
- Store and deferred MCP handler tests retain the accepted snapshot across an
overlapping update. Atomic rejection, duplicate keys, revision conflicts,
idempotence, reset, isolation and explicit WABT argument precedence are covered.
- The real Ghidra verifier reaches a natural one-second decompile timeout, raises
the runtime deadline to 60 seconds, and obtains pseudocode for the same slow
function without replacing the Program. Initial and final session run IDs
match; subsequent analysis succeeds and cleanup completes. Its generated
6,000-branch fixture is compiled but never executed.
- The focused suite passed 459 tests across 22 files, with 10 platform-specific
skips. Typecheck/lint, bridge source compilation, runtime build and generated
documentation checks passed.
## Review follow-up
Invalid provider-local launch settings remain typed configuration failures rather
than preventing MCP startup. Inspection reports `source: "invalid"`, omits
`value` and `launch_value`, and includes `configuration_error`; a runtime
replacement supplies a usable value, while reset restores the invalid launch
state. JADX availability and execution resolve the same admitted timeout, so
repair and reset also update capability guidance. The availability regression
uses a fixture JAR and simulated JDK; it does not establish real JADX behavior.
Update history omits an unknown old or new value instead of using null
(the explicit unbounded Ghidra value).
Regex controls apply to IDA, Hopper and Kuna's supervised ECMAScript matcher.
Ghidra uses its own Java matcher and is excluded from these controls. IDA's bound
client and capability descriptions consume the same accepted configuration.
WASM passes its effective deadline separately from caller parameters, preserving
omitted arguments in both successful and partial Evidence. Shared error copying
retains structured limit recovery through attribution and explicit redaction.
## Representative task costs
Measurements below precede the review follow-up; task costs were not remeasured.
One measurement per scenario, using the built stdio server and a valid ASAR with
an index just above 16 MiB and one small JavaScript module. Each scenario ends
with successful full application analysis delivered as a summary. Discovery,
failed calls and recovery are included. Fixture creation, build and tokenizer
work are excluded. Filesystem caches were uncontrolled.
| Recovery workflow | Elapsed | Sampled server-family CPU | Sampled peak server-family RSS |
| ---------------------------------------------------------------------- | ------: | ------------------------: | -----------------------------: |
| Fresh server: fail, runtime update, retry | 3.68 s | 1.81 s | 680 MiB |
| Warm server: fail, runtime update, retry | 0.25 s | 0.49 s | 625 MiB |
| Launch-environment recovery: fail, close, restart with override, retry | 6.02 s | 2.85 s | 674 MiB |
The restart comparison uses the same current build to model the previous
recovery workflow; it is not a benchmark of the previous commit. CPU and RSS
are 100 ms `ps` samples of the server and descendants. Short-lived processes and
unsampled peaks can be missed; CPU estimates exclude the client and sampler.
These observations do not establish a general speedup or performance guarantee.
Tokenized with `cl100k_base` / tiktoken 0.12.0, not billed model usage. Catalog
projections include tool name, description and input schema. The two new tools
add 1,181 tokens; the full 229-tool projection is 59,734 tokens. The runtime
recovery task uses 5,880 tokens for all requests and complete tool replies, plus
1,710 for its selected tools (7,590 total), or 65,614 including full discovery.
Warm recovery reuses discovery. The restart workflow uses 5,492 request/reply
tokens plus two full discoveries (124,960 total). A host that caches discovery
or loads only selected tools has a different context cost. System instructions,
reasoning and client-specific presentation overhead are unmeasured.
## Coverage limits
The archive fixture isolates header admission; it is not the original Loom
archive. Real engine coverage here is Ghidra on macOS. Hopper, JADX, capture
and regex setting integration have source/boundary coverage, not a new real
provider run for every setting. Linux/Windows engines and Bun are unverified
for this change. Client deadlines and receive buffers remain externally owned;
active worker values are explicitly unobserved in the settings inspection.
+19
View File
@@ -71,6 +71,19 @@ selected byte SHA-256 and size, section payload ranges, full header/detail outpu
and its UTF-8 SHA-256. Producer format drift and invalid or truncated modules
are failures, with validation diagnostics and partial Evidence retaining
selected byte identity and tool provenance.
If inspection stops after selected files were read, partial Evidence also keeps
the completed candidate file identities and glue parse observations. A stable
read remains observed when descriptor finalization later fails; cleanup still
requires retry. Partial Evidence for a glue read whose finalization fails
before decoding retains its digest with `not-performed` parse status. Invalid
UTF-8 retains the digest with failed parse status. Neither case invents
references, and a failed file read contributes no invented facts.
These independent observations do not establish that the selected WASM module is
valid: partial results state whether WABT validation is unknown, rejected the
module, or succeeded before a later inspection failure.
CLI errors include this partial Evidence inline. MCP errors can return its
`retained-evidence` reference in `details.partial_observation`; use
`get_evidence_bundle` with `detail: "complete"` to retrieve the matching record.
Core validation remains authoritative when an optional custom section cannot
be interpreted. WABT warning output is preserved in `custom_section_warnings`;
@@ -102,6 +115,12 @@ actual selected deadline and these recovery controls; increasing only the client
deadline does not change the provider budget. No server restart is required to
change this per-call setting. Resolve incomplete cleanup before another attempt.
To change the default across new MCP operations, set `wasm.command_timeout_ms`
through `update_analysis_settings`. An explicit `command_timeout_ms` still wins;
correct that argument if it caused the timeout. Runtime defaults do not insert
omitted arguments into Evidence parameters. See the
[settings design](design/limits-and-recovery.md#precedence-and-lifetime).
For example, `rea inspect-wasm-artifact /artifacts/module.wasm
--command-timeout-ms 60000 --format json` uses a 60-second per-command deadline;
the equivalent MCP input is `{"path":"/artifacts/module.wasm",
+69 -13
View File
@@ -737,21 +737,72 @@ request body must be a usable JSON object (as response bodies are usable), and
the written entity must show in that call's response or in a later read before
the next write. Each field of a created entity gets the first rule that
explains it: generated `identity` (a sequence, UUID or token, by the recorded
value's shape), `echo` of a body member, `copy` of a body string of 8 or more
characters, a `path` value, a `slug` of a body member, a `clock` value within 10
value's shape), `echo` of a body member (`sorted` when the application stored
its strings in code-point order rather than as sent), `copy` of a body string of
8 or more characters, a `path` value, a `slug` of a body member, a `clock` value within 10
minutes of the request in its recorded format, a `sequence` one past the
largest value of its collection or type, a `unique` UUID or opaque token, a
`nested-clock` object whose clocks the request stamped, or a `template` around
a generated value; other fields keep the observed value. A body that wraps one
largest value of its collection or type, a `unique` UUID, opaque token or
integer of 10 or more digits that no entity of the type holds (where every one
holds a distinct integer of the same length, as time-based ids do), a
`nested-clock` object whose clocks the request stamped, a `template` around
a generated value, a `reference` to an entity, or a `composed` string; other
fields keep the observed value. When the first read of a created entity before
the next write shows fields the create's answer lacks, or its clocks to another
precision, the observed entity takes them (`observation.read`). An item read
that agrees with the answer otherwise lends its fields and clock spellings and
no longer confirms the create; one that disagrees or omits an answer field its
item view renders still checks it. Clock agreement uses the coarser recorded
precision, including microseconds. A list read
only adds fields the answer lacks and still checks it. Serving answers a create
in its answer's shape and clock precision and stores the fields of both with
the item read-back's clock precision. A `reference` is an object holding fields of the
entity a sent member names, as the state holds it (a parent beside `parentId`):
the member is accepted with any value, serving looks the entity up and refuses a
call naming one the environment does not hold or holds in more than one
collection, and the field is absent, from the entity and the create's answer,
when the call does not send the member. An id naming entities of one type in
several collections teaches no reference. Each later create widens its plan
when, explained on its own, it agrees with it: both calls show the same fields
but for a reference whose member a call without it did not send (one an earlier
create added stays), every field both rule they rule alike (an echo one showed
sorted stays sorted, unless a call stored its strings out of order as sent),
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
(`body.defaults`). A clock rule widens to `trimmed` only when no held entity or
earlier call showed that clock at a fixed width. A later create that disagrees
leaves the plan as it was. A create whose observed reference differs from the
entity the plan rebuilds, whether or not it sent the reference member, is
`unexplained-change`. So is a create that left out an echoed member yet shows a
value different from the learned default.
A `composed` string is literal text and parts read from the fields other rules
write: a field's value, its slug, or a clock's calendar digits
(`YYYYMMDDhhmmss`, `YYYYMMDD`, `YYYY-MM-DD` or `YYYY/MM/DD`, in the clock's
zone). A create composes a string only when at least one other entity of the
type spells it with the same parts and none spells it otherwise, and never when
one text could come from two sources serving could tell apart. A clock format
is `trimmed` when the field's values differ only in how many fraction digits
they show and none ends a fraction in a zero, as Go spells nanoseconds: its
`fraction` is the most it shows, and serving drops trailing zeros and the point
with them. Other clocks the plan stamps take that spelling too unless their own
value shows a fixed width. A body that wraps one
object in a one-element array is that object, wrapped so on every call
(`body.envelope`). An update's members
echo when stored as sent; a member stored differently is accepted only with its
echo when stored as sent. A clock sent back as the entity held it and stored as
a fresh clock near the call is a `clock`, and a member stored as the slug of
another member stored as sent is a `slug`, both accepted with any value, as a
whole-object write that sends what it read shows. A save that echoes an
already-derived slug or holds a clock in its own second remains compatible
with that derivation in either call order; earlier saves must also reproduce
a newly learned slug. Any other member stored
differently is accepted only with its
recorded value (`body.fixed`) and stores the value the call that showed it
recorded (the `value` of its `constant` rule), and a field it changed without
sending must be a clock, a nested clock, an increment, a slug, a fresh `unique`
value, a `stamp` of the acting user, a value `derived` from the one member it
stores nowhere, or for an item action a `set` of a scalar to the value it
showed, or the plan is `unexplained-change`; an increment, slug, stamp, derived
stores nowhere, a `reference` to the entity a sent member names (kept by a call that names the entity the field already repeats), a `composed` string whose parts read members it stored as sent
or fields it left as they were and spell the value before the call too, or for
an item action a `set` of a scalar to the value it showed, or the plan is
`unexplained-change`; an increment, slug, stamp, derived
value or set must hold for every call of the tool, and an action is served
only once a call found what it sets already set. A write is served only
with the query its first call sent, which serving applies to a call that
@@ -841,9 +892,11 @@ HAR, SAZ and qlog run in an owned Node old-generation heap with one V8 worker.
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` is a decimal integer of mebibytes from
8 to 16384. Unset uses 192, the previous constant. Invalid values fail
configuration and name the setting; they do not fall back to 192. The setting
is read from the environment that starts REA, so a change needs a restart.
A memory failure names the effective mebibytes, whether they came from the
default or the setting, the default, the floor, and the maximum. The decoder
seeds the launch value. In MCP, set `capture.decoder_old_generation_mib`
through `update_analysis_settings` for decoders belonging to newly accepted
operations, without restarting REA. Changing the launch environment still
requires a new process. A memory failure names the effective mebibytes, known
source, default, floor, and maximum. The decoder
passes that value as `--max-old-space-size` and removes inherited
`NODE_OPTIONS`, so additional host memory and `NODE_OPTIONS` do not raise the
heap. The 8 MiB semi-space stays fixed; a smaller old generation aborts Node
@@ -865,8 +918,11 @@ hosts enforce no decoder heap limit, so raising the setting does not bound them.
Observed decoder memory exhaustion returns `resource_constraint`, with the
selected capture, applicable limits and retained process diagnostics. Raising
host memory or `NODE_OPTIONS` does not raise the old-generation heap. Set
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` and restart REA.
host memory or `NODE_OPTIONS` does not raise the old-generation heap. Update
`capture.decoder_old_generation_mib` through MCP, or set
`REA_CAPTURE_DECODER_OLD_GENERATION_MIB` before starting a new REA process.
Retry after owned cleanup completes; the update does not release resources.
See the [settings design](design/limits-and-recovery.md#failure-and-retry).
Record selection happens after complete decoding and does not reduce that
workload. Use a smaller capture exported by its producer or another decoder
with sufficient capacity, retaining the original for provenance. Subsets do
+15
View File
@@ -111,6 +111,10 @@ are made.
## Verification
Browser bundle collection uses absolute section offsets for nested inline maps.
If caller cancellation stops browser map fetching after some items complete,
the failed bundle request retains those items with the captured page in partial
Evidence and marks bundle analysis `not_performed`. This is distinct from the
local source-map decoder's failure semantics above.
An enclosing column offset contributes only when the nested section starts on
that enclosing section's first generated line. Parent boundaries still clip
preceding browser mappings when a nested section starts with a gap or has no
@@ -136,6 +140,17 @@ requests are left unprocessed and reported as unknown through the top-level
items.
Malformed UTF-8 map responses are reported as invalid rather than repaired with
replacement characters.
For browser fetching, an `invalid` map or structural or decoder capacity
failure has `artifact: null`, empty decoded arrays, and an explicit limitation.
When its bounded response body was read in full, optional `response_identity`
records that body's SHA-256 digest and byte count after HTTP content decoding
and before UTF-8 decoding. It is not a digest of partial reads or a claim that
the map was valid; historical items may omit it. Malformed text is omitted so
it does not consume the 32 MiB inline budget needed by later valid maps. A
response whose text cannot fit that budget is refused before JSON parsing and
source-map decoding, and retains the same
completed-body identity. Successful and partial maps keep their full original
text, including BOM and XSSI framing; only their parsing view removes it.
Focused tests cover duplicate mappings, indexed offsets/overlap/local indexes,
32-bit VLQ overflow, nullable content, UTF-16/CRLF positions, immutable artifact
+35
View File
@@ -0,0 +1,35 @@
{
"$schema": "./node_modules/knip/schema.json",
"entry": [
"src/main.ts!",
"src/cli.ts!",
"src/cliOutput.ts!",
"src/cliBuiltinArguments.ts!",
"src/cli/cliResultOutput.ts!",
"src/mcpDoctor.ts!",
"src/composition/javascriptAnalysisWorkerProcess.ts!",
"src/composition/webBundleAnalysisWorkerProcess.ts!",
"src/sqlite/SqliteDatabaseWorker.ts!",
"src/evm/EvmoleInterfaceWorker.ts!",
"src/browser/history/NodeCaptureProcess.ts!",
"src/javascript/sourceMaps/SourceMapCodecProcess.ts!",
"scripts/rea.mjs!",
"scripts/rea-android-workspace-guardian.mjs!",
"scripts/electron-active-hook.cjs!",
"scripts/prepack.mjs!",
"scripts/prepare.mjs!",
"scripts/verify-windows-native-artifact.mjs!"
],
"project": ["src/**/*.ts!", "!src/**/*.test.ts", "!src/**/*.fixture.ts"],
"ignoreExportsUsedInFile": true,
"include": [
"files",
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
"duplicates"
]
}
+36 -1
View File
@@ -2,7 +2,7 @@ REA npm dependency inventory
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
License identifiers are upstream lockfile declarations, not a legal assessment.
Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
Locked package entries: 956; runtime: 180.
Locked package entries: 963; runtime: 180.
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
@algolia/abtesting@1.25.0 | development | MIT
@@ -1100,6 +1100,36 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@oxfmt/binding-win32-x64-msvc/v/0.72.0
Archive: https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.72.0.tgz
Integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==
@oxlint-tsgolint/darwin-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/darwin-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz
Integrity: sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==
@oxlint-tsgolint/darwin-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/darwin-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz
Integrity: sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==
@oxlint-tsgolint/linux-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/linux-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz
Integrity: sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==
@oxlint-tsgolint/linux-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/linux-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz
Integrity: sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==
@oxlint-tsgolint/win32-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/win32-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz
Integrity: sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==
@oxlint-tsgolint/win32-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/win32-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz
Integrity: sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==
@oxlint/binding-android-arm-eabi@1.87.0 | development, optional | MIT
Lockfile path: node_modules/@oxlint/binding-android-arm-eabi
Package: https://www.npmjs.com/package/@oxlint/binding-android-arm-eabi/v/1.87.0
@@ -3540,6 +3570,11 @@ oxlint@1.87.0 | development | MIT
Package: https://www.npmjs.com/package/oxlint/v/1.87.0
Archive: https://registry.npmjs.org/oxlint/-/oxlint-1.87.0.tgz
Integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==
oxlint-tsgolint@7.0.2005 | development | MIT
Lockfile path: node_modules/oxlint-tsgolint
Package: https://www.npmjs.com/package/oxlint-tsgolint/v/7.0.2005
Archive: https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz
Integrity: sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==
p-limit@2.3.0 | development | MIT
Lockfile path: node_modules/p-limit
Package: https://www.npmjs.com/package/p-limit/v/2.3.0
+105 -2
View File
@@ -1,12 +1,12 @@
{
"name": "rea-agents",
"version": "6.7.0",
"version": "6.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rea-agents",
"version": "6.7.0",
"version": "6.8.0",
"license": "MIT",
"dependencies": {
"@babel/parser": "8.0.4",
@@ -73,6 +73,7 @@
"lint-staged": "16.4.0",
"oxfmt": "0.72.0",
"oxlint": "1.87.0",
"oxlint-tsgolint": "7.0.2005",
"release-please": "17.3.0",
"rolldown": "1.2.12",
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
@@ -3491,6 +3492,90 @@
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxlint-tsgolint/darwin-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz",
"integrity": "sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint-tsgolint/darwin-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz",
"integrity": "sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint-tsgolint/linux-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz",
"integrity": "sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint-tsgolint/linux-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz",
"integrity": "sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint-tsgolint/win32-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz",
"integrity": "sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxlint-tsgolint/win32-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz",
"integrity": "sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxlint/binding-android-arm-eabi": {
"version": "1.87.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.87.0.tgz",
@@ -10137,6 +10222,24 @@
}
}
},
"node_modules/oxlint-tsgolint": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz",
"integrity": "sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==",
"dev": true,
"license": "MIT",
"bin": {
"tsgolint": "bin/tsgolint.js"
},
"optionalDependencies": {
"@oxlint-tsgolint/darwin-arm64": "7.0.2005",
"@oxlint-tsgolint/darwin-x64": "7.0.2005",
"@oxlint-tsgolint/linux-arm64": "7.0.2005",
"@oxlint-tsgolint/linux-x64": "7.0.2005",
"@oxlint-tsgolint/win32-arm64": "7.0.2005",
"@oxlint-tsgolint/win32-x64": "7.0.2005"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+8 -5
View File
@@ -1,7 +1,7 @@
{
"name": "rea-agents",
"mcpName": "io.github.morluto/rea",
"version": "6.7.0",
"version": "6.8.0",
"description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
"license": "MIT",
"repository": {
@@ -112,7 +112,7 @@
"prebuild": "npm run deps:check",
"build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force",
"precheck": "npm run deps:check",
"check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards",
"check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards && npm run knip:production && npm run lint:typed",
"precheck:fast": "npm run deps:check",
"check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only",
"precheck:changed": "npm run deps:check",
@@ -120,14 +120,14 @@
"precheck:test": "npm run deps:check",
"check:test": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run",
"precheck:pr": "npm run deps:check",
"check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos",
"check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos && npm run knip:production && npm run lint:typed",
"precheck:ci": "npm run deps:check",
"precheck:ci:fast": "npm run deps:check",
"precheck:ci:deep": "npm run deps:check",
"check:ci": "npm run check:ci:fast && npm run check:ci:deep",
"check:ci:fast": "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks",
"check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check",
"check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python",
"check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python && npm run knip:production && npm run lint:typed",
"check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos",
"check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos",
"ci:static-context": "node scripts/ci/static-task-context.mjs",
@@ -326,7 +326,9 @@
"docs:prepare": "node scripts/generate-package-metadata.mjs && node scripts/run-turbo.mjs run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema",
"verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.mjs",
"licenses:generate": "node scripts/generate-third-party-inventory.mjs",
"licenses:check": "node scripts/generate-third-party-inventory.mjs --check"
"licenses:check": "node scripts/generate-third-party-inventory.mjs --check",
"lint:typed": "node scripts/ci/static-check-report.mjs typed-lint",
"knip:production": "node scripts/ci/production-reachability-report.mjs"
},
"lint-staged": {
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
@@ -404,6 +406,7 @@
"lint-staged": "16.4.0",
"oxfmt": "0.72.0",
"oxlint": "1.87.0",
"oxlint-tsgolint": "7.0.2005",
"release-please": "17.3.0",
"rolldown": "1.2.12",
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
+1 -1
View File
@@ -108,7 +108,7 @@ function classifyPackage(base, head) {
continue;
}
if (
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip$|jscpd$)/.test(
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip(?::|$)|jscpd$)/.test(
script,
)
)
@@ -0,0 +1,33 @@
export interface ProductionReachabilityFinding {
readonly name: string;
readonly namespace?: string;
readonly kind?: string;
readonly specifier?: string;
readonly line?: number;
readonly col?: number;
readonly pos?: number;
}
export interface ProductionReachabilityIssue {
readonly file: string;
readonly owners?: readonly { readonly name: string }[];
readonly files?: readonly ProductionReachabilityFinding[];
readonly exports?: readonly ProductionReachabilityFinding[];
readonly nsExports?: readonly ProductionReachabilityFinding[];
readonly types?: readonly ProductionReachabilityFinding[];
readonly nsTypes?: readonly ProductionReachabilityFinding[];
readonly enumMembers?: readonly ProductionReachabilityFinding[];
readonly namespaceMembers?: readonly ProductionReachabilityFinding[];
readonly duplicates?: readonly (readonly ProductionReachabilityFinding[])[];
}
export interface ProductionReachabilityReport {
readonly issues: readonly ProductionReachabilityIssue[];
}
export function parseProductionReachabilityReport(
text: string,
): ProductionReachabilityReport;
export function recordProductionReachabilityReport(
root?: string,
): Promise<ProductionReachabilityReport>;
@@ -0,0 +1,86 @@
import { spawnSync } from "node:child_process";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
const require = createRequire(import.meta.url);
const finding = z.object({
name: z.string(),
namespace: z.string().optional(),
kind: z.string().optional(),
specifier: z.string().optional(),
line: z.number().optional(),
col: z.number().optional(),
pos: z.number().optional(),
});
const issue = z.strictObject({
file: z.string(),
owners: z.array(z.object({ name: z.string() })).optional(),
files: z.array(finding).optional(),
exports: z.array(finding).optional(),
nsExports: z.array(finding).optional(),
types: z.array(finding).optional(),
nsTypes: z.array(finding).optional(),
enumMembers: z.array(finding).optional(),
namespaceMembers: z.array(finding).optional(),
duplicates: z.array(z.array(finding)).optional(),
});
const reportSchema = z.strictObject({ issues: z.array(issue) });
/** Admit the installed Knip JSON reporter's candidate records. */
export const parseProductionReachabilityReport = (text) =>
reportSchema.parse(JSON.parse(text));
/** Run production reachability without turning candidates into failures. */
export const recordProductionReachabilityReport = async (
root = process.cwd(),
) => {
const reportDirectory = join(root, ".cache", "static-checks");
const reportPath = join(reportDirectory, "production-reachability.json");
// Findings are advisory. Execution, configuration and report-admission failures
// still fail, and must never leave an older successful report at this path.
await mkdir(reportDirectory, { recursive: true });
await rm(reportPath, { force: true });
const result = spawnSync(
process.execPath,
[
join(dirname(require.resolve("knip")), "..", "bin", "knip.js"),
"--config",
"knip.production.json",
"--production",
"--reporter",
"json",
"--no-exit-code",
],
{ cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 },
);
if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error;
if (result.status !== 0) {
if (result.stdout) process.stderr.write(result.stdout);
throw new Error(
result.status === null
? `Production reachability analysis terminated by ${result.signal}`
: `Production reachability analysis failed with status ${result.status}`,
);
}
const report = parseProductionReachabilityReport(result.stdout);
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
console.log(
`Production reachability: ${report.issues.length} files with advisory candidates.`,
);
console.log(`Full report: ${reportPath}`);
console.log(
"Candidates require runtime, verifier and published-contract review before removal; ordinary Knip remains separate.",
);
return report;
};
if (
process.argv[1] !== undefined &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
)
await recordProductionReachabilityReport();
+3
View File
@@ -144,6 +144,9 @@ export const SCOPES = {
"scripts/windows-capabilities.mjs",
"scripts/prepack.mjs",
"scripts/prepare.mjs",
"scripts/lib/release-proposal.mjs",
"patches/release-please+17.3.0.patch",
"tests/boundary/filesystem/releaseProposal.test.ts",
"scripts/rea.mjs",
"scripts/lib/cli-process-environment.mjs",
"install.sh",
+35 -11
View File
@@ -48,7 +48,12 @@ const duplicateReport = z
})
.passthrough();
const run = (packageName, entrypoint, arguments_) => {
const run = (
packageName,
entrypoint,
arguments_,
environment = process.env,
) => {
const executable = join(
dirname(require.resolve(`${packageName}/package.json`)),
entrypoint,
@@ -56,6 +61,7 @@ const run = (packageName, entrypoint, arguments_) => {
const result = spawnSync(process.execPath, [executable, ...arguments_], {
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
env: environment,
});
if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error;
@@ -66,14 +72,30 @@ const run = (packageName, entrypoint, arguments_) => {
try {
await mkdir(reportRoot, { recursive: true });
if (mode === "lint") {
const reportPath = join(reportRoot, "oxlint.json");
if (mode === "lint" || mode === "typed-lint") {
const typed = mode === "typed-lint";
const reportPath = join(
reportRoot,
typed ? "typed-oxlint.json" : "oxlint.json",
);
await rm(reportPath, { force: true });
const result = run("oxlint", "bin/oxlint", [
...extraArguments,
"--format",
"json",
]);
const result = run(
"oxlint",
"bin/oxlint",
[
...(typed ? ["--type-aware", "--threads", "1"] : []),
...extraArguments,
"--format",
"json",
],
typed
? {
...process.env,
GOMAXPROCS: process.env.GOMAXPROCS ?? "2",
GOMEMLIMIT: process.env.GOMEMLIMIT ?? "1024MiB",
}
: process.env,
);
let report;
try {
report = lintReport.parse(JSON.parse(result.stdout));
@@ -89,7 +111,7 @@ try {
({ severity }) => severity === "error",
);
console.log(
`Lint: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
`${typed ? "Typed lint" : "Lint"}: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
);
for (const diagnostic of errors) {
const location = diagnostic.labels[0]?.span;
@@ -106,7 +128,9 @@ try {
)
.slice(0, 8))
console.log(` ${rule}: ${count} warnings`);
console.log(`Full diagnostics: ${reportPath}; npm run lint:details`);
console.log(
`Full diagnostics: ${reportPath}${typed ? "" : "; npm run lint:details"}`,
);
process.exitCode = result.status;
} else if (mode === "duplicates") {
const output = join(reportRoot, "jscpd");
@@ -143,7 +167,7 @@ try {
console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`);
process.exitCode = result.status;
} else {
throw new Error("Expected lint or duplicates");
throw new Error("Expected lint, typed-lint or duplicates");
}
} catch (cause) {
console.error(cause instanceof Error ? cause.message : String(cause));
+5 -7
View File
@@ -1,5 +1,5 @@
import { createVerifierHttpServer } from "../lib/verifier-http-server.mjs";
import { once } from "node:events";
import { createServer } from "node:http";
import { gzipSync } from "node:zlib";
export const networkFixtureSecret = "私密-network-fixture";
@@ -23,7 +23,7 @@ const html = `<!doctype html><meta charset="utf-8"><body><button id="start">Run<
export async function startBrowserNetworkSite() {
const counts = new Map();
const timers = new Set();
const server = createServer(async (request, response) => {
const handleRequest = async (request, response) => {
const path = new URL(request.url, "http://fixture.test").pathname;
counts.set(path, (counts.get(path) ?? 0) + 1);
if (path === "/same") {
@@ -89,7 +89,8 @@ export async function startBrowserNetworkSite() {
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
response.end(html);
}
});
};
const { server, close } = createVerifierHttpServer(handleRequest);
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
@@ -100,10 +101,7 @@ export async function startBrowserNetworkSite() {
counts,
async close() {
for (const timer of timers) clearTimeout(timer);
server.closeAllConnections();
await new Promise((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
);
await close();
},
};
}
+5 -9
View File
@@ -1,5 +1,5 @@
import { createVerifierHttpServer } from "../lib/verifier-http-server.mjs";
import { once } from "node:events";
import { createServer } from "node:http";
/** The CSRF cookie and the stored bearer token the page holds; request steps must send both and the capture must not keep them. */
export const requestFixtureCsrf = "csrf-fixture-7c1d2e";
@@ -52,7 +52,7 @@ export async function startBrowserRequestSite() {
};
const writes = [];
const hosts = [];
const server = createServer(async (request, response) => {
const handleRequest = async (request, response) => {
const url = new URL(request.url, "http://fixture.test");
hosts.push(`${request.headers.host} ${url.pathname}`);
const chunks = [];
@@ -108,7 +108,8 @@ export async function startBrowserRequestSite() {
}
const [status, value] = answerItems(store, request.method, id, body);
json(response, status, value);
});
};
const { server, close } = createVerifierHttpServer(handleRequest);
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
@@ -120,11 +121,6 @@ export async function startBrowserRequestSite() {
/** Host header and path of every request the server received. */
hosts,
items: store.items,
async close() {
server.closeAllConnections();
await new Promise((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
);
},
close,
};
}
@@ -0,0 +1,18 @@
import { tsImport } from "tsx/esm/api";
const { agentEvaluationPassed } = await tsImport(
new URL(
"../../tests/evaluation/model-evals/AgentEvaluationReport.ts",
import.meta.url,
).href,
import.meta.url,
);
/** Fail the runner after publishing a report with any rejected scenario. */
export const assertAgentEvaluationReportPassed = (results) => {
const failed = results.filter((result) => !agentEvaluationPassed(result));
if (failed.length > 0)
throw new Error(
`Agent routing/workflow/answer checks failed: ${failed.map(({ id }) => id).join(", ")}`,
);
};
+1
View File
@@ -123,6 +123,7 @@ export const toolFamilyCatalog = (sources) => {
surface: "application-workflow",
contracts: [
...sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
...sources.analysisSettingsContracts.ANALYSIS_SETTINGS_TOOL_CONTRACTS,
...sources.analysisViewContracts.ANALYSIS_VIEW_TOOL_CONTRACTS,
...sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
...sources.javascriptRecoveryContracts
+1
View File
@@ -41,6 +41,7 @@ const SOURCE_PATHS = {
officialContracts: "dist/contracts/officialToolContracts.js",
enhancedContracts: "dist/contracts/enhancedToolContracts.js",
sessionContracts: "dist/contracts/sessionToolContracts.js",
analysisSettingsContracts: "dist/contracts/analysisSettingsContracts.js",
binaryDiagnosticsContracts:
"dist/contracts/native/binaryDiagnosticsToolContracts.js",
cutterContracts: "dist/contracts/cutterToolContracts.js",
+3 -1
View File
@@ -529,9 +529,11 @@ export async function verifyGhidraBoundaries(
regexError.remediation.action,
/literal mode or simplify the regex/u,
);
// The retry advice lives in the remediation; the reason states that the
// session survived the overflow.
assert.match(
regexError.details.reason,
/exhausted its stack.*Use literal mode/u,
/exhausted its stack.*session and annotations remain available/u,
);
rejectedCalls++;
const nestedPattern = "(".repeat(12000) + "a" + ")".repeat(12000);
+1 -1
View File
@@ -104,7 +104,7 @@ export async function readReleaseHistory(run, base, target) {
// GitHub maps these coding-agent trailers to ordinary User actors.
// Keep those source identities in the inventory while excluding automation credit.
const AGENT_ACCOUNTS = new Set(["claude", "cursoragent", "codex"]);
const AGENT_ACCOUNTS = new Set(["claude", "cursoragent", "codex", "copilot"]);
// REA's maintainer requested external-contributor thanks without self-credit.
const MAINTAINER_ACCOUNTS = new Set(["morluto"]);
const human = (value) =>
+8 -2
View File
@@ -1,7 +1,7 @@
import { execFile } from "node:child_process";
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, relative } from "node:path";
import { dirname, relative, sep } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { requireCompleteReleaseHistory } from "./release-notes.mjs";
@@ -24,7 +24,13 @@ export async function applyReleasePleasePatch(
"The release proposal patch requires release-please 17.3.0",
);
}
const args = ["--directory", relative(project, directory), patch];
// Git prefixes patch filenames with a slash-separated path, even on Windows.
// Split only native separators so POSIX backslashes remain filename bytes.
const args = [
"--directory",
relative(project, directory).split(sep).join("/"),
patch,
];
const check = async (reverse) => {
try {
await execute(
+1
View File
@@ -1,6 +1,7 @@
export interface TestProjectDefinition {
readonly name: string;
readonly group: "source" | "built";
readonly isolate?: boolean;
readonly include: readonly string[];
readonly exclude?: readonly string[];
}
+7
View File
@@ -5,11 +5,13 @@ export const SOURCE_TEST_PROJECTS = [
{
name: "domain",
group: "source",
isolate: false,
include: ["src/{contracts,domain}/**/*.test.ts"],
},
{
name: "services",
group: "source",
isolate: false,
include: ["src/application/**/*.test.ts"],
},
{
@@ -23,17 +25,20 @@ export const SOURCE_TEST_PROJECTS = [
{
name: "composition",
group: "source",
isolate: false,
include: ["tests/composition/**/*.test.ts"],
},
{
name: "conformance",
group: "source",
isolate: false,
include: ["tests/conformance/**/*.test.ts"],
exclude: ["tests/conformance/providers/hopper/realHopperSearch.test.ts"],
},
{
name: "evaluation",
group: "source",
isolate: false,
include: ["tests/evaluation/**/*.test.ts"],
},
];
@@ -55,6 +60,7 @@ export const BUILT_TEST_PROJECTS = [
{
name: "cli-boundary",
group: "built",
isolate: false,
include: ["tests/boundary/cli/**/*.test.ts"],
},
{
@@ -65,6 +71,7 @@ export const BUILT_TEST_PROJECTS = [
{
name: "mcp-boundary",
group: "built",
isolate: false,
include: ["tests/boundary/mcp/**/*.test.ts"],
},
{
+12
View File
@@ -0,0 +1,12 @@
import type { IncomingMessage, Server, ServerResponse } from "node:http";
/** Own admitted fixture requests through transport closure and report unexpected failures. */
export function createVerifierHttpServer(
handleRequest: (
request: IncomingMessage,
response: ServerResponse,
) => Promise<void>,
): {
readonly server: Server;
close(): Promise<void>;
};
+39
View File
@@ -0,0 +1,39 @@
import { createServer } from "node:http";
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
/** Own admitted fixture requests through transport closure and report unexpected failures. */
export function createVerifierHttpServer(handleRequest) {
const pending = new Set();
let failure;
const server = createServer((request, response) => {
const work = handleRequest(request, response)
.catch((cause) => {
failure ??= { cause };
response.destroy();
})
.finally(() => {
pending.delete(work);
});
pending.add(work);
});
return {
server,
close() {
return withVerifierCleanup(
() =>
new Promise((resolve, reject) => {
server.close((cause) =>
cause === undefined ? resolve() : reject(cause),
);
server.closeAllConnections();
}),
[
async () => {
await Promise.all(pending);
if (failure !== undefined) throw failure.cause;
},
],
);
},
};
}
+12
View File
@@ -0,0 +1,12 @@
/** Resolve the lexical binding instead of treating a matching name as an API. */
export const findBinding = (sourceCode, identifier) => {
for (
let scope = sourceCode.getScope(identifier);
scope !== null;
scope = scope.upper
) {
const variable = scope.set.get(identifier.name);
if (variable !== undefined) return variable;
}
return undefined;
};
+4
View File
@@ -0,0 +1,4 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const cancellationCauseRule: Parameters<RuleTester["run"]>[1];
export default cancellationCauseRule;
+200
View File
@@ -0,0 +1,200 @@
import { findBinding } from "./bindings.mjs";
/**
* Require an explicit cancellation reason when a directly checked caller signal
* is converted to an AnalysisCancelledError. This is deliberately syntactic:
* internal shutdown and cancellation forwarded through other owners are outside
* the rule.
*/
const coreModule = /(?:^|\/)analysisErrorCore(?:\.js|\.ts)?$/;
const unwrapChain = (node) =>
node?.type === "ChainExpression" ? node.expression : node;
const propertyName = (property) =>
property.computed
? property.property.type === "Literal"
? property.property.value
: undefined
: property.property.name;
const annotationType = (node) => {
const annotation = node?.typeAnnotation?.typeAnnotation;
return annotation?.type === "TSTypeReference" &&
annotation.typeName.type === "Identifier"
? annotation.typeName
: undefined;
};
const soleDefinition = (variable) =>
variable?.defs.length === 1 ? variable.defs[0] : undefined;
const isKnownType = (node, kind, context) => {
const type = annotationType(node);
if (type === undefined) return false;
const definition = soleDefinition(findBinding(context.sourceCode, type));
if (kind === "AbortSignal")
return type.name === "AbortSignal" && definition === undefined;
return (
definition?.type === "ImportBinding" &&
definition.parent?.type === "ImportDeclaration" &&
/(?:^|\/)AnalysisProvider(?:\.js|\.ts)?$/.test(
definition.parent.source.value,
) &&
definition.node.type === "ImportSpecifier" &&
definition.node.imported.name === "ExecutionOptions"
);
};
const unmodified = (variable) =>
variable !== undefined &&
variable.references.every(
(reference) => !reference.isWrite() || reference.init,
);
const isOptionsSignal = (node, context, seen) => {
node = unwrapChain(node);
if (node?.type !== "MemberExpression" || propertyName(node) !== "signal")
return false;
return isKnownBinding(node.object, "ExecutionOptions", context, seen);
};
const isKnownBinding = (node, kind, context, seen) => {
if (node?.type !== "Identifier") return false;
const variable = findBinding(context.sourceCode, node);
if (!unmodified(variable) || seen.has(variable)) return false;
seen.add(variable);
const definition = soleDefinition(variable);
if (definition?.type === "Parameter")
return isKnownType(definition.name, kind, context);
if (definition?.type !== "Variable" || definition.parent?.kind !== "const")
return false;
const declaration = definition.node;
if (isKnownType(declaration.id, kind, context)) return true;
return kind === "AbortSignal"
? isKnownSignal(declaration.init, context, seen)
: false;
};
const isKnownSignal = (node, context, seen = new Set()) =>
isKnownBinding(unwrapChain(node), "AbortSignal", context, seen) ||
isOptionsSignal(node, context, seen);
const isAbortRead = (node, context) => {
node = unwrapChain(node);
return (
node?.type === "MemberExpression" &&
propertyName(node) === "aborted" &&
isKnownSignal(node.object, context)
);
};
const isTrueLiteral = (node) => node?.type === "Literal" && node.value === true;
const positiveAbortGuard = (test, context) => {
if (isAbortRead(test, context)) return true;
if (
test?.type === "BinaryExpression" &&
(test.operator === "===" || test.operator === "==")
)
return (
(isAbortRead(test.left, context) && isTrueLiteral(test.right)) ||
(isTrueLiteral(test.left) && isAbortRead(test.right, context))
);
if (test?.type === "LogicalExpression" && test.operator === "&&")
return (
positiveAbortGuard(test.left, context) ||
positiveAbortGuard(test.right, context)
);
return false;
};
const isCanonicalConstructor = (node, context) => {
if (node?.type !== "Identifier") return false;
const definition = soleDefinition(findBinding(context.sourceCode, node));
return (
definition?.type === "ImportBinding" &&
definition.parent?.type === "ImportDeclaration" &&
coreModule.test(definition.parent.source.value) &&
definition.node.type === "ImportSpecifier" &&
definition.node.imported.name === "AnalysisCancelledError"
);
};
const hasExplicitCause = (node) => {
const options = node.arguments[1];
if (options !== undefined && options.type !== "ObjectExpression") return true; // Forwarded context is owned elsewhere.
if (options === undefined) return false;
return options.properties.some(
(property) =>
property.type === "SpreadElement" ||
(property.type === "Property" &&
(property.computed
? property.key.type !== "Literal" || property.key.value === "cause"
: (property.key.type === "Identifier"
? property.key.name
: property.key.value) === "cause")),
);
};
const inspectExpression = (expression, context) => {
if (expression?.type === "NewExpression") {
if (
isCanonicalConstructor(expression.callee, context) &&
!hasExplicitCause(expression)
)
context.report({
node: expression,
messageId: "missingCause",
});
return;
}
if (expression?.type === "CallExpression") {
// Common Result adapter: return err(new AnalysisCancelledError(...)).
for (const argument of expression.arguments)
inspectExpression(argument, context);
} else if (expression?.type === "AssignmentExpression") {
inspectExpression(expression.right, context);
}
};
const inspectBranch = (branch, context) => {
if (branch.type === "BlockStatement") {
for (const statement of branch.body) inspectBranch(statement, context);
} else if (branch.type === "VariableDeclaration") {
for (const declaration of branch.declarations)
inspectExpression(declaration.init, context);
} else if (
branch.type === "ThrowStatement" ||
branch.type === "ReturnStatement"
) {
inspectExpression(branch.argument, context);
} else if (branch.type === "ExpressionStatement") {
inspectExpression(branch.expression, context);
}
};
const cancellationCauseRule = {
meta: {
type: "problem",
docs: {
description:
"Preserve a directly checked caller AbortSignal reason in AnalysisCancelledError",
},
messages: {
missingCause:
"Pass the checked AbortSignal's reason as an explicit cancellation cause.",
},
},
create(context) {
return {
IfStatement(node) {
if (positiveAbortGuard(node.test, context))
inspectBranch(node.consequent, context);
},
};
},
};
export default cancellationCauseRule;
+19
View File
@@ -0,0 +1,19 @@
import noBufferDeepEquality from "./no-buffer-deep-equality.mjs";
import cancellationCause from "./cancellation-cause.mjs";
import noEvidenceJsonPreadmission from "./rules/no-evidence-json-preadmission.mjs";
import noFixtureRootCleanup from "./rules/no-fixture-root-cleanup.mjs";
import noPositionalToolContract from "./rules/no-positional-tool-contract.mjs";
import noSharedModuleMock from "./rules/no-shared-module-mock.mjs";
export default {
meta: { name: "rea" },
rules: {
"no-buffer-deep-equality": noBufferDeepEquality,
"cancellation-cause": cancellationCause,
"no-evidence-json-preadmission": noEvidenceJsonPreadmission,
"no-fixture-root-cleanup": noFixtureRootCleanup,
"no-positional-tool-contract": noPositionalToolContract,
"no-shared-module-mock": noSharedModuleMock,
},
};
@@ -0,0 +1,4 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const noBufferDeepEquality: Parameters<RuleTester["run"]>[1];
export default noBufferDeepEquality;
+166
View File
@@ -0,0 +1,166 @@
import { findBinding } from "./bindings.mjs";
const memberName = (node) =>
node?.type === "MemberExpression" &&
!node.computed &&
node.property.type === "Identifier"
? node.property.name
: null;
const unwrap = (node) => {
while (
node?.type === "TSAsExpression" ||
node?.type === "TSSatisfiesExpression" ||
node?.type === "TSNonNullExpression" ||
node?.type === "TSTypeAssertion" ||
node?.type === "ParenthesizedExpression"
)
node = node.expression;
return node;
};
const importedKind = (variable) => {
const definition = variable?.defs[0];
if (definition?.type !== "ImportBinding") return null;
const source = definition.parent?.source?.value;
const node = definition.node;
if (node?.type === "ImportNamespaceSpecifier") {
if (source === "node:fs/promises") return "fsPromises";
if (source === "node:fs") return "fs";
return null;
}
if (node?.type !== "ImportSpecifier") return null;
const imported = node.imported?.name ?? node.imported?.value;
if (source === "vitest" && imported === "expect") return "expect";
if (source === "node:buffer" && imported === "Buffer") return "Buffer";
if (source === "node:fs/promises" && imported === "readFile")
return "readFile";
if (source === "node:fs" && imported === "readFileSync")
return "readFileSync";
return null;
};
const constInitializer = (variable) => {
if (variable?.defs.length !== 1) return null;
const definition = variable.defs[0];
if (
definition?.type !== "Variable" ||
definition.node?.type !== "VariableDeclarator" ||
definition.parent?.kind !== "const" ||
variable.references.some(
(reference) => reference.isWrite() && !reference.init,
)
)
return null;
return definition.node.init;
};
/** Flag byte comparisons that make Vitest enumerate a Node Buffer. */
const noBufferDeepEquality = {
meta: {
type: "suggestion",
messages: {
nativeBytes:
"Compare Node Buffers with Buffer.equals or Buffer.compare; deep equality can enumerate every byte and exhaust memory.",
},
},
create(context) {
const { sourceCode } = context;
const symbolKind = (expression, seen = new Set()) => {
const node = unwrap(expression);
if (node?.type === "Identifier") {
const variable = findBinding(sourceCode, node);
if (
(variable === undefined || variable.defs.length === 0) &&
sourceCode.isGlobalReference(node)
)
return node.name === "Buffer" ? "Buffer" : null;
const imported = importedKind(variable);
if (imported !== null) return imported;
if (variable === undefined || seen.has(variable)) return null;
const initializer = constInitializer(variable);
if (initializer === null) return null;
seen.add(variable);
return symbolKind(initializer, seen);
}
const property = memberName(node);
if (property === null) return null;
const owner = symbolKind(node.object, seen);
if (owner === "fsPromises" && property === "readFile") return "readFile";
if (owner === "fs" && property === "readFileSync") return "readFileSync";
if (
owner === "Buffer" &&
["from", "alloc", "allocUnsafe", "allocUnsafeSlow", "concat"].includes(
property,
)
)
return "bufferFactory";
return null;
};
const isBuffer = (expression, seen = new Set(), awaited = false) => {
const node = unwrap(expression);
if (node?.type === "AwaitExpression")
return isBuffer(node.argument, seen, true);
if (node?.type === "Identifier") {
const variable = findBinding(sourceCode, node);
if (variable === undefined || seen.has(variable)) return false;
const initializer = constInitializer(variable);
if (initializer === null) return false;
seen.add(variable);
return isBuffer(initializer, seen, awaited);
}
if (node?.type !== "CallExpression" || node.optional) return false;
const kind = symbolKind(node.callee);
return (
kind === "bufferFactory" ||
((kind === "readFileSync" || (kind === "readFile" && awaited)) &&
node.arguments.length === 1 &&
node.arguments[0].type !== "SpreadElement")
);
};
const expectedCall = (object, settlement = null) => {
const node = unwrap(object);
const property = memberName(node);
if (property === "not") return expectedCall(node.object, settlement);
if (property === "resolves" || property === "rejects")
return expectedCall(node.object, property);
return node?.type === "CallExpression" &&
!node.optional &&
symbolKind(node.callee) === "expect"
? { call: node, settlement }
: null;
};
return {
CallExpression(node) {
if (node.optional) return;
const matcher = memberName(node.callee);
if (matcher !== "toEqual" && matcher !== "toStrictEqual") return;
const assertion = expectedCall(node.callee.object);
if (
assertion === null ||
![1, 2].includes(assertion.call.arguments.length) ||
assertion.call.arguments.some(
(argument) => argument.type === "SpreadElement",
) ||
node.arguments.length !== 1
)
return;
if (
(assertion.settlement !== "rejects" &&
isBuffer(
assertion.call.arguments[0],
new Set(),
assertion.settlement === "resolves",
)) ||
isBuffer(node.arguments[0])
)
context.report({
node: node.callee.property,
messageId: "nativeBytes",
});
},
};
},
};
export default noBufferDeepEquality;
+4
View File
@@ -0,0 +1,4 @@
export declare const resolveImportSource: (
filename: string,
specifier: unknown,
) => string | undefined;
+20
View File
@@ -0,0 +1,20 @@
import { dirname, isAbsolute, resolve } from "node:path";
import { fileURLToPath } from "node:url";
/** Resolve local import spellings without assigning local meaning to bare or HTTP imports. */
export const resolveImportSource = (filename, specifier) => {
if (typeof specifier !== "string") return undefined;
if (isAbsolute(specifier)) return resolve(specifier);
if (specifier.startsWith("file:")) {
try {
const url = new URL(specifier);
if (url.search !== "" || url.hash !== "") return undefined;
return fileURLToPath(url);
} catch {
return undefined;
}
}
if (specifier.startsWith("./") || specifier.startsWith("../"))
return resolve(dirname(filename), specifier);
return undefined;
};
@@ -0,0 +1,4 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,137 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { resolveImportSource } from "./import-source.mjs";
const domain = fileURLToPath(new URL("../../../src/domain/", import.meta.url));
const payloadFields = new Set(["parameters", "result", "rawResult"]);
const propertyName = (node) => {
if (!node.computed && node.property?.type === "Identifier")
return node.property.name;
if (
node.property?.type === "Literal" &&
typeof node.property.value === "string"
)
return node.property.value;
return undefined;
};
const fieldName = (node) => {
if (node.type !== "Property") return undefined;
if (!node.computed && node.key.type === "Identifier") return node.key.name;
if (node.key.type === "Literal" && typeof node.key.value === "string")
return node.key.value;
return undefined;
};
const importedBinding = (sourceCode, node) => {
if (node.type !== "Identifier") return undefined;
const variable = findBinding(sourceCode, node);
if (!variable || variable.defs.length !== 1) return undefined;
const definition = variable.defs[0];
return definition.type === "ImportBinding" ? definition : undefined;
};
const isImported = (context, node, module, names) => {
const member = node.type === "MemberExpression" && !node.optional;
const binding = importedBinding(
context.sourceCode,
member ? node.object : node,
);
if (!binding || binding.parent?.type !== "ImportDeclaration") return false;
const declaration = binding.parent;
if (declaration.importKind === "type" || binding.node.importKind === "type")
return false;
const specifier = declaration.source.value;
const target = resolveImportSource(
context.physicalFilename ?? context.filename,
specifier,
);
if (
target !== resolve(domain, `${module}.js`) &&
target !== resolve(domain, `${module}.ts`)
)
return false;
if (member)
return (
binding.node.type === "ImportNamespaceSpecifier" &&
names.has(propertyName(node))
);
if (binding.node.type !== "ImportSpecifier") return false;
return names.has(binding.node.imported.name ?? binding.node.imported.value);
};
const evidenceCreator = new Set(["createEvidence"]);
const genericJsonSchemas = new Set(["jsonValueSchema", "jsonObjectSchema"]);
export default {
meta: {
type: "suggestion",
docs: {
description:
"Leave ordinary Evidence JSON payload admission with createEvidence.",
},
schema: [],
messages: {
redundantJsonAdmission:
"Pass the {{field}} payload directly to createEvidence; it owns generic JSON admission. Keep semantic validation at its own boundary.",
},
},
create(context) {
return {
CallExpression(node) {
if (
node.optional ||
!isImported(context, node.callee, "evidence", evidenceCreator)
)
return;
const observation = node.arguments[2];
if (observation?.type !== "ObjectExpression") return;
for (const [index, property] of observation.properties.entries()) {
const field = fieldName(property);
if (
!payloadFields.has(field) ||
property.kind !== "init" ||
property.method
)
continue;
// A later spread or unknown key may replace this field before admission.
if (
observation.properties
.slice(index + 1)
.some(
(later) =>
later.type !== "Property" ||
fieldName(later) === undefined ||
fieldName(later) === field,
)
)
continue;
const parsed = property.value;
if (
parsed.type !== "CallExpression" ||
parsed.optional ||
parsed.arguments.length !== 1
)
continue;
const parse = parsed.callee;
if (
parse.type !== "MemberExpression" ||
parse.optional ||
propertyName(parse) !== "parse"
)
continue;
if (
isImported(context, parse.object, "jsonValue", genericJsonSchemas)
)
context.report({
node: parsed,
messageId: "redundantJsonAdmission",
data: { field },
});
}
},
};
},
};
@@ -0,0 +1,3 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,149 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { resolveImportSource } from "./import-source.mjs";
const root = fileURLToPath(new URL("../../../", import.meta.url));
const fixtureModule = resolve(root, "tests/fixtures/temporaryDirectory");
const workspaceModule = resolve(
root,
"tests/support/workspace/workspaceFixture",
);
const hooks = new Set(["afterEach", "afterAll", "onTestFinished"]);
const unwrap = (node) => {
while (
node &&
[
"AwaitExpression",
"TSAsExpression",
"TSNonNullExpression",
"ChainExpression",
].includes(node.type)
)
node = node.argument ?? node.expression;
return node;
};
const isFunction = (node) =>
node &&
[
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
].includes(node.type);
export default {
meta: {
type: "problem",
docs: {
description: "Let createTestTempDirectory own removal of its root.",
},
schema: [],
messages: {
duplicate:
"createTestTempDirectory already owns root cleanup; remove the duplicate teardown deletion, retaining cleanup of other resources.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const filename = context.physicalFilename ?? context.filename;
const calls = [];
const variable = (node) => findBinding(sourceCode, node);
const imported = (node) => {
const member = node?.type === "MemberExpression" ? node : undefined;
const binding = member ? member.object : node;
if (binding?.type !== "Identifier") return undefined;
const definition = variable(binding)?.defs[0];
if (
definition?.type !== "ImportBinding" ||
(member
? definition.node.type !== "ImportNamespaceSpecifier"
: definition.node.type !== "ImportSpecifier")
)
return undefined;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
if (typeof specifier !== "string") return undefined;
return {
name: member
? member.computed
? member.property.value
: member.property.name
: (definition.node.imported.name ?? definition.node.imported.value),
module:
resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
) ?? specifier,
};
};
const fixtureRoot = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (node?.type === "CallExpression") {
const identity = imported(node.callee);
return (
identity?.module === fixtureModule &&
identity.name === "createTestTempDirectory"
);
}
if (node?.type !== "Identifier") return false;
const binding = variable(node);
if (!binding || seen.has(binding)) return false;
seen.add(binding);
const definition = binding.defs[0];
// Mutable ownership and mixed root registries require flow analysis, not
// a guess based on the variable name or one assignment.
return (
definition?.type === "Variable" &&
definition.parent.kind === "const" &&
definition.node.id.type === "Identifier" &&
fixtureRoot(definition.node.init, seen)
);
};
const callback = (raw) => {
const node = unwrap(raw);
if (isFunction(node)) return node;
if (node?.type !== "Identifier") return undefined;
const definition = variable(node)?.defs[0];
if (definition?.type === "FunctionName") return definition.node;
if (
definition?.type === "Variable" &&
definition.parent.kind === "const"
) {
const value = unwrap(definition.node.init);
if (isFunction(value)) return value;
}
return undefined;
};
return {
CallExpression(node) {
calls.push(node);
},
"Program:exit"() {
const teardowns = new Set();
for (const call of calls) {
const identity = imported(call.callee);
if (identity?.module === "vitest" && hooks.has(identity.name)) {
const body = callback(call.arguments[0]);
if (body) teardowns.add(body);
}
}
for (const call of calls) {
const identity = imported(call.callee);
if (!identity) continue;
const removesRoot =
(identity.module === "node:fs/promises" &&
["rm", "rmdir"].includes(identity.name)) ||
(identity.module === "node:fs" &&
["rm", "rmSync", "rmdir", "rmdirSync"].includes(identity.name)) ||
(identity.module === workspaceModule &&
identity.name === "removeTestWorkspace");
if (!removesRoot || !fixtureRoot(call.arguments[0])) continue;
let parent = call.parent;
while (parent && !isFunction(parent)) parent = parent.parent;
if (teardowns.has(parent))
context.report({ node: call, messageId: "duplicate" });
}
},
};
},
};
@@ -0,0 +1,3 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,218 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { collectToolCatalogSources } from "./tool-catalog-sources.mjs";
import { resolveImportSource } from "./import-source.mjs";
const root = fileURLToPath(new URL("../../../", import.meta.url));
const catalogs = collectToolCatalogSources(
resolve(root, "src/contracts/toolContracts.ts"),
);
const unwrap = (node) => {
while (
node &&
[
"TSAsExpression",
"TSSatisfiesExpression",
"TSNonNullExpression",
"ChainExpression",
].includes(node.type)
)
node = node.expression;
return node;
};
const numeric = (node) =>
(node?.type === "Literal" &&
(typeof node.value === "number" ||
(typeof node.value === "string" &&
/^(?:0|[1-9][0-9]*)$/u.test(node.value)))) ||
(node?.type === "UnaryExpression" &&
["+", "-"].includes(node.operator) &&
node.argument.type === "Literal" &&
typeof node.argument.value === "number");
export default {
meta: {
type: "problem",
docs: {
description:
"Select canonical tool contracts by name, never catalog position.",
},
schema: [],
messages: {
positional:
"Select a named tool contract; catalog ordering must not select an operation or schema.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const filename = context.physicalFilename ?? context.filename;
const variable = (node) => findBinding(sourceCode, node);
const imported = (node) => {
if (node?.type !== "Identifier") return undefined;
const definition = variable(node)?.defs[0];
if (definition?.type !== "ImportBinding") return undefined;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
const path = resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
);
const expected = catalogs.get(path);
if (expected === undefined) return undefined;
return { definition, expected };
};
const isCatalog = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (!node) return false;
if (node.type === "Identifier") {
const binding = variable(node);
if (!binding || seen.has(binding)) return false;
seen.add(binding);
const identity = imported(node);
if (identity) {
const specifier = identity.definition.node;
return (
specifier.type === "ImportSpecifier" &&
identity.expected.has(
specifier.imported.name ?? specifier.imported.value,
)
);
}
const definition = binding.defs[0];
return (
definition?.type === "Variable" &&
definition.parent.kind === "const" &&
definition.node.id.type === "Identifier" &&
isCatalog(definition.node.init, seen)
);
}
if (node.type === "MemberExpression" || node.type === "TSQualifiedName") {
const object =
node.type === "MemberExpression" ? node.object : node.left;
const identity = imported(object);
if (identity?.definition.node.type !== "ImportNamespaceSpecifier")
return false;
const property =
node.type === "MemberExpression" ? node.property : node.right;
const name = node.computed ? property.value : property.name;
return identity.expected.has(name);
}
return false;
};
const selectedContract = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (!node) return undefined;
if (
node.type === "MemberExpression" &&
node.computed &&
numeric(node.property) &&
isCatalog(node.object)
)
return node;
if (node.type === "CallExpression") {
const callee = unwrap(node.callee);
if (
callee?.type === "MemberExpression" &&
(callee.computed ? callee.property.value : callee.property.name) ===
"at" &&
numeric(node.arguments[0]) &&
isCatalog(callee.object)
)
return node;
}
if (node.type !== "Identifier") return undefined;
const binding = variable(node);
if (!binding || seen.has(binding)) return undefined;
seen.add(binding);
const definition = binding.defs[0];
if (definition?.type !== "Variable" || definition.parent.kind !== "const")
return undefined;
if (definition.node.id.type === "Identifier")
return selectedContract(definition.node.init, seen);
if (
definition.node.id.type === "ArrayPattern" &&
definition.node.id.elements.some(
(element) =>
element?.type === "Identifier" && element.name === node.name,
) &&
isCatalog(definition.node.init)
)
return definition.node;
return undefined;
};
const bindingFields = new Set(["name", "inputSchema", "outputSchema"]);
const isRegistrationOptions = (node) => {
if (node?.type !== "Identifier") return false;
const definition = variable(node)?.defs[0];
if (
definition?.type !== "ImportBinding" ||
definition.node.type !== "ImportSpecifier"
)
return false;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
const path = resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
);
const name =
definition.node.imported.name ?? definition.node.imported.value;
return (
path === resolve(root, "src/server/toolRegistrationOptions") &&
[
"toolRegistrationOptions",
"applicationToolRegistrationOptions",
].includes(name)
);
};
const reported = new Set();
const reportSelection = (raw) => {
const node = selectedContract(raw);
if (node && !reported.has(node)) {
reported.add(node);
context.report({ node, messageId: "positional" });
}
};
return {
MemberExpression(node) {
const name = node.computed ? node.property.value : node.property.name;
const parent = node.parent;
const writesProperty =
(parent?.type === "AssignmentExpression" && parent.left === node) ||
parent?.type === "UpdateExpression" ||
(parent?.type === "UnaryExpression" && parent.operator === "delete");
if (bindingFields.has(name) && !writesProperty)
reportSelection(node.object);
},
CallExpression(node) {
if (isRegistrationOptions(unwrap(node.callee)))
reportSelection(node.arguments[0]);
},
VariableDeclarator(node) {
if (
node.id.type === "ObjectPattern" &&
node.id.properties.some(
(property) =>
property.type === "Property" &&
bindingFields.has(
property.computed ? property.key.value : property.key.name,
),
)
)
reportSelection(node.init);
},
TSIndexedAccessType(node) {
if (
node.objectType.type === "TSTypeQuery" &&
node.indexType.type === "TSLiteralType" &&
numeric(node.indexType.literal) &&
isCatalog(node.objectType.exprName)
)
context.report({ node, messageId: "positional" });
},
};
},
};
@@ -0,0 +1,4 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,116 @@
import { relative, isAbsolute } from "node:path";
import { fileURLToPath } from "node:url";
import { findBinding } from "../bindings.mjs";
import { projectForTest } from "../../lib/test-projects.mjs";
const repository = fileURLToPath(new URL("../../../", import.meta.url));
const mockMethods = new Set(["mock", "doMock"]);
const unwrap = (node) =>
node?.type === "ChainExpression" ? node.expression : node;
const propertyName = (member) => {
if (!member?.computed && member?.property?.type === "Identifier")
return member.property.name;
if (member?.computed && member.property?.type === "Literal")
return member.property.value;
return undefined;
};
const importedFromVitest = (sourceCode, identifier) => {
const binding = findBinding(sourceCode, identifier);
const definition = binding?.defs[0];
if (definition?.type !== "ImportBinding") return undefined;
const declaration = definition.parent ?? definition.node.parent;
if (declaration?.source?.value !== "vitest") return undefined;
return definition.node;
};
const isViImport = (sourceCode, identifier) => {
const specifier = importedFromVitest(sourceCode, identifier);
return (
specifier?.type === "ImportSpecifier" &&
(specifier.imported.name ?? specifier.imported.value) === "vi"
);
};
const isVitestNamespace = (sourceCode, identifier) =>
importedFromVitest(sourceCode, identifier)?.type ===
"ImportNamespaceSpecifier";
const isVitestViMember = (sourceCode, node) => {
const member = unwrap(node);
if (
member?.type !== "MemberExpression" ||
propertyName(member) !== "vi" ||
member.object?.type !== "Identifier"
)
return false;
return isVitestNamespace(sourceCode, member.object);
};
const isModuleMockCall = (sourceCode, callee) => {
const method = unwrap(callee);
if (
method?.type !== "MemberExpression" ||
!mockMethods.has(propertyName(method))
)
return false;
const receiver = unwrap(method.object);
return (
(receiver?.type === "Identifier" && isViImport(sourceCode, receiver)) ||
isVitestViMember(sourceCode, receiver)
);
};
const isolationForFile = (filename, cwd) => {
if (typeof filename !== "string" || !filename.endsWith(".test.ts"))
return undefined;
const testPath = relative(cwd, filename).split("\\").join("/");
if (
testPath === "" ||
isAbsolute(testPath) ||
testPath === ".." ||
testPath.startsWith("../")
)
return undefined;
try {
return projectForTest(testPath).isolate !== false;
} catch (error) {
if (
error instanceof Error &&
error.message.startsWith("No test project owns ")
)
return undefined;
throw error;
}
};
export default {
meta: {
type: "problem",
docs: {
description:
"Keep Vitest module mocks in projects with per-file module isolation.",
},
schema: [],
messages: {
shared:
"Vitest module mocks need an isolated test project; move the test to an isolated project or replace the module mock with a scoped spy or injected dependency.",
},
},
create(context) {
const filename = context.physicalFilename ?? context.filename;
const sourceCode = context.sourceCode;
const cwd =
context.cwd ?? context.getCwd?.() ?? sourceCode.getCwd?.() ?? repository;
if (isolationForFile(filename, cwd) !== false) return {};
return {
CallExpression(node) {
if (isModuleMockCall(sourceCode, node.callee))
context.report({ node, messageId: "shared" });
},
};
},
};
@@ -0,0 +1,4 @@
export declare const collectToolCatalogSources: (
entryPath: string,
readSource?: (path: string) => string,
) => ReadonlyMap<string, ReadonlySet<string>>;
+118
View File
@@ -0,0 +1,118 @@
import { parse } from "@babel/parser";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
const withoutExtension = (path) => path.replace(/\.(?:[cm]?[jt]s)$/u, "");
const arrayExpression = (raw) => {
let node = raw;
while (
node &&
["TSAsExpression", "TSSatisfiesExpression"].includes(node.type)
)
node = node.expression;
return node;
};
/** Read only source composition, never execute the runtime contract catalog. */
export const collectToolCatalogSources = (
entryPath,
readSource = (path) => readFileSync(path, "utf8"),
) => {
const catalogs = new Map();
const modules = new Map();
const active = new Set();
const complete = new Set();
const moduleFacts = (path) => {
let facts = modules.get(path);
if (facts) return facts;
// Read/parse errors intentionally escape: a broken inventory cannot disable
// the rule by presenting an empty set of catalog bindings.
const ast = parse(readSource(path), {
sourceType: "module",
plugins: ["typescript"],
});
const imports = new Map();
const exports = new Map();
const locals = new Map();
for (const statement of ast.program.body) {
if (statement.type === "ImportDeclaration") {
for (const specifier of statement.specifiers) {
if (specifier.type === "ImportSpecifier")
imports.set(specifier.local.name, {
source: statement.source.value,
name: specifier.imported.name ?? specifier.imported.value,
});
}
}
const declarations =
statement.type === "ExportNamedDeclaration"
? statement.declaration
: statement;
if (declarations?.type === "VariableDeclaration")
for (const declaration of declarations.declarations)
if (declaration.id.type === "Identifier") {
locals.set(declaration.id.name, declaration.init);
if (statement.type === "ExportNamedDeclaration")
exports.set(declaration.id.name, declaration.init);
}
}
facts = { imports, exports, locals };
modules.set(path, facts);
return facts;
};
const visit = (path, name) => {
const key = JSON.stringify([path, name]);
if (complete.has(key)) return;
if (active.has(key))
throw new Error(`Cyclic tool catalog composition: ${path}#${name}`);
active.add(key);
const facts = moduleFacts(path);
const array = arrayExpression(facts.exports.get(name));
if (array?.type !== "ArrayExpression")
throw new Error(`Expected exported tool catalog array: ${path}#${name}`);
const source = withoutExtension(path);
const names = catalogs.get(source) ?? new Set();
names.add(name);
catalogs.set(source, names);
const composition = (raw, localPath = new Set()) => {
const expression = arrayExpression(raw);
if (expression?.type === "ArrayExpression") {
for (const element of expression.elements)
if (element?.type === "SpreadElement")
composition(element.argument, localPath);
return;
}
// Calls such as HOPPER_MEMORY_TOOL_DEFINITIONS.map(official) construct
// entries locally; they are not imported catalog arrays. Do not execute
// or infer catalog ownership from arbitrary computed expressions.
if (expression?.type !== "Identifier") return;
const binding = facts.imports.get(expression.name);
if (binding) {
if (!binding.source.startsWith("."))
throw new Error(`Expected local catalog module: ${path}#${name}`);
visit(
`${withoutExtension(resolve(dirname(path), binding.source))}.ts`,
binding.name,
);
return;
}
if (localPath.has(expression.name))
throw new Error(
`Cyclic local catalog composition: ${path}#${expression.name}`,
);
if (!facts.locals.has(expression.name))
throw new Error(
`Unresolved local catalog spread: ${path}#${expression.name}`,
);
composition(
facts.locals.get(expression.name),
new Set([...localPath, expression.name]),
);
};
composition(array);
active.delete(key);
complete.add(key);
};
visit(entryPath, "TOOL_CONTRACTS");
return catalogs;
};
+3 -6
View File
@@ -15,6 +15,7 @@ import { tsImport } from "tsx/esm/api";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import { createAgentEvaluationCommands } from "./lib/agent-evaluation-commands.mjs";
import { assertAgentEvaluationReportPassed } from "./lib/agent-evaluation-report-gate.mjs";
import {
closeVerifierResources,
withVerifierCleanup,
@@ -44,7 +45,7 @@ const {
).href,
import.meta.url,
);
const { agentEvaluationPassed, summarizeFactualCorrectness } = await tsImport(
const { summarizeFactualCorrectness } = await tsImport(
new URL(
"../tests/evaluation/model-evals/AgentEvaluationReport.ts",
import.meta.url,
@@ -413,11 +414,7 @@ await withVerifierCleanup(
await writeFile(resolve(reportPath), encoded);
}
const failed = results.filter((result) => !agentEvaluationPassed(result));
if (failed.length > 0)
throw new Error(
`Agent routing/workflow/answer checks failed: ${failed.map(({ id }) => id).join(", ")}`,
);
assertAgentEvaluationReportPassed(results);
},
// Even a deliberately retained fixture directory must never retain auth.
[
+175 -2
View File
@@ -35,11 +35,27 @@ if (
!isAbsolute(strace ?? "")
)
throw new Error(
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld and strip must be available. No target is executed.",
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
);
const run = createVerifierRun();
const execute = promisify(execFile);
for (const command of ["gcc", "ld", "strip", "/usr/bin/prlimit", strace]) {
const ELF32_TOOLCHAINS = [
["i686-linux-gnu", "EM_386", "little"],
["arm-linux-gnueabi", "EM_ARM", "little"],
["mips-linux-gnu", "EM_MIPS", "big"],
["mipsel-linux-gnu", "EM_MIPS", "little"],
];
for (const command of [
"gcc",
"ld",
"strip",
"/usr/bin/prlimit",
strace,
...ELF32_TOOLCHAINS.flatMap(([prefix]) => [
`${prefix}-gcc`,
`${prefix}-readelf`,
]),
]) {
try {
await execute(command, [command === strace ? "-V" : "--version"], {
timeout: 10_000,
@@ -328,6 +344,163 @@ try {
cases++;
}
}
// ELF32 profiles: cross-compiled, never executed, checked against GNU readelf.
for (const [
prefix,
machine,
byteOrder,
kind,
flags,
] of ELF32_TOOLCHAINS.flatMap((toolchain) => [
[...toolchain, "executable", ["-fPIE", "-pie"]],
[...toolchain, "relocatable", ["-c", "-fPIC"]],
])) {
const path = join(root.path, `${prefix}-${kind}`);
await execute(`${prefix}-gcc`, ["-O1", ...flags, source, "-o", path], {
timeout: 30_000,
maxBuffer: 1024 * 1024,
});
const { stdout } = await execute(`${prefix}-readelf`, ["-SWsr", path], {
timeout: 10_000,
maxBuffer: 16 * 1024 * 1024,
env: { ...process.env, LC_ALL: "C" },
});
const expectedSections = [
...stdout.matchAll(
/^\s*\[\s*(\d+)\]\s+(\S+)\s+\S+\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+[0-9a-f]+\s+[A-Za-z]*\s+(\d+)\s+\d+\s+\d+\s*$/gm,
),
]
.filter(([, index]) => index !== "0")
.map(([, index, name, address, offset, size, link]) => ({
index: Number(index),
name,
address: BigInt(`0x${address}`),
offset: BigInt(`0x${offset}`),
size: BigInt(`0x${size}`),
link: Number(link),
}));
assert.ok(
expectedSections.length > 0,
`readelf listed no sections for ${path}`,
);
const expectedSymbols = [];
for (const [, name, count, body] of stdout.matchAll(
/^Symbol table '([^']+)' contains (\d+) entr(?:y|ies):\n((?:[^\n]+\n?)*)/gm,
)) {
const table = expectedSections.find((section) => section.name === name);
assert.notEqual(table, undefined, `readelf symbol table absent: ${name}`);
const rows = [...body.matchAll(/^\s*(\d+):\s+([0-9a-f]+)\s+(\d+)\s/gm)];
assert.equal(rows.length, Number(count), `readelf symbol count: ${name}`);
for (const [, index, value, size] of rows)
expectedSymbols.push({
table_index: table.index,
entry_index: Number(index),
value: BigInt(`0x${value}`),
size: BigInt(size),
});
}
assert.ok(
expectedSymbols.length > 0,
`readelf listed no symbols for ${path}`,
);
const expectedRelocations = [];
for (const [, name, count, body] of stdout.matchAll(
/^Relocation section '([^']+)' at offset 0x[0-9a-f]+ contains (\d+) entr(?:y|ies):\n((?:[^\n]+\n?)*)/gm,
)) {
const section = expectedSections.find((section) => section.name === name);
assert.notEqual(
section,
undefined,
`readelf relocation section absent: ${name}`,
);
const rows = [...body.matchAll(/^\s*([0-9a-f]+)\s+([0-9a-f]+)\s+\S+/gm)];
assert.equal(
rows.length,
Number(count),
`readelf relocation count: ${name}`,
);
for (const [entryIndex, [, offset, info]] of rows.entries()) {
// ELF32 r_info uses the low byte for type and the upper 24 bits for
// the symbol index. readelf has already decoded the file's byte order.
const decodedInfo = BigInt(`0x${info}`);
expectedRelocations.push({
section_index: section.index,
entry_index: entryIndex,
reported_offset: BigInt(`0x${offset}`),
type: Number(decodedInfo & 0xffn),
symbol_table_index: section.link,
symbol_index: Number(decodedInfo >> 8n),
});
}
}
assert.ok(
expectedRelocations.length > 0,
`readelf listed no relocations for ${path}`,
);
const bytes = await readFile(path);
for (const mode of ["cli", "mcp"]) {
const value = await inspect(mode, path);
assert.deepEqual(value.architecture, {
machine,
bits: 32,
byte_order: byteOrder,
});
assert.equal(
value.image_type,
kind === "executable" ? "ET_DYN" : "ET_REL",
);
for (const section of expectedSections) {
const reported = value.sections[section.index];
assert.equal(reported.name.display, section.name);
assert.equal(BigInt(reported.address), section.address);
assert.equal(BigInt(reported.offset), section.offset);
assert.equal(BigInt(reported.size), section.size);
}
assert.equal(value.sections.length, expectedSections.length + 1);
assert.deepEqual(
value.symbols.map(({ table_index, entry_index, value, size }) => ({
table_index,
entry_index,
value: BigInt(value),
size: BigInt(size),
})),
expectedSymbols,
`${mode} symbol values differ from readelf for ${path}`,
);
assert.deepEqual(
value.relocations.map(
({
section_index,
entry_index,
reported_offset,
type,
symbol_table_index,
symbol_index,
}) => ({
section_index,
entry_index,
reported_offset: BigInt(reported_offset),
type,
symbol_table_index,
symbol_index,
}),
),
expectedRelocations,
`${mode} relocations differ from readelf for ${path}`,
);
if (kind === "executable") {
assert.ok(value.segments.length > 0);
assert.equal(value.linkage.interpreters.length, 1);
assert.ok(
value.linkage.needed_libraries.some(
(library) => library.display === "libc.so.6",
),
);
}
assert.deepEqual(await readFile(path), bytes);
cases++;
}
}
const protectedReport = reports.get("protected");
// Ordinary data markers are not a request to discover/decompress a kernel config.
const markerBytes = Buffer.from(await readFile(join(root.path, "protected")));
Loaded 100 of 998 files, more files were not shown because too many files have changed in this diff. Show more