Commit Graph
205 Commits
Author SHA1 Message Date
morluto 5cfaac0834 refactor(process): require the platform for environment snapshots instead of reading the host (#11028) 2026-11-15 18:33:23 +08:00
morluto 4126698407 feat(mcp): project compact input schemas into the portable advertised dialect (#10632)
* test(mcp): check advertised schemas against a portable dialect in every profile

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

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

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

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

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

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

* fix(mcp): preserve schema widening through references

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

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

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

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

* fix(mcp): keep projected schemas permissive

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix(mcp): select schemas in verification workflows

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The reduced fallback derived a property's types and emitted them as a type
list, which Gemini, Moonshot and Ark reject. A property whose canonical
schema is anyOf string and number therefore traded a typed union for a
rejected construct when the inline projection fell back. Emit one type, or
anyOf typed branches, so [T, null] becomes the dialect's nullable union.
2026-11-15 18:26:13 +08:00
morluto 78fd365bdd refactor(macho): name load-command codes through the shared LC table (#10714)
* refactor(macho): name load-command codes through the shared LC table

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

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

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

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

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

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

* refactor(apktool): drop the unused ParseFailureError import

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

nativeMachoSourcePrimary mapped every non-limit ParseFailureError to
AnalysisOutputError, so a malformed or truncated selected Mach-O was
reported as unreadable tool output and an unsupported one lost its
category. It now uses parseFailureAnalysisError like the ELF, Go and
other byte-format providers: malformed and truncated bytes are an
invalid path, unsupported bytes an unsupported target, and a limit
still falls through to its producer's budget.
2026-11-15 18:24:35 +08:00
morluto 422cff00ab refactor(process): give POSIX PATH search order one owner (#10891)
* refactor(process): give POSIX PATH search order one owner

Program lookup, the packet-tool locator and Jadx's Java lookup each split
PATH and resolved empty and relative entries themselves. They now take the
candidates from posixPathCandidates in src/process/executableSearch and keep
only their own probe and failure reporting. A counted architecture guard
keeps new PATH splits from returning; the remaining Windows and Ghidra
searches are baselined.

* refactor(process): move the colon-split PATH searches onto the search owner

REA's launcher-alias lookup and Ghidra's macOS Java lookup split PATH on
":" and skipped relative entries themselves; they now use
posixPathCandidates with relativeEntries: "skip", and the guard counts
colon splits of PATH as well.

* fix(process): produce PATH candidates lazily so an unused entry cannot fail an earlier match

* refactor(process): give Windows PATH/PATHEXT search order the executable search owner (#10916)

* refactor(process): give Windows PATH/PATHEXT search order the executable search owner

* fix(process): keep later PATH entries searchable after a stray quote or a removed working directory

* fix(reverse): refuse a Windows objdump or Rizin command that PATH does not find (#10953)

* fix(reverse): refuse an objdump or Rizin command Windows PATH does not find instead of launching the bare name

* fix(reverse): report a Windows lookup refusal after cancellation and to capability discovery

* fix(desktop): search a Windows host's PATH as Windows does when doctor looks up a program

programOnPath sent every host through the POSIX PATH search, so on Windows a
relative PATH entry reached absoluteCallerPath with platform linux and the
Windows working directory, which resolved against itself until the stack
overflowed. programOnPath now searches PATH and PATHEXT through
windowsPathCandidates on a Windows host, and absoluteCallerPath refuses a
non-POSIX working directory with a TypeError instead of recursing.
2026-11-15 18:24:04 +08:00
morluto e9f9d8b880 fix(rizin): abort in-flight debug session starts when the manager closes (#11020)
OwnedDebuggerSessionRegistry now owns in-flight starts: it relays the
caller's abort, aborts each start as owner-closed on close and waits for it,
and reports the caller's reason when the caller aborted first. GDB's private
start map and Rizin's counter-and-waiters drain are removed; Rizin's closeAll
previously waited for a start without aborting it.
2026-11-15 18:21:09 +08:00
morluto 57f75bb723 refactor(native): build debugger and native cancellations with a required reason (#10599)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* fix(errors): read the reason of any signal-shaped cancellation source

* refactor(native): build debugger and native cancellations with a required reason

Migrate every AnalysisCancelledError construction under src/native,
src/gdb and src/rizin to cancelledBy and delete Rizin's local
rizinCancellation constructor; its reason choice becomes a
CancellationReason. GDB close aborts active startups with
OwnerCancellation.ownerClosed.

* test(ci): cover the counted-baseline comparison behind the architecture guard

* refactor(native): drop an unused cancellation import from native UI observation
2026-11-15 18:07:27 +08:00
morluto f6752bbe2c refactor(process): let debugger session cleanup errors read a confirmed stop (#10805)
* 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 (#10807)

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.
2026-11-15 18:05:34 +08:00
morluto add15d65b3 refactor(verify): give verifier fixture roots one lease on src/process (#10988)
* refactor(verify): give verifier fixture roots one lease on src/process

* fix(verify): keep fixture registration closed, carry the lease on the caught failure, share cleanups once
2026-11-15 18:02:28 +08:00
morluto b3cdbb92ee refactor(browser): build browser cancellations with a required reason (#10581)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(browser): build browser cancellations with a required reason

Migrate every AnalysisCancelledError construction under src/browser,
src/javascript, src/electron, src/inspector and src/captures to
cancelledBy and lower the guard baseline from 671 to 572. Service-worker
setup aborts with OwnerCancellation.ownerClosed on close.

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* fix(errors): read the reason of any signal-shaped cancellation source

* test(ci): cover the counted-baseline comparison behind the architecture guard

* refactor(electron): drop an unused error import from the fuse failure mapper
2026-11-15 17:46:20 +08:00
morluto 26520bd290 refactor(providers): finalize apktool and shared artifact resources through runOwned (#10653)
* refactor(providers): finalize apktool and shared artifact resources through runOwned

runOwned moves to the provider layer so provider sites can use it without depending on application workflows. It gains a completed-value projection (partial) for values that are not themselves partial observations, an optional signal, and cleanupLeases that bind a close failure to the exact leases whose confirmed release resolves it.

ArtifactAnalysisResources.finish is removed; TShark and sigrok finalize through runOwned with the shared release as close. Apktool's hand-written release, cancellation and lease-group composition is replaced by runOwned. A failed primary now keeps a cancellation observed during final cleanup as a secondary failure, as the documented precedence requires.

A counted architecture guard refuses new hand-written completed-result abort reclassification outside runOwned.

* refactor(adb): finalize runtime-root work through runOwned (#10660)

* refactor(adb): finalize runtime-root work through runOwned

The ADB runtime-root finalizer re-derived the precedence by hand and kept its own cancellation-with-cleanup combiner. It now runs through runOwned: the work body maps snapshot failures, close releases the root lease and binds its own cleanup error, and partial projects the completed execution. A cancellation whose cleanup fails is now the shared cleanup composite with the cancellation as its primary, as runOwned produces for every other owner.

* refactor(providers): keep completed values as diagnostics in runOwned and migrate four finalizers (#10691)

* refactor(providers): keep completed values as diagnostics in runOwned and migrate signature, Ghidra, bundle and LLDB finalization

* test(providers): run the lifecycle scenarios over every runOwned adopter shape (#10707)

One table of testing.md lifecycle rows runs over partial, diagnostics, publishing and lease adopters, replacing the hand-written precedence cases it covers.

* chore(lint): move runOwned's classifier baseline entry with the file
2026-11-15 17:44:13 +08:00
morluto b87b318ae7 refactor(wasm): leave WABT cancellation to the shared owners (#10796)
* 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.
2026-11-15 17:42:49 +08:00
morluto 2caedcfb57 refactor(platform): give the host platform one owner and ratchet ambient process.platform reads (#10911)
* refactor(platform): give the host platform one owner and ratchet ambient process.platform reads

* refactor(process): pass the host platform to process sampling instead of reading it ambiently

#10755 added two process.platform reads (ps selection in process sampling and the
desktop build doctor's ps fact) after the host-platform ratchet was measured.
The desktop fact already receives the host platform from doctor; process sampling
now receives it from the capture harness, removing all five ambient reads there.
2026-11-15 17:42:23 +08:00
morluto b655645608 refactor(errors): give caller aborts a typed reason at the adapters (#10633)
* refactor(errors): give caller aborts a typed reason at the adapters

Squashes 908813551f, 87858de4dc (fix(mcp): preserve cancellation reasons in handlers) and fa151dc3f6 (test(server): race handler-entry gates against the triggering tool call) onto main without #10566's pre-squash commits.

* refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters (#10692)

* refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters

* refactor(verify): keep the abort-reason scan receiver-independent

Playwright's route.abort takes a network error code; the one call site now names its default code instead of the guard exempting a variable name.

* refactor(browser): give owner-initiated CDP aborts typed reasons (#10706)

* refactor(browser): give owner-initiated CDP aborts typed reasons

* fix(browser): give the page-side request fetch page-native abort reasons

The request callback runs in the page through page.evaluate and cannot
reach REA's modules. Define its deadline and cleanup reasons as
DOMExceptions inside the callback, and make the test fake evaluate the
callback from its source so module imports are unavailable, as in a page.

* test(process): expect the reader close's typed owner reason

Closing the Darwin run-token reader aborts its compilation with OwnerCancellation.ownerClosed since the owner-abort change in this PR; the boundary test still expected the default DOMException.

* fix(server): report HTTP session replacement and shutdown to pending tools as an owner close
2026-11-15 17:28:05 +08:00
morluto df0bdd9f58 refactor(process): let Darwin helper cleanup errors read their owner's release (#10801)
* 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 (#10802)

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.
2026-11-15 17:26:36 +08:00
morluto 85df21992f refactor(native): read retained inventory views through the operation fact contracts (#10883)
* refactor(native): read retained inventory views through the operation fact contracts

* refactor(native): read matching inventories through the operation fact contracts (#10887)

* refactor(native): read dispatch symbol fallbacks as lenient name facts at the port (#10889)

* refactor(native): read dispatch symbol fallbacks as lenient name facts at the port

* test(native): accept undeclared name-row fields in both dispatch consumers

* refactor(native): read literal searches and overview counts as operation facts (#10894)

* refactor(native): read literal searches and overview counts as operation facts

* refactor(native): read addressed inventory rows as operation facts in every reader (#10899)

* refactor(native): read addressed inventory rows as operation facts in every reader

* test(native): cover xrefs name resolution through the name fact contract

* test(server): pin procedure completion to the procedure inventory contract
2026-11-15 17:24:24 +08:00
morluto 5bf66f7500 refactor(domain): one owner for the non-array record predicate (#10588)
* refactor(domain): one owner for the non-array record predicate

Thirty-four source files each declared isRecord as a non-null object that is
not an array, and six more spelled the same predicate as isObject,
isSchemaRecord or isStorageRecord. They now import isRecord from
src/domain/isRecord.ts, whose overloads keep JsonValue narrowing for the
desktop and environment readers. DshCordisPatch's predicate also excludes
Cordis expressions, so it is renamed isPatchRecord.

architecture-guards refuses a new local copy under any name. The seven files
that still hold one are listed and must leave the list when their copy goes.

* fix(guards): count record predicate copies precisely

* test(hopper): retry after confirmed application exit

* fix: reject stale record predicate exceptions
2026-11-15 17:05:54 +08:00
morluto d5e0747025 refactor(process): build infrastructure cancellations with a required reason (#10595)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* refactor(process): build infrastructure cancellations with a required reason

Migrate every AnalysisCancelledError construction in shared process,
provider, artifact, domain, server and CLI code and in the smaller
providers to cancelledBy, lowering the guard baseline from 671 to 566.
Option bags that carried a cause are split so the cancellation branch
cannot receive one.

* fix(errors): read the reason of any signal-shaped cancellation source

* test(ci): cover the counted-baseline comparison behind the architecture guard
2026-11-15 17:05:23 +08:00
morluto 5d5ab53536 refactor(native): select ELF sections through the keyed owner (#10780)
* refactor(native): select ELF sections through the keyed owner

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix(verify): count a filter index only on the filter's own result
2026-11-15 17:03:25 +08:00
morluto 2af3be132c refactor(process): let worker cleanup errors read their root epoch's release (#10789)
* 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.
2026-11-15 17:02:25 +08:00
morluto 43934682ab refactor(gdb): parse MI result records with one typed record parser (#10799)
* refactor(gdb): parse MI result records with one typed record parser

GdbSessionManager recognized ^done with a prefix regex at seven sites, kept a hand-written span scanner and a second character scanner for -list-thread-groups, and decoded C-string fields with a dynamic RegExp. src/gdb/GdbMiRecord.ts now parses a result record into its token, class and typed results (C-string bytes, tuples, value lists and result lists), and returns a typed malformed-mi-record failure with a reason and offset. The done checks, the thread-group PID inventory and the C-string field reader use it; the stream dispatcher reads only the record header through the same module. An architecture guard keeps MI result-record regexes in the owner.

* perf(gdb): encode each unescaped C-string run once

The MI C-string decoder allocated a Buffer per unescaped character, pushed every byte into a number array, and copied the rest of the line for each hex or octal escape. It now validates unescaped runs in place, encodes each run once, reads escapes with sticky patterns, and joins the chunks once. On a 12.1 MiB groups reply: 887/832 ms elapsed and 818/775 ms CPU before, 76/76 ms elapsed and 131/128 ms CPU after; peak RSS 152-170 MiB both.

* test(gdb): refuse a malformed ^done for a startup setting through a real session

A fake GDB answers -gdb-set auto-load off with ^done,msg="unterminated. The session must not start: the typed MI record parser refuses the record, where main's ^done prefix match accepted it (the case fails against main's GdbSessionManager).

* feat(gdb): record the GDB version from -gdb-version in session Evidence (#10865)

* feat(gdb): record the GDB version from -gdb-version in session Evidence

Startup now asks -gdb-version before the auto-load setting and reads its first console stream record through a new MI stream-record parser (parseGdbMiStreamRecord). Each session's Evidence names that version; an unrecognised banner, malformed record or refused query leaves it null (unknown), as before.

Fake GDB fixtures answer -gdb-version from one owner in gdbTestHelpers: a stdin preamble for scripted fakes and answerFakeGdbStartup for in-process fakes, so scenarios model only their own commands.

* fix(gdb): leave a lossily decoded version unknown and frame fake startup replies by line

* test(gdb): reject unmodeled commands in the lossy version banner fixture

* test(gdb): refuse commands the scripted fake GDBs do not model (#10947)

* test(gdb): refuse commands the scripted fake GDBs do not model

* test(gdb): match the migrated fakes' special commands exactly
2026-11-15 17:01:45 +08:00
morluto 479abaf3b2 refactor(python): read pyc and PYZ marshal values through one shared reader (#10806)
* refactor(python): read pyc and PYZ marshal values through one shared reader

* refactor(python): read pyc 64-bit values and header fields through shared readers (#10834)
2026-11-15 16:50:58 +08:00
morluto 8443930bd3 refactor(limits): declare the Node timer bound and duration schema once (#10640)
* refactor(limits): declare the Node timer bound and duration schema once

Fifteen modules re-declared 2_147_483_647 as their own timer maximum and
eleven built the same 1..2^31-1 millisecond schema. They now use
MAX_NODE_TIMER_DELAY_MS and timerDurationMsSchema from
src/domain/analysisResourceLimits.ts, and an architecture guard rejects the
literal outside that owner and five non-timer int32 bounds. The generated
MCP tool catalog is byte-identical.

* refactor(limits): keep the JVM processor-count bound separate from the timer bound

REA_JADX_ACTIVE_PROCESSOR_COUNT is a Java int count, not a timer delay, so it keeps its own named bound and a documented exception in the signed 32-bit guard. Remove the timer-bound and zod imports the duration-schema migration left unused.

* refactor(limits): keep the TShark packet-count bound separate from the timer bound

packet_limit is passed to TShark's -c as a C int packet count, not a timer delay, so it keeps its own named bound and a documented exception in the signed 32-bit guard.
2026-11-15 16:40:03 +08:00
morluto 6a4caa065b refactor(providers): classify raw provider failures through one owner (#10757)
* 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.
2026-11-15 16:28:42 +08:00
morluto c06a0b9ee8 feat(pcap): read tshark JSON packets through one envelope owner with an adjustable expansion bound (#10788)
* refactor(pcap): read tshark JSON packets through one envelope owner

PcapOutput and the packet-capture reader each decoded tshark -T json and checked the packet envelope. The capture reader let SyntaxError escape and accepted any shape until its field schema ran. src/pcap/TsharkJsonPackets.ts now owns the envelope (malformed JSON, the packet array, each packet's _source.layers) as typed failures, and the pcap byte and expansion budget. Both consumers map those failures to their existing error kinds. An architecture guard keeps _source.layers reads in the owner.

* fix(captures): keep producer output on a refused packet reply

parsePacketOutput threw AnalysisOutputError itself, so the provider's failure mapping returned it without the captured stdout and stderr, both for envelope refusals and for unordered or out-of-limit frames. It now returns a Result with the reason and cause, and the provider builds the AnalysisOutputError with capturedOutput.

* feat(captures): bound packet-capture JSON expansion by an adjustable setting

The capture path parsed TShark's selected-field JSON without the expansion
estimate the pcap reader charges. It now reads through readTsharkJsonPackets
under capture.packet_json_expansion_bytes (REA_CAPTURE_PACKET_JSON_EXPANSION_BYTES).
The estimate charges at most 130 bytes per code unit and the output is one
Node string, so no admissible output reaches 69,793,215,440 bytes; the default
and maximum are 128 GiB, so the budget refuses only when lowered. A refusal is
an adjustable_budget resource constraint naming the setting and keeps the
producer output; a malformed launch seed is refused before the capture is read.
2026-11-15 16:17:24 +08:00
morluto 04cb416726 chore(architecture): lower the stale McpRegistrationGit cleanup-composition baseline 2026-11-15 03:07:35 -05:00
morluto dd7597a108 refactor(errors): read cleanup completion from dependencies instead of pushing it to dependents (#10744)
* 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
2026-11-15 15:22:21 +08:00
morluto c296c91770 fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal (#10634)
* 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(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.
2026-11-15 15:11:31 +08:00
morluto 1f7884b5b9 refactor(process): classify owned command outcomes through one owner (#10628)
* 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(pcap): attribute a limited capture's exit through the prlimit wrapper

* 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

* 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
2026-11-15 13:45:46 +08:00
morluto b33e5c4867 refactor(python): report pyc format failures through ParseFailure (#10664) 2026-11-15 13:45:40 +08:00
morluto a67dbe07a4 refactor(process): own deadlines as one monotonic type with a finalize budget (#10723)
* refactor(process): own deadlines as one monotonic type with a finalize budget

Deadline (src/domain/deadline) holds a monotonic work budget and a separate finalize budget that starts when finalization begins. ProviderStartupDeadline is built on it and exposes finalize(), so a failed Ghidra startup drains launcher output under its own budget and the caller's signal instead of the expired startup signal. The six wall-clock deadline sums in src move onto Deadline; a counted guard keeps the one page-clock conversion and rejects new copies.

* refactor(process): share one finalize window across repeated beginFinalize calls
2026-11-15 13:45:27 +08:00
morluto 2e1e6593f2 refactor(application): own the finalization precedence in runOwned (#10645)
* refactor(application): own the finalization precedence in runOwned

runOwned runs owned work, closes its owner and applies the documented
precedence once: a recorded failure stays primary, cancellation before
commit keeps the completed value as a partial observation, cancellation
after commit keeps the value, and unconfirmed cleanup reports
cleanup_incomplete while keeping the completed value. finishAnalysisCommand
now projects its Finalized outcome; its 31 callers are unchanged.

* fix(application): preserve finalization outcomes

* fix(application): classify finalization by the primary error and keep cancellation through recovered publication

A producer composite whose primary is a cancellation finalized as failed because the kind read only the top-level error class; it now follows primaryAnalysisError. A cancellation that arrived while a recovered value's publication failed was dropped because the signal was read before that last await; it is now attached to the publication failure as a secondary.

* fix(cli): keep the initial failure snapshot whenever close itself succeeded
2026-11-15 10:24:25 +08:00
morluto 61fd6336bf refactor(hopper): build Hopper cancellations with a required reason (#10601)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* fix(errors): read the reason of any signal-shaped cancellation source

* test(ci): cover the counted-baseline comparison behind the architecture guard

* refactor(hopper): build Hopper cancellations with a required reason

Migrate the remaining AnalysisCancelledError constructions under
src/hopper to cancelledBy. Regex-search close aborts its workers with
OwnerCancellation.ownerClosed. HopperCleanup and HopperClient are not
touched.

* test(hopper): cover cancellation during search shutdown
2026-11-15 10:20:08 +08:00
morluto d9daf09ea5 refactor(capture): report decoder failures through ParseFailure with named limits (#10708) 2026-11-15 10:13:55 +08:00
morluto 3a29b6b8ea refactor(il2cpp): report format failures through ParseFailure (#10701) 2026-11-14 21:11:29 -05:00
morluto 89640a446a fix(parsers): stop reading every RangeError as a format failure (#10602)
* refactor(parsers): report format failures through one ParseFailure algebra

Add ParseFailure (malformed, truncated, unsupported, limit, with format,
offset and field) and its one carrier, ParseFailureError, in the domain.
BinaryReader now names truncation and the field it read, and its native
format and resource failures are ParseFailureError subclasses.

Providers projected a format failure with their own instanceof ladder,
and some dropped the selected path from the invalid_format message.
parseFailureAnalysisError now owns that projection for the native,
Go, devicetree, V8, Windows Shortcut and Electron fuse providers.
The V8 code cache, Windows Shortcut and Electron fuse format classes
are removed in favour of the algebra.

The architecture guard counts raw Buffer and DataView reads, plain
Error throws in parser folders and plain JSON.parse of parser input
against baselines that can only shrink.

* fix(parsers): stop reading every RangeError as a format failure

Mach-O, Swift and Objective-C metadata parsers threw RangeError for a
truncated or malformed record, so their catch sites treated any
RangeError as a partial decode: a call stack overflow or a host string
limit became an "unresolved" metadata reason, and the IL2CPP Mach-O
path reported it as a malformed container.

Those parsers and the CIL method body reader now throw ParseFailureError.
hostRangeErrorKind is the one interpreter of a host RangeError (stack
overflow, host capacity, raw out-of-bounds read), and
parseFailureFromCause admits only a parse failure or a raw
out-of-bounds read as evidence about the input. Fixup stream catch-alls
rethrow host limits. The architecture guard counts the remaining direct
instanceof RangeError checks.

* fix(parsers): preserve Apple metadata failure ownership

* test(architecture): shrink parser failure baseline

* fix: preserve uncertainty for host range errors
2026-11-15 10:03:26 +08:00
morluto e652f4409e fix(verify): rewrite counted baselines only when a count was lowered (#10669)
* fix(verify): rewrite counted baselines only when a count was lowered

* test(verify): own the baseline write decision in a tested helper
2026-11-15 09:42:02 +08:00
morluto 133e7762b6 refactor(source-maps): report decoder failures through ParseFailure (#10690)
SourceMapFormatFailure and its format/unsupported/limit/section-overlap reasons are replaced by ParseFailureError with format source-map. Limits carry the selected allowance name and maximum; a section-boundary overlap is a malformed failure on the section-boundary field. The codec reply protocol and every public error are unchanged.
2026-11-15 09:36:58 +08:00
morluto d482bb9369 refactor(apple-ips): report crash-report format failures through ParseFailure (#10665) 2026-11-15 08:59:47 +08:00
morluto a22fb6e60b fix(model): report malformed GGUF and Safetensors headers as invalid_format through ParseFailure (#10667)
* refactor(model): report GGUF and Safetensors format failures through ParseFailure

* fix(model): report malformed model headers as invalid_format through the shared mapping

Malformed and truncated GGUF and Safetensors headers now take the shared ParseFailure mapping (invalid_request, invalid_format on path) instead of the model provider's own BinaryTargetError file_format_rejected. Unsupported forms stay unsupported_target and model allowances keep their resource-constraint recovery.
2026-11-14 18:58:59 -05:00
morluto ef15353c69 refactor(android): build mobile-provider cancellations with a required reason (#10590)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(android): build mobile-provider cancellations with a required reason

Migrate every remaining AnalysisCancelledError construction under
src/adb, src/android, src/apktool, src/apkSignature, src/flutter,
src/il2cpp, src/dotnet and src/frida to cancelledBy and lower the guard
baseline from 671 to 608. A closed adb provider reports
OwnerCancellation.ownerClosed for later probes, and JADX and apktool
shutdown abort with that typed reason.

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* fix(errors): read the reason of any signal-shaped cancellation source

* test(ci): cover the counted-baseline comparison behind the architecture guard
2026-11-14 18:58:38 -05:00
morluto b342254f9a refactor(macho): read universal Mach-O slices only where admission selected them (#10642)
Hopper and native dispatch metadata re-ranked FAT slices from the target's
architecture name instead of reading the admitted universalSlice. Both now
consume the admitted range and refuse when it is missing or undeclared.
Mach-O load-command codes move to one table with an architecture guard.
2026-11-14 18:57:55 -05:00
morluto 29fb608559 refactor(rust): report cargo-auditable section failures through ParseFailure (#10652)
* refactor(rust): report cargo-auditable section failures through ParseFailure

RustAuditableFormatFailure restated the malformed/unsupported algebra and
the provider translated it by hand; container BinaryFormatFailures were
re-wrapped and lost their kind. The reader now throws ParseFailureError
(format rust-auditable), container failures propagate unchanged, and the
provider maps both through parseFailureAnalysisError. A counted guard
keeps new per-format failure classes from returning.

* test(rust): pin the container's truncated failure facts through the Rust reader
2026-11-14 15:28:33 -05:00
morluto 85da0bd4ee refactor(errors): require a reason for every analysis cancellation (#10566)
* refactor(errors): require a reason for every analysis cancellation

Add cancelledBy, the one constructor for AnalysisCancelledError. Its
reason argument is required: an aborted signal, an earlier cancellation,
a reason carried from an inner failure, or an OwnerCancellation when REA
closed the work's owner itself.

Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and
binary-session record owners now report OwnerCancellation.ownerClosed
instead of an absent reason. Firmware's predecessor wait returns its
cancellation as a Result, removing an unreachable reason-less fallback.

An architecture guard rejects new direct constructions, with a counted
per-file baseline that can only shrink.

* refactor(errors): share one aborted-signal predicate with cancelledBy

* refactor(process): return a departing caller's reason from a shared startup wait

* refactor(providers): build decompiler-provider cancellations with a required reason (#10587)

Migrate every AnalysisCancelledError construction under src/ghidra,
src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the
guard baseline from 671 to 572. Closed Kuna target commands report
OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and
JEB close abort their lifetimes with that typed reason.

* fix(errors): read the reason of any signal-shaped cancellation source

* test(ci): cover the counted-baseline comparison behind the architecture guard
2026-11-14 15:20:03 -05:00
morluto a0d9025202 refactor(parsers): report format failures through one ParseFailure algebra (#10593)
* refactor(parsers): report format failures through one ParseFailure algebra

Add ParseFailure (malformed, truncated, unsupported, limit, with format,
offset and field) and its one carrier, ParseFailureError, in the domain.
BinaryReader now names truncation and the field it read, and its native
format and resource failures are ParseFailureError subclasses.

Providers projected a format failure with their own instanceof ladder,
and some dropped the selected path from the invalid_format message.
parseFailureAnalysisError now owns that projection for the native,
Go, devicetree, V8, Windows Shortcut and Electron fuse providers.
The V8 code cache, Windows Shortcut and Electron fuse format classes
are removed in favour of the algebra.

The architecture guard counts raw Buffer and DataView reads, plain
Error throws in parser folders and plain JSON.parse of parser input
against baselines that can only shrink.

* fix(parsers): retain malformed range offsets
2026-11-14 14:35:28 -05:00
morluto b138f52e12 refactor: prune internal seams and preserve configuration ownership (#10430)
* refactor: prune internal seams and preserve configuration ownership

* fix(setup): keep YAML aliases and inherited registration settings on refresh

- Reuse an unchanged YAML collection only while every alias in it still
  resolves after the edit; otherwise rebuild it so the alias becomes its
  value. A sibling anchor replaced or moved in the same refresh no longer
  leaves an unresolved alias in Hermes or Goose output.
- Keep a connection selector whose existing value already chooses stdio
  (for example an inherited Hermes transport: stdio) instead of superseding
  it; remote selectors are still replaced. Explicit stdio selectors are
  therefore no longer rewritten (Kimi expectation updated).
- When the whole Hermes registration is inherited through a merge,
  override it with that merge and write only the changed fields.

* fix(setup): edit registration fields whose YAML key is an alias

A key written as an alias to a string scalar (for example *field with
field: &field env) was treated as a source-only key, so any change to
its value was refused in Hermes and Goose. Resolve such keys to their
string, keep the aliased spelling while its anchor survives the edit,
and write the plain key when the same edit rebuilds the anchor.

* test(process): bound cleanup-boundary waits and always release gates

Both cancellation tests awaited a bare started promise and released
their gates only on success, so a missed boundary waited for the outer
deadline and could leave cleanup parked. Use awaitAcknowledgement and
release and join the cleanup in finally.

* fix(setup): repair only aliases an edit leaves unresolved

Replace the pre-edit alias survival guess, which expanded aliases to
sibling anchors that survive unchanged, with an exact repair: record
what each alias in the replaced value resolves to, write the edit, then
replace only aliases left without a preceding anchor by that value.

- Keep an aliased key's source position, so its anchor stays ahead of
  later aliases.
- Treat an aliased key as its string only when no other key in the map
  resolves to the same string; an ambiguous one stays guarded.
- Override an inherited Hermes registration only when the requested
  entry keeps every inherited field, so an explicit replacement can drop
  remote selectors.

* fix(setup): repair aliases whose binding an edit changes, keeping comments

An alias whose own anchor was removed could resolve to an earlier anchor
with the same name and silently change value. Record the node each alias
resolved to and repair any alias that no longer resolves to that node.
Carry the alias's comments and spacing onto the repaired value.

* test(setup): split the Hermes anchor and merge refresh cases

* fix(setup): refuse removing an overridden key its merge would restore

The merge-retention check skipped keys the mapping also overrides, so
dropping such a key (for example a remote url during an explicit stdio
replacement) let the retained merge restore it. Check removal before
skipping overridden keys, so the edit is refused with the existing
message instead of producing a registration that cannot be applied.
2026-11-14 04:08:37 -05:00
67ce671d66 fix(verifiers): release fixture roots only after their owners finish (#10491)
* fix(verifiers): release fixture roots only after their owners finish

Several real-tool verifiers released a fixture root even when a cleanup
that still used it had failed, so a surviving MCP server, session or
descendant kept running against a deleted directory and the failure
looked like ordinary cleanup.

- scripts/lib/verifier-cleanup.mjs: new releaseAfter(dependents,
  release, retained) task. It attempts every dependent cleanup and
  releases the resource only if all succeed; otherwise it keeps the
  resource and reports it with the dependent failures.
- verify-signal-recording: the root is released after the MCP client
  and transport close, and the host-limits read moves before the root
  is acquired so its failure cannot leak the root.
- verify-recorded-crash: sessions, the sentinel and the process-lineage
  check must all succeed before the root is released. A transport close
  failure after a failed connect keeps the connect failure.
- verify-wasm-artifact: both roots are released after the MCP client
  and transport close; the residue check and the two directories stay
  independent after that.
- verify-native-ui: `open -W` exits only after the app exits, so the
  root is removed only once that exit is observed; otherwise it is
  retained and reported.

Fixes #8561
Fixes #8562
Fixes #8564
Refs #8559

Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(verifiers): treat a signaled open as unproven fixture exit

A terminated `open` launcher does not prove the LaunchServices app it
started has exited, so only an `open -W` that exits on its own counts.
Also clear the grace timer so a prompt exit does not hold the process
for the rest of the grace period.

Refs #8559

Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(verifiers): model open -W exiting after the fixture is signaled

The native UI verifier now removes its root only after `open -W` exits
on its own. The authored recording's `open` never exited when the
fixture was signaled and had no signalCode, so the complete case waited
out the grace period and kept the root. The double now behaves like
`open -W`: it exits once the fixture process is signaled.

Refs #8559

Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 09:56:18 +01:00
morluto cdc4541ef7 refactor: remove redundant seams and preserve cleanup ownership (#10375)
* refactor: remove redundant seams and preserve cleanup ownership

* fix: validate PE resource extents before budget checks
2026-11-13 10:11:06 -05:00
morluto bbe2bcc979 refactor: remove legacy seams and consolidate resource ownership (#10349) 2026-11-13 04:45:16 -05:00
Kaoru0822 54906fdb1f fix(verify): check Android readiness repair guidance (#10242) 2026-11-12 20:40:42 -08:00
morluto b6820d5794 chore(verification): retire historical calibration studies (#10098) 2026-11-11 08:56:55 -05:00