Commit Graph
7749 Commits
Author SHA1 Message Date
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 598a0c4034 feat(providers): let runOwned's deadline timeout carry the provider's recovery (#11089)
* feat(providers): let runOwned's deadline timeout carry the provider's recovery

A deadline-caused AnalysisTimeoutError from runOwned named only the operation and its work budget, so providers whose timeouts carry their own guidance (LLDB, Hopper, Kuna, Rizin) could not adopt the deadline without losing it. runOwned now takes an optional timeoutRecovery (user message, remediation, accepted setting limit and diagnostics) and the timeout it builds carries it; its diagnostics are added to the replaced outcome's.

* test(providers): report an already-elapsed budget in the recovery diagnostics test
2026-11-15 20:34:47 +08:00
morluto 0b95e55655 refactor(verify): give the failure-retaining Ghidra lanes' workspaces to VerifierFixture (#11097)
The m68k, MIPS, MIPS16 shared, MIPS relocatable, PowerPC, RISC-V and DOL lanes kept their mkdtemp workspace for diagnosis whenever verification or cleanup failed, and removed it with rm after success. VerifierFixture now allocates the workspace (a plain root: Ghidra children use it as TMPDIR). Every failure branch that names the retained workspace records that reason with fixture.retain, and success releases the fixture. The verifier-fixture-root baseline shrinks by 7.
2026-11-15 20:32:59 +08:00
morluto 5b5ca68bb3 refactor(verify): release the agent-experience run root through VerifierFixture (#11103) 2026-11-15 20:32:38 +08:00
morluto 144d6f959f fix(agentenv): record a verified teardown through the long-run release claim
#11095 called ProviderCleanupError.resolveCleanup(), which #10855 removed,
so main stopped typechecking. Each retained failure's release claim already
reads job membership, so clearing the jobs after a verified computer
teardown is what releases them.
2026-11-15 07:23:59 -05:00
morluto 97ffa67f84 refactor(setup): require the platform in client-layer readers and private-profile helpers (#11074)
* refactor(setup): require the platform in client-layer readers and private-profile helpers

* refactor(setup): drop the client-layer readers' unused host import
2026-11-15 20:21:04 +08:00
ce9bdc526a fix(agentenv): settle cancelled long runs and preserve teardown receipts (#11095)
Bound AgentEnv long-run cancellation, preserve partial command facts and
complete computer teardown before resolving retained jobs. Includes the
supervisor and cleanup implementation from #10675 plus polled-receipt and
explicit-unknown exit-status fixes.

Validation: 14 Python controller tests, 31 host tests and 30 compiled
MCP/lifecycle tests passed. Static, documentation and discovery checks
passed. Real AgentEnv deployment was not exercised: runner unavailable.

Fixes #10676
Related: #10675

Co-authored-by: Adolanium <94890352+Adolanium@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-15 20:20:47 +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 c813adb331 fix(verify): expect the current instruction schema to refuse the removed undecodable status (#11092)
verify-real-ghidra-com asserted that an unlisted-address record relabelled with the legacy undecodable status still parsed. #10946 removed that value with the other historical native enum values, so the lane failed on main after a correct not-in-listing result. The verifier now checks that the producer's record parses and that the relabelled record is refused.
2026-11-15 20:19:07 +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 c043e4e3a8 refactor(firmware): compose workspace and publication cleanup failures through the shared owner (#11094)
Three firmware sites built analysisErrorWithCleanupFailure(...).bindCleanupDependencies(...) by hand. They now use analysisErrorWithCleanupDependencies, which completes once the primary's and the cleanup's records both release. The phase-interruption site binds only the cleanup today; its primary is a fresh cancellation or timeout without cleanup, so the shared owner's binding is equivalent. The cleanup-dependency-binding baseline shrinks by 3.
2026-11-15 20:18:21 +08:00
morluto 4c375b82c8 refactor(doctor): compose system diagnostics from one host platform (#11096) 2026-11-15 20:17:55 +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
morluto 12a0f4a94f fix(il2cpp): read ELF hash-table words through BinaryReader (#11083) 2026-11-15 20:05:19 +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 cabd56ea71 refactor(domain): give strict UTF-8 decoding one owner with an explicit BOM policy (#10728)
* refactor(domain): give strict UTF-8 decoding one owner with an explicit BOM policy

* fix(domain): keep the leading-BOM behaviour every migrated UTF-8 decoder had

* refactor(domain): read the domain layer's strict UTF-8 through the BOM-policy owner (#10803)

Twelve domain decoders built their own fatal TextDecoder with ignoreBOM: true. They now use strictUtf8Decoder("preserve"), which keeps that behaviour: each reads bytes where a leading U+FEFF is content (binary-format names and fields, JSON and XML read byte-for-byte, recorded bodies, registry seeds whose kept bytes are re-emitted). The counted baseline drops from 74 to 62.

* refactor(artifacts): read artifact readers' strict UTF-8 through the BOM-policy owner (#10871)

Fourteen artifact reader modules (17 sites) built their own fatal TextDecoder. They now take it from strictUtf8Decoder with the policy each had: every one kept a leading BOM (ignoreBOM: true), so they use "preserve"; JavaScriptArtifactFiles keeps it for JavaScript and HTML entries and strips it otherwise, as before. The strict-utf8-decoder baseline drops those 17 sites.

* refactor(domain): drop TextDecoder imports left by the strict UTF-8 migration

* refactor(application): read MCP registration metadata through the strict UTF-8 owner

* refactor(formats): read binary-format readers' strict UTF-8 through the BOM-policy owner (#10903)

* refactor(formats): read binary-format readers' strict UTF-8 through the BOM-policy owner

* refactor(formats): drop the model readers' unused TextDecoder imports

* refactor(browser): read browser capture and fetch strict UTF-8 through the BOM-policy owner (#10935)

* refactor(browser): read browser capture and fetch strict UTF-8 through the BOM-policy owner

* refactor(native): read LLDB and Mach-O name strict UTF-8 through the BOM-policy owner (#10936)

* refactor(native): read LLDB and Mach-O name strict UTF-8 through the BOM-policy owner

* refactor(process): read MCP and NDJSON transport frames' strict UTF-8 through the BOM-policy owner (#10937)

* refactor(process): read MCP and NDJSON transport frames' strict UTF-8 through the BOM-policy owner

* refactor(application): read JSON and file inputs' strict UTF-8 through the BOM-policy owner (#10938)

* refactor(application): read JSON and file inputs' strict UTF-8 through the BOM-policy owner

* refactor(providers): read IDA, Electron, Ghidra and reference strict UTF-8 through the BOM-policy owner (#10939)

* refactor(providers): read IDA, Electron, Ghidra and reference strict UTF-8 through the BOM-policy owner

* refactor(evm): read the hex bytecode carrier's strict UTF-8 through the owner and refuse new local decoders (#10940)

* refactor(evm): read the hex bytecode carrier's strict UTF-8 through the BOM-policy owner

* chore(architecture): match local strict UTF-8 decoders in any option order
2026-11-15 19:54:39 +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 5d1a47768a feat(artifacts): surface the named integrity check in public artifact errors (#11052)
* feat(artifacts): surface the named integrity check in public artifact errors

ArtifactOperationError now carries integrityCheck from the reader failure
and projects it as details.integrity_check, including with artifact
integrity details. Rebuild sites copied a different subset of reader facts
each (ProcessCli.artifactFailureFacts, withCleanup, desktop, file-operation
cleanup, resource export, DMG inventory, safe output tree); they now spread
ArtifactReaderFailure.primaryFacts(), so a new reader fact has one owner.
ArtifactIntegrityCheck moves to the domain beside ArtifactAdmissionLimit.

* test(artifacts): check rebuilt cleanup failures keep the named integrity check
2026-11-15 19:53:05 +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 ecab89f3e6 fix(native): report a signature inspection defect as an adapter failure (#11069)
withSignatureTarget wrapped every throw from the inspection callback as
AnalysisOutputError("Native output parsing failed"), so an
InternalInvariantError (a REA defect) was reported as malformed native
command output. A defect now leaves the owned work, so runOwned releases
the private signature copy first and reports the defect as the native
provider's adapter failure. If releasing the copy also fails, the defect
stays primary and the cleanup failure joins it through the shared cleanup
composition (analysisErrorWithCleanupFailure + bindCleanupDependencies).
2026-11-15 19:52:19 +08:00
morluto 1b8dc202d1 refactor(verify): count plain throws in parser-folder process-lifecycle owners (#11071)
JadxWorkspaceRoot and PwntoolsDecoder are exempt from parser-plain-throw because their plain throws report a private helper's process failures. The exemption left later throws in those files unchecked. A counted lifecycle-plain-throw guard now records their current throws (4 and 1); the baseline may only shrink, so a parse fault or defect added there must use ParseFailureError or invariantViolation.
2026-11-15 19:51:55 +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 998faa4118 refactor(ida): launch stdio registrations through the owned MCP transport (#10739)
* refactor(ida): launch stdio registrations through the owned MCP transport

IDA stdio servers now run as an owned process group (POSIX) or job (Windows) through OwnedMcpStdioTransport instead of the SDK's unowned child. Windows bare commands resolve against the registration PATH/PATHEXT and .cmd/.bat launchers run through the shared WindowsBatchCommand cmd.exe invocation. The frame limit is classified from the transport's typed OwnedMcpOutputLimitError instead of parsing SDK error text, detach no longer reaches into the SDK's private _process, and the per-operation byte budget is optional.

* fix(ida): keep owned stdio cleanup observable and resolve Windows interpreter paths

- StructuredContentMcpClient observes SDK-initiated closes like OwnedMcpClient,
  so a failed handshake with incomplete owned cleanup cannot reject unhandled.
- OwnedMcpStdioTransport attaches an owner-redacted captured output to cleanup
  failures; IDA passes its retained stderr tail.
- findWindowsExecutable resolves path-qualified commands at their own location
  with PATHEXT; IDA routes every Windows registration through it and refuses
  non-drive paths with an actionable remedy before the owned launcher.

* fix(process): end a stalled stdio launch when the owned MCP transport closes

OwnedMcpStdioTransport passes the launcher a signal it aborts with OwnerCancellation.ownerClosed when it stops, so IDA's Windows PATH/PATHEXT lookup stalled on an unreachable directory no longer holds close() open; IDA reports the interrupted lookup by its abort reason. The IDA environment tests use createTestTempDirectory for owned temporary roots.

* fix(ida): bound the Windows command lookup by the registration timeout

The PATH/PATHEXT search for a stdio registration now runs under a ProviderStartupDeadline of the registration's timeoutMs that also follows the transport's close signal. A lookup stalled on an unreachable PATH directory ends as an AnalysisTimeoutError (phase windows_path_lookup) instead of leaving connect() pending, matching Apktool's Windows lookup.

* test(ida): stub REA's PATH again now that process inspection runs ps from fixed locations

* test(ida): pass the required registration protocol in owned-stdio fixtures

* fix(ida): report an uninspectable Windows command candidate with its path and code

* refactor(ida): take the host platform from composition, not an ambient read

* fix(verify): pass the host platform to the real IDA upstream check

* fix(process): keep a failed launch's preparation cleanup owned by the stdio transport's close

OwnedMcpStdioTransport discarded the launch rejection before a supervisor
existed, so an ownership-helper preparation that transferred a cleanup owner
(or reported a cleanup failure without one) was dropped and close() reported
success. The transport's close now closes each transferred owner from
processInspectionCleanupFailures, and reports a ProviderCleanupError naming the
preparation resource until it does; a later close retries it.
2026-11-15 19:38:13 +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 a6cbe8b12a feat(providers): bound runOwned work and close by one deadline (#10996)
* feat(providers): bound runOwned work and close by one deadline

* fix(providers): read an expired budget at settle time and keep a caller abort during cleanup

* fix(providers): follow a late close rejection's owner and keep captured output through budget replacements

* fix(providers): track budget expiry apart from the caller, keep replaced work's cleanup, treat empty leases as none

* fix(providers): classify thrown abort reasons, keep recorded failures and follow a late close owner's resources

Work that throws its signal's abort reason (throwIfAborted) is now a
cancellation, so an elapsed budget becomes a timeout and a caller abort stays a
cancellation. A budget cancellation replaced by a timeout or a caller
cancellation keeps the secondary failures it recorded. A late close rejection
that names its own owner now also supplies the retained failure's resources.

* fix(providers): classify only the budget's own abort reason, and follow only a late rejection that reports its own cleanup

A caller's abort reason is opaque, so a thrown value equal to it stays the
work's failure with the caller's cancellation retained; only the budget's typed
OwnerCancellation.deadlineElapsed marks a thrown abort as the budget's. A late
close rejection without unreleased cleanup of its own (such as a cancellation)
no longer confirms release or replaces the fallback resource.

* fix(providers): follow a late close owner that already released, by cleanup ownership not completion

AnalysisError.carriesCleanup owns whether an error ever owned cleanup, apart
from whether it finished. runOwned's late close rejection uses it, so a
producer error released by a concurrent retry still confirms release, while a
cancellation without cleanup keeps the close's lease pending. Uninstall and
SetupCleanupOwner used the same four-clause predicate locally; they now read
the owner.
2026-11-15 19:36:38 +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 7f2141b568 refactor(ghidra): take the host from HOST_PLATFORM instead of reading process.platform (#11054) 2026-11-15 19:33:25 +08:00
morluto c0dd084376 refactor(verify): give seven verifier directories' fixture roots to VerifierFixture (#11055)
* refactor(verify): give the android, electron, firmware, ghidra-seed, harmony, inspector and managed verifiers' roots to VerifierFixture

Each root is removed only after the MCP clients, transports, owned processes
and file handles that may still use it confirm cleanup; a failed cleanup
retains and names the root. The verifier-fixture-root baseline shrinks from
90 files / 95 sites to 80 / 85.

* fix(verify): keep nested Electron and inspector children owned by the fixture, and plain roots where Windows creates directories

The Electron cancellation CLI and application, and each inspector target,
now register their stop with the fixture (onceConfirmed keeps the early stop),
so the root is removed only after they confirm. The android and inspector
verifiers keep a plain directory as before: tools create directories beneath
the root, which a private root blocks on Windows.
2026-11-15 19:33:03 +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 1151763582 fix(contracts): remove the duplicate advertisedOutputJsonSchema declaration (#11057)
#11005 left two exported declarations of advertisedOutputJsonSchema in
src/contracts/toolSchemaMetadata.ts, so main no longer typechecks
(TS2451). The second declaration takes an optional target that defaults
to the first one's fixed draft-2020-12, so it covers every caller. The
first is removed and its documentation moves onto the remaining one.
2026-11-15 06:21:39 -05:00
Ysopwnableandgensokyo-dev 08420be3bd fix(ghidra): explain Program reuse after startup timeouts
Explain Program reuse in cold-start timeout recovery and distinguish query
snapshots from saved Ghidra analysis. Preserve explicit startup-mode selection,
first-analysis requirements, and custom-analyzer choices.

Refs #10365

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-15 19:14:41 +08:00