* fix(native): retain valid Objective-C properties beside a malformed record
readObjcPropertyList decoded a whole property_list_t in one operation.
When a later entry's name pointer was unmapped, the list-level catch
discarded the properties already decoded. An empty property name passed
decoding and then failed schema validation of the whole result, so
inspect-native-dispatch-metadata exited 1 with no metadata.
Each property_t entry is now decoded on its own. A failing entry, or an
empty name, becomes a property_decode_failed issue at the entry address,
and sibling properties are retained. Incomplete-attribute issues also
report their entry address. List-header failures still fail only that
list.
Fixes#3336
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(native): cover property record isolation on every platform
Move the synthetic Objective-C property Mach-O into a shared fixture and
decode it through decodeAppleDispatchMetadata in a portable test, so
Linux and Windows runs also catch a regression that drops valid sibling
properties. The macOS CLI cases keep exercising the built command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Replace fixed Linux Hopper release pins with current vendor feed metadata,
complete detached-PGP package verification under the stable publisher primary
key, and authenticated package evidence for offline admission. Share parsed
publisher facts across approved setup, doctor and the pre-execution guard.
Preserve existing ownership, cancellation, private-display and cleanup rules.
Fixes#3577
Normalize empty XDG settings, preserve backups during approved cache repair,
and retain publisher cleanup through cancellation and shutdown. Exercise cold
offline verification without reusing process-local proof state. Serialize
approved cache publication across processes, restore old evidence on failure,
and share the existing lease owner from process without changing its identities.
Validation: 438 focused tests (six platform-related skips), full static/documentation checks, real Ubuntu
24.04 official Hopper 6.6.0 setup/CLI/MCP, offline ordinary-user admission,
launcher mutation rejection, and cold/warm verification measurements. Official
RPM metadata mismatch is tracked separately in #3578 and fails closed.
BREAKING CHANGE: Older Linux Hopper builds admitted by the fixed allowlist
now require retained publisher-signed package evidence or an approved update
to the current public demo. Update explicit HOPPER_LAUNCHER_PATH values when
migrating; the live feed exposes only its current release.
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
An Objective-C property attribute string whose quoted class name never
closes, such as T@"NSString, was treated as complete. The missing N and
R were then read as the Objective-C defaults, so the property was reported
atomic and read-write, and objc_properties_categories coverage stayed
complete. The documented semantics require null flags for incomplete
attribute metadata.
The attribute splitter now reports whether every quote closed. An
incomplete string leaves atomicity and any unestablished read-only value
null, while explicit N or R tokens remain established and the raw type is
preserved. Each affected property adds a property_attributes_incomplete
issue, which marks the facet partial.
Fixes#3345
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dataEffectLiteralString asked the literal owner for an exact value, and
when a template recovered with an invalid escape had no cooked value it
fell back to the raw spelling. Child-process commands, request endpoints
and event names then became resolved values that are not JavaScript
strings.
Use only the owner's cooked value; a template without one stays dynamic.
Fixes#3361
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): accept --flag=value for global output flags
`rea <command> --format=json` failed with "Unknown flag: --format".
`--filter-output=...`, `--token-limit=...` and `--token-offset=...`
failed the same way. Incur reads these global flags only in spaced form,
so the equals form fell through to the command parser. Command options
already accept both forms, and `rea mcp doctor` parses `--format=` itself.
A valued output flag with no value (`--format` last, or `--format=`)
also reported the flag as unknown with code UNKNOWN.
normalizeCliOutputArguments, in the module that owns output-argument
interpretation, now splits the equals forms of the four valued output
flags. The entry point passes the normalized argv to rea's output
validation and to Incur, so both read the same effective values. A
missing value now fails with MISSING_OUTPUT_VALUE and names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): detect missing output values while parsing output flags
The missing-value check scanned argv again after parsing, so it also
checked tokens the parser had already consumed as values. For example,
`--filter-output --format` selects the field `--format`, yet the second
scan reported `--format` as missing a value. parseCliOutputArguments
now records the first valued flag it skips for lack of a value, in the
same pass that consumes values.
Cover normalizeCliOutputArguments directly: values containing `=`, empty
values, and command options that only share a prefix. Export it in the
dispatcher test's runtime stub as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(comparison): keep unnamed reference endpoints distinct or unknown
compare_functions projected reference endpoints to procedure names only,
so two edges whose targets had no named procedure both became null and
compared equal, even when their recorded addresses differed.
Identify an unnamed endpoint by its address when both dossiers describe
the same artifact, where that address names one location. Across
artifacts the endpoint has no stable identity, so equal-looking
projections that contain one leave the references dimension unknown
with an explicit limitation. Named endpoints still survive relocation.
Fixes#3388
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(comparison): report known reference-kind changes before unnamed-endpoint uncertainty
Compare known reference kinds first and keep a differing kind as a
change; the unnamed-endpoint limitation now applies only when equality
remains unproven. The kind projection uses the same endpoint identity
as the endpoint projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(javascript): reject FIFO recovery inputs without blocking
scanRecoveryFile opened the selected path with a blocking O_RDONLY open
before checking that it is a regular file. A FIFO with no writer blocks
that open in the libuv threadpool, so cancellation and the recovery
deadline could not settle the read, and the process could not exit.
Admit the input through the shared openRegularFile owner, which opens
with O_NONBLOCK and verifies a regular file before any read. A FIFO,
directory or socket now fails admission immediately with the existing
regular-file error; symlink rejection and cancellation are unchanged.
Fixes#3332
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(javascript): keep a failed admission close owned on cancellation
Rethrow RegularFileAdmissionFailure before mapping cancellation, so a
descriptor whose close failed stays owned by its error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(reference): stop attributing overwritten require members to Node
import-reference-source treated require.resolve(...) and require.main(...)
as Node's resolver even after the file replaced that member, so a literal
argument became a parsed internal dependency that the source never loads.
Attribute member calls only while the loader's resolve and main members
keep Node's identity: no write to either member or a computed member
(assignment, update, delete, destructuring) and no use of require as a
value where a mutator or alias could replace them. Bare require(...) calls
are unaffected.
Fixes#3393
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(reference): keep optional and wrapped loader uses intact
An optional call require?.(...) or a TypeScript or parenthesis wrapper
around require was read as require escaping as a value, which disabled
every member-call link in the file. Look through inert wrappers and
accept optional calls before deciding the loader escaped, and add
controls for bare calls after a member overwrite, a local loader's
member write, and optional calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A plain new Worker("./w.js") or navigator.serviceWorker.register("./sw.js")
resolves against the document base URL in a browser, and a Node
worker_threads string resolves against the working directory. Static
analysis was resolving browser strings against the calling script, which
recorded a resolved mapping to the wrong file.
Only new Worker(new URL(path, import.meta.url)) now produces a specifier.
Other worker and service-worker strings keep their expression and stay
unresolved, matching the existing worker_threads string handling.
Fixes#3309
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
plutil writes years past 9999 and negative years in a form the pinned
XML decoder cannot parse, and binary plists can hold dates outside the
JavaScript Date range. The decoder returns an Invalid Date. Its
projection's toISOString() call then threw, so inspect_plist failed the
whole plist as "unreadable_output" with a `rea doctor` remediation.
Keyed archive inspection and Interface Builder decoding rejected the
archive in the same way.
projectPlistValue, the shared projection, now reports such a date as
{ "$plist_type": "date", "iso8601": null } and counts it. inspect_plist,
keyed archive inspection and Interface Builder decoding each add a
counted limitation, as they already do for non-finite reals.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(javascript): keep empty URL path segments significant
ESM imports and HTML script references were joined with POSIX path
rules, which collapse '//' before applying '..'. URL resolution removes
the empty segment instead, so './assets//../app.js' names assets/app.js,
not app.js, and the graph linked the importer and renderer to the wrong
file. URL references refused every '//' even when dot segments cancel it.
Resolve ESM, HTML (including document base) and URL references through
one URL dot-segment helper. A remaining empty segment stays unavailable,
references above the artifact root are still rejected, and CommonJS keeps
filesystem path semantics.
Fixes#3307
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(javascript): resolve HTML base and reference as one URL path
The base href was turned into a canonical path before the script
reference was applied. A base empty segment that the reference's '..'
would remove was rejected early, and an above-root '..' from the base
became a removable segment that the reference's '..' could cancel.
Keep one URL path state through base and reference resolution, with
above-root escapes held apart from removable segments, and reject empty
segments only in the final path. Remove the superseded htmlBaseDirectory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
resolveInternalSpecifier offered posix.join(dir, "index") as the
second candidate, ahead of every extension. With both dep.js and an
extensionless dep/index present, require("./dep") therefore resolved to
dep/index, a file that neither Node nor TypeScript ever selects.
Directory candidates now carry an extension. File and index ordering is
otherwise unchanged, so Node-validated cases such as dep/index.js
winning over dep.cjs keep their meaning.
Fixes#3381
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The endpoint index from #3779 still scanned every earlier finding at the
same endpoint and serialized each invocation location. One wrapper called
N times for one channel shares an endpoint, so duplicate checks stayed
quadratic in N.
Record per endpoint whether a directly observed send/invoke exists and the
set of serialized invocation locations. The duplicate rule is unchanged and
each check is constant time.
Refs #3670
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): stop quietly when the stdout reader closes early
`rea ... | head` crashed once head closed the pipe. TOON and YAML output
raised an unhandled EPIPE 'error' event with a Node stack trace. JSON
output reported {"code":"UNKNOWN","message":"write EPIPE"} on stderr.
Both exited 1, as if the analysis had failed.
The CLI entry point owns process.stdout, so it now treats EPIPE as the
reader declining the rest of the output. It stops writing, suppresses
the failure report and exits 141 like a SIGPIPE-terminated writer, also
when Incur exits through its own failure path. Other stdout errors are
handled as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): guard mcp doctor output and cover midstream reader closure
The closed-reader guard was only installed for ordinary CLI commands. As a
result `rea mcp doctor | head` still crashed on EPIPE. The guard now lives
in one entry-point function shared by both CLI branches; MCP stdio keeps
its own lifecycle.
The tests now also close the reader after the first chunk of a 4 MiB
streamed JSON result, so later writes in writeJsonOutput are covered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
parserPluginsForPath enabled Babel's typescript plugin for .js, .jsx,
.mjs and .cjs sources. TypeScript reads some valid JavaScript
differently: a conditional whose consequent is an arrow with a
parenthesized body (c?x=>(a):y=>(b)) throws as a return-type
annotation, and a<b>(c) becomes a generic call. VS Code's 20 MB
workbench bundles therefore returned no AST at all.
Select JavaScript grammar (JSX plus legacy decorators) for explicit
JavaScript extensions. TypeScript dialects and unnamed sources keep the
existing TypeScript-capable contract.
Fixes#3780
Refs #3670
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Static analysis rebuilt the full lexical definitions index three times per
file (open receivers, RPC client roots, binding lookup) and the Electron
wrapper pass checked each substituted IPC registration against every prior
finding with JSON.stringify, which is quadratic in registrations.
Classify open receivers and RPC client roots in one traversal over a single
definitions-only index, then extend that same state with ambient writes for
binding lookup. Index IPC findings by side, operation, channel and location
so duplicate checks only consult same-endpoint candidates.
Static facts are byte-identical on the measured corpora.
Refs #3670
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
inspect_plist rooted every file target at the file's own directory, so a
program file under App.app/Contents/MacOS defaulted to
Contents/MacOS/Contents/Info.plist and --relative-path resolved from MacOS/
instead of the app root the CLI documents. A selected plist file likewise
looked for <dir>/Contents/Info.plist instead of reading itself.
A program file directly under a bundle's Contents/MacOS now roots at that
bundle, and a plist artifact target is its own default plist. The missing
default message names the default plist rather than Contents/Info.plist.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): charge admitted member facts once in the final projection
inspect_managed_members admits method bodies, signatures, call edges,
field accesses and user strings as they are produced, then admits the
whole result again before returning it. The final pass re-counted every
already-admitted record, so the 64 MiB representation budget was spent
at roughly twice the result's JSON size. Ordinary assemblies such as
MathNet.Numerics, MongoDB.Driver, Npgsql, ClosedXML, System.Reactive and
Azure.Storage.Blobs returned resource-limit with no members.
The budget now remembers each admitted projection. A containing
projection spends that charge once instead of counting the subtree
again; a value that serializes more than once still pays for every
further occurrence. Results that already fit are byte-identical.
Refs #3449
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(managed): check a repeated occurrence pays with fresh credit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
build_reconstruction_obligation_ledger accepted a manifest original case or
fixture whose Evidence ID was absent from the bundle whenever another entry
of the same case kind was valid. The obligation was reported verified, the
ledger ready, and the dangling ID appeared in evidence_links with no
diagnostic, although the documented contract requires every referenced
Evidence ID to exist in the bundle.
Report each absent original-case or fixture reference as missing-original-case
or missing-case, naming the ID. Any such diagnostic withholds verification and
readiness.
Fixes#3363
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Two capture-process records of the same executable and scenario generate
the same obligation ID. addCandidate added their definition counts, so the
ledger reported duplicate-definition, a closure blocker meant for competing
definitions, merely because the claim was observed twice. The merge also
kept only the first record's unavailable_authority and
required_next_evidence.
Generated IDs digest their derivation key, so merging two generated
candidates keeps one definition. A reviewed definition that reuses an ID is
still a duplicate. The merge now unions unavailable_authority and
required_next_evidence from every observation.
Fixes#3375
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
inspect_managed_native_boundaries derived summary.mixed_mode_or_native_header
only from the managed native header and the native entry-point flag. A C++/CLI
assembly with ILONLY clear and no ReadyToRun signature was classified as
mixed-clr-native by inspect_managed_artifact but summarized as not mixed.
Give the rule one owner, managedCliIsMixedClrNative in ManagedPeReader, and
use it for both the artifact runtime family and the boundary summary. The
summary now reads the CLI header directly instead of the cli_native projection.
Fixes#3366
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
inspect_macho passed the CPU-family spelling (`arm64e`) for every dotted lipo
slice name to otool, nm, dwarfdump and vtool. That is right for the
pointer-auth ABI version suffix (`arm64e.v1`), but `arm64e.x1` is a distinct
subtype: on a universal x86_64 + arm64e.x1 executable otool failed with
"does not contain architecture: arm64e" and the whole inspection failed.
Strip only the `.v<N>` ABI version suffix and pass other variant names
exactly. vtool has no spelling for a variant subtype, so skip it for such
slices and report that build metadata comes from otool only.
Fixes#3389
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ManagedMetadataLayout's range check reported offsets relative to the buffer
it was given: the metadata root for version and stream headers, and the
table stream for the tables header, row counts and table data. Other
diagnostics in the same reader already use artifact file offsets, so a
truncated table stream at file offset 888 was reported at offset 24, inside
the DOS header.
Pass each buffer's artifact file offset to the range check so every
invalid-metadata-root issue carries a file offset.
Fixes#3364
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
inspect_adb_package converted the dumpsys versionCode, a signed 64-bit long
(versionCodeMajor << 32 | versionCode), to a JavaScript number. Values above
2^53 were rounded, so 9007199254740993 was reported as 9007199254740992 with
the same Evidence ID, and negative values became null.
Return version_code as the exact decimal string dumpsys prints, matching the
apktool and JADX version_code contracts.
BREAKING CHANGE: inspect_adb_package version_code is now a decimal string
("45") instead of a JSON number (45). Read it as a string, or parse it with
BigInt when numeric comparison is needed.
Fixes#3368
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>