* refactor(contracts): derive the public tool catalog from one contract-group list
toolContracts.ts and toolFamilies.ts each imported and listed the same
contract groups. toolContracts.ts now owns one ordered group object;
TOOL_CONTRACTS is its flattened values in the same catalog order, and
toolFamilies.ts reads TOOL_CONTRACT_GROUPS from it instead of re-importing
every group. The static catalog-source lint rule learns the
Object.values(groups).flat() composition so positional-contract enforcement
still discovers every group without executing modules.
* fix(lint): reject computed tool catalog groups
Ghidra projected the bridge's invalid_regex_pattern code through an inline
copy of the refusal that RegexMatcher's invalidRegexPatternError already owns
for Kuna, Hopper and IDA. Ghidra now calls that owner, passing the bridge's
own message, which already names the pattern and Java's syntax error, as the
user message. The owner accepts error options so Ghidra keeps its cause and
diagnostics.
Problem: code that Ghidra auto-analysis never reaches, such as DOS MZ
handlers stored only in runtime-filled dispatch tables, has no Ghidra
function. procedure_* lookups then returned a plain procedure_not_found
whose remediation pointed only at list_procedures, and
resolve_containing_procedure returned not_in_procedure with no caveat.
Neither outcome named the supported recovery: declaring the entry as a
function seed in REA_GHIDRA_SEED_FILE.
Fix: one Ghidra-owned recovery, ghidraFunctionSeedRecovery(source), names
the seed mechanism without claiming that the address holds code. It
follows the session source, because seeds apply only to a fresh import:
selected projects and owned checkpoints name the setting to unset first.
The procedure_not_found remediation appends it. A not_in_procedure
resolution now carries ghidraNotInProcedureLimitation, attached through
the same absence-caveat path as the empty-reference caveats and read from
the domain containing-procedure parser. outside_segments and found
results are unchanged. The installation guide's seed section says where
the recovery appears.
Validation: src/ghidra/GhidraProvider.behavior.test.ts fails on the base
(4 failed, 291 passed: both procedure_not_found rows lack the setting,
the selected-project row lacks the fresh-import seed recovery, and
not_in_procedure has no seed limitation) and passes after the fix
(295 passed). npm run check:fast passes. Real Ghidra, including seeding
real-mode DOS linear addresses, was not exercised.
Refs morluto/rea#3767
* refactor(ghidra): drop the unreleased checkpoint v1 reader and a one-time pattern proof
The Ghidra checkpoint manifest v1 shape and its upgrade to v2 arrived in the
same commit (c07ab4b994, first released in 6.9.0), and every release writes
v2, so no released build produced v1. A v1 manifest is now refused as
malformed and left unchanged, like any other unknown manifest.
portableSchemaPatterns.test.ts proved two advertised patterns equal the
look-ahead regexes they replaced by enumerating 90,000 strings. The lasting
invariant (no look-around in advertised patterns) is asserted catalog-wide by
advertisedPatternsVMode.test.ts for the default and full input profiles. The
current accept/refuse contract for dylib roots and the reserved process
environment name moves to the owning schema tests as small tables.
Refs #10515
* test(process): retain malformed environment name coverage
The doctor cancellation fixture wrote its readiness JSON with
writeFileSync, which truncates and then writes. The test polls only for
the file's existence, so a read between the two steps parsed an empty
file and failed with "Unexpected end of JSON input" (main run
43604752864, PR run 43606296871).
Write to a pending path and rename it into place, as ghidraJavaDoctor and
darwinProcessRunTokenReader fixtures already do. Apply the same fix to the
two sibling fixtures that poll for a PID file's existence and then parse
it: jadxConfiguration and runnerCancellation, where an empty read becomes
PID 0.
* fix(ghidra): commit analysis profiles to the bridge script sources
Snapshot reuse for Ghidra depends on the analysis profile digest, but the
profile committed only to hand-bumped semantic markers. A bridge change that
forgot to bump its marker left earlier snapshots admissible (#3664).
Add ghidraBridgeSourcesDigest, a SHA-256 over every packaged bridge source by
name, and commit it as bridge_sources_sha256 in every Ghidra profile, for new
imports and existing projects alike. A changed script now changes the profile
digest without anyone remembering a marker. Existing snapshots are not reused
once, after upgrading.
GhidraRawProfileDigest.test.ts pinned literal digests that every bridge or
marker change had to re-record; it now checks the committed parameters and
that the digest is the profile owner's digest of them. A new test checks that
the source list covers every packaged .java file.
* fix(ghidra): frame bridge digest metadata as a tuple
* fix(cli): validate plist and dylib-root options with their contract fields
inspect-plist --relative-path and trace-dylib-resolution --root were
declared as bare strings on the CLI. --schema under-advertised them, and an
empty or NUL value reached the operation contract, whose refusal named the
contract field (path, roots) instead of the CLI option.
Declare both options with the contract's own field schemas
(localPathStringSchema; dylibResolutionInputSchema.shape.roots), so the CLI
refuses them first and names --relative-path or --root.
Fixes#9764Fixes#9823
* fix(mcp): observe SDK initiated cleanup failures
Configuration roots read from XDG_CONFIG_HOME, VSCODE_APPDATA and
VSCODE_PORTABLE now come from one module whose functions reproduce a named
reader: xdg-basedir (OpenCode, Kilo), the XDG rule used by Rust dirs and
etcetera (Zed, Goose), and VS Code's getUserDataPath. mcp add's ad hoc
platformPaths() is removed. Zed no longer takes a relative XDG_CONFIG_HOME
that Zed ignores, and the Cline extension and Kilo's VS Code marker follow
VS Code's portable and VSCODE_APPDATA selection.
BinaryTargetResolver kept a private copy of StableFileHash's descriptor read
loop because it must hash the same descriptor it classified, admitted with
symlinks rejected, while hashStableFile opens its own descriptor and follows
symlinks. The loop is now hashStableRegularFileDescriptor, which takes the
admitted descriptor and the caller's symlink setting; readStableFile and the
resolver both use it. The resolver keeps symlinks: reject and maps a change
to its existing BinaryTargetError and cancellation to AnalysisCancelledError.
* fix(setup): name client configuration syntax from one reader table
Each client format now maps to one reader description (syntax, strict or
JSONC, BOM and empty-file handling, strict-JSON repair). The document
parser, the strict-JSON rewrite decision and uninstall's diagnostic all
read it, so uninstall no longer re-derives a syntax name that called
Goose's YAML and JSONC-admitting clients "JSON".
* test(hopper): retry cleanup after confirmed application exit
* feat(errors): type refusals at the producer with a total recovery
A resource refusal without authored text fell back to resource-kind advice:
a fixed parser ceiling reported as memory told the caller to add host
memory, an upstream IDA reply ceiling pointed at REA's MCP transport, and a
fixed Kuna text ceiling reported as capacity was marked retryable. An
ambiguous selector was reported as invalid input that pointed at the schema.
Add AnalysisRefusal with three variants built from facts producers already
own: fixed_ceiling (FixedAllowance, now with limit, unit and narrowing),
adjustable_budget (AnalysisSettingLimit) and ambiguous_selector (new
AnalysisInputError option). Each variant is attributed to the error's
operation and projected under details.refusal. Presentation is a total
switch over the variants, and authored text still takes precedence.
Migrate the first producers. GraphQL nesting, Playwright trace input, the
Kuna function text and the IDA upstream reply become fixed ceilings. jadx
overloads and Kuna ambiguous names become ambiguous selectors, replacing the
two Kuna-specific ambiguity texts. Seven occupied admissions declare
measuredExhaustion. The rea/typed-resource-refusal lint rule rejects new
untyped resource constraints, and the remaining 24 are counted in a baseline
that can only shrink.
* fix(errors): honor refusal recovery ownership
* refactor(parsers): report format failures through one ParseFailure algebra
Add ParseFailure (malformed, truncated, unsupported, limit, with format,
offset and field) and its one carrier, ParseFailureError, in the domain.
BinaryReader now names truncation and the field it read, and its native
format and resource failures are ParseFailureError subclasses.
Providers projected a format failure with their own instanceof ladder,
and some dropped the selected path from the invalid_format message.
parseFailureAnalysisError now owns that projection for the native,
Go, devicetree, V8, Windows Shortcut and Electron fuse providers.
The V8 code cache, Windows Shortcut and Electron fuse format classes
are removed in favour of the algebra.
The architecture guard counts raw Buffer and DataView reads, plain
Error throws in parser folders and plain JSON.parse of parser input
against baselines that can only shrink.
* fix(parsers): retain malformed range offsets
* fix(hopper): name the unconfirmed shutdown step in retained cleanup
When close_binary retains a Hopper document, the cleanup error lists the
retained resources but not why shutdown went unconfirmed. On Linux CI,
hopperClient.handshakeRecovery "recovers an application exit after the
first valid handshake occurs during cleanup" fails intermittently. The
vitest blob reports show only hopper-document and the runtime root
retained with authenticated_application "readable". From those facts we
cannot tell whether the shutdown request was not sent, failed, or was
sent while the application was still observed as present.
Record the shutdown request outcome (method, status, error tag and
provider state, or why it was not sent) and the final exit observation
of the authenticated application in the retained-document diagnostics.
Exit observation now reports a positively observed Linux exit in
progress (PF_EXITING before state Z) as "exiting", separate from
"present". Neither confirms exit. The flaky test asserts the projected
error, so a later failure prints these diagnostics in the CI log.
Refs #10556
* fix(hopper): retain fallback shutdown diagnostics
* test(hopper): retry cleanup after confirmed application exit
TOOL_EFFECT_AUDIT was typed Record<string, EffectAudit>, so a tool missing
from the audit or a misspelled entry was found only by a runtime throw or
the key-equality unit test.
Check the audit with satisfies Record<PublicToolName, EffectAudit>, where
PublicToolName is the union of TOOL_CONTRACTS names. A missing, extra or
misspelled entry is now a compile error. String lookups for callers that
hold an unchecked name keep their runtime refusal.
Delete the unit test that compared audit keys with contract names; the
type now owns that check.
Table-backed add-mcp agents now project their client-table row instead of
redeclaring format, display name and profile in a second table. The row
also carries add-mcp aliases and project documents. mcp add now applies
the read-scope rules setup already enforced: Gemini settings policy blocks,
the Claude Desktop MSIX write blocker and MSIX AppData redirection.
* refactor(javascript): classify implicit execution in one owner
Add javascriptSemanticExecutionPoints, a total classification of every
runtime Babel node type into the ways its evaluation can run code without
call syntax. The member-mutation escape walk, the accessor operand
collector and the catch-source reachability check consult it instead of
their own syntax lists. Kinds those collectors have never applied are now
named in exhaustive switches rather than absent.
A guard test fails when the installed @babel/types defines a node type the
table does not classify, and unknown node types classify as unsupported
syntax rather than inert.
* test(hopper): retry cleanup after confirmed application exit
* fix(javascript): classify enumeration and catch binding effects
* fix(javascript): retain catch uncertainty for identifier reads
A cancelled GDB startup whose process stop could not be confirmed retained a
capability error. The next cleanup retry then reported "This analysis is
unavailable for the current target" instead of the caller's cancellation.
Move Rizin's retention rule (the #5963 fix) into one shared
retainedDebuggerCleanupFailure in src/process/ownedDebuggerSession.ts and use
it from both session managers. The GDB command-cancel path now retains the
cancellation itself, so retry reports no longer depend on Set insertion order.
Fifteen domain schema files each declared z.string().min(1), eight of them
as boundedTextSchema although it bounds nothing. They now share
nonEmptyTextSchema from src/domain/textSchemas.ts; the one trimming copy in
reconstructionCoverage.ts is renamed trimmedTextSchema.
Three private UTF-8 prefix truncators (gdb recent MI records, rizin
diagnostics, environment replay) and src/process/boundedUtf8Text.ts are
replaced by utf8HeadExcerpt in src/domain/utf8HeadExcerpt.ts, which encodes
only the retained prefix and returns a slice of the source text.
The fake application now leaves a marker when its children never report
ready, and the tests' session requests throw on it, so capture releases the
children and the test fails at once instead of waiting for its deadline.
Refs #10366
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Doctor reads transport, falling back to type, and reports a remote http/sse or
url-only entry as unknown rather than invalid; only a non-text tag is
invalid.
Refs #7650
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
README.md's packet-summary row says tshark/capinfos support is
Linux-verified, but sixteen translations omitted that caveat.
Fixes#7655
Supersedes #7972
The translated phrases are from #7972, reapplied to the moved pcap
guide links; simplified Chinese, which #7972 did not cover, follows the
traditional Chinese wording.
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Use createNodeDiscoveryTempDirectory for the target-location and
runtime-observation fixtures too, so a TMPDIR containing `_` cannot make
them fail for the same reason as the cancellation test.
Refs #9996
The sibling sites are the ones #4484 switched.
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The readiness wait from the previous commit continued after its bound
even when a child had not installed its TERM trap, so a slow runner
would reintroduce the race silently. It now exits with an error after
five seconds, and the state line keeps its original escaping.
Refs #10366
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The changed-prefix regression rewrote its fixture at the same size within
milliseconds of creating it. Linux stamps mtime/ctime from a coarse clock,
so both writes can share every stat field the target guard compares, and the
classifier parsed the rewritten header without seeing a change. Age the
fixture's timestamps before the mutation so the rewrite is observable on any
filesystem while the test still requires a changed-input refusal.
Fixes#8685
Supersedes #8689
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The client configuration reference had no entries for three clients that
the installation guide lists as supported and sends readers here for.
Add a section for each naming the configuration file setup writes, the
mcpServers.rea entry key, the entry fields, and the shared skill root, as
defined in src/application/SupportedClients.ts and
src/application/ClientConfigurationDocument.ts.
Fixes#7650
Supersedes #7750
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The parser's work budget was a fixed 64 Mi units, so a real 26 MB .NET 8
NativeAOT image (bflat 8.0.2) stopped frozen-string recovery at 4,433 of
6,685 strings and reported partial. Give each image a base budget plus 8
units per admitted source byte, bounded by the source-byte ceiling, and
record the scaling in the analysis profile (contract revision v11). With it,
that image completes with all 6,685 frozen strings using 71.6 M units.
Only the budget scaling of #4559 is taken: main already resolves the
base-type closure in linear time.
Fixes#4445
Supersedes #4559
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
RFC 6891 makes a second OPT record a format error. Reading every OPT record
let a later one with extended RCODE 0 clear an earlier nonzero one, so an
error response could attribute its addresses.
Refs #10211
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
A retroactive protocol conformance sets flag bit 0x40. The static
witness check admitted only the type-reference kind bits (0x38), so a
retroactive conformance lost its static witness table. The admitted
mask is now 0x78; conditional requirements, resilient witnesses and
generic witness tables stay unadmitted.
Fixes#7445
Only the mask change and its tests are taken from #7489.
Supersedes #7489
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
An XML plist whose DOCTYPE ends in an empty internal subset ("[]",
optionally with whitespace) declares nothing that could expand, but the
preflight refused it as internal entity expansion. Interface Builder
bundles then omitted the nib and blamed aggregate decode budget
exhaustion. Skip whitespace-only subsets; any declaration still refuses.
Fixes#8407
Supersedes #8438
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The nesting guard ran on a child visit of GgufHeader#value, so an empty
innermost array never produced one and a 33rd frame parsed as success
while the reported limit stayed maximum_array_frames: 32. Charge the
frame when entering the ARRAY branch instead, so the bound counts the
advertised unit and no longer depends on a scalar child that is not a
frame.
Fixes#7312
Supersedes #7698
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
inspect-native-exports treated any nonzero byte in .dynsym entry 0 as a
malformed table. That is right when the reserved slot holds a real symbol,
which was #7389, but some Android arm64 libraries set only st_name. Those
files were rejected before any export could be read.
Skip that entry, keep the inventory complete, and record the nonzero
st_name in limitations. Any other nonzero field still fails the table.
Fixes#9700
Supersedes #9752
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Charge the manifest split facts against the native inventory's retained JSON
allowance and refuse more than 64 split types or 256-character names as an
unknown, so a crafted manifest cannot overflow the response. Reports saved
before split facts existed still parse: the report schema treats absent
facts as optional while the live bridge contract requires them. The JADX
regression now decodes a generated binary manifest through
nativeDeclarations, with named and resource-ID-only attributes.
Refs #9756
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
A base APK whose manifest requires ABI or density splits keeps its native
libraries in those splits, yet inspect_android_jni_bindings reported a
complete library inventory with zero libraries. The bridge's native
declaration inventory now also decodes the root binary manifest without a
session and reports its requiredSplitTypes and isSplitRequired facts, or why
they are unknown (bridge identity 11). When splits are required or unknown,
the library inventory is partial with an unknown that says so, and a binding
without a candidate is unknown instead of unmatched.
The binary manifest fixture from #9769 was used to check the bridge with the
real jadx-headless-mcp 0.7.1 JAR, including stripped attribute names that
resolve through framework resource IDs.
Fixes#9756
Co-Authored-By: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-Authored-By: Phon <twilyt1@proton.me>
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Setup's result is agent-only, so incur hides it when stdout is a terminal and
no format was chosen. Only an interactive setup or a fully interactive TTY dry
run rendered a summary, so `rea setup --yes` printed nothing. Render the dry
run plan or the existing result summary under incur's own hidden-output
condition instead.
Fixes#10531
Supersedes #2185
Co-Authored-By: balkan16 <57933796+balkan16@users.noreply.github.com>
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
JSON.parse keeps only the last value for a repeated name, so an ASAR header
that declared a path twice listed one member, hid the other and still
reported complete coverage.
Add findDuplicateJsonMember, an iterative scan of validated JSON text that
does not recurse, so the deeply nested headers REA already admits stay
readable. readAsarHeader now refuses a repeated member name as a malformed
index, the same way the listing refuses unsafe path components.
trace_native_values ended at a provider-classified thunk whose model
holds only a synthetic return (an external import) or borrows its
target's operations, without saying the target was never examined, so
a stub's small model read as the end of the call chain. Thunk targets
are now reconciled after traversal: one another path traced, attempted
or queued is left to that path, and the rest are reported in unknowns
with the target in a new optional related_procedure field.
Fixes#8476
Supersedes #8678
Based on #8678 and rebased onto main's separate call-binding module.
related_procedure is exactOptional and set only on the thunk unknown,
so other unknowns and retained traces keep their shape.
Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>