* fix(artifacts): read ASCII control bytes in legacy ZIP names as ASCII
ZIP names without the UTF-8 flag were decoded with zip.js's CP437 table,
which maps bytes 0x01-0x1F and 0x7F to IBM PC display glyphs. A folder
zipped on macOS with a custom icon therefore listed and extracted
`Folder/Icon\r` as `Folder/Icon♪`, a name that is not in the archive and
differs from REA's directory inventory of the same folder. Tabs became `○`
and DEL became `⌂`.
Decode legacy names in REA's existing decodeText hook with the Unicode
CP437 mapping that Python's zipfile also uses: bytes below 0x80 as ASCII,
and the upper half with the same table zip.js uses.
Windows file names cannot contain U+0001-U+001F, so a Windows extraction
destination now refuses such a path with a typed path failure before
creating anything, as it already does for ':'.
* test(artifacts): check extracted legacy ZIP control names on disk
The control-name cases checked only the logical path that inventory
reports. Extraction writes through SafeOutputTree, so a change that
renamed the extracted files would still pass those cases.
Extract `Icon\r` and `é\t\u007F.a`, then compare the reported
relative_path, the directory listing, and the file contents. The new
cases skip on Windows, which refuses these names.
safeOutputTree.test.ts covers that refusal.
Select SQLite compatibility by explicit source, fixture, lifecycle, and script ownership while retaining every version in full qualification. Share the canonical scope inventory between planning and the required gate.
Reuse recent exact-SHA full qualification only for hourly schedules, with trusted successful-run metadata and receipts. Force a daily refresh, reject stale original runs even after retries, and never renew proof from reused validation.
Report attempt-specific queue, execution, and matched step timing in a separate completion workflow using trusted default-branch code and read-only API access. Preserve raw metadata and unknown timings without delaying validation or publication.
Validation: 177 focused tests; typecheck/lint; knip; actionlint 1.7.7; all 1046 tests discovered; live 37-job API report smoke. Remote workflow scheduling and real-provider matrices remain CI verification.
deriveFindings looked up each traced root's slice again by architecture
name. A universal executable with arm64e and arm64e.x1 slices reports
both as "arm64e", so both roots resolved to the first slice: a later
slice's LC_DYLD_ENVIRONMENT produced no finding, or the first slice's
command was reported twice. The per-slice dyld_environment arrays were
already correct.
traceDylibLoading already has the exact slice when it records a root, so
pass it to deriveFindings instead of re-deriving it from the name. The
public roots and findings shapes are unchanged.
Fixes#2661
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Remove redundant ref-based concurrency from reusable provider and website workflows. Their old groups could cancel qualification from another CI tier even though the callers used separate groups. Disposable hosted jobs now inherit caller scheduling; direct manual dispatches remain independent.
Validate every called CI workflow has no independent cancellation group. Validation: 100 focused scope/gate tests and actionlint 1.7.7.
Scope main pushes from a successful ancestor so canceled runs cannot hide changes. Keep the complete portable suite in fast feedback and move coverage plus Intel macOS to hourly, manual, explicit-full, and release qualification. Let qualification finish independently of superseded feedback.
Bind publication to full qualification at its exact revision and publish the verified tarball after checking workflow identity, version, and SHA-256. Keep trusted cache writers and reject incomplete merged test inventories.
Validation: 151 focused CI tests; typecheck/lint; actionlint 1.7.7; knip; all 1044 files discovered; bounded blob/merge smoke; macOS package verifier with retention and cleanup; npm pack dry run. Native Linux/ARM/Windows and remote workflow execution remain CI verification.
* fix(native): select one slice when universal Mach-O slices share a CPU type
On macOS 27 most system binaries are universal files whose slices share
CPU_TYPE_ARM64: arm64e + arm64e.x1 (Calculator, TextEdit), x86_64 + arm64e +
arm64e.x1 (/bin/ls), or arm64 + arm64e.x1 (ShortcutDroplet). Target admission
names slices by CPU family, so these targets have availableArchitectures
["arm64"] while the file holds two arm64 slices.
inspect_macho read that family count as a slice count and ran otool, nm,
dyld_info, dwarfdump and vtool without -arch. ShortcutDroplet failed with
"Malformed otool load-command output" (40 load commands against ncmds 20).
Calculator reported both slices' imports (4138 instead of 2069), nm's
"(for architecture ...)" header lines as exports, and no selected slice.
selectArchitecture now also filters when lipo lists more than one slice. Its
preference is unchanged: the family name, then arm64e, then the first
arm64e.<variant>.
inspect_native_dispatch_metadata matched FAT slices by CPU type only and
refused /bin/ls and TextEdit as "Ambiguous FAT architecture slice".
selectMachoSlice now ranks slices of the requested CPU type by base subtype in
the same order: arm64 (0, then 1) or x86_64 (3), then arm64e (2), then other
variants such as arm64e.x1 in FAT table order. It reports ambiguity only when
the top-ranked slices share a subtype.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(native): share Mach-O slice preference across inspections
* chore: preserve upstream process capture test during merge
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
HAR validation reported only the AJV keyword, so a capture missing
`log.version` failed with "HAR schema validation failed: required." and
the caller could not tell which property to supply. Include AJV's
constraint message, which names the missing property, expected type, or
pattern without restating captured values.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Declare the synthetic 4 KiB Mach-O slice alignment accurately while preserving the IDA thinning scenario. Share web cleanup aggregation and process-trace fixture facts instead of duplicating them across consumers and tests; keep the 3% duplication threshold unchanged.
Validation: npm run check:ci, test discovery, and 42 focused IDA, process-trace, web workflow and cleanup boundary tests. Duplication falls from 3.01% to 2.99%.
Replace the implicit local-provenance fallback with explicit locally constructed values. Keep opaque results, parameters and caught values unknown; follow assignment and sequence results while retaining exact module aliases. Clarify that alias provenance does not represent transitive data taint.
Validation: 26 focused provenance/module-link tests, 135 semantic and graph-builder tests, six application module-relationship boundary tests, and the fast typecheck/lint gate.
Keep failed stable-read handles and WABT runtime roots under retryable resource scopes. Preserve cancellation causes and primary errors, close the same service instances bound to MCP tools, and consolidate CLI operation/cleanup projection.
Validation: fast typecheck and lint, cached runtime build, docs checks, SDK schema validation, focused service and real-file cleanup tests, CLI cleanup regressions, and MCP lifecycle/shutdown workflows. Provider process fixtures simulate engine output; real WABT and Linux EVM execution are not established by this pass.
Compare import and require outcomes for static TypeScript references when compiler options are unknown. Preserve branch facts through worker IR and both graph projections; retain declaration fallbacks as candidates without changing explicitly selected paths.
Validation: 210 focused resolver and semantic tests; 21 production-boundary and source-extension tests. A bounded 2,000-reference probe adds about 16–17 ms wall time for dual resolution; full task token costs and real compiled application behavior remain unmeasured.
* perf(native): add opt-in Mach-O summary delivery and retained pages
inspect_macho returns every import and export inline. For VS Code's
Electron Framework one MCP call carries 2.5 MB of text (about 828k o200k
tokens), and the same again as structuredContent; the MCP response
budget admits it. Agents asking for slice identity, UUID or dependency
counts paid that cost on every first call.
Add the established opt-in detail selection: omitted or complete keeps
the existing complete result and Evidence identity, while summary
retains the complete Evidence and returns slice identity with
per-collection returned/total/exhaustive counts (956 tokens).
inspect_analysis_view kind macho pages the original rows of any
collection or the command provenance, repeating the producer's own
total and exhaustive flag so page exhaustion is never read as absence.
Delivery detail never reaches the native tools or the source Evidence
ID: registerEvidenceTools gains a parametersForEvidence hook, so the
summary's parent is the same Evidence as a complete call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): refuse unretainable summary delivery before analysis
A server without Evidence retention (a fixed analysis source) ran the
complete inspect_macho or inspect_artifact analysis for detail summary
and only then refused it in summarizeRetainedAnalysis, paying the very
cost summary delivery exists to avoid. registerEvidenceTools gains a
preflight hook; both registrations check retention there, as the WASM,
EVM, managed, pcap and capture handlers already do before dispatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(native): keep whole rows in Mach-O architecture and segment pages
The page item union listed plain-object row arrays, and zod returns the
first union member that parses while dropping unknown keys. Entrypoint
rows declare only file_offset, so every architecture and segment row
(which also carry a numeric file_offset) matched it and lost its name,
CPU fields, permissions and sections. Rows now use exact key sets, and
imports/exports share one member. The MCP test pages every facet of a
real /bin/ls and compares it with the unparsed complete rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Once a Frida session reached its 16 MiB capture budget, every new message
evicted the oldest one with Array#shift, re-serialized it to recover its
byte charge, and searched and spliced its per-script array. Each of
those is linear in the retained messages, so with about 188k small
messages retained the cost per message rose from 0.14 µs to 32 µs and
capped the main thread at roughly 31k messages per second, which a
hooked hot function can exceed.
Keep both queues as head-offset FIFOs that compact once half their slots
are evicted, and record each message's charge when it is admitted.
Arrival order already makes the globally oldest message the oldest one
of its script, so per-script eviction becomes a head check. Eviction is
now amortized O(1) at about 0.14 µs per message; retained messages,
their order and the truncation flag are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(native): name the ownership helper prerequisite in command failures
Every supervised native command launch needs the Darwin process-ownership
helper. When that helper cannot be compiled (relative REA_DARWIN_SWIFTC_PATH,
missing SDKROOT, a missing compiler, or an SDK/compiler mismatch), the
runner reduced the typed DarwinProcessOwnershipInspectionError to a bare
`io` failure. inspect_macho then reported `execution_failure` with no hint,
and inspect_artifact on any universal Mach-O said only "lipo could not
enumerate universal Mach-O slices".
The runner now keeps the inspection message as the failure's `detail` at
the launch boundary and carries it through xcrun resolution. The native
provider adds it to its diagnostics and the slice reader appends it to its
message, matching what the SQLite and capture providers already report.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(native): cover the provider's ownership helper diagnostics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Android 10 and 11 print restricted entries of the requested-permissions
list as "NAME: restricted=true". The parser took the first such line as
the end of the list, so the count stopped there while coverage still
read complete.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(windows): preserve caller affinity only within one processor group
`spawnProcess` read the caller's affinity mask and applied it to the
suspended child unconditionally. A mask names processors relative to one
processor group, and Windows assigns each new process to a group of its
own choosing, so on hosts with more than 64 logical processors the child
is often placed in another group. `SetProcessAffinityMask` then rejects
the mask with ERROR_INVALID_PARAMETER (87) and the spawn fails, making
every owned provider process start intermittently on such hosts.
Affinity is a nicety, not an authority or isolation control, so it is now
applied only when the caller and the suspended child both occupy the same
single processor group. A child placed in another group keeps that group's
default affinity and the spawn proceeds. Job Object membership,
kill-on-owner-close policy, owner normalization and priority preservation
are unchanged, and the mask failure remains fatal within a shared group,
where it is not expected.
Single-group hosts, including every host with 64 or fewer logical
processors, are unaffected: `GetProcessGroupAffinity` reports one group
for both processes and the same mask is applied as before.
The optional process-boundary fixture gains an `affinity` observation,
and the native verification lane asserts that an owned spawn succeeds and
that the child's mask matches the caller's whenever both share a single
group.
Fixes#2181
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): refuse to widen a pinned caller's affinity across groups
Addresses review feedback on the first commit.
An unconstrained caller loses nothing when Windows places an owned child
in another processor group, so that spawn proceeds with the group's
default. A caller deliberately pinned to fewer processors than its group
holds is a different case: silently accepting the default would hand the
provider tree the whole group, which `docs/installation.md` tells
operators to rely on affinity to prevent. Such a spawn now fails naming
the constraint instead. `GetProcessAffinityMask` already reports the
group's active set, which distinguishes the two without another query.
Selecting the caller's group at creation through
PROC_THREAD_ATTRIBUTE_GROUP_AFFINITY was tried first and rejected:
`CreateProcessW` fails with ERROR_INVALID_PARAMETER on a host with a
single processor group, both alongside PROC_THREAD_ATTRIBUTE_JOB_LIST and
on its own, so it cannot carry the ordinary path.
The boundary fixture's affinity observation now reports a process id's
own mask rather than its own, because a separate probe process is
assigned its own group and cannot stand in for the process that spawns
the child. Masks and the group's active set cross as fixed-width
hexadecimal strings: a mask can set bits above the safe-integer range,
where `JSON.parse` would round two different masks to one value.
The verification lane asserts the mask is inherited exactly when caller
and child share a group, and otherwise that the caller was unconstrained.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(windows): record what owned-process affinity does not preserve
A mask covering its group's whole active set does not distinguish a
caller the system placed in that group from one that selected the group
deliberately, so an owned child can still be placed elsewhere with its
processor count bounded and its group placement lost. Treating that case
as constrained instead is not available: on a multi-group host an
ordinary process is assigned a group by the system and its mask is that
group's active set, so refusing it would fail every owned spawn there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The trace specification owns the declared identifier vocabulary and the
comparison result echoes those identifiers verbatim into raw_trace[].event_id,
matched_variant and diagnostic event_ids. The result module restated the shared
IDENTIFIER_PATTERN by hand with a 64-character cap, so a specification with a
longer identifier validated and then its own comparison result failed to parse:
compareProcessTraces threw a raw ZodError at the caller instead of returning a
comparison.
Export the identifier schema from the specification as the single owner and
reuse it in the evaluation module, removing the capped copy. IDENTIFIER_PATTERN
has no length bound and every other consumer uses it unbounded, so the
duplicate was the outlier. Removing the bound rather than adding one keeps the
advertised trace_spec input contract unchanged; identifiers of 64 characters or
fewer behave exactly as before.
Verified with 48 process tests, 24 boundary CLI/MCP trace tests, check:fast and
verify:test-discovery on macOS arm64. The regression test failed with the
ZodError before the change and passes after it.
Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
* test(ida): expect the private input of a universal Mach-O to end at the selected slice
Cover a universal file larger than one read chunk with another slice after the selected one, for both a middle slice and the first slice.
* fix(ida): stop the private input of a universal Mach-O at the end of the selected slice
A read chunk that starts after the slice produced a negative end offset, and Buffer.subarray counts a negative bound from the end, so the head of the following slice was written to the private input and its digest.
Use POSIX path semantics for recorded POSIX roots when reconciling runtime Evidence on Windows. Preserve Windows mapping behavior and cover implicit and explicit file roots through the reconciliation service.
Assisted-by: OpenAI GPT-6 <noreply@openai.com>
Co-authored-by: Aurantium-47 <280805072+Aurantium-47@users.noreply.github.com>
The pcap summary sorted equal-count endpoints and protocol stacks with
String#localeCompare, and Flutter build inspection sorted APK ABI
directory names the same way. localeCompare follows the host's ICU
locale, so the same capture produced differently ordered Evidence on
different hosts: under da_DK "aarp" sorts after "zz", under lt_LT
"syslog" sorts before "ssdp", and every locale puts "fe80::1" before
"fe80:1::1". Use the repository's compareUnicodeCodePoints, as the
other Evidence producers already do.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>