Commit Graph
4972 Commits
Author SHA1 Message Date
morluto fe05a4c56d refactor(verify): release five verifier roots through VerifierFixture after their clients confirm (#11118)
* refactor(verify): release five verifier roots through VerifierFixture after their clients confirm

* fix(verify): keep the binary-layout and EVM roots until their auxiliary MCP clients confirm

* fix(verify): carry the retained root on each migrated verifier's final failure
2026-11-15 21:07:13 +08:00
44d359bd05 fix(javascript): omit effects refused by host construction (#11122)
Filter child-process, fetch/WebSocket and TLS effects for literal calls
the host refuses to construct. Share copied child options across validation
and projection, preserving uncertainty from own and inherited getters.
Includes the original implementations from #10528, #10529 and #10530.

Validation: 394 domain/public CLI/MCP tests, then 54 request/public tests after the
review correction, static checks and discovery passed. Also verified 29
Node 24.22.1 probes and actual Chromium 156 header/Request constructors.
Full browser app/network behavior was not tested. Analysis does not execute
target source.

Fixes #10522
Fixes #10523
Fixes #10524
Related: #10528, #10529, #10530

Co-authored-by: RRFZ <247457381+Plastic-time@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-15 21:05:27 +08:00
morluto 6363881faa feat(artifacts): name the integrity check for target digests, Apple bundle readers and extraction (#11125) 2026-11-15 21:05:00 +08:00
morluto 28d28b0578 refactor(setup): take the platform in doctor, setup, uninstall and update maintenance (#11082)
* refactor(setup): take the platform in doctor, setup, uninstall and update maintenance

* test(setup): cover a caller-selected platform in update maintenance
2026-11-15 20:53:25 +08:00
morluto 5d4e9b8532 refactor(android): take the host in JADX configuration, doctor and workspace roots (#11086)
* refactor(android): take the host in JADX configuration, doctor and workspace roots

* fix(android): pass the host in verifiers, follow it for JAVA_HOME, and test the native cache split

* test(platform): count tests that redefine process.platform or process.arch (#11102)

Tests select a host by injection. A redefined process.platform stops exercising its branch once the module under test reads HOST_PLATFORM, and still passes. A counted baseline (105 sites in 20 files) may only shrink; each module migration switches its tests to injection.
2026-11-15 20:52:58 +08:00
morluto eab8abe948 feat(evidence): name the constraint each comparison result refinement checks (#11100)
* feat(evidence): name the constraint each comparison result refinement checks

* fix(evidence): name only comparison constraints that reach a public error
2026-11-15 20:52:17 +08:00
morluto 8110c98c72 fix(process): give process-command failures their own recovery instead of doctor (#11101)
* fix(process): give process-command failures their own recovery instead of doctor

* test(process): check the delivered process-command recovery instead of banning a word
2026-11-15 20:50:38 +08:00
morluto 2a640c198c test(providers): assert native timeout, plist allowance and Cutter limit facts by field (#11104) 2026-11-15 20:49:53 +08:00
morluto 39537b95af refactor(reverse): let objdump and Rizin own their Evidence limitations and name the provider a deadline bounded (#11113) 2026-11-15 20:49:24 +08:00
morluto bf638b47f2 fix(native): keep a completed LLDB trace when the deadline elapses during cleanup (#11116)
observe_native_calls no longer reports provider_timeout when LLDB exited
within its deadline and the deadline elapsed during process cleanup. The
completed trace is returned; unconfirmed cleanup is reported as
cleanup_incomplete with the trace as its partial observation.
2026-11-15 20:48:48 +08:00
morluto a8e472967b refactor(process): read Ghidra and Hopper replies through one bounded JSON-line reader (#10699)
* refactor(process): read Ghidra and Hopper replies through one bounded JSON-line reader

Both clients now share JsonLineBridgeReader: UTF-8 framing with a mandatory
reply line ceiling (bridge.max_reply_bytes, REA_BRIDGE_MAX_REPLY_BYTES,
default 256 MiB), one fatal UTF-8 policy, request-id correlation including
rejections on the reserved id 0, and typed stream failures. An oversized
reply is refused as a transport resource constraint naming the setting and
its bytes are discarded. Hopper rejections on id 0 now reach the request on
the wire instead of being dropped.

Deletes GhidraResponseRouter and HopperResponseStream.

* fix(process): bound each bridge reply by the ceiling its own request admitted

* fix(process): admit the bridge reply ceiling before launching Hopper or Ghidra

* fix(ghidra): keep a refused setting's configuration error through the provider boundary

* fix(process): admit both bridge ceilings per request as a typed configuration refusal

A malformed REA_BRIDGE_MAX_REQUEST_BYTES or REA_BRIDGE_MAX_REPLY_BYTES seed covered by a runtime override at startup is refused by the runtime view once the override is reset. Hopper and Ghidra read those getters in the request path (and Hopper re-read the request ceiling when a queued request reached the wire, outside the caller's context), so the refusal was thrown instead of returned. Both clients now copy both ceilings once at admission through readRuntimeSetting, return the ConfigurationError (Ghidra as an admission failure), and write queued requests under the ceilings they were admitted with.

* refactor(config): read rizin, gdb, Kuna, Ghidra and regex settings through readRuntimeSetting (#10795)

Each site caught the ConfigurationError a setting view throws for a malformed launch seed by hand. They now read through readRuntimeSetting and return its Result. GDB reads both debugger limits once per console command and reuses those accepted values for its limitations, timeout and diagnostics instead of re-reading the views after the command ran. Kuna's single-function read returns the refusal after releasing admission instead of throwing it. A counted architecture guard keeps the remaining hand-caught sites from growing.

* refactor(hopper): return typed bridge replies from HopperClient.callTool (#10833)

* refactor(hopper): return typed bridge replies from HopperClient.callTool

The reply contract table existed, but the provider, caller reconciliation and
regex search each applied it after callTool returned JsonValue. callTool is now
typed by method: it validates the reply against that method's contract and
returns the typed reply, or a HopperReplyContractError that names the method
and keeps the raw reply. The caller scan keeps that raw reply as its unread
page or native result. The provider dispatches list_names, file offsets and
the three reconciled operations through their literal methods, and health has
its own contract. Transport tests inject a JSON-value table for the fake
bridge's own methods.

* fix(hopper): refuse value-changing reply contracts and uninstalled methods

parseHopperReply returns the raw reply, so a contract whose input and output types differ (a coercion, default or transform) would promise a type the reply does not hold. ValueKeepingContracts refuses such a schema at compile time, for the production table and any injected one. A client whose type argument names a table it was not given now returns AnalysisCapabilityUnavailableError for a method with no installed contract instead of throwing.

* refactor(hopper): read the startup handshake through the health reply contract (#10853)

parseHopperServerInfo hand-checked each health field that hopperReplySchemas.health already defines. It now parses through that contract, keeps the zod failure as the protocol error's cause, and binds run_id to the launch.

* refactor(kuna): read the project deadline through readRuntimeSetting at each request (#10837)

* refactor(kuna): read the project deadline through readRuntimeSetting at each request

The string, call-graph and xref scans read kuna.project_timeout_ms inside the background producer they start, and the project export read it inside the shared startup promise. A malformed launch seed without an override was thrown from there and reached the caller only through the generic failure mapper; a concurrent waiter that swallows the startup promise's rejection never saw it. Each site now reads the deadline with readRuntimeSetting before starting its work and returns the refusal as that request's value; the export start returns it to each caller that needs the project, and the next request reads it again.

* fix(kuna): return a call-graph deadline refusal from analyze_function

The dossier reported any relationship failure as unavailable call coverage. A call-graph deadline refusal is the request's own setting failure, so it now returns as that ConfigurationError, as cleanup and cancellation already did.

* refactor(kuna): require the accepted project deadline on every export request

Remove acceptedExportDeadline's fallback read of the throwing configuration view. KunaProjectRequest and KunaExportRequest now require the deadline the starting operation accepted; the session passes the one it admitted through readRuntimeSetting into #lookupOrBuild. Direct cache callers in tests take it from one shared helper, withKunaProjectDeadline.

* test(mcp): carry main's Evidence-format const count (#10995) beside the bridge reply setting's

* fix(process): clean Hopper and Ghidra sessions up under the ceilings their start admitted

Cleanup requests re-admitted bridge.max_request_bytes and bridge.max_reply_bytes from the current settings. Once an override covering a malformed launch seed was reset, Hopper's shutdown_document and Ghidra's closing checkpoint_program and shutdown were refused before writing, leaving Hopper's document and files or Ghidra's JVM behind. Each session now keeps the ceilings it admitted at start, and its cleanup requests use them.

* fix(hopper): reauthenticate for cleanup under the session's admitted ceilings

The cleanup reconnect's health handshake still re-admitted the ceilings from the current settings, so a seed refused after an override reset blocked reauthentication and the shutdown after it. It now uses the same session ceilings as the shutdown requests, through one #cleanupLimits accessor.

* refactor(process): report an NDJSON line above its ceiling as a limit parse failure

Utf8NdjsonOversizeError now extends ParseFailureError<"limit"> (format ndjson, limit ndjson-line-bytes) and keeps the observed bytes the transport error reports, so parseFailureFromCause recognizes it like every other byte-format budget.
2026-11-15 20:37:21 +08:00
morluto 781f89e6f4 refactor(cli): project observe-native-calls onto a session target and its contract document (#10700)
* refactor(cli): project observe-native-calls onto a session target and its contract document

* refactor(cli): project endpoint comparison and browser scenario documents with delivery options (#10711)

* refactor(cli): carry document command delivery as options and project endpoint and browser scenario commands

A JSON-document projection can now take the contract's detail and view
fields as --detail, --evidence_output and --view beside the document.
The document refuses those fields with the option to use, and the
delivery is admitted by the contract's own view schema before the
document is read.

compare-static-and-observed-endpoints, capture-browser-scenario and
validate-browser-scenario now register from their contracts; validation
projects the capture contract without detail.

* fix(cli): admit the endpoint comparison document before reading its Evidence bundle

The document command context carries the document's transport frame, so
a handler that reads an Evidence bundle first can refuse a malformed
document beforehand, as the hand-written command did.

* refactor(cli): pass only object documents to the document field refusal

* refactor(cli): project inspect-artifact and extract-artifact with open_binary target options (#10722)

* refactor(cli): project inspect-artifact and extract-artifact with open_binary target options

A declared session target can now take fields of the contract an MCP
client opens the target with as options. They are admitted by that
contract, with the target as its path, before the target is opened, and
the projection can narrow an enum to the values the command supports.

inspect-artifact and extract-artifact are derived from their contracts
with open_binary's format and ESP flash fields. MCP open_binary and the
CLI share one mapping from those fields to the target selection.
extract-artifact now runs under the command's cancellation signal.

* fix(cli): read open_binary from its session contract so startup stays lazy

* build(lint): refuse value imports of the complete tool catalog in CLI startup modules
2026-11-15 20:36:57 +08:00
morluto e97b6c5282 refactor(ci): plan change scopes through an injectable Git owner (#10804)
* refactor(ci): plan change scopes through an injectable Git owner

ciChangeScope.test.ts spent about five processes per case (copy the seed
repository, git add, commit and rev-parse, then node plan.mjs with its own
merge-base and diff) to ask which lanes a path selects. Move the planner
into change-plan.mjs as planChanges(request, repository), keep plan.mjs as
the workflow entrypoint over gitRepository(), and answer the path and
package-script tables from an in-memory commit. History, renames,
deletions, push baselines, malformed metadata and GITHUB_OUTPUT keep the
Git fixture or the spawned entrypoint.

378 tests: 30.6 s elapsed and 32.6 s CPU before, 4.0 s and 4.0 s after.

* test(ci): cover workflow environment wiring through the plan entrypoint
2026-11-15 20:36:28 +08:00
morluto 483f53fdef fix(electron): refuse captures handed off to an already-running app (#10989)
* fix(electron): refuse captures handed off to an already-running app

When another instance holds the app's single-instance lock, Electron forwards the launch and its arguments to that instance. The active hook now stops the launched app at the refused requestSingleInstanceLock call and reports the refusal in its snapshot; the provider fails the capture with application_already_running before driving it.

* fix(electron): stop pending actions on a late lock refusal and keep the final snapshot

* docs(electron): describe late single-instance refusals during actions

* test(electron): join the late-refusal capture even when the test fails
2026-11-15 20:35:48 +08:00
morluto 7134053d27 feat(artifacts): name integrity checks for DMG, directory and release-exposure readers (#11056)
* feat(artifacts): name integrity checks for DMG, directory and release-exposure readers

Directory members and release-exposure members whose file identity or
bytes changed since inventory report member-identity; a directory entry
without captured state reports entry-metadata and a changed directory root
container-identity; an hdiutil checksum failure reports image-checksum.
AnalysisArtifactChangedError carries the check from the reader failure and
projects it as details.integrity_check.

* fix(artifacts): name missing directory entry state and keep integrity checks through error copies

* feat(artifacts): name integrity checks for ZIP, PyInstaller and changed release files (#11081)

feat(artifacts): name integrity checks for ZIP, PyInstaller and changed release files, and keep them through reader projections

* feat(artifacts): name integrity checks for the remaining readers and their projections (#11098)
2026-11-15 20:35:28 +08:00
morluto 5b5ca68bb3 refactor(verify): release the agent-experience run root through VerifierFixture (#11103) 2026-11-15 20:32:38 +08:00
morluto 7dd24dc08c feat(evidence): report comparison constraints, remaining issue counts and observed workflows as fields (#11078)
* feat(evidence): report comparison-source constraints, remaining issue counts and observed workflows as fields

* fix(evidence): locate snapshot bundle schema issues within the snapshot document

* test(evidence): check each comparison-input mismatch and terminal omission once (#11087)
2026-11-15 20:20:40 +08:00
morluto de7d4dec83 test(windows): select the Windows host through HOST_PLATFORM in the native-control capability test (#11088) 2026-11-15 20:20:06 +08:00
morluto b06c34031d refactor(artifacts): take the host from HOST_PLATFORM or the caller instead of process.platform (#11090) 2026-11-15 20:19:46 +08:00
morluto 3f88f69746 feat(verify): allocate kept verifier artifact directories through VerifierFixture (#11091)
The agent-client verifiers keep their lab directory as the run's artifact record (receipt, transcripts, profiles, Evidence) and name it as artifacts in the receipt, so it is not a fixture root that release should remove. VerifierFixture.keepArtifacts allocates such a directory canonically, never removes it, and names it on stderr at allocation so a run that fails before its receipt still reports where its artifacts are. The 11 client verifiers use it, and the verifier-fixture-root baseline shrinks by 11 sites.
2026-11-15 20:19:27 +08:00
morluto 0a5df9fa32 test(javascript): give the worker checkpoint fixture the coverage its producer always writes (#11093) 2026-11-15 20:18:45 +08:00
morluto 382ad0343f refactor(sqlite): classify worker-reply read failures through the shared provider classifier (#10849)
* fix(pcap): report a refused tool launch as an unavailable tool

When a capture read's tool launch was refused with EPERM, pcap reported it
as unreadable tool output (AnalysisProtocolError) instead of an unavailable
tool. Pcap also never told "not executable" apart from "missing".

Add classifyLaunchFailure in src/process as the one owner of which spawn
errnos mean missing (ENOENT, ENOTDIR) and which mean present but not
executable (EACCES, EPERM, ENOEXEC, EISDIR). Pcap, apktool and gdb used three
different errno lists; all three now use it.

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* refactor(process): classify Java and adb launch refusals with the shared owner (#10598)

The Ghidra and JADX Java probes and adb's version probe now take
missing-versus-not-executable from classifyLaunchFailure instead of their
own errno checks. ELOOP and ENAMETOOLONG join the owner's missing set, so
adb keeps its existing classification and both Java probes now treat a
non-directory path component as a missing runtime.

* fix(pcap): preserve command launch attribution

* test(pcap): expect an unknown failing stage when a limited capture tool exits unsuccessfully

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* test(lint): cover baselined files in the provider-failure classifier rule

* fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal

The supervisor now watches for a managed runtime's out-of-memory report while output streams (JVM java.lang.OutOfMemoryError, V8 'JavaScript heap out of memory'), so a retention budget cannot hide it. ProviderOutcome gains a runtime-exhausted kind and classifyProviderOutcome maps it once. Apktool decode opts in and moves to the shared classifier; Ghidra's single-purpose output token becomes the JVM runtime option and its area matching reuses the shared table.

* fix(process): keep the runtime's out-of-memory report on the memory error it supports

* fix(pcap): attribute a limited capture's exit through the prlimit wrapper

* fix(process): keep a shortened out-of-memory line marked as shortened

* fix(process): identify the refused executable for every launcher shape

Name the executable on errnos Node throws synchronously from spawn
(ENOEXEC, ENOTDIR, ...) and read the Windows native launcher's
executable-launch constraint, so pcap attributes those refusals to the
selected tool or limiter instead of an unknown stage.

* test(pcap): prove synchronous launch naming with ENOTDIR on every POSIX host

* fix(apktool): advise the JVM limit that governs the reported memory area

* fix(ghidra): read the supervisor's parsed OOM area instead of reparsing lines

* fix(sqlite,capture): read V8 heap exhaustion from streamed output, not retained stderr (#10724)

The SQLite worker and the Node capture decoders now declare runtime: "v8", so the supervisor observes V8's heap report as output streams. Both classifiers read the runtime-exhausted outcome instead of matching retained stderr, so a report beyond the retention budget, or one whose process then overflowed its output budget, is still reported as memory exhaustion.

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier (#10716)

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier

The EVMole worker's cancellation, deadline, output budget and CPU/file-size signals now go through classifyProviderOutcome; only the prlimit launch refusal and the reserved EFBIG exit remain in its dialect. The worker is opted into V8 exhaustion scanning so a heap report names its fixed 256 MiB heap. providerOutcomeOf now reads a launch refusal only from a spawn errno, so a private-storage or carrier read errno is no longer reported as a limiter launch failure.

* fix(evm): keep a stream failure's exit status and update Linux worker expectations

* refactor(process): read launch refusals through the launcher identity owner

* refactor(wasm): classify WABT command outcomes through the shared provider classifier (#10727)

* refactor(wasm): classify WABT command outcomes through the shared provider classifier

WABT's cancellation, command deadline, output budget, and inherited-limit signals now go through classifyProviderOutcome; its dialect keeps only the configuration-probe and launch-refusal unavailability and the unclassified-exit output error. A tool ended by SIGXCPU/SIGXFSZ or the memory limit is now a resource constraint instead of an output error, and a refused launch in the producer phase is reported as unavailable. The shared timeout context gains the analysis setting that selected the deadline, and the output-limit context may name its exceeded budget.

* fix(wasm): keep setup guidance when a WABT version probe's output cannot be collected

* refactor(providers): classify raw provider failures through one owner

* refactor(process): build classified cancellations with cancelledBy and the carried caller reason

* chore(lint): count runOwned's close-failure mapper from main in the classifier baseline

* fix(process): carry the runtime's exhaustion report on every memory error that claims it

SQLite and historical-capture memory errors recognised a streamed V8 heap report but dropped it, so a report outside retained output reached callers without its source line or omission flags. One projection, runtimeExhaustionReport, now owns that shape for the shared classifier and both consumers. Also lowers the EVMole and WABT cancellation-constructor baselines.

* fix(process): keep the provider's recovery for a native-thread exhaustion report

The shared classifier replaced the provider-supplied recovery with generic capacity advice whenever the JVM reported it could not create a native thread, dropping Apktool's launcher guidance and flipping retryable to true. The provider's recovery now wins; the capacity advice remains the fallback for providers that supply none.

* refactor(pwntools): classify pwntools failures through the shared provider classifier (#10782)

* refactor(pwntools): classify pwntools failures through the shared provider classifier

pwntools' per-site classifier is replaced by pwntoolsFailureDialect for classifyProviderFailure. Command outcomes go through classifyProviderOutcome, launch refusals through launcherRefusalOf instead of a 'spawn ' syscall prefix check, the input limit through the file dialect's fileSize, and the cleanup report through commandCleanup. The shared read cancellation now carries the reader's partial observation, and resource-signal reports accept provider detail.

* fix(pwntools): report a failed command's output once and expect resource_constraint for overflow

The shared outcome owner already attaches the command's output as capturedOutput, so the pwntools adapter fallback no longer copies the same text into diagnostics.stdout, diagnostics.stderr and diagnostics.captured_output; diagnostics keep the exit facts. Update the overflow expectations the dialect migration missed (layout provider cleanup and over-budget rows, pwntools cleanup) to resource_constraint.

* refactor(wasm): leave WABT cancellation to the shared owners

WabtFailures matched any Error named AbortError as a cancellation. No WABT path produces one: reads, path checks and owned commands report cancellation as ArtifactReaderFailure or OwnedCommandFailure, which classifyProviderFailure and classifyProviderOutcome already classify with the caller's reason. The provider's two signal checks now build the cancellation with cancelledBy. Lowers the AnalysisCancelledError constructor baseline for both WABT files to zero.

* fix(wasm): report a cancelled snapshot write as the caller's cancellation

The private snapshot write passed the caller's signal to Node's writeFile, whose abort envelope reached the dialect's unclassified output failure once the name-based AbortError guess was removed. The write now turns the envelope carrying its own signal's reason into cancelledBy at the producer.

* refactor(firmware): classify firmware failures through the shared provider classifier (#10810)

Firmware producers now report their own deadline expiry: the start check, the output inventory and launcher admission throw the phase timeout where they observe the abort, and a read the phase signal cancelled reports it through the dialect's interruption. The classifier no longer rereads a cancellation's reason as a timeout. A new readCleanup hook keeps firmware's cleanup report shape.

* refactor(captures): classify TShark, sigrok and pcap failures through the shared provider classifier

TsharkCaptureProvider and SigrokRecordingProvider replace #failure with a dialect, and PcapProvider replaces analysisFailure and the tool-identity classifier. ArtifactAnalysisResources exposes its command context and a readCleanupFailure composer so the readCleanup hook keeps each provider's cleanup report shape.

* refactor(jeb): classify JEB failures through the shared provider classifier

JEB's raw MCP failures go through classifyProviderFailure with a JEB dialect;
a typed attribution step reports a shared phase's failure under the REA
operation. Each MCP phase deadline now reports its own interruption when it
aborts, deciding a cancellation's owner from the caller's own signal instead
of comparing abort reasons. A refusal carrying the JVM's OutOfMemoryError
report becomes a resource constraint naming JEB's Java heap.

* fix(jeb): advise the JVM limit that governs the reported memory area

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* refactor(adb): report a pulled file's digest failure from its own type

* refactor(sqlite): classify worker-reply read failures through the shared provider classifier

* refactor(ghidra): classify Ghidra failures through the shared provider classifier (#10836)

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier (#10843)

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* chore(guards): shrink the cancellation construction baseline after merging the base

* refactor(ida): classify IDA operation failures through the shared provider classifier (#10859)

* refactor(ida): classify IDA operation failures through the shared provider classifier

* refactor(process): let artifact-resource consumers classify through the shared provider classifier (#10867)

* test(providers): pin attribution of a cleanup-failed read's typed primary
2026-11-15 20:07:20 +08:00
morluto df1e647266 refactor(desktop): let build-computer and agentenv cleanup errors read their owner's release (#10855)
* refactor(errors): read cleanup completion from dependencies instead of pushing it to dependents

AnalysisError.cleanupIncomplete becomes a getter over the error's own unresolved state and its bound dependencies. The WeakRef dependents graph and resolveCleanup's push propagation are removed, and subclasses mark their initial state through markCleanupIncomplete instead of overriding a public mutable field.

* fix(errors): settle bound dependencies before binding more

A composite whose dependencies had released but whose status was never read
stayed incomplete when a later dependency was bound.

* refactor(errors): let errors read a lease group's release instead of resolving them (#10759)

AnalysisCleanupLeaseGroup no longer calls resolveCleanup() on its error.
The error holds the group as a release claim and reads cleanupReleased when
asked, so completion follows the lease owner without a push.

* chore(verify): count resolveCleanup and bindCleanupDependencies calls so they only shrink (#10763)

* refactor(errors): let errors read a lease group's release instead of resolving them

AnalysisCleanupLeaseGroup no longer calls resolveCleanup() on its error.
The error holds the group as a release claim and reads cleanupReleased when
asked, so completion follows the lease owner without a push.

* chore(verify): count resolveCleanup and bindCleanupDependencies calls so they only shrink

* refactor(process): let worker cleanup errors read their root epoch's release

OwnedWorkerRuntime's root epoch is now an AnalysisCleanupReleaseClaim. Errors bound to it read its release when asked instead of the epoch pushing resolveCleanup() through weak references, and a runtime without a retained root binds an already-released owner. Removes the OwnedWorkerCleanupFailure structural type and four resolveCleanup() pushes; the counted baseline shrinks accordingly.

* refactor(pcap): let pcap cleanup errors read command and snapshot release (#10792)

Retained pcap commands, retained snapshot failures and snapshot completion descriptors now own their release and errors read it: commands and snapshot failures implement AnalysisCleanupReleaseClaim, and a failed completion-descriptor close is retried through an AnalysisCleanupLease bound to its error. Removes four resolveCleanup() pushes; the counted baseline shrinks accordingly.

* refactor(process): let Darwin helper cleanup errors read their owner's release

Darwin helper cache and run-token reader root cleanup now record an AnalysisCleanupRelease that every failure reports; a successful retry confirms it. Replaces the first-failure anchor that later failures depended on and the resolveCleanup() push. Removes two resolveCleanup() and two bindCleanupDependencies() calls; both counted baselines shrink.

* refactor(ghidra): let Ghidra cleanup errors read the client's and checkpoint's release

GhidraClient and GhidraCheckpoint bind each cleanup failure to an AnalysisCleanupRelease and confirm it when a later close or retry releases everything, instead of keeping a set of failures and pushing resolveCleanup() to each. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(process): let debugger session cleanup errors read a confirmed stop

OwnedDebuggerSessionRegistry binds each retained gdb or rizin cleanup failure to the session's AnalysisCleanupRelease and confirms it when a stop is confirmed, keeping the latest failure for retry reporting, instead of pushing resolveCleanup() to a set of failures. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(process): drop the debugger registry's unused closeOperation option

Returning the latest retained failure removed the last reader of closeOperation; remove the option and the values gdb and rizin supplied.

* refactor(server): let environment shutdown errors read a successful close

serveEnvironment binds each shutdown failure to an AnalysisCleanupRelease that a later successful close confirms, instead of pushing resolveCleanup() to a set of failures. The state-file retry test now also requires the earlier report to read the release. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(desktop): release retained screen-judge cleanup through its lease

Each retained judge or screenshot cleanup owner is wrapped in an AnalysisCleanupLease bound to its failure; close() releases the lease, which records the release the failure reads, instead of closing the owner and pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(ida): let IDA close failures read the session's later close

IdaSessionClient's close failure binds an owner that is both its resource owner and its release claim, read from the client's closed state, instead of keeping weak references and pushing resolveCleanup() after a successful retry. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(javascript): let source-map codec cleanup errors read a later close (#10814)

* refactor(javascript): let source-map codec cleanup errors read a later close

OwnedCodec binds each close failure to an AnalysisCleanupRelease that a later successful close confirms, replacing the first-failure anchor that later failures depended on and the resolveCleanup() push. Removes one resolveCleanup() and one bindCleanupDependencies() call; both counted baselines shrink.

* refactor(cutter): let pending Cutter command errors read the completion receipt (#10815)

* refactor(cutter): let pending Cutter command errors read the completion receipt

Each pending Cutter command keeps an AnalysisCleanupRelease bound to its timeout failure; the producer's terminal completion receipt confirms it instead of pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(application): let provider candidate cleanup errors read the candidate's close (#10817)

* refactor(application): let provider candidate cleanup errors read the candidate's close

AnalysisProviderRegistry keeps one AnalysisCleanupRelease per candidate whose resource close is unconfirmed; failures bind to it and a later confirmed close confirms it, instead of pushing resolveCleanup() to a set of failures. Removes one resolveCleanup() call; the counted baseline shrinks.

* fix(errors): bind a cleanup release claim to an error at most once

AnalysisError kept release claims in an array, so a provider whose retried close returned the same error bound the same claim again on every retry and each cleanupIncomplete read scanned the duplicates. Keep the claims in a set.

* refactor(application): let reverse-tool cleanup errors read a verified stop (#10819)

* refactor(application): let reverse-tool cleanup errors read a verified stop

Each retained provider process group in ReverseEngineeringService keeps an AnalysisCleanupRelease; its command and retry failures bind to it and a later verified stop confirms it, instead of collecting failures and pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(sqlite): let SQLite worker and root cleanup errors read their release (#10821)

Each retained SQLite snapshot cleanup records an AnalysisCleanupRelease for its worker and for its runtime root; the resource failures bind to them and a verified stop or root removal confirms them, instead of pushing resolveCleanup(). Composite failures keep reading those resource failures. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(android): let JADX cleanup errors read their retained cleanup's release

Each retained JADX cleanup records an AnalysisCleanupRelease for the admission failure's descriptor owners and one for the whole retained cleanup. The admission failure and every error the cleanup reports bind to them, and a confirmed close confirms them, instead of pushing resolveCleanup() through WeakRefs to reported errors. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(application): let session close failures read the client's later close

A retained session cleanup records an AnalysisCleanupRelease; unowned cleanup failures the client's close reported bind to it and the successful close confirms it, instead of the session collecting them and pushing resolveCleanup(). Failures that already follow their own owner keep reading it. Adds a regression for an unowned close failure. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(desktop): let build-computer and agentenv cleanup errors read their owner's release

Build-computer teardown, retained task readers, remote reader folders and agentenv solve sandboxes each record an AnalysisCleanupRelease that their bound failures read; a verified teardown or stop confirms it instead of pushing resolveCleanup() through WeakRef sets. Long-run job failures read whether every job they name has confirmed its stop, so a failure naming two jobs no longer reads complete when only one stops (regression added). WeakRef sets kept only where late facts (completion receipts, reader views) are still retained as diagnostics. Removes six resolveCleanup() calls; the counted baseline shrinks.

* refactor(kuna): let source-map and Kuna cache cleanup errors read their owner's release (#10856)

* refactor(browser): let late source-map fetch errors read their response's release

A pending source-map fetch and a response cancellation each record an AnalysisCleanupRelease that their cancellation or deadline failure reads; releasing the late response or the reader lock confirms it instead of pushing resolveCleanup(). Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(kuna): let project-cache cleanup errors read each owner's release

Each retained Kuna resource owner (process, staging, hash descriptor, retired file) records a release that the errors reporting it read; an error completes once every owner it reports confirms, and retirement that moves staging to a retained file forwards the staging owner's release to the file's. Replaces the WeakRef report list, its pruning and the resolveCleanup() push. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(process): forbid resolveCleanup() pushes; native markers read their supervisor's release (#10857)

* refactor(providers): let an attributed client failure read its source's completed cleanup

Re-attributing a failure whose source cleanup already completed now binds a claim reading that source's release instead of pushing resolveCleanup() onto the new error; a pending source is still bound as a dependency. Resources stay as history, as before. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(process): let native command markers read their supervisor's release

ProviderProcessSupervisor records an AnalysisCleanupRelease that a stop proving the process ended confirms. Native command and LLDB resolver cleanup markers bind to their supervisor's release, so a verified stop by either owner completes them, instead of each owner pushing resolveCleanup() onto the markers it tracks. Removes the last two resolveCleanup() calls outside the error base; the counted baseline is empty.

* chore(verify): forbid resolveCleanup() pushes outside the error base

Every production owner now confirms a release its errors read, so the counted baseline is empty. The guard becomes an outright ban with no baseline entry.

* test(native): complete native command and LLDB markers through a direct supervisor stop

* refactor(errors): complete an error's cleanup from its owner's record and every dependency (#10892)

* refactor(errors): complete an error's cleanup from its owner's record and every dependency

* refactor(errors): complete test errors through an owner release and delete resolveCleanup() (#10898)

* refactor(android): compose JADX and Apktool cleanup failures through one shared owner (#10900)

* refactor(android): compose JADX and Apktool cleanup failures through one shared owner

* refactor(application): compose service cleanup failures through the shared owner (#10902)
2026-11-15 20:06:47 +08:00
morluto 7826e9688d refactor(reverse): fake objdump and Rizin commands only through the owned supervisor (#11076)
* refactor(reverse): fake objdump and Rizin commands only through the owned supervisor

* test(reverse): fake launch refusals and failed exits as the owned supervisor reports them

* refactor(reverse): name objdump and Rizin command repairs through the command port (#11080)
2026-11-15 20:06:13 +08:00
Ysopwnableandgensokyo-dev 29dd58c96d fix(il2cpp): resolve ELF ABS64 and GLOB_DAT image definitions (#11075)
Derive bounded ELF ABS64 and GLOB_DAT link-image values for IL2CPP module
scanning while preserving the default conservative relocation policy.
Retain ambiguous hash declarations, validate complete hash-table mappings,
and disclose runtime-binding limitations in CLI and MCP evidence.

Validation: 408 focused tests, check:fast and docs:check passed. Public
CLI/MCP and independently linked ARM64/x86-64 ELF arithmetic were checked;
the original Unity binaries remain unavailable.

Refs #10478
Related: #10495

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-15 19:55:43 +08:00
morluto abd36dd250 refactor(browser): parse Runtime and Debugger events once by registered schemas (#10742)
* refactor(browser): parse frame lifecycle events once by a registered event schema

CdpConnection admits each event through protocol/CdpEvents.ts, which parses
the five Page frame lifecycle events once into identity facts plus a frame
URL observation, like a frame-tree node. Screenshot, page capture, the
observation timeline, WebMCP, Electron inspection and the execution window
read those facts instead of re-parsing the raw payload; CdpFrameFacts no
longer carries the raw source record. Playwright CDP session subscriptions
name an event from Playwright's protocol table, and an architecture guard
counts the remaining raw event payload reads so they can only shrink.

* refactor(browser): parse Runtime and Debugger events once by registered schemas

Register Debugger.scriptParsed, Debugger.scriptFailedToParse,
Runtime.executionContextCreated, Runtime.executionContextDestroyed and
Runtime.consoleAPICalled. Script and context payloads carry both the
tolerant facts passive capture uses and the active runtime's strict
declaration, derived from one parse, plus the declaration's byte size for
the runtime retention budget. Page capture, Electron script capture, the
V8 Inspector provider and the runtime source inventory now read the
registered payload. The cdp-raw-event-params baseline drops from 20 to 7.

* refactor(browser): admit transport events through the registered-event owner

Remove the unused cdpFrameUrl wrapper; its fragment cases now exercise
observeCdpFrameUrl. Add a connection-level test that two subscribers share
one parsed payload and the raw params.

* fix(browser): report an unserializable event declaration's size instead of throwing

JSON.stringify can throw on a declaration JSON.parse accepted (nesting
deeper than the serializer's stack). The registry sized script and context
declarations inside the connection's WebSocket callback, so such an event
escaped as an uncaught exception. The size is now a Result; the runtime
source inventory raises the failure inside its own catch, as before.

* refactor(browser): parse Network events once by registered schemas (#10743)

* refactor(browser): parse Network events once by registered schemas

Register Network.requestWillBeSent, responseReceived, loadingFinished,
loadingFailed, webSocketCreated and webSocketFrameSent/Received. The
request payload carries the tolerant facts page capture and the timeline
read plus the active runtime's strict declaration. Page capture's network
and WebSocket handlers, the timeline's request tracking and the runtime
request inventory now read the registered payload. The
cdp-raw-event-params baseline drops from 7 to 5.

* refactor(browser): drop payload helpers the capture handlers no longer use

* refactor(browser): read every CDP event payload through the registry (#10745)

* refactor(browser): read every CDP event payload through the registry

Register Target.attachedToTarget, Target.detachedFromTarget,
Page.lifecycleEvent and WebMCP.toolsAdded/toolsRemoved, and carry a
requested navigation's reason and a commit's transition type in the
frame-event payload. The timeline no longer threads raw params into its
entries: each entry receives its timestamp, frame and loader identity
explicitly. WebMCP discovery, the service-worker policy blocker and the
target-event matcher read the registered payload. With no raw reads left,
the cdp-raw-event-params guard is enforced at zero instead of counted.

* refactor(browser): drop raw payload helpers the timeline and worker blocker no longer use

* refactor(browser): type Playwright CDP session events by the registry (#10751)

PlaywrightCdpSession.on subscribes only to registered events and hands
the listener the payload parsed by its registered schema, returning the
unsubscribe function. Private storage seeding reads registered payloads,
including the events of non-flat child sessions, and applies its strict
seeding policy to the parsed facts. Its local attach, detach, nested
message and pause schemas and the storage-only script/context schemas are
removed; Debugger.paused, Runtime.executionContextsCleared and
Target.receivedMessageFromTarget join the registry.

* test(browser): admit fake policy events through the event registry

The service-worker policy now reads event.registered, but the PlaywrightScenarioSession fake connection delivered raw events, so three detached-target tests never reached the policy. The fake now admits each event with receiveCdpEvent as CdpConnection does.

* fix(browser): fail the CDP session closed when an event listener throws (#10758)

* fix(browser): fail the CDP session closed when an event listener throws

An exception from a CDP event subscriber escaped CdpTransportCore's socket message callback uncaught (for example the service-worker blocker's protocol_error on a Target event without a sessionId). The transport now stops delivery, fails the connection as a protocol error whose cause is the listener's exception, rejects pending and later commands with it, and notifies protocol-failure subscribers. The Playwright CDP session relay applies the same rule to its event listeners.

* fix(browser): report a failed service-worker policy connection at the next policy check

The policy's event listener rejects a malformed Target event, which now fails its connection. With no policy command pending, nothing reported it and later iframe targets went unprotected. The policy records its connection's protocol failure and assertBlocked, run before each scenario step, rethrows it.

* fix(browser): keep cleanup commands available after a listener failure

A listener failure no longer marks the wire as broken. The transport still rejects pending commands, notifies protocol-failure subscribers, stops delivering events and refuses reuse, but it keeps reading replies and admits commands sent in cleanup grace. The service-worker policy can then remove its init scripts, restore the main realm, detach and release its socket. Wire-level protocol errors still refuse every later command. The PlaywrightScenarioSession policy fake gains onProtocolFailure, which the policy now subscribes to.

* refactor(browser): keep raw CDP event payloads out of the received event (#10764)

* refactor(browser): keep raw CDP event payloads out of the received event

CdpReceivedEvent and CdpTransportEvent no longer carry params: receiveCdpEvent hands the raw payload only to the registered schema, so a subscriber cannot read it by property access, destructuring or computed keys. This supersedes the event.params text guard in architecture-guards, which is removed.

* refactor(browser): read registered event payloads in the JavaScript verifiers

The runtime, storage and URL-userinfo verifiers subscribe to CdpConnection, which no longer hands subscribers raw params. They now read event.registered. A test scans every scripts/ onEvent callback for raw payload reads, since the TypeScript check does not cover them. The storage verifier's pause trace records a malformed pause instead of the unregistered reason member.

* refactor(browser): track the timeline's main frame with the document tracker (#10779)

TimelineCapture kept its own mutable main frame id and replaced it on a root replacement. It now holds the CdpDocumentTracker that CdpTargetSession owns and observes the replacement document through it; every main-frame check (lifecycle, navigation, document request, redirect) reads the tracker's current frame. A characterization test pins that the replaced root's lifecycle events are no longer recorded.

* refactor(browser): select CDP events only by their registered name (#10829)

CdpReceivedEvent carried the wire method as a plain string, so a subscriber
could compare it with a misspelt or unregistered name and the check compiled
but never matched. Three owners selected Inspector.targetCrashed, an
unregistered event, that way.

Register Inspector.targetCrashed and drop the wire method from the received
event. Subscribers select events by registered.method, whose type is the
registered event names. The capture coverage start keeps reading the
wire domain, now the event's domain field.

* fix(browser): retire a policy child session that CDP reports gone

After a listener failure the connection stops delivering events, so an
iframe's Target.detachedFromTarget never retires its policy target. Cleanup
then sent Page.removeScriptToEvaluateOnNewDocument to the dead session,
kept the stale child, and every retry failed before detaching the root.

The service-worker policy owner now treats CDP's session-not-found
rejection as proof that the child is gone and retires it. The classifier
moves from CdpTargetSession to CdpCommandRejection, its owner, and the
target-session detach path uses the same one.

* refactor(browser): route target events through one session owner (#10835)

* refactor(browser): route target events through one session owner

Page capture, WebMCP discovery, screenshot capture and the runtime sources
each compared the envelope session ID themselves, while the observation
window and execution collection used cdpTargetEventMatches, which also
routes the root Target.detachedFromTarget that names the target's session.

onCdpTargetEvent in CdpTargetEvents.ts now subscribes every target consumer
with that one rule; the matcher is private and the per-subscriber
comparisons are removed. Electron inspection keeps its own filter while
its capture isolation awaits a maintainer decision.

* fix(browser): report WebMCP subframe tools whose document is unverified (#10839)

* fix(browser): report WebMCP subframe tools whose document is unverified

A committed subframe navigation kept the frame's tools silently when CDP
reported no loader at the same URL, and removed them on any URL change even
when the loader proved the same document.

Each frame now carries a CdpDocumentTracker, the session's document
identity owner. Only a proven change removes the frame's tools. An unknown
comparison keeps them visible, marks webmcp_tools attach-limited and names
the frames whose document identity could not be established.

* test(browser): prove an unverified live subframe limits WebMCP coverage

* fix(browser): key a WebMCP registration by its frame and tool name

A subframe tool kept across a committed navigation that changed the URL
but not the loader was stored under a key built from the old URL, so a
later toolsAdded for the same frame and name added a second entry.
Registrations are now keyed by frame and name; tool_key still records the
registering frame URL.

* test(browser): deliver fake CDP events through the transport's admission (#10840)

Event fakes each rebuilt the received event: CdpAgentHints typed its
listeners as raw wire events behind an unknown cast, and the Electron,
scenario-session and routing tests each wrapped receiveCdpEvent by hand.

fakeCdpEvents (tests/fixtures) is a CdpConnection.onEvent fake whose emit
admits each raw event with receiveCdpEvent and the wire byte count, so a
change to the received event shape fails at the fake instead of silently
not exercising subscribers.

* refactor(browser): own the selected page generation in one target document tracker (#10860)

* refactor(browser): own the selected page generation in one target document tracker

Page capture, screenshot, the observation window, the runtime session and
the execution window each built a document tracker or rule of their own
from root-frame navigation events. CdpTargetDocument now follows the target
session's root-frame events and the frame-tree replies its consumers read,
classifies root identity once, and reports each navigation to consumers
that decide only what it means for their evidence.

* fix(browser): report a lost scenario policy connection before the next step (#10864)

* fix(browser): report a lost scenario policy connection before the next step

The service-worker policy recorded only protocol failures. When its CDP
socket closed or its page session detached, later documents lost the
blocking script, but the scenario learned of it only when a frame check
found an unprotected realm. The owner now records the disconnect or
detach when CDP reports it; the next policy check and the next step's
action report it first.

* fix(browser): refuse an unregistered CDP command before it reaches the browser (#10868)

JavaScript verifiers can name any command, and the send owners refused an
unregistered one only when its reply arrived, after the browser had acted
on it. CdpConnection and the Playwright session now refuse the name before
sending, and the Playwright session refuses an unregistered event name
instead of subscribing to events no listener receives. The verifier text
scan stays as an early warning and documents what it cannot see.

* fix(browser): keep a proven root change and the loaderless selection's uncertainty

Two review findings on the target document owner. A root event naming
another frame with a malformed loader set `changed` but was announced as
unidentified, so the execution window failed with protocol_error and
dropped what it had collected; the owner now announces it as the proven
change it is. The runtime session compared each frame-tree reply with the
tracker's current document, which can learn a loader from an event, so a
loaderless selection followed by an event and a reply with that loader
compared as same; it now compares the reply with the selected document
through the owner, and reports target_changed as before.

* test(browser): refuse the next scenario step after the policy session detaches

* refactor(browser): read WebMCP document identity from the target document owner (#10884)

* refactor(browser): read WebMCP document identity from the target document owner

WebMCP discovery compared the first and last frame-tree roots itself and
kept a document tracker per subframe. CdpTargetDocument now follows the
selected root and each committed subframe for it: the final frame tree
feeds the owner, a root change proven by a navigation event during
discovery is target_changed as in page capture, and a subframe whose
committed document changed loses its tools. A subframe navigation without
a loader still keeps the tools and reports them as unverified.

* fix(browser): start a subframe first seen within its document with an unknown loader

A subframe absent from the initial frame tree and first seen through Page.navigatedWithinDocument had no document tracker, so a later commit started a fresh one and the tools it registered meanwhile lost the unverified-document limitation. The owner now starts such a subframe with an unknown loader.

* fix(browser): refuse a verifier's unregistered CDP event name at subscription (#10885)

JavaScript verifiers subscribed to every event with CdpConnection.onEvent
and compared registered.method with a string literal. A misspelt or
unregistered name never matched and the verifier waited until its timeout.
CdpConnection.onRegisteredEvent now takes one event name, refuses an
unregistered one before subscribing (the same registry check the Playwright
session applies), and hands the listener only that event's registered
parse. The verifiers subscribe only through it, and the evaluation test
rejects a raw onEvent subscription in scripts.

* fix(browser): keep subframe documents by frame and loader without their URLs
2026-11-15 19:54:17 +08:00
morluto b98fbdc442 refactor(ida): let IDA close failures read the session's later close (#10811)
* refactor(errors): read cleanup completion from dependencies instead of pushing it to dependents

AnalysisError.cleanupIncomplete becomes a getter over the error's own unresolved state and its bound dependencies. The WeakRef dependents graph and resolveCleanup's push propagation are removed, and subclasses mark their initial state through markCleanupIncomplete instead of overriding a public mutable field.

* fix(errors): settle bound dependencies before binding more

A composite whose dependencies had released but whose status was never read
stayed incomplete when a later dependency was bound.

* refactor(errors): let errors read a lease group's release instead of resolving them (#10759)

AnalysisCleanupLeaseGroup no longer calls resolveCleanup() on its error.
The error holds the group as a release claim and reads cleanupReleased when
asked, so completion follows the lease owner without a push.

* chore(verify): count resolveCleanup and bindCleanupDependencies calls so they only shrink (#10763)

* refactor(errors): let errors read a lease group's release instead of resolving them

AnalysisCleanupLeaseGroup no longer calls resolveCleanup() on its error.
The error holds the group as a release claim and reads cleanupReleased when
asked, so completion follows the lease owner without a push.

* chore(verify): count resolveCleanup and bindCleanupDependencies calls so they only shrink

* refactor(process): let worker cleanup errors read their root epoch's release

OwnedWorkerRuntime's root epoch is now an AnalysisCleanupReleaseClaim. Errors bound to it read its release when asked instead of the epoch pushing resolveCleanup() through weak references, and a runtime without a retained root binds an already-released owner. Removes the OwnedWorkerCleanupFailure structural type and four resolveCleanup() pushes; the counted baseline shrinks accordingly.

* refactor(pcap): let pcap cleanup errors read command and snapshot release (#10792)

Retained pcap commands, retained snapshot failures and snapshot completion descriptors now own their release and errors read it: commands and snapshot failures implement AnalysisCleanupReleaseClaim, and a failed completion-descriptor close is retried through an AnalysisCleanupLease bound to its error. Removes four resolveCleanup() pushes; the counted baseline shrinks accordingly.

* refactor(process): let Darwin helper cleanup errors read their owner's release

Darwin helper cache and run-token reader root cleanup now record an AnalysisCleanupRelease that every failure reports; a successful retry confirms it. Replaces the first-failure anchor that later failures depended on and the resolveCleanup() push. Removes two resolveCleanup() and two bindCleanupDependencies() calls; both counted baselines shrink.

* refactor(ghidra): let Ghidra cleanup errors read the client's and checkpoint's release

GhidraClient and GhidraCheckpoint bind each cleanup failure to an AnalysisCleanupRelease and confirm it when a later close or retry releases everything, instead of keeping a set of failures and pushing resolveCleanup() to each. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(process): let debugger session cleanup errors read a confirmed stop

OwnedDebuggerSessionRegistry binds each retained gdb or rizin cleanup failure to the session's AnalysisCleanupRelease and confirms it when a stop is confirmed, keeping the latest failure for retry reporting, instead of pushing resolveCleanup() to a set of failures. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(process): drop the debugger registry's unused closeOperation option

Returning the latest retained failure removed the last reader of closeOperation; remove the option and the values gdb and rizin supplied.

* refactor(server): let environment shutdown errors read a successful close

serveEnvironment binds each shutdown failure to an AnalysisCleanupRelease that a later successful close confirms, instead of pushing resolveCleanup() to a set of failures. The state-file retry test now also requires the earlier report to read the release. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(desktop): release retained screen-judge cleanup through its lease

Each retained judge or screenshot cleanup owner is wrapped in an AnalysisCleanupLease bound to its failure; close() releases the lease, which records the release the failure reads, instead of closing the owner and pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(ida): let IDA close failures read the session's later close

IdaSessionClient's close failure binds an owner that is both its resource owner and its release claim, read from the client's closed state, instead of keeping weak references and pushing resolveCleanup() after a successful retry. Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(javascript): let source-map codec cleanup errors read a later close (#10814)

* refactor(javascript): let source-map codec cleanup errors read a later close

OwnedCodec binds each close failure to an AnalysisCleanupRelease that a later successful close confirms, replacing the first-failure anchor that later failures depended on and the resolveCleanup() push. Removes one resolveCleanup() and one bindCleanupDependencies() call; both counted baselines shrink.

* refactor(cutter): let pending Cutter command errors read the completion receipt (#10815)

* refactor(cutter): let pending Cutter command errors read the completion receipt

Each pending Cutter command keeps an AnalysisCleanupRelease bound to its timeout failure; the producer's terminal completion receipt confirms it instead of pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(application): let provider candidate cleanup errors read the candidate's close (#10817)

* refactor(application): let provider candidate cleanup errors read the candidate's close

AnalysisProviderRegistry keeps one AnalysisCleanupRelease per candidate whose resource close is unconfirmed; failures bind to it and a later confirmed close confirms it, instead of pushing resolveCleanup() to a set of failures. Removes one resolveCleanup() call; the counted baseline shrinks.

* fix(errors): bind a cleanup release claim to an error at most once

AnalysisError kept release claims in an array, so a provider whose retried close returned the same error bound the same claim again on every retry and each cleanupIncomplete read scanned the duplicates. Keep the claims in a set.

* refactor(application): let reverse-tool cleanup errors read a verified stop (#10819)

* refactor(application): let reverse-tool cleanup errors read a verified stop

Each retained provider process group in ReverseEngineeringService keeps an AnalysisCleanupRelease; its command and retry failures bind to it and a later verified stop confirms it, instead of collecting failures and pushing resolveCleanup(). Removes one resolveCleanup() call; the counted baseline shrinks.

* refactor(sqlite): let SQLite worker and root cleanup errors read their release (#10821)

Each retained SQLite snapshot cleanup records an AnalysisCleanupRelease for its worker and for its runtime root; the resource failures bind to them and a verified stop or root removal confirms them, instead of pushing resolveCleanup(). Composite failures keep reading those resource failures. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(application): let JADX and session close failures read their owner's release (#10854)

* refactor(android): let JADX cleanup errors read their retained cleanup's release

Each retained JADX cleanup records an AnalysisCleanupRelease for the admission failure's descriptor owners and one for the whole retained cleanup. The admission failure and every error the cleanup reports bind to them, and a confirmed close confirms them, instead of pushing resolveCleanup() through WeakRefs to reported errors. Removes two resolveCleanup() calls; the counted baseline shrinks.

* refactor(application): let session close failures read the client's later close

A retained session cleanup records an AnalysisCleanupRelease; unowned cleanup failures the client's close reported bind to it and the successful close confirms it, instead of the session collecting them and pushing resolveCleanup(). Failures that already follow their own owner keep reading it. Adds a regression for an unowned close failure. Removes one resolveCleanup() call; the counted baseline shrinks.
2026-11-15 19:53:53 +08:00
morluto d22da738d5 refactor(adb): report a pulled file's digest failure from its own type (#10848)
* fix(pcap): report a refused tool launch as an unavailable tool

When a capture read's tool launch was refused with EPERM, pcap reported it
as unreadable tool output (AnalysisProtocolError) instead of an unavailable
tool. Pcap also never told "not executable" apart from "missing".

Add classifyLaunchFailure in src/process as the one owner of which spawn
errnos mean missing (ENOENT, ENOTDIR) and which mean present but not
executable (EACCES, EPERM, ENOEXEC, EISDIR). Pcap, apktool and gdb used three
different errno lists; all three now use it.

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* refactor(process): classify Java and adb launch refusals with the shared owner (#10598)

The Ghidra and JADX Java probes and adb's version probe now take
missing-versus-not-executable from classifyLaunchFailure instead of their
own errno checks. ELOOP and ENAMETOOLONG join the owner's missing set, so
adb keeps its existing classification and both Java probes now treat a
non-directory path component as a missing runtime.

* fix(pcap): preserve command launch attribution

* test(pcap): expect an unknown failing stage when a limited capture tool exits unsuccessfully

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* test(lint): cover baselined files in the provider-failure classifier rule

* fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal

The supervisor now watches for a managed runtime's out-of-memory report while output streams (JVM java.lang.OutOfMemoryError, V8 'JavaScript heap out of memory'), so a retention budget cannot hide it. ProviderOutcome gains a runtime-exhausted kind and classifyProviderOutcome maps it once. Apktool decode opts in and moves to the shared classifier; Ghidra's single-purpose output token becomes the JVM runtime option and its area matching reuses the shared table.

* fix(process): keep the runtime's out-of-memory report on the memory error it supports

* fix(pcap): attribute a limited capture's exit through the prlimit wrapper

* fix(process): keep a shortened out-of-memory line marked as shortened

* fix(process): identify the refused executable for every launcher shape

Name the executable on errnos Node throws synchronously from spawn
(ENOEXEC, ENOTDIR, ...) and read the Windows native launcher's
executable-launch constraint, so pcap attributes those refusals to the
selected tool or limiter instead of an unknown stage.

* test(pcap): prove synchronous launch naming with ENOTDIR on every POSIX host

* fix(apktool): advise the JVM limit that governs the reported memory area

* fix(ghidra): read the supervisor's parsed OOM area instead of reparsing lines

* fix(sqlite,capture): read V8 heap exhaustion from streamed output, not retained stderr (#10724)

The SQLite worker and the Node capture decoders now declare runtime: "v8", so the supervisor observes V8's heap report as output streams. Both classifiers read the runtime-exhausted outcome instead of matching retained stderr, so a report beyond the retention budget, or one whose process then overflowed its output budget, is still reported as memory exhaustion.

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier (#10716)

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier

The EVMole worker's cancellation, deadline, output budget and CPU/file-size signals now go through classifyProviderOutcome; only the prlimit launch refusal and the reserved EFBIG exit remain in its dialect. The worker is opted into V8 exhaustion scanning so a heap report names its fixed 256 MiB heap. providerOutcomeOf now reads a launch refusal only from a spawn errno, so a private-storage or carrier read errno is no longer reported as a limiter launch failure.

* fix(evm): keep a stream failure's exit status and update Linux worker expectations

* refactor(process): read launch refusals through the launcher identity owner

* refactor(wasm): classify WABT command outcomes through the shared provider classifier (#10727)

* refactor(wasm): classify WABT command outcomes through the shared provider classifier

WABT's cancellation, command deadline, output budget, and inherited-limit signals now go through classifyProviderOutcome; its dialect keeps only the configuration-probe and launch-refusal unavailability and the unclassified-exit output error. A tool ended by SIGXCPU/SIGXFSZ or the memory limit is now a resource constraint instead of an output error, and a refused launch in the producer phase is reported as unavailable. The shared timeout context gains the analysis setting that selected the deadline, and the output-limit context may name its exceeded budget.

* fix(wasm): keep setup guidance when a WABT version probe's output cannot be collected

* refactor(providers): classify raw provider failures through one owner

* refactor(process): build classified cancellations with cancelledBy and the carried caller reason

* chore(lint): count runOwned's close-failure mapper from main in the classifier baseline

* fix(process): carry the runtime's exhaustion report on every memory error that claims it

SQLite and historical-capture memory errors recognised a streamed V8 heap report but dropped it, so a report outside retained output reached callers without its source line or omission flags. One projection, runtimeExhaustionReport, now owns that shape for the shared classifier and both consumers. Also lowers the EVMole and WABT cancellation-constructor baselines.

* fix(process): keep the provider's recovery for a native-thread exhaustion report

The shared classifier replaced the provider-supplied recovery with generic capacity advice whenever the JVM reported it could not create a native thread, dropping Apktool's launcher guidance and flipping retryable to true. The provider's recovery now wins; the capacity advice remains the fallback for providers that supply none.

* refactor(pwntools): classify pwntools failures through the shared provider classifier (#10782)

* refactor(pwntools): classify pwntools failures through the shared provider classifier

pwntools' per-site classifier is replaced by pwntoolsFailureDialect for classifyProviderFailure. Command outcomes go through classifyProviderOutcome, launch refusals through launcherRefusalOf instead of a 'spawn ' syscall prefix check, the input limit through the file dialect's fileSize, and the cleanup report through commandCleanup. The shared read cancellation now carries the reader's partial observation, and resource-signal reports accept provider detail.

* fix(pwntools): report a failed command's output once and expect resource_constraint for overflow

The shared outcome owner already attaches the command's output as capturedOutput, so the pwntools adapter fallback no longer copies the same text into diagnostics.stdout, diagnostics.stderr and diagnostics.captured_output; diagnostics keep the exit facts. Update the overflow expectations the dialect migration missed (layout provider cleanup and over-budget rows, pwntools cleanup) to resource_constraint.

* refactor(wasm): leave WABT cancellation to the shared owners

WabtFailures matched any Error named AbortError as a cancellation. No WABT path produces one: reads, path checks and owned commands report cancellation as ArtifactReaderFailure or OwnedCommandFailure, which classifyProviderFailure and classifyProviderOutcome already classify with the caller's reason. The provider's two signal checks now build the cancellation with cancelledBy. Lowers the AnalysisCancelledError constructor baseline for both WABT files to zero.

* fix(wasm): report a cancelled snapshot write as the caller's cancellation

The private snapshot write passed the caller's signal to Node's writeFile, whose abort envelope reached the dialect's unclassified output failure once the name-based AbortError guess was removed. The write now turns the envelope carrying its own signal's reason into cancelledBy at the producer.

* refactor(firmware): classify firmware failures through the shared provider classifier (#10810)

Firmware producers now report their own deadline expiry: the start check, the output inventory and launcher admission throw the phase timeout where they observe the abort, and a read the phase signal cancelled reports it through the dialect's interruption. The classifier no longer rereads a cancellation's reason as a timeout. A new readCleanup hook keeps firmware's cleanup report shape.

* refactor(captures): classify TShark, sigrok and pcap failures through the shared provider classifier

TsharkCaptureProvider and SigrokRecordingProvider replace #failure with a dialect, and PcapProvider replaces analysisFailure and the tool-identity classifier. ArtifactAnalysisResources exposes its command context and a readCleanupFailure composer so the readCleanup hook keeps each provider's cleanup report shape.

* refactor(jeb): classify JEB failures through the shared provider classifier

JEB's raw MCP failures go through classifyProviderFailure with a JEB dialect;
a typed attribution step reports a shared phase's failure under the REA
operation. Each MCP phase deadline now reports its own interruption when it
aborts, deciding a cancellation's owner from the caller's own signal instead
of comparing abort reasons. A refusal carrying the JVM's OutOfMemoryError
report becomes a resource constraint naming JEB's Java heap.

* fix(jeb): advise the JVM limit that governs the reported memory area

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* refactor(adb): report a pulled file's digest failure from its own type

* refactor(ghidra): classify Ghidra failures through the shared provider classifier (#10836)

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier (#10843)

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* chore(guards): shrink the cancellation construction baseline after merging the base
2026-11-15 19:53:32 +08:00
morluto 47d14d0fb8 refactor(verify): give the javascript verifiers' roots to VerifierFixture (#11068)
* refactor(verify): give the javascript verifiers' roots to VerifierFixture

VerifierFixture.retain(reason) keeps a root that an owned command whose
cleanup is incomplete may still use, replacing each verifier's local
retain flag and cleanup closure.

* refactor(verify): give the Ghidra COM, DOS, 6502, NativeAOT and raw verifiers' workspaces to VerifierFixture (#11070)

* refactor(verify): give the Ghidra COM, DOS, 6502, NativeAOT and raw verifiers' workspaces to VerifierFixture

* refactor(verify): keep the Ghidra decompiler-failure, decompile-timeout and no-return workspaces through VerifierFixture until owned cleanup is verified (#11077)
2026-11-15 19:52:38 +08:00
morluto bae5d74eb0 refactor(ida): require the host platform for IDA MCP connections and snapshot the child environment with it (#11073) 2026-11-15 19:51:33 +08:00
morluto 9be6a96fd6 refactor(cli): project reconstruction and source-to-bundle documents from their contracts (#10697)
* refactor(cli): project reconstruction ledger and coverage commands from their contracts

* refactor(cli): project compare-source-to-bundle from its contract and refuse omitted document fields

* fix(cli): advertise caller paths in JSON documents as the CLI admits them

* fix(cli): advertise that JSON document caller paths exclude URLs

* refactor(cli): narrow omitted document field refusal to object documents

* test(cli): give the coverage document fixture the current evidence_format
2026-11-15 19:40:22 +08:00
morluto fd2eabb12a feat(setup): manage fx, Junie, Goose and Kilo registrations through the client table (#10709)
* feat(setup): manage fx registrations with its private profile modes

fx becomes a client-table row, so doctor, update and uninstall see the
registration rea mcp add wrote, and setup offers fx when ~/.fx or
FX_HOME exists. Setup now publishes a private profile through the same
owner as mcp add (folder 0700, file 0600) and repairs those modes when
the row is otherwise current. Doctor parses fx's array launcher instead
of reporting it invalid.

* fix(setup): judge fx's private profile by its targets and keep its cleanup owner

Address review: privateProfileModesCurrent reads the modes of the canonical file and folder, so a linked profile becomes current; clientConfigurationAligned applies the same modes rule as plan and apply; a private-folder close failure reaches the caller with its cleanup owner instead of becoming a write failure; and the setup plan names the folder and file modes it sets.

* fix(setup): keep fx's recovery backups owner-only beside a linked profile

Setup and uninstall copied a private profile's original bytes with copyFile, so a backup beside a linked ~/.fx/mcp.json kept the target's readable mode outside the folder setup secures. Both now use the owner-only backup owner that mcp add already used.

* feat(setup): manage Junie registrations and apply its location policy (#10712)

* feat(setup): manage Junie registrations and apply its location policy

Junie becomes a client-table row, so doctor, update and uninstall see
the registration rea mcp add wrote, and setup offers Junie when its home
exists. Whether Junie loads its default user MCP file now has one owner,
read through the client policy block that setup, doctor and mcp add
already consult: setup and mcp add refuse, and doctor reports, a
registration Junie would not load.

* fix(setup): read Junie's user configuration as strict JSON

Address review: Junie loads config.json as strict JSON, so comments, trailing commas or an empty file leave its MCP location choice unknown, as before the move into the row. Filesystem tests also clear JUNIE_*, FX_HOME and MCPORTER_CONFIG so a developer's own client locations cannot be inspected or removed.

* test(setup): keep the detected client order out of the lifecycle suite body

The expected detection order grew with each folded client until the suite callback exceeded the max-lines-per-function lint limit.

* fix(update): report a client whose policy setup refuses instead of failing maintenance

Junie's disabled default locations (and Gemini's settings policy) make
doctor report the registration stale, but setup refuses that client, so
selecting it failed every other client's maintenance. The registration
inspection now carries the setup blocker, and maintenance lists that
client with its reason instead of selecting it.

* refactor(setup): read fx's private-profile host through HOST_PLATFORM

The fx private-profile mode checks and permissions describe the host REA runs
on, so they take it from the host-platform owner instead of reading
process.platform (refused by the ratchet from #10911).

* feat(setup): manage Goose registrations across its configuration layers (#10715)

* feat(setup): manage Goose registrations across its configuration layers

Goose becomes a client-table row, so doctor, update and uninstall see
the registration rea mcp add wrote, and setup offers Goose when its user
file or system file exists. Goose loads its system file and
GOOSE_ADDITIONAL_CONFIG_FILES beneath the user file and overlays each
extension one field deep; doctor now reports that effective
registration, while setup, update and uninstall change only the user
file. Doctor also parses Goose's cmd launcher instead of reporting it
invalid.

* fix(setup): keep Goose's layered environment and load order through setup and uninstall

Address review: read Goose's env alias and apply requested settings over envs; keep an inherited lower envs map instead of masking it with an empty default; keep each layer's last load so a repeated user file keeps its precedence; split GOOSE_ADDITIONAL_CONFIG_FILES with Windows quoting; validate only the effective environment across read-only layers; refuse uninstall while a read-only layer keeps REA enabled; and clear Goose's location variables in tests.

* fix(setup): apply requested Goose settings over the inherited environment

* fix(setup): leave lower-layer Goose settings in force and read admin layers read-only

Setup no longer writes its default timeout, enabled, name or description into Goose's user entry when only a lower layer declares that setting, so the inherited value keeps applying and a repeat setup stays current. Uninstall reads Goose's read-only lower layers where Goose reads them, without the write target's ownership check, so an administrator-owned system file no longer blocks removal. The client table accepts an injected Goose system-config path so tests no longer read or skip on the host's /etc/goose/config.yaml; adds a shadowed-invalid-lower-layer regression.

* fix(setup): keep an inherited Goose env alias's spelling when writing the user entry

Goose overlays each extension field separately, so writing envs into the user file beside a lower layer's env alias left both in force, which readers reject as ambiguous. Setup now writes the user map under the alias a lower layer declares, and mcp add neither adds a default envs map beside an inherited alias nor renames a user alias the lower layer shares.

* test(setup): inject Goose's system layer into mcp add selection

resolveMcpRegistrationClients accepts the caller's system configuration paths, so the Goose layers test no longer reads the host's /etc/goose/config.yaml.

* fix(setup): report REA's Goose registration that only a read-only layer defines

* test(setup): pass an isolated Goose system path through update maintenance and the mcp add table

* fix(setup): judge Goose uninstall by the merged entry and stop on an unreadable lower layer

* fix(setup): select Goose's env alias by presence so a null alias stays malformed

* fix(setup): use the shared record predicate for Goose's layered merge after #10588

* feat(setup): give Kilo a client-table row with an async layered location (#10823)

* feat(setup): give Kilo a client-table row with an async layered location

The client table gains a layeredLocation hook and resolveSupportedClients, which awaits it. Kilo's row resolves its user-scope layers through the same owner rea mcp add uses (KiloClientLocation), so both pick the same write target; the synchronous row keeps kilo.json in Kilo's config root with every candidate layer. The table refuses a relative KILO_CONFIG, KILO_CONFIG_DIR or XDG_CONFIG_HOME because Kilo's working directory is unknown.

Refs #7181

* fix(setup): treat every folder Kilo reads as evidence that it is installed

* fix(setup): normalize Kilo's absolute table paths and parse its command array

* feat(setup): resolve the client table's layered locations in setup, doctor, update and uninstall (#10832)

SetupHost (detection and the selectable table), ClientRegistrationStatus, uninstall, doctor's selected-registration environment and the mcp add catalog now read resolveSupportedClients, so each sees the Kilo layer Kilo writes rather than the synchronous default.

Refs #7181

* docs(adr): record the one-client-table decision for setup and mcp add

* fix(setup): refuse to read a client row whose location is unresolved

Doctor's selected-client settings read a Kilo row whose relative
KILO_CONFIG left only path evidence, so it inspected a file relative to
REA's folder. The shared configuration reader now refuses such a row,
so every reader reports the registration as unreadable.

* docs(adr): name the row's layeredLocation hook

* feat(setup): read KILO_CONFIG_CONTENT as Kilo's top read-only layer (#10851)

* feat(setup): read KILO_CONFIG_CONTENT as Kilo's top read-only layer

* fix(setup): refuse a Kilo uninstall that would expose or keep another layer's rea entry (#10852)

* fix(setup): recheck KILO_CONFIG_CONTENT at write time and name it in doctor's recovery

* refactor(mcp): register Kilo through its table row and remove the registration-only catalog kind (#10862)

* refactor(mcp): register Kilo through its table row and remove the registration-only catalog kind

* refactor(mcp): resolve user-scope targets in the catalog so location modules never import the client table

* test(setup): cover Kilo's KILO_CONFIG, home-folder and inline-only layers

* fix(setup): report REA's Kilo entry that only KILO_CONFIG_CONTENT sets during uninstall

Doctor reports a registration that KILO_CONFIG_CONTENT alone defines, but
uninstall found no file entry and reported Kilo as skipped. Uninstall now
reports it once as failed and names the variable to edit.

* docs(adr): record Kilo's inline layer in the client-table decision

* fix(setup): compare Windows environment names without case when a higher layer overrides REA's entry

Kilo's inline-layer check and Gemini's system-override check compared
environment maps by exact key, so an equal Path beside PATH on Windows was
reported as an override and setup refused the client. Both compare the
entry as the client launches it.

* fix(update): leave a stale Kilo registration KILO_CONFIG_CONTENT sets out of maintenance

Setup refuses a refresh the inline layer would override, and one refused
client failed every other client's maintenance. The registration inspection
now names the inline layer as the setup blocker of a stale entry, and
maintenance reports Kilo with that recovery instead of selecting it.

* fix(setup): refuse relative JUNIE_HOME and Cline CLI overrides like the other client roots (#10909)

* fix(setup): refuse relative JUNIE_HOME and Cline CLI overrides like the other client roots

* fix(setup): refuse Windows client paths without a drive that follow the client's current drive

* fix(update): select maintenance clients through setup's own refresh refusal check

Maintenance predicted setup's refusal with its own rule: any inline rea entry
blocked a stale registration, while an aligned one was never checked. A
harmless KILO_CONFIG_CONTENT entry (enabled: true) kept a stale Kilo launcher
pinned, and an inline value setup would not write (an extra environment
variable) let an aligned Kilo registration block every client's maintenance.

Setup's refusal checks (client policy, then the inline override comparison of
the entry it would write) now live in one reader that setup's alignment check
and maintenance share. Maintenance asks it for every owned enabled candidate
and reports a refused client with setup's remediation and its real state.

* fix(update): keep failed refresh-check reads owned by the supplied maintenance scope

clientRefreshRefusal read the client's layers, target file and policy without
the caller's resource scope, so a close that failed during those reads threw a
cleanup failure no scope retained; maintenance then reported it as text and
the scope's close could not retry the descriptor. The refresh reader now takes
the scope and passes it to every read.

* fix(setup): take the Kilo layer readers' default host from HOST_PLATFORM and the shared record predicate

After rebasing on main (#10911, #10588): the new inlineRegistrationOverride and
resolveSupportedClients defaults read the running host through the
host-platform owner, and the layered launcher merge uses isRecord.

* fix(update): keep an unreadable layer's reason in the refresh refusal check

readClientRefresh returned undefined for an unreadable or malformed layer, so
clientRefreshRefusal reported no refusal and maintenance selected a client
whose setup preflight then failed. The reader returns the typed file failure,
and the refusal check reports its detail like setup's preflight does.

* fix(setup): count REA's entry in KILO_CONFIG_CONTENT as Kilo lifecycle evidence

Doctor, update and uninstall inspected a client only when one of its folders
existed, so a registration set only by KILO_CONFIG_CONTENT was skipped. A
read-only inline layer that sets REA's entry is now evidence on its own.

* fix(update): report a client whose only conflict is a discovered provider path as needing setup

* fix(update): match discovered settings by the platform's environment identity

* fix(update): preflight maintenance with setup's write-target checks and treat discovered inline paths as unknown
2026-11-15 19:39:33 +08:00
morluto 025d1b365f refactor(browser): parse frame lifecycle events once by a registered event schema (#10730)
* refactor(browser): parse frame lifecycle events once by a registered event schema

CdpConnection admits each event through protocol/CdpEvents.ts, which parses
the five Page frame lifecycle events once into identity facts plus a frame
URL observation, like a frame-tree node. Screenshot, page capture, the
observation timeline, WebMCP, Electron inspection and the execution window
read those facts instead of re-parsing the raw payload; CdpFrameFacts no
longer carries the raw source record. Playwright CDP session subscriptions
name an event from Playwright's protocol table, and an architecture guard
counts the remaining raw event payload reads so they can only shrink.

* refactor(browser): admit transport events through the registered-event owner

Remove the unused cdpFrameUrl wrapper; its fragment cases now exercise
observeCdpFrameUrl. Add a connection-level test that two subscribers share
one parsed payload and the raw params.
2026-11-15 19:39:05 +08:00
morluto 607a7872bb refactor(jeb): classify JEB failures through the shared provider classifier (#10831)
* fix(pcap): report a refused tool launch as an unavailable tool

When a capture read's tool launch was refused with EPERM, pcap reported it
as unreadable tool output (AnalysisProtocolError) instead of an unavailable
tool. Pcap also never told "not executable" apart from "missing".

Add classifyLaunchFailure in src/process as the one owner of which spawn
errnos mean missing (ENOENT, ENOTDIR) and which mean present but not
executable (EACCES, EPERM, ENOEXEC, EISDIR). Pcap, apktool and gdb used three
different errno lists; all three now use it.

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* refactor(process): classify Java and adb launch refusals with the shared owner (#10598)

The Ghidra and JADX Java probes and adb's version probe now take
missing-versus-not-executable from classifyLaunchFailure instead of their
own errno checks. ELOOP and ENAMETOOLONG join the owner's missing set, so
adb keeps its existing classification and both Java probes now treat a
non-directory path component as a missing runtime.

* fix(pcap): preserve command launch attribution

* test(pcap): expect an unknown failing stage when a limited capture tool exits unsuccessfully

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* test(lint): cover baselined files in the provider-failure classifier rule

* fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal

The supervisor now watches for a managed runtime's out-of-memory report while output streams (JVM java.lang.OutOfMemoryError, V8 'JavaScript heap out of memory'), so a retention budget cannot hide it. ProviderOutcome gains a runtime-exhausted kind and classifyProviderOutcome maps it once. Apktool decode opts in and moves to the shared classifier; Ghidra's single-purpose output token becomes the JVM runtime option and its area matching reuses the shared table.

* fix(process): keep the runtime's out-of-memory report on the memory error it supports

* fix(pcap): attribute a limited capture's exit through the prlimit wrapper

* fix(process): keep a shortened out-of-memory line marked as shortened

* fix(process): identify the refused executable for every launcher shape

Name the executable on errnos Node throws synchronously from spawn
(ENOEXEC, ENOTDIR, ...) and read the Windows native launcher's
executable-launch constraint, so pcap attributes those refusals to the
selected tool or limiter instead of an unknown stage.

* test(pcap): prove synchronous launch naming with ENOTDIR on every POSIX host

* fix(apktool): advise the JVM limit that governs the reported memory area

* fix(ghidra): read the supervisor's parsed OOM area instead of reparsing lines

* fix(sqlite,capture): read V8 heap exhaustion from streamed output, not retained stderr (#10724)

The SQLite worker and the Node capture decoders now declare runtime: "v8", so the supervisor observes V8's heap report as output streams. Both classifiers read the runtime-exhausted outcome instead of matching retained stderr, so a report beyond the retention budget, or one whose process then overflowed its output budget, is still reported as memory exhaustion.

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier (#10716)

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier

The EVMole worker's cancellation, deadline, output budget and CPU/file-size signals now go through classifyProviderOutcome; only the prlimit launch refusal and the reserved EFBIG exit remain in its dialect. The worker is opted into V8 exhaustion scanning so a heap report names its fixed 256 MiB heap. providerOutcomeOf now reads a launch refusal only from a spawn errno, so a private-storage or carrier read errno is no longer reported as a limiter launch failure.

* fix(evm): keep a stream failure's exit status and update Linux worker expectations

* refactor(process): read launch refusals through the launcher identity owner

* refactor(wasm): classify WABT command outcomes through the shared provider classifier (#10727)

* refactor(wasm): classify WABT command outcomes through the shared provider classifier

WABT's cancellation, command deadline, output budget, and inherited-limit signals now go through classifyProviderOutcome; its dialect keeps only the configuration-probe and launch-refusal unavailability and the unclassified-exit output error. A tool ended by SIGXCPU/SIGXFSZ or the memory limit is now a resource constraint instead of an output error, and a refused launch in the producer phase is reported as unavailable. The shared timeout context gains the analysis setting that selected the deadline, and the output-limit context may name its exceeded budget.

* fix(wasm): keep setup guidance when a WABT version probe's output cannot be collected

* refactor(providers): classify raw provider failures through one owner

* refactor(process): build classified cancellations with cancelledBy and the carried caller reason

* chore(lint): count runOwned's close-failure mapper from main in the classifier baseline

* fix(process): carry the runtime's exhaustion report on every memory error that claims it

SQLite and historical-capture memory errors recognised a streamed V8 heap report but dropped it, so a report outside retained output reached callers without its source line or omission flags. One projection, runtimeExhaustionReport, now owns that shape for the shared classifier and both consumers. Also lowers the EVMole and WABT cancellation-constructor baselines.

* fix(process): keep the provider's recovery for a native-thread exhaustion report

The shared classifier replaced the provider-supplied recovery with generic capacity advice whenever the JVM reported it could not create a native thread, dropping Apktool's launcher guidance and flipping retryable to true. The provider's recovery now wins; the capacity advice remains the fallback for providers that supply none.

* refactor(pwntools): classify pwntools failures through the shared provider classifier (#10782)

* refactor(pwntools): classify pwntools failures through the shared provider classifier

pwntools' per-site classifier is replaced by pwntoolsFailureDialect for classifyProviderFailure. Command outcomes go through classifyProviderOutcome, launch refusals through launcherRefusalOf instead of a 'spawn ' syscall prefix check, the input limit through the file dialect's fileSize, and the cleanup report through commandCleanup. The shared read cancellation now carries the reader's partial observation, and resource-signal reports accept provider detail.

* fix(pwntools): report a failed command's output once and expect resource_constraint for overflow

The shared outcome owner already attaches the command's output as capturedOutput, so the pwntools adapter fallback no longer copies the same text into diagnostics.stdout, diagnostics.stderr and diagnostics.captured_output; diagnostics keep the exit facts. Update the overflow expectations the dialect migration missed (layout provider cleanup and over-budget rows, pwntools cleanup) to resource_constraint.

* refactor(wasm): leave WABT cancellation to the shared owners

WabtFailures matched any Error named AbortError as a cancellation. No WABT path produces one: reads, path checks and owned commands report cancellation as ArtifactReaderFailure or OwnedCommandFailure, which classifyProviderFailure and classifyProviderOutcome already classify with the caller's reason. The provider's two signal checks now build the cancellation with cancelledBy. Lowers the AnalysisCancelledError constructor baseline for both WABT files to zero.

* fix(wasm): report a cancelled snapshot write as the caller's cancellation

The private snapshot write passed the caller's signal to Node's writeFile, whose abort envelope reached the dialect's unclassified output failure once the name-based AbortError guess was removed. The write now turns the envelope carrying its own signal's reason into cancelledBy at the producer.

* refactor(firmware): classify firmware failures through the shared provider classifier (#10810)

Firmware producers now report their own deadline expiry: the start check, the output inventory and launcher admission throw the phase timeout where they observe the abort, and a read the phase signal cancelled reports it through the dialect's interruption. The classifier no longer rereads a cancellation's reason as a timeout. A new readCleanup hook keeps firmware's cleanup report shape.

* refactor(captures): classify TShark, sigrok and pcap failures through the shared provider classifier

TsharkCaptureProvider and SigrokRecordingProvider replace #failure with a dialect, and PcapProvider replaces analysisFailure and the tool-identity classifier. ArtifactAnalysisResources exposes its command context and a readCleanupFailure composer so the readCleanup hook keeps each provider's cleanup report shape.

* refactor(jeb): classify JEB failures through the shared provider classifier

JEB's raw MCP failures go through classifyProviderFailure with a JEB dialect;
a typed attribution step reports a shared phase's failure under the REA
operation. Each MCP phase deadline now reports its own interruption when it
aborts, deciding a cancellation's owner from the caller's own signal instead
of comparing abort reasons. A refusal carrying the JVM's OutOfMemoryError
report becomes a resource constraint naming JEB's Java heap.

* fix(jeb): advise the JVM limit that governs the reported memory area

* refactor(ghidra): classify Ghidra failures through the shared provider classifier (#10836)

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier (#10843)

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* chore(guards): shrink the cancellation construction baseline after merging the base
2026-11-15 19:37:46 +08:00
morluto 15de1d4dce feat(evidence)!: require JavaScript, browser, managed and reference-source facts their producers always write (#10990)
* feat(javascript)!: require the runtime parse-failure count and active Electron execution paths

The V8 Inspector projection always writes scripts.parse_failed_total and the
active Electron service always commits parameters.resolved_paths, so both are
now required. Runtime reconciliation reads the admitted execution paths only
and no longer falls back to request-spelled paths in parameters, and checks
the observed executable path against them on every capture.

JavaScriptStaticAnalysis.routes and module_runtime keep their omission, which
current producers still make (a checkpoint that cannot retain route facts; no
recovered factory runtime); their comments now say so instead of naming
historical analyses.

BREAKING CHANGE: JavaScript runtime observations without
scripts.parse_failed_total, and active Electron Evidence without
parameters.resolved_paths, are refused as malformed; recreate the observation.

* test(javascript): give the shared Node reconciliation fixture the required parse-failure count

* feat(browser)!: require browser observation facts their producers always write (#10991)

* feat(browser)!: require browser observation facts their producers always write

The source-map fetcher always writes effective_url (string or null), the web
bundle analyzer always writes schema_property_names_coverage, and the CDP
storage fingerprinter always marks IndexedDB schema fingerprints with
projection_version 2. Each is now required. The IndexedDB marker is tied to
its scope by a discriminated union and derived from the scope in the
producer, so compare_web_captures no longer carries the store-only
projection branch.

response_identity and the WebMCP consequential hint keep their omission,
which current producers still make (a body not fully read; an invalid
annotation); the guide no longer attributes it to historical captures.

BREAKING CHANGE: source-map items without effective_url, WebMCP declarations
without schema_property_names_coverage, and IndexedDB schema fingerprints
without projection_version are refused as malformed; recreate the capture.

* feat(managed)!: refuse members beside a null signature resolution (#10992)

* feat(managed)!: refuse members beside a null signature resolution

signature_resolution is null only when member decoding exceeded capacity,
and the capacity-recovery producer then examines no members. The managed
member inspection schema now enforces that: a null resolution with any
field, method, member reference, call edge or field access is malformed.

With that rule, every admitted inspection with members attests type-name
resolution, so the raw-digest fallback is unreachable and is deleted:
comparableSides, hasSignatureResolution, the comparison's
signatureResolutionExamined flag and the "does not attest" limitation. The
shared workflow example that carried resolved digests beside a null
attestation now attests resolution.

BREAKING CHANGE: managed member Evidence with a null signature_resolution
and any examined member is refused as malformed; recreate the observation.

* docs: describe historical Evidence by what the format revision now refuses (#10993)

SQLite worker_timeout_ms is required, so the SQLite guide no longer reads a
missing value as unknown settings. The IL2CPP, ESP image and process capture
guides now say that bundles exported without evidence_format are refused,
while a standalone record from an earlier release is still read with the
stated omission semantics (records carry no format marker yet). The route
inventory guide describes a parent without route_inventory by its meaning
rather than by its age.

* refactor(javascript): require static analysis coverage from the JavaScript worker (#11001)

* refactor(javascript): require static analysis coverage from the JavaScript worker

Both JavaScript static producers always write analysis_coverage: the
analyzer measures it on every parsed source and the failed-parse result
carries an unexamined coverage. The worker schema and the static analysis
type therefore require it, the preload-discovery checkpoint copies it
unconditionally, and ElectronBoundaryAnalysis reads Electron applicability
and examination limits from it alone. The fallback that re-derived Electron
use from semantic module links and retained references for analyses
without coverage is deleted, as are the graph and builder branches for a
missing coverage.

Retained Evidence graph observations are unchanged: the analysis view still
counts a stored JavaScript observation without analysis_coverage as
unmeasured, since standalone records carry no format marker yet.

The worker runs from the same build as its parent, so no external caller
sees the stricter reply schema.

* feat(browser)!: require storage fingerprint, Link context, mapping index and invalid-map identity facts (#11002)

* feat(browser)!: require storage fingerprint, Link context, mapping index and invalid-map identity facts

Each field is written by its only producer on every capture:

- The CDP storage capture always writes content_fingerprints,
  fingerprint_algorithm and fingerprints_complete, so their schema defaults
  are removed.
- The CDP Link header projection always writes context_url and
  context_scope, so they are required.
- The source-map fetcher writes source_index and declaration_index on every
  mapping row, so they are required.
- The fetcher judges a map invalid only after reading its complete body. A
  separate invalidSourceMapItem builder now takes the response identity as a
  required argument, and the schema's invalid member requires
  response_identity; fetch_failed keeps it optional because those rows can
  come from a body that was not fully read.

The browser and source-location guides no longer describe these fields as
optional for older saved captures.

BREAKING CHANGE: page inspections without the storage fingerprint fields or
Link context fields, source-map mapping rows without their indexes, and
invalid source-map items without response_identity are refused as malformed;
recreate the capture.

* test(browser): keep source-omission coverage on a still-defaulted field and refuse a missing fingerprint algorithm

* feat(browser)!: require web-bundle target origin, inspection producer facts, scenario redirect state and exclusion kind (#11014)

feat(browser)!: require web-bundle target origin, page inspection producer facts, scenario redirect state and exclusion kind

Each field is written by its only producer on every record:

- buildCaptureObservation always writes capture.target_origin (the validated
  origin or null), so analyze_web_bundle Evidence and both web-bundle view
  results require it; the view's conditional spread is gone.
- The CDP page capture always writes accessibility node states, script
  frame_id (null when unattributed) and worker opener_target_id and
  parent_frame_id, so their schema defaults are removed.
- PlaywrightScenarioNetwork writes redirected_from_transaction_state exactly
  when the predecessor ID is null, so request events now require it there and
  still refuse it on linked events.
- The reference-source importer writes entry_kind on every exclusion
  (unknown when the kind was not observed), so it is required.

The browser scenario contract and CLI guide no longer describe older
captures or exclusions without these fields.

BREAKING CHANGE: web-bundle analyses without capture.target_origin, page
inspections without these script, accessibility or worker fields, scenario
network events with a null predecessor and no redirect state, and reference
source graphs whose exclusions lack entry_kind are refused as malformed;
recreate the capture or re-import the source.
2026-11-15 19:37:10 +08:00
morluto d8c0a71c53 feat(artifacts): name which ASAR integrity check failed on the reader failure (#11021)
* feat(artifacts): name which ASAR integrity check failed on the reader failure

* test(artifacts): assert the ASAR stream's declared-size and read-coverage checks

* fix(artifacts): report a close queued behind ASAR preparation as unavailable

The missing-snapshot check was reachable when close() ran between a
prepareContainer() and the following entries() or openContainer()
operation, and reported a caller close as an integrity failure. Classify
it like the sibling closed-inventory check and cover foreign-entry with a
stale entry opened after close.
2026-11-15 19:35:44 +08:00
morluto 63b0426e40 fix(errors): rethrow internal invariant violations from plist consumers and the native output projection (#11029)
* fix(plist): rethrow internal invariant violations from plist consumers

KeyedArchiveReader reported any non-limit decode failure as a format
failure, InterfaceBuilderAnalysis listed it as an invalid archive,
ReleaseExposureReader fell back to scanning raw bytes, and the native
plist parser reported it as provider output. An InternalInvariantError
is a REA defect, not evidence about the input, so each consumer now
rethrows it. The native parser's binary path now shares its one cause
projection instead of a duplicate.

* fix(native): rethrow internal invariant violations from the native output projection

nativeOutputFailure wrapped any producer failure as AnalysisOutputError,
so a REA defect thrown while reading a plist or dispatching a command was
reported as native output parsing. It now rethrows InternalInvariantError,
which reaches the provider's adapter-failure path with the invariant as
its cause.

* fix(artifacts): rethrow internal invariant violations from the shared artifact failure mapper

translateArtifactFailure turned every unrecognised cause into an
ArtifactOperationError with reason io, so the keyed-archive and
Interface Builder rethrows still reached users as artifact read
failures. The shared mapper now rethrows InternalInvariantError, and the
deferred client reports it as a ProviderAdapterError. Provider-level
tests drive both operations through that client.

* refactor(dotnet): report managed reader defects through invariantViolation (#11030)

* refactor(dotnet): report managed reader defects through invariantViolation

The managed readers threw plain TypeError and RangeError for states their
callers rule out: an allowance or method page outside the input schema, a
missing snapshot after identity admission, and a missing CLI header after
the caller already returned for it. These are REA defects, not malformed
targets, so they throw invariantViolation. The two CLI re-checks are gone:
the callers pass the CLI header they already narrowed. The dotnet
parser-plain-throw baseline drops from 6 to 0.

* refactor(providers): refuse unusable tool paths and record marker mismatches without throwing to their own catch (#11031)

* refactor(firmware): refuse unusable tool paths without throwing to its own catch

resolveFirmwareCommand threw a plain TypeError for a configured engine or
limiter path that is not a regular file, only to catch it in the next line
and wrap it as an unavailable capability. A helper now returns why a path
cannot run, and each caller builds the one refusal directly. Refusal
reasons are unchanged; the directory tests now pin them exactly. The
firmware parser-plain-throw baseline drops from 2 to 0.

* refactor(pwndbg): refuse unusable debugger paths without throwing to its own catch

inspectPwndbgCore threw plain Errors for a relative or wrong-kind
configured path only to catch them and wrap them as an unavailable
capability. A helper now returns why a path cannot be used, with any host
error as its cause, and the loop builds the one refusal. Cancellation
between host checks still propagates. Refusal reasons are unchanged. The
PwndbgCoreContext parser-plain-throw baseline entry is removed.

* refactor(pwntools): record a mismatched failure marker without throwing to its own catch

readPwntoolsFailureEvidence threw a plain Error when the private bridge
marker disagreed with the exit status, only to catch it and record its
message as the marker failure. The mismatch is now recorded directly. The
recorded evidence is unchanged; a unit test pins both the matching and
the mismatched marker. The PwntoolsResourceLimits parser-plain-throw
baseline entry is removed.

* fix(android): report an unobserved Java probe as a REA defect, not a Java refusal (#11032)

* fix(android): report an unobserved Java probe as a REA defect, not a Java refusal

When runOwnedCommand resolved without observing the completed run, the
probe threw a plain Error that its catch then classified as a Java
runtime refusal, telling the caller to review their launcher. The state
is a broken internal assumption, so the probe throws invariantViolation
and projects it as a jadx adapter failure. The JadxJavaProbe
parser-plain-throw baseline entry is removed.

* refactor(native): report native provider defects through invariantViolation (#11033)

* refactor(native): report native provider defects through invariantViolation

Native providers threw plain Error and TypeError for states their own
earlier steps rule out: a client with no target, a measured slice with no
selection, a read longer than requested, a decoder command without its
runtime, an LLDB root released before its children, and an IPS integer
fact from another reader. These are REA defects, so they throw
invariantViolation. NativeUiInterruptionControl no longer needs a throw:
recording the deadline interruption returns the interruption its error is
built from. Six native parser-plain-throw baseline entries drop.

* refactor(native): report malformed lipo, swift-demangle and UI helper output through ParseFailure (#11034)

* refactor(native): report malformed lipo and swift-demangle output through ParseFailure

The lipo and swift-demangle output parsers threw plain TypeError for
output that breaks their framing, and the lipo result classifier caught
every TypeError as malformed output, so a host TypeError from a REA defect
was reported as lipo output. Both parsers now throw malformedInput with
their own format, and the lipo classifier accepts ParseFailureError
instead of TypeError. Messages and the consumers' output and format
failure categories are unchanged. Four native parser-plain-throw
baseline entries drop.

* refactor(native): report the UI helper's non-JSON output through ParseFailure

The native UI helper runtime threw a plain Error when the helper's
stdout was not JSON. It now throws malformedInput with its own format and
the JSON failure as cause. The observation boundary still reports it as a
native-macos adapter failure with the same reason. The
NativeUiHelperRuntime parser-plain-throw baseline entry is removed.

* refactor(verify): keep process-lifecycle owners out of the parser plain-throw guard (#11035)

* refactor(verify): keep process-lifecycle owners out of the parser plain-throw guard

JadxWorkspaceRoot and PwntoolsDecoder sit in parser folders but own a
private helper process; their remaining plain throws report the helper's
startup, handshake and cleanup outcomes, not parse faults. The guard now
names them as process-lifecycle owners and leaves them out, so their
five baseline entries drop without inventing parse errors for process
failures.

* refactor(parsers): report the remaining artifact, il2cpp, model and domain defects through invariantViolation (#11036)

* refactor(parsers): report the remaining artifact, il2cpp, model and domain defects through invariantViolation

Nine plain Error, TypeError and RangeError throws in parser folders
reported broken internal assumptions or caller contracts (an
uninitialized ZIP or DMG reader, a missing scan result, a non-byte
stream chunk, an invalid traversal or marshal limit, a header count or
linking fact the reader itself should have produced, a misordered
Safetensors budget, an append after fingerprinting). None is an input
fault, and no catch site depends on their class. They now throw
invariantViolation with the same messages, and their parser-plain-throw
baseline entries drop.

* refactor(evidence): refuse an earlier Evidence format through ParseFailure (#11037)

* refactor(evidence): refuse an earlier Evidence format through ParseFailure

UnsupportedEvidenceFormatError restated the ParseFailure algebra as its
own TypeError subclass. The format owner now throws unsupportedInput for
the evidence-bundle format at the evidence_format field and exports the
predicate its consumers use. The bundle reader's unsupported-format kind
and message are unchanged. The snapshot reader surfaced only TypeError
messages, so it now also surfaces the format refusal and keeps its
recreate guidance; a boundary test pins that.

* refactor(native): report Mach-O reader and inspection defects through invariantViolation (#11042)

The Mach-O inspection plan reads the lipo, nm and dyld_info captures it
just ran, the dispatch metadata reader is called only for Mach-O
executables, and the command-name reader takes limits its callers
derive. A missing capture, a non-Mach-O target or a malformed limit is a
REA defect, not an input fault, so these six sites throw
invariantViolation with their existing messages instead of TypeError.
No catch site depends on their class. Their parser-plain-throw baseline
entries drop.

* fix(artifacts): join the Interface Builder cleanup failure to a catalog defect

When catalog work failed while the concurrent Interface Builder reader
failed to close, analyzeAppleAssetCatalogs built the primary through
translateArtifactFailure before joining cleanup. That mapper now rethrows
an InternalInvariantError, so a defect skipped the join and the reader's
retained resources were lost. The defect now becomes the artifact graph
provider's adapter failure at that site and joins the cleanup failure as
before.
2026-11-15 19:35:22 +08:00
morluto 530faf43bd refactor(process): take the host from HOST_PLATFORM and require the platform in process helpers (#11038)
* refactor(process): take the host from HOST_PLATFORM and require the platform in process helpers

* refactor(process): require the settlement platform and pass it in string-embedded ownership checks
2026-11-15 19:35:03 +08:00
morluto 0d86953c32 feat(evidence): name a standalone Evidence record with detected_kind on every bundle refusal (#11039)
The file reader, inline bundle reader and investigation ledger refused a
single Evidence record with only a message saying so. The file reader's
wrong-kind failure now carries detected_kind "evidence-record" (as it
already did for analysis snapshots), and the inline and ledger refusals
carry it on their input issue through one domain helper. Tests assert the
field instead of the sentence. The inline CLI/MCP case now asserts full
parity: since compact input schemas, MCP reaches the same domain reader.
2026-11-15 19:34:40 +08:00
morluto bcb068cc04 fix(rizin): refuse a start after shutdown began as an owner-closed cancellation (#11041)
OwnedDebuggerSessionRegistry owns the rule: closeStarts refuses later starts
as owner-closed (a caller that already aborted keeps its own reason) and
aborts in-flight ones. Rizin no longer reports an inferior_tty input error
for a start made after closeAll began; GDB's local copy of the rule is gone.

User-visible: start_rizin_debug_session called while REA is shutting down
now fails as a cancellation (owner closed) instead of an input error naming
inferior_tty, matching start_gdb_session.
2026-11-15 19:34:18 +08:00
morluto 423489a761 feat(evidence): name the side and issue path of comparison source integrity failures (#11049)
* feat(process): name the side and issue path of process-capture integrity failures

compare_process_captures reported which side failed validation, and where,
only in the message. EvidenceIntegrityError now carries an optional side
and issue path, projected as details.side and details.issue_path, and the
CLI's malformed-capture issue carries observed.issue_path. The first issue
path comes from the same selection describeValidationFailure names.

* feat(evidence): report source authentication issue paths from the Evidence owner

readEvidence and readImmutableEvidence now set the record-relative issue
path on their integrity failure, and readEvidencePayload sets it when the
caller says where the payload sits. compare_functions passes its side, so a
tampered function source reports details.side and details.issue_path like
compare_process_captures. Process capture result issue paths are now
relative to the Evidence record (normalized_result.*) on both adapters.
2026-11-15 19:33:47 +08:00
morluto 3bbdec3fc8 test(artifacts): compare ASAR block-integrity limitations with their owning constant (#11061)
Two tests matched three fragments of one limitation sentence, twice for the
same row. They now compare the row with BLOCK_INTEGRITY_LIMITATION from
AsarListing, beside the existing declared-integrity and hash-status facts.
2026-11-15 19:32:40 +08:00
morluto b3cba0d762 test(process): check each capture provider field once instead of per side (#11067)
The provider admission matrix ran each of the three provider fields for
both sides through CLI and MCP. Alternating sides keeps every field and
both side labels, and removing the version check or the side label still
fails a remaining row.
2026-11-15 19:31:44 +08:00
morluto 7181e7f515 perf(mcp): advertise large output union alternatives as shared definitions (#11005)
* perf(mcp): advertise large output union alternatives as shared definitions

inspect_analysis_view's advertised output schema held every view result inline in one union definition, which the MCP SDK's default validator compiled into one function: about 1.7 s CPU and 500 MiB peak. Large union alternatives now become definitions of their own and identical definitions are merged, at the owning output projection. Tests that check results against the advertised output contract now compile that schema instead of a separate inline projection, which overflowed the main-thread stack under forked workers.

* perf(tests): run source test projects in forked workers (#11016)

Thread workers of the source lane share one process, which grew to 3.8-5.0 GiB; forked workers keep each heap separate so the largest process stays near 1.2-1.8 GiB.

* test(mcp): select the analysis view contract from its family
2026-11-15 06:13:21 -05:00
morluto 557bf41c2c refactor(macho): select slices through one owner with typed refusals (#10874)
* refactor(macho): name load-command codes through the shared LC table

* refactor(macho): name version-min and string commands through the shared tables

* refactor(parsers): report XML, web artifact and Mach-O failures through ParseFailure (#10760)

* refactor(parsers): report XML, web artifact and Mach-O failures through ParseFailure

* refactor(xml): type XML parse failures as malformed or unsupported only

* refactor(parsers): catch per-format document and source failure classes (#10794)

* refactor(parsers): report apktool strings and XML plist source failures through ParseFailure

* refactor(apktool): drop the unused ParseFailureError import

* refactor(native): select ELF sections through the keyed owner

* test(native): pin returned exports and versions for repeated version sections

* refactor(nativeaot): read neither of repeated frozen object regions (#10790)

* refactor(nativeaot): read neither of repeated frozen object regions through the keyed owner

* refactor(parsers): retire every first-match section lookup (#10793)

* fix(nativeaot): report an ambiguous frozen object region as partial (#10812)

* refactor(native): select program headers and load commands through the keyed owner (#10827)

* refactor(native): select the GOT base and dependency segments through the keyed owner (#10847)

* refactor(native): select the GOT base and dependency segments through the keyed owner

resolveGotBase selects the SHT_DYNAMIC section and the allocated .got.plt/.got sections with uniqueKeyed, so a repeat leaves the GOT base unknown instead of taking the first match; elfDependencies reads PT_LOAD segments through the same keyed program table as PT_DYNAMIC. A new keyed-first-match guard recognizes a find/findLast or filter-then-[0] whose predicate compares a key field, whatever the table is called; existing matches are baselined. The unwind fixture can declare PT_DYNAMIC headers and repeated .dynamic/.got.plt sections.

* test(native): pin the PT_DYNAMIC path and destructured keyed lookups

The keyed-first-match guard moves to scripts/verify/keyed-first-match.mjs with focused tests, and now recognizes a destructured key in any position and a destructured filter predicate. The single PT_DYNAMIC unwind test declares .dynamic as SHT_PROGBITS so only the program header supplies DT_PLTGOT, and asserts the resolved ranges.

* fix(macho): refuse repeated chained-fixup and dyld-info tables (#10850)

parseMachoLayout kept the last LC_DYLD_CHAINED_FIXUPS range and merged the rebase/bind streams of every LC_DYLD_INFO(_ONLY) command. A repeat now refuses the slice as malformed. NativeMachoInspection's chained import-name reader already declined repeated LC_DYLD_CHAINED_FIXUPS; that rule is now covered by a test.

* refactor(parsers): retire the Mach-O, source-map, NDJSON and managed-graph failure classes (#10818)

* refactor(parsers): retire the Mach-O, source-map, NDJSON and managed-graph failure classes

* refactor(plist): report property-list and keyed-archive failures through ParseFailure (#10822)

* refactor(plist): report property-list and keyed-archive failures through ParseFailure

* refactor(kuna): report Kuna document failures through ParseFailure (#10824)

* refactor(kuna): report Kuna document failures through ParseFailure

* test(kuna): pin the limit and malformed facts of Kuna document failures

* refactor(plist): report plist decode budgets through ParseFailure (#10842)

* refactor(plist): report plist decode budgets through ParseFailure

The plist preflight owner throws a ParseFailure limit (plist-decode-bytes) for its byte budget, the shared PlistNestingLimitError for nesting depth, and a malformed failure for a reference cycle, instead of a per-format PropertyListDecodeBudgetFailure whose meaning each caller re-mapped through a fail callback. The fail option, PropertyListDecodeBudgetFailure and interfaceBuilderDecodeBudgetFailure are removed; the parser-format-failure-class baseline reaches zero.

* refactor(plist): report keyed-archive allowances as typed limit failures (#10844)

keyedArchive.ts and decodeKeyedArchiveBytes throw decodeLimit parse failures (keyed-archive-bytes, -object-table-entries, -values-per-object, -reference-edges) and malformed parse failures instead of plain RangeError/TypeError. KeyedArchiveReader no longer classifies every RangeError as a limit: only a limit parse failure or a host stack/capacity limit is one. The unreachable 128-level scan nesting check is removed; projectPlistValue already refuses the archive first.

* test(plist): expect the nesting limit as its ParseFailure carrier

* fix(verify): count a filter index only on the filter's own result

* refactor(macho): select slices through one owner with typed refusals

* refactor(keyed): select FIT properties and certificate extensions through uniqueKeyed

* refactor(keyed): key each FIT node once, pin repeated FIT properties and subjectKeyIdentifiers, and type the unknown Mach-O header as unsupported

* refactor(plist): report plist and NIBArchive input faults through ParseFailure and defects through invariantViolation (#10895)

* refactor(plist): report plist and NIBArchive input faults through ParseFailure and defects through invariantViolation

* fix(plist): report a cut-short NIB header or plist length record as truncated

* fix(native): project the selected Mach-O's parse failures through the shared owner

nativeMachoSourcePrimary mapped every non-limit ParseFailureError to
AnalysisOutputError, so a malformed or truncated selected Mach-O was
reported as unreadable tool output and an unsupported one lost its
category. It now uses parseFailureAnalysisError like the ELF, Go and
other byte-format providers: malformed and truncated bytes are an
invalid path, unsupported bytes an unsupported target, and a limit
still falls through to its producer's budget.
2026-11-15 18:36:30 +08:00
morluto 54230c2e7b refactor(setup): require the platform for skill destinations instead of reading the host (#11006)
* refactor(setup): require the platform for skill destinations instead of reading the host

The setup skill owner defaulted seven entry points to process.platform. Every
production caller (setup host, doctor, uninstall, update maintenance) already
passes the platform it was composed with, so the defaults only let a caller
silently mix the host REA runs on with the one it asked about. The platform is
now a required parameter; tests name the platform they exercise, the setup
fixture passes its declared host, and the ambient-read baseline drops
SetupSkill.ts (7 to 0).

* test(setup): expect the fake host's POSIX skill paths in planning tests
2026-11-15 18:35:01 +08:00
morluto 482a98a95b perf(test): run CI change-scope cases concurrently in workspace-owned repositories (#11009)
* perf(test): run CI change-scope cases concurrently in workspace-owned repositories

* test(ci): own the change-scope seed workspace from creation so failed setup is removed
2026-11-15 18:34:39 +08:00
morluto f46cd6770c test: advance the Rizin and build computer fixed waits instead of sleeping through them (#11026) 2026-11-15 18:33:52 +08:00
morluto 5cfaac0834 refactor(process): require the platform for environment snapshots instead of reading the host (#11028) 2026-11-15 18:33:23 +08:00
morluto 4126698407 feat(mcp): project compact input schemas into the portable advertised dialect (#10632)
* test(mcp): check advertised schemas against a portable dialect in every profile

* fix(mcp): re-emit advertised patterns in a portable regex grammar

* fix(mcp): present discover_tools input through the selected profile

The hot-cold catalog registered discover_tools directly with the SDK, so
the default compact input profile still advertised its canonical schema,
including page-size defaults clients apply on their own (#5322). Route
every listed input schema, transport discovery included, through one
server-owned presentation, and pass the selected presentation to the
deferred server, which serves recorded tool schemas but not discovery.

* fix(mcp): keep the Evidence union on described borrowed inputs

borrowEvidenceInput installed its advertised projection on one schema
instance. A call site's .describe() rebuilds the schema from its
definition, so match_native_functions advertised left and right as
unconstrained values in every input profile. Install the projection in
the borrowed parser's constructor so derived schemas keep it, for both
borrowed inputs and borrowed arrays.

* fix(mcp): validate advertised dialect by selected profile

* fix(mcp): preserve schema widening through references

* feat(mcp): project compact input schemas into the portable advertised dialect

One projection (advertisedDialectProjection.ts) owns the compact input
advertisement: one type per node, items on every array, string literals
as enums, oneOf widened to anyOf, allOf merged, tuples as element unions,
exclusive bounds made inclusive, and every keyword outside the dialect
dropped. Each rewrite is exact or widening; server validation is
unchanged. The root description and sentences for dropped cross-field
constraints move to the tool description, which Codex and Zed keep.

The compact baseline falls to nullable values only (29), which the
dialect cannot express without narrowing an input. The gateway shape
repairs (typeArrayItemUnions, hasGatewayIncompatibleShape), the second
dependentRequired-to-prose copy and the strict-gateway test denylist are
removed; the dialect checker gates the inline presentation instead.

* fix(mcp): refuse zero-padded repeat bounds and record host-specific path patterns

* fix(mcp): keep projected schemas permissive

* fix(mcp): keep references valid when presentation widens their targets

* fix(mcp): keep a single union branch's closure scoped to its properties

* fix(mcp): keep an allOf member's tuple tail with its own positions

* fix(mcp): relax references into negations and conditionals presentation may remove

* test(mcp): select the borrowed Evidence contract from its family

* fix(mcp): keep astral literals and dynamic references widening in every mode

* fix(mcp): omit astral literals and relax untracked references when presentation widens

* fix(mcp): read reference pointers by schema position, not segment spelling

* fix(mcp): protect references into collapsing boolean unions and keep referencing branches out of the collapse

* fix(mcp): keep reduced schemas open when their root admits names by pattern

* test(mcp): keep the reduced-schema pattern case outside the size-limited block

* fix(mcp): omit patterns whose classes name astral characters

* fix(mcp): decide union collapse and pointer protection from canonical branches

* docs(mcp): document non-u astral matching and pin BMP agreement across V8 modes

* fix(mcp): keep prototype-named properties as own properties in the dialect projection

* test(mcp): expect the advertised base64 schema to omit the unbounded pad-bit probe

* fix(mcp): protect references into unevaluated keywords lost evaluation removes

* fix(mcp): emit stack-safe classes with non-ASCII members and protect draft-07 reference siblings

* fix(mcp): protect references into literal siblings of relaxed draft-07 references

* feat(mcp): omit output schemas from tools/list by default (#10646)

* feat(mcp): omit output schemas from tools/list by default

No surveyed model API reads an output schema, MCP client validators
repeat REA's own result validation, and complete output schemas were
about 92% of the default tools/list. REA_MCP_OUTPUT_SCHEMA_PROFILE now
resolves to off through one owner (resolveMcpOutputSchemaProfile);
compact and full remain selectable. structuredContent is still
delivered, and the deferred server serves both off and full from the
recorded catalog with the matching recorded identity.

Tests and artifact verifiers that validate results against complete
advertised output contracts select full explicitly.

* fix(mcp): select schemas in verification workflows

* fix(mcp): select complete output schemas in the Electron verifier

* fix(mcp): select complete output schemas in standalone verifier probes

* fix(mcp): select complete output schemas in every verifier transport

* test(mcp): select full output schemas in packet and signal schema checks

* fix(mcp): validate the doctor probe reply against the canonical output contract (#10655)

* fix(mcp): validate the doctor probe reply against the canonical output contract

* test(mcp): replay doctor probe validation under every output profile

* test(mcp): accept typed-branch nullable unions in the advertised dialect (#10649)

* test(mcp): select full output schemas in HTTP session workflows

* fix(mcp): keep the Evidence union on described borrowed inputs (#10597)

* fix(mcp): keep the Evidence union on described borrowed inputs

borrowEvidenceInput installed its advertised projection on one schema
instance. A call site's .describe() rebuilds the schema from its
definition, so match_native_functions advertised left and right as
unconstrained values in every input profile. Install the projection in
the borrowed parser's constructor so derived schemas keep it, for both
borrowed inputs and borrowed arrays.

* test(mcp): select the borrowed Evidence contract from its family

* docs(mcp): admit baseline growth only for restored contract declarations

* fix(mcp): state reduced multi-type properties as typed branches, not type lists

The reduced fallback derived a property's types and emitted them as a type
list, which Gemini, Moonshot and Ark reject. A property whose canonical
schema is anyOf string and number therefore traded a typed union for a
rejected construct when the inline projection fell back. Emit one type, or
anyOf typed branches, so [T, null] becomes the dialect's nullable union.
2026-11-15 18:26:13 +08:00