Commit Graph
42 Commits
Author SHA1 Message Date
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 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 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 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 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
morluto bbe2bcc979 refactor: remove legacy seams and consolidate resource ownership (#10349) 2026-11-13 04:45:16 -05:00
morluto cb4e5cfbff feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)
* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
2026-11-10 05:11:20 -05:00
3230f50b16 feat(sqlite)!: page selected rows with a stated order and row offset (#9331)
* feat(sqlite): page selected rows with a stated order and row offset

readRows read `NOT INDEXED LIMIT ?` with no ORDER BY and reported
order: "unspecified", so an agent could not reach rows past row_limit
deterministically: raising row_limit hits the fixed 16 MiB reply budget,
and the sampled order depends on whichever index SQLite happened to scan.

Read the window in a unique, stated order and expose the position:

- rowid tables order by rowid unless all of rowid, _rowid_ and oid are
  declared columns, in which case the order stays unspecified.
- WITHOUT ROWID tables order by the primary-key columns, reusing each
  key's DESC flag and declared collation from pragma_index_xinfo so the
  plan keeps index order instead of materialising a sorter.
- rows.order, rows.row_offset and rows.next_row_offset report the window;
  next_row_offset is null once the window reaches the end.
- row_offset (CLI --row-offset) requires table and defaults to 0. A
  non-zero offset is refused when the table declares no usable order,
  because the skipped prefix would be arbitrary.

The schema now checks next_row_offset against truncated and the offset so
a worker reply cannot claim a page it did not read.

One attribution fix came with it: adding ORDER BY text can push an
over-wide projection past SQLite's SQL-text limit, which fails as
SQLITE_TOOBIG before the result-column limit is reported. Attribute that
case to column capacity too, so a 250k-column request still reads as
capacity rather than a byte budget.

Verified: npm run check:fast, npm run format:check on the touched files,
npm run verify:module-boundaries, npm run docs:check, and 196 tests
across the sqlite boundary, application and CLI/MCP suites. A 1002-row
inspection and in-memory EXPLAIN QUERY PLAN checks confirmed the
WITHOUT ROWID path adds no sorter. Full check:pr and the complete test
suite were not run.

* fix(sqlite): resolve the rowid alias and keep paging claims reachable

Three defects in the row paging added alongside row_offset:

- readRowOrder emitted ORDER BY rowid for every rowid table, but a table
  that declares a rowid column shadows that spelling, so the sort used the
  stored column and could repeat or drop rows across pages. Order now names
  the first alias the table leaves unbound, read from raw name bytes so an
  unrelated malformed identifier is not decoded just to choose an order.

- The inventory projection could hide the primary key a WITHOUT ROWID table
  orders by, silently degrading a paginated read to unspecified order. The
  order key is now read from the table itself rather than from the columns
  the caller asked to see.

- A truncated table with no stated order reported a next_row_offset that the
  row_offset rule then refused. It is null there, and the reply limitation
  no longer claims an unspecified order for windows that have one.

rowEvaluationFailure also probed the first rows of the table instead of the
requested window, so a failure confined to a later page was attributed to
columns that evaluate fine there.

* test(sqlite): assert the concatenated pages

The paging regression checked only counts, offsets and flags, so a second
page that repeated the first page's rows would still pass. Assert the values
read across all three pages instead.

* fix(sqlite): decode row keys in the database encoding

readRowKeyFacts decoded primary-key names as UTF-8 and probed a fixed
hidden set, so two supported schemas could not be paged at all:

- A UTF-16 database stores key names as UTF-16 bytes, so "k" decoded to
  NUL-padded text and ORDER BY named an identifier that does not exist.
  Pass the detected encoding, as readColumns and the index-metadata path
  already do.
- A virtual generated column named rowid (hidden=2) was absent from the
  shadow check, so ORDER BY rowid evaluated the generated expression
  instead of the rowid and could sort non-uniquely or fail outright.
  Include generated columns in the alias probe.

Also stop decoding primary-key names for rowid tables: their ORDER BY
names an alias, never a declared key, so an unselected malformed key
name could abort an otherwise readable selection.

The page-concatenation assertion now narrows SqliteValue before reading
.value, which the null/blob arms do not carry; this was failing the
Static checks typecheck job.

* fix(sqlite): order WITHOUT ROWID rows by the index's own key sequence

`readRowOrder` filed `pragma_index_xinfo` key terms into a map keyed by
column name, so a valid composite key that repeats a column —
`PRIMARY KEY(a COLLATE BINARY, a COLLATE NOCASE DESC)` — collapsed to one
term. The generated clause then overstated uniqueness: it omitted the
BINARY term, so `A`/`a` ties survived, and the `row_offset` paging
advertised a `primary_key` order that the actual key does not establish.

Build the terms from the index rows in `seqno` order instead, keeping
repetitions, and bail to `unspecified` if any declared key column never
appears in the index. The declared collation stays explicit so a caller
column's own collation cannot re-sort the window.

Verified against a real SQLite database that the repeated key is accepted
and that the old clause orders A, a, B while the index orders A, B, a.

* fix(sqlite): accept a null next offset on unordered truncated rows

readRows already withholds next_row_offset when a table shadows rowid, _rowid_ and oid and declares no primary key, because every non-zero offset is refused there. The reply schema still required the offset whenever truncated was set, so the provider rejected its own worker result and the first sample never reached CLI or MCP. Require the offset only when the order is stated.

* fix(sqlite): probe row keys only for ordinary tables and name row_offset

readRowKeyFacts ran for a selected virtual shadow table before readRows
could reject it, so an unavailable module surfaced as SQLite's
"no such module" instead of the selection refusal. Probe the key only
when the selected kind is an ordinary table and leave the order
unspecified otherwise, since no rows are read for that kind.

The unordered-offset refusal also reported the generic selection reason,
which callers see against table. Give it its own reason so the failure is
attributed to row_offset.

* feat(sqlite)!: page selected rows with a stated order and row offset

Selected-row inspection currently reports only rows.order: "unspecified" and
offers no way to reach rows past the first sample deterministically. Main also
refuses non-zero row_offset today, so nothing relies on that omission; this
change makes the stated order and the offset the contract, reports the next
offset the caller feeds back, and keeps the existing byte, heap, statement and
deadline limits.

BREAKING CHANGE: rows.order becomes rowid | primary_key | unspecified, and
rows gains required row_offset and next_row_offset members. A client that
validates selected-table output against the previous schema (where order was
the only permitted value) rejects a now-valid reply. Migration: accept the two
new members and treat order: "rowid" | "primary_key" as the stated order the
offset applies to; a null next_row_offset means the window reached the end.

* docs(sqlite): say a null next offset also means no stated order

The selected-rows limitation described next_row_offset as null only when the window reached the end. An unordered table whose row_limit truncates the sample also publishes null, because an offset there would name a request the row-offset rule refuses, so the Evidence read as if the sample were complete. State the second cause and pin it in the existing regression.

* test(sqlite): drop the limitation wording assertion and state the unordered exception

The boundary test pinned the sentence a caller reads in `limitations`,
so a harmless rewrite of that text failed a paging test whose real
subject is `order`, `truncated` and `next_row_offset`.
docs/development/testing.md asks for facts over frozen explanatory prose,
and the three structured expectations already fail if the paging fact
changes.

The tool contract promised that retained rows always have a unique order
and can be paged. A rowid table that shadows rowid, _rowid_ and oid and
declares no primary key has no such order: it returns
order=unspecified with a null next offset and refuses a non-zero
row_offset. Say so in the description so callers do not plan a
continuation the tool always refuses.

* docs(sqlite): say unordered tables cannot be paged

The description told callers that a truncated table without a stated order
needs "the table read without offsets", but omitting row_offset starts at
zero again: with an unchanged row_limit the next call repeats the first
sample instead of continuing past it. Say that these tables cannot be
paged and point at row_limit for a larger first sample.

* style(cli): wrap the native data type path description

The `inspect-native-data-type` argument description line is 82 columns, over the repository's 80-column `.oxfmtrc.json` limit, so `npm run format:check` fails on main and on every branch merged from it. It came in with #9465 and the following commits to this file did not touch it. Reformat the one call the formatter objects to; no behaviour change.

* fix(sqlite): bound rowid alias comparison by name byte length

readRowKeyFacts decoded every column name before testing it against three short aliases, so a table declaring a multi-megabyte name could exhaust the worker heap even when the caller projected the column away. Reject names that cannot be an alias by their byte length, accounting for the database encoding, before building any string.

---------

Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 21:01:58 +08:00
morluto 694edef951 fix(decompile): retain published output receipts (#9633)
* fix(decompile): retain published index on failure

* fix(decompile): preserve publication failure facts

* docs(decompile): explain retained publication receipts

* fix(decompile): retain receipts across provider cleanup chains

* fix(evidence): retain diagnostics through cleanup primary chains

* refactor(evidence): retain receipts once on the returned error
2026-11-09 20:55:53 +08:00
Dhruvilkumar Ashokbhai Chodvadiyaanddhruvac29 85be0f70a6 fix(artifacts): guide retries for symlinked files (#8725)
* fix(go): suggest resolved path for rejected symlinks

* test(go): cover unresolved symlink recovery paths

* fix(go): omit schema recovery for resolved symlinks

* fix(artifacts): share resolved-path recovery across inspectors

* fix(sqlite): retain resolved database path through snapshot reads

* fix(captures): retain symlink retry guidance

* fix(artifacts): preserve retry hints through redaction and selected reads

---------

Co-authored-by: dhruvac29 <dhruvac29@users.noreply.github.com>
2026-11-04 19:58:06 +08:00
Kaoru0822andClaude Opus 5.5 85013da0ce fix(cli): describe one-shot timeouts for an invocation whose session has ended (#8535)
* fix(cli): describe one-shot timeouts for an invocation whose session has ended

A one-shot CLI analysis that reached a provider deadline told its caller
that the Ghidra session remained usable and reported a runtime-editable
limit applying to the next operation, although the command had already
released its provider session and runtime.

The shared error projection now takes an explicit caller lifetime.
Providers keep guidance that holds only for a continuing caller (session
reuse, same-session runtime updates) apart from the request outcome, and
the presentation adds it only for a continuing caller. One-shot owners
(runDirectAnalysis, runProviderAnalysis, finishAnalysisCommand and the
JavaScript application command) project for the invocation: details.limit
reports scope rea-invocation, applies_at next_invocation and the launch
environment seed, and the remediation leads with that setting for a new
invocation. MCP output keeps its runtime-update and same-session guidance.

Covers the Ghidra decompile timeout, decompiler failure, regex stack
exhaustion and startup timeout, the shared regex deadline, the Kuna
decompile timeout and the Hopper startup timeout.

Fixes #8311

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

* fix(cli): project composed child failures for the caller lifetime

Cleanup composition embedded its primary and cleanup failures as
continuing-caller projections, and baked the primary's message and the
cleanup's remediation into the composed error. A one-shot command whose
Ghidra startup timed out while shutdown also failed therefore still
reported a rea-runtime limit and update_analysis_settings in
details.diagnostics.primary_error.

childFailureDiagnostics now snapshots each embedded child at composition
for both lifetimes when its projection depends on the caller, so cleanup
history keeps its composition-time state and the final projection selects
the caller's form. composedCallerGuidance re-attributes message and
remediation with their continuing-only parts kept separate. Error copies
and redaction keep the invocation diagnostics. The Hopper startup timeout
now carries its continuing guidance on the error like the other producers.

Invocation limits keep agent_editable and rea_restart_required, with
values that hold for a new invocation, so only scope and applies_at
change and environment is added.

In existing-project mode a decompile timeout or a cancelled session now
says the next session copies and opens REA_GHIDRA_PROJECT_PATH again
rather than repeating import and analysis.

Tests compare delivered guidance with the owning formatters and constants
instead of freezing prose.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:57:32 -08:00
gensokyo-dev e18594b247 refactor(composition): unify capability bindings and cleanup ownership (#8441)
Bind typed service creation, observed availability and retryable cleanup in one composition owner. Reuse selected CLI/MCP factories and canonical contract groups without copying provider operation inventories.

Closes #8429. Refs #740.

Validated paired real application workflows, actual local Codex CLI Terra/high use, complete Evidence and Unknown checkpoint parity, compiler-backed import boundaries, SDK schemas and final real mixed routing. Automated review findings were fixed at their producing owners. Existing exact-base CodeQL alerts are documented on the PR under the owner-authorized unrelated-check exception; no global alerts or security rules were changed.

Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
2026-11-01 13:30:21 +08:00
morluto 128c2cf632 fix(sqlite): report SQL statement resource allowance (#7829) 2026-10-29 01:30:08 +08:00
morluto 669e617eec fix(sqlite): preserve completed member facts on acquisition failure 2026-10-27 02:02:27 +08:00
morluto c2b0c955cd fix(workers): reject malformed UTF-8 in JSON result files 2026-10-27 00:28:42 +08:00
morluto e596a48251 fix(sqlite): retain completed evidence through reply finalization 2026-10-26 17:56:47 +08:00
morluto 9e4702b7a7 fix(sqlite): preserve schema-owned input issue paths 2026-10-26 15:00:30 +08:00
morluto 25aaf89287 fix(sqlite): budget schema names by returned JSON text
Charge the whole-catalog admission guard by escaped decoded names, preserving raw-byte lookup identity and final worker reply limits. Keep UTF-16 inventory and detailed-schema results whose returned facts fit the documented allowance.

Fixes #6888
2026-10-24 02:34:35 +08:00
morluto ef475fcf8b fix(sqlite): distinguish query capacity and unavailable row sources
Use one bound JSON selection set for inventory metadata, keeping raw-name byte
comparison and filtering inside SQLite while preserving caller projection order
and repetition. Large requests reach native result-column capacity attribution.

Separate unavailable row sources from generated-expression failures. Preserve
SQLite's reason and selected table/path, offer schema-only recovery, and avoid
claiming corruption or recommending unreadable columns.

Validated UTF-8/UTF-16 identity, native inspection/worker tests and CLI/MCP failure
and recovery workflows. The large CLI selection uses the existing framed CLI
fixture to avoid host argv limits. Source bytes and identity remain unchanged.

Fixes #6855
Fixes #6604
2026-10-24 01:24:43 +08:00
morluto 6fdcac6cad fix(sqlite): preserve catalog byte identity and worker context
Match table metadata by stored name bytes and fold only ASCII code units for relationships, reusing parsed facts without per-trigger queries. Preserve caller paths on structured worker limits and V8 heap failures.

Validated invalid UTF-8 collisions, distinct UTF-16 names and real worker transport refusal through CLI and stdio MCP. Controlled stderr covers OOM classification; actual memory exhaustion was not induced.

Fixes #6795

Fixes #6607
2026-10-24 01:00:14 +08:00
morluto bd4f5e9c19 fix(parsers): preserve format evidence and caller input identity
Retain bounded FAT and offset-tagged PE observations through archive hashing,
classification, and content nodes. Preserve PyInstaller member paths according
to the observed carrier dialect and share APK v1 signing-name semantics.

Read SQLite's implicit sqlite_schema table without inventing a stored schema
declaration. Distinguish unsupported ELF numbering and decoded-name admission
from malformed data and fixed scan allowances. Preserve Sigrok basenames and
classify matching explicit input-format refusals at their owning boundary.

Validation: 554 distinct tests passed, 3 skipped across serial focused runs;
build, typecheck/lint, docs, formatting, and discovery (2141 files) passed.
Sigrok command-boundary simulators do not establish real-tool decoding.
Small repeated public PyInstaller CLI runs had equivalent inventory facts;
warm median elapsed 0.5431s before / 0.5426s after, with no speed claim.

Fixes #6733
Fixes #6741
Fixes #6722
Fixes #6732
Fixes #6736
Fixes #6734
Fixes #6764
Fixes #6766
Refs #6739
2026-10-23 22:54:28 +08:00
Dery Ferdika 89fea8130a fix(sqlite): report result-column capacity refusals as capacity (#6327)
A projection wider than SQLite's native result-column limit failed with the
generic "too many columns in result set" SQL error. The inspector could not
attribute that error to a generated column, so the worker reported "format"
and the parent blamed the caller's path as invalid_format even though the
selected database was readable.

Classify the refusal from SQLite's own column limit for the private
connection, carry the selected and engine counts through the worker reply,
and project it as a capacity resource constraint. The database stays
readable, no partial rows are returned, and the recovery names the column
counts and splitting the projection across calls.
2026-10-23 23:42:20 +09:00
morluto 2bcd7e6d13 refactor(artifacts): centralize completed file outcomes
Project completed values and cancellation from the descriptor owner once across artifact consumers. Preserve completed void outcomes and format-specific validation and cleanup policies.

Keep later aborts from replacing captured APK and SQLite close failures, and retain the exact APK cancellation reason. Exercise real descriptors, public workflows and cleanup retries.
2026-10-23 21:42:40 +08:00
dogan 02a0e72f70 fix(sqlite): honor selected-table reply budgets (#4216) 2026-10-23 02:30:36 +08:00
郭立 Guo Li 7590b50801 perf(schema): reduce retained integer validator allocations (#4360)
Construct integer formats directly while preserving bounds, refinements, metadata and current shared schema owners. Keep Incur-dependent CLI numeric coercion declarations unchanged.

Verify canonical advertisements, 126 numeric boundary cases, 974 actual CLI parser cases, broad source tests and real public-artifact SDK workflows. Record 72 normal tasks and 12 separate owned-inspector heap diagnostics with whole-family resource bounds; distinguish SDK spans and asymmetric audit overhead. Generate numerical report sections from receipts and reject stale tables through documentation conformance checks.

Closes #6236. Current-head CI and Greptile pass; actionable review findings are addressed. Codex review was unavailable due repository usage limits.
2026-10-22 05:11:08 +00:00
2947a77507 fix(engines): keep format ceilings fixed and recover the EVMole deadline (#6088)
* fix(engines): keep format ceilings fixed and recover the EVMole deadline

Promote the owned EVMole worker wall clock to timeout_ms and name it in
recovery. Fixed CPU, byte, and address-space refusals in this theme say
the adjustable deadline does not raise them.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(engines): recover the allowance that refused the request

Model capacity recovery follows a structured limit instead of message text,
and the shared header ceiling no longer names Safetensors. Firmware signal
errors keep captured tool output, oversized reports do not suggest an
intermittent rerun, and NativeAOT hydration budget exhaustion explains the
fixed parser allowance on the partial report.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 03:06:59 +08:00
gensokyo-dev aa53c06538 feat(firmware): make analysis deadlines recoverable per call
Expose per-call firmware analysis budgets with exact recovery guidance, shared
CLI/MCP validation and retained actual settings. Keep publication and fixed
resource policy independent; preserve typed interruption and one captured-output
owner through command and one-shot cleanup.

Fixes #5851. Executed verification, real-provider limits, known baseline issues
and authorized readiness override are recorded in #5863.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-21 19:25:22 +08:00
Kaoru0822andClaude Opus 5.5 bcfcd8b6e2 fix(sqlite): attribute generated-column evaluation failures to the selection (#5879)
* fix(sqlite): attribute generated-column evaluation failures to the selection

A selected virtual generated column whose expression needs a function the
read-only connection lacks (or raises an SQL error for a sampled row) was
classified by the worker's catch-all as a malformed database and projected as
an invalid_format issue on `path`.

Row reads now attribute SQLITE_ERROR from selected cells by probing each
selected virtual generated column over the sampled rows, and report a
typed capability_unavailable refusal naming the table, each failing column
with SQLite's message, and the remaining columns to select. Other causes,
including corruption, TOOBIG and NOMEM, keep their classification.

Fixes #5719

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

* fix(sqlite): recommend only probed-readable columns after evaluation failures

Record every per-column probe failure with its own reason, discover stored
columns outside an inventory-scope selection without decoding unrelated
malformed names, and present recovery as the structured columns selection
instead of shell-quoted command text built from database-supplied names.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:08:49 -07:00
gensokyo-dev f801b2fbfd feat(sqlite): make worker deadlines adjustable with recovery guidance (#5818)
Expose a per-call SQLite worker deadline with exact CLI/MCP recovery controls,
parent-owned execution metadata and historical unknown preservation. Keep fixed
resource limits and cleanup admission intact. Share typed cleanup composition
with one captured-output owner and preserve the timeout after cleanup resolves.

Fixes #5812. Follow-up tracking: #5808. Existing CLI test-oracle failure: #5845.

Verified 155 SQLite tests (3 skipped), static/docs/discovery checks, six real
CLI/MCP pairs and 13 actual Codex sessions including four corrected deadline
recoveries. Three pre-dispatch discovery failures remain explicit, not passes.
Review findings include real retained-cleanup and fixture ownership regressions.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-21 16:45:46 +08:00
Kaoru0822andClaude Opus 5.5 c1e04bf8c0 fix(sqlite): name the selected path in malformed-database errors (#5564)
The owned worker inspects only a private snapshot, so its format reason
cannot name the caller's selection. Project path-scoped worker rejections
with the selected database path so distinct malformed inputs report their
own source, matching the documented failure contract.

Fixes #5520

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:52:18 -07:00
morlutoandCursor Agent ef59174c1e fix(errors): name fixed allowances in resource-constraint recovery (#5633)
* fix(errors): name fixed allowances in resource-constraint recovery

Fixed parser, count, report, input, and capture refusals inherited
host-memory, wait-and-retry, or retained-Evidence advice. Producers now
attach allowance-specific recovery, and deterministic capacity caps are
not retryable. IL2CPP reader occupancy stays retryable capacity.

Refs #5556, #5405, #5456, #5459, #5460, #5504, #5505, #5506, #5537, #5628, #5630, #5631, #5632

* fix(il2cpp): keep a closing metadata reader non-retryable

close() stops admission before the active inspection finishes. A second
inspect in that window was projected as retryable capacity. Closing and
closed readers now report capability_unavailable. Occupied readers that
remain open stay retryable.

* fix(errors): align fixed-cap recovery with retained facts

IL2CPP report refusals keep observed source facts instead of denying Evidence. Command-output caps use the capture allowance. Ghidra seed recovery names the limit that was exceeded.

* fix(errors): call byte caps a byte allowance

WASM representation limits and the IL2CPP Ghidra seed-file cap are byte budgets. Their recovery no longer says the exceeded allowance is a fixed count.

* test(ida): keep unresolved procedure lookups non-retryable

Main now treats an unchanged invalid request as non-retryable. The IDA lookup tests follow that projection.

* fix(errors): qualify capture recovery for live output

A fixed capture allowance still rejects the same output. Live captures such as adb logcat can change between requests, so recovery no longer says every unchanged request fails again.

* fix(adb): keep a truncated live capture retryable

Device output can shrink between calls, so an adb capture that exceeds the allowance stays retryable. Deterministic capture and version-output limits stay non-retryable.

* fix(adb): keep truncated mutations non-retryable

Only live observations opt into retrying a capture that exceeded the allowance. Push, install, uninstall, and other mutations stay non-retryable.

* fix(errors): keep live capture recovery off deterministic limits

Artifact-derived capture allowances again say the unchanged request fails. Only volatile adb observations use the live-output sentence.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-21 03:45:34 +08:00
morluto 1006714271 fix(sqlite): retain primary outcomes through cleanup retries 2026-10-20 17:17:15 +08:00
morluto 748ac7f0df fix(sqlite): preserve column identity before row selection
Read raw column metadata through the built-in PRAGMA in the private empty temp schema. Validate bytes before names become SQL identifiers, and select inventory subsets by exact encoded identity so unrelated malformed names cannot alias valid columns.

Validation: 5 focused identity controls; 15 selected producer/provider controls (33 skipped); public CLI baseline collision, fixed format refusal and valid-subset success; check:fast and docs:check. Generated inert SQLite fixtures on macOS arm64 with Node 24.18.0; no Windows qualification or large-file/stress workload.

Refs #4710
2026-10-17 15:33:59 +08:00
morluto effb71f59c fix(providers): preserve bounded artifact reads and cleanup owners
Refs #4373, #4455, #4457, #4458.

Use canonical artifact ownership in Go, Rust, SQLite and PE readers and their failure consumers. Admit PE read budgets before allocation and ignore zero-extent sections in interval ownership. Close Rust resources through CLI and MCP lifecycles.
2026-10-17 00:44:02 +08:00
bc51d0ea5d fix(sqlite): preserve snapshot admission failure semantics
Refuse oversized database and WAL inputs before acquisition with exact observed bytes and export guidance. Keep initial path failures separate from file-set changes and retain cleanup and partial observations on late growth.

Refs #4208, #4188, #4141, #4127

Co-authored-by: hima <1ep23ic023@eastpoint.ac.in>
Co-authored-by: Royce <roycelam@umich.edu>
2026-10-16 03:49:44 +08:00
Kaoru0822andClaude Opus 5.5 bdacd4140e fix(sqlite): keep a retained worker owner after a failed worker exit (#3242)
* fix: abort invalid JSON parses and keep retained worker owners

Main (7375ab6e3) fails four boundary tests from its latest batch.

- f79fd59e9 moved JSON value validation into a non-aborting superRefine, so
  authenticated Evidence still computed its recursive identity digest over
  a 10,000-level `parameters` value and overflowed the stack. The process
  comparison CLI then reported "Maximum call stack size exceeded" instead of
  the depth limit. Abort the parse on a JSON validation issue so enclosing
  refinements never walk invalid data.
- runOwnedCommand retains an incomplete cleanup through retainCleanup and
  then rethrows the command failure without a cleanupOwner. The EVM and SQLite
  providers replaced their retained owner with that undefined value, so an
  output-limit failure with unresolved cleanup lost cleanupIncomplete and its
  owner. Keep the retained owner unless the failure carries one.
- The EVM malformed-reply test expected a nonexistent `output_failure` code;
  AnalysisOutputError projects to `unreadable_output`. The pcap capinfos stub
  prints its report with a trailing newline, which retained output preserves.

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

* test(sqlite): keep a retained worker owner after a failed worker exit

Cover the path the existing worker-cleanup test bypasses: a real owned worker
exits nonzero, runOwnedCommand retains its incomplete cleanup and rethrows the
failure without a cleanupOwner. The provider must still report
cleanupIncomplete and block a new worker until the retained cleanup succeeds.
Fails on the previous owner assignment.

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

* fix(evm): retain the compact worker failure beside retained cleanup

When the worker command fails and its cleanup is retained, the cleanup error
now reports the primary failure in the same compact form as an unretained
command cleanup failure: failure kind, message, exit code and signal. Its
output is already retained once as captured_output, so the previous error no
longer repeats up to a megabyte of diagnostics, and failure_kind stays at the
top level that the provider test reads. Both paths share
ownedCommandPreviousError.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:56:22 -07:00
morluto b940ad0d65 fix(sqlite): retain completed worker evidence and unresolved cleanup ownership 2026-10-14 09:47:27 +08:00
morluto 5b7fe94710 fix(analysis): preserve extraction gaps and database failure reasons
Derive firmware completeness from examined coverage, retain linked continuation evidence, and preserve reported extraction limits. Keep Android package attribution and Go image fixtures coherent, and classify SQLite busy failures without discarding descriptor cleanup facts.

Reviewed fixes include firmware, ADB, SQLite, and recorded-transport bug reports and their focused PRs.

Validation: firmware report source tests passed 21 cases; prior focused artifact/provider boundaries passed. Real Android devices and external firmware tools were not rerun.
2026-10-14 08:32:15 +08:00
Dragolone 94072364c7 fix(sqlite): keep triggers whose declaration spells the table in another case (#2807)
* fix(sqlite): keep triggers whose declaration spells the table in another case

SQLite stores a trigger's target in sqlite_schema.tbl_name exactly as
written in CREATE TRIGGER and resolves it by ASCII case folding, while an
index stores the table's own name. The selected_table scope filtered
tbl_name with COLLATE BINARY, so a trigger declared ON users for table
Users was omitted while the schema still reported complete. Match
dependents with COLLATE NOCASE, and report each trigger's table as the
stored table or view name so the selected-table contract still holds.

* fix(sqlite): refuse an inexact table before the case-folded schema scan

With the NOCASE expansion, a selection such as users for table Users
expanded the stored table's declarations before readRows reported the
missing exact name, so large trigger definitions could surface as an
output-limit failure instead of the selection error. Check the exact
table name against PRAGMA table_list first and share the selection
failure with readRows.

* fix(sqlite): fold dependent names only for a row-selectable table

An exact view, virtual or possible shadow table passed the exact-name
guard, so the NOCASE scan could still expand a differently cased trigger
past the reply budget before readRows refused the selection. Fold case
only when the selection is an ordinary table that is not a possible
shadow table, and keep the exact filter otherwise so every refused
selection fails as it did before the case-folding change.

* fix(sqlite): resolve trigger tables without folding unrelated names

The stored-name lookup case-folded every PRAGMA table_list name up front,
so a selected-table inspection of a tiny table paid for large unrelated
identifiers. A selected-table scan only returns the selected table's
dependents, so report the selected name directly; other scopes use an
exact stored name when one matches and fold the stored names once, only
when a trigger spells its target differently.

* perf(sqlite): fold each table name once when matching shadow parents

The extracted shadow-parent helper folded the table name inside the find
callback, once per unresolved virtual table, and the selected table was
checked both before and during the schema scan. Fold the name once per
call as before, and reuse the selected table's result in the scan.
2026-10-13 22:37:47 +08:00
4c79127fcb fix(sqlite): classify a missing database path as an invalid value (#2775)
* fix(sqlite): classify a missing database path as an invalid value

The snapshot reported an absent selected database as an artifact path
failure, which the adapter projects as invalid_format alongside malformed
paths and content. The snapshot owns the absence observation, so it now
returns an invalid_value input issue on path directly, matching the
adapter's existing ENOENT/ENOTDIR classification.

Fixes #2614

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

* fix(sqlite): classify a missing database path as an invalid value

The snapshot reported an absent selected database as an artifact path
failure, which the adapter projects as invalid_format alongside malformed
paths and content. The snapshot owns the absence observation, so it now
returns an invalid_value input issue on path directly, matching the
adapter's existing ENOENT/ENOTDIR classification.

Fixes #2614

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-13 21:48:39 +08:00
yasshmittalandClaude Opus 5.5 d2e16b2a49 fix(sqlite): attribute unknown column selections to columns (#2774)
Every worker "selection" rejection was projected onto the table argument,
including an exact column name that does not exist in an existing table.
Give column rejections their own worker reason and map each input reason
to the caller argument it requires correcting. Table rejections keep the
table path.

Fixes #2613

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:03:14 +08:00
YsopwnableandY0UW1SH aeee5074d1 feat(sqlite): inspect offline snapshots with selective evidence (#2271)
Add offline SQLite database/WAL inspection through the shared CLI/MCP.

Use compact inventory, selected-table schema, exact ordered columns and
bounded row samples while preserving source identity and lossless values.
Retain completed Evidence through late cancellation and private-root cleanup
failures, bound concurrent inspections, and recheck journal validity after
acquisition. Reuse prepared CI runtime artifacts across supported Node lines.

Co-authored-by: Y0UW1SH <29498139+Y0UW1SH@users.noreply.github.com>
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-12 18:33:23 +00:00