Address review findings in trace_dylib_resolution:
- Resolve LC_LAZY_LOAD_DYLIB dependencies without traversing them, since
dyld loads them on first use. An unresolved one becomes
`lazy-load-unresolved`, not a launch failure.
- Accept FAT tables up to 128 records, as target opening does. A table
that names no Mach-O CPU type, such as a Java class file, is not
Mach-O.
- Require 8-byte load-command alignment in 64-bit images. 1,508 real
slices still match otool.
- Report EACCES/EPERM while probing, reading, or hashing as an
`unavailable` failure naming the code and path, rather than generic
io.
Findings and limitations move to dylibResolutionFindings.ts to keep the
resolver under the module size limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file such as Versions/.DS_Store beside the only real version made the
framework look multi-versioned, so its Info.plist became unknown with a
misleading limitation. Version directories are now limited to entries
observed as directories or implied by deeper paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review findings in trace_dylib_resolution:
- Choose child slices by dyld's graded architectures, so an x86_64h
process loads a generic x86_64 slice instead of reporting
architecture-missing.
- Read FAT containers with the byte-swapped FAT_CIGAM and FAT_CIGAM_64
magics, which target opening already accepts.
- Report string-bearing load commands too short to hold their string
offset as malformed instead of failing with a RangeError.
- Keep recognized but unsupported images as an `unsupported` candidate
outcome. It is undeterminable like an outside-root path, not
malformed.
- List Mach-O files that do not parse during root enumeration as
unparsed images, so coverage is partial rather than silently complete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(artifacts): accept the bundle subject when extracting an app bundle
For a .app target the session target is the bundle's executable, while the
artifact provider reports the bundle itself as the subject. The subject
check exempted inventory_artifact and inspect_artifact but not
extract_artifact, so extracting any .app failed with execution_failure after
the output tree had already been written, and the output path was never
reported.
* refactor(binary): rename artifact bundle subject helper
isArtifactInventorySubject also covers extract_artifact for .app
bundles, so name it for the shared bundle-subject exemption.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(javascript): resolve bare source-map and worker URLs next to the script
A sourceMappingURL like `main.js.map` (the default of webpack, esbuild,
rollup, Vite and rolldown) and `new Worker("worker.js")` were resolved as
module specifiers, so the bare name was looked up as an npm package and the
map or worker script was never linked. Resolve them as URLs relative to the
declaring script.
* fix(javascript): tighten url-reference resolution edge cases
Use looksExternal for non-module contexts so protocol-relative URLs
cannot be misread as root-absolute paths, cover url-reference unit
cases, and assert bare service-worker script resolution.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
A ZIP or DMG holding two applications produced bridge candidates from
scripts in one app to native code in the other. Candidates now pair only
components owned by the same application root; components outside every
root pair only with each other.
A versioned framework with several real version directories now keeps
its Info.plist unknown even when only one version has a plist, because
Versions/Current, which is not inventoried, decides which applies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(pages): reserve publishing for the REA website
* docs(website): add worked guides and agent prompt examples
* ci(website): check relevant PRs and publish only on demand
* docs(website): clarify IDA coverage and analysis session lifetime
Add trace_dylib_resolution (CLI trace-dylib-resolution) to the artifact
family. For the active Mach-O, or every executable in the active .app
(the main app, XPC services, extensions, login items, helpers), it
reports which file each dylib load reaches and how.
A pure TypeScript reader decodes FAT and Mach-O load commands:
LC_LOAD/WEAK/REEXPORT/LAZY/UPWARD_DYLIB, including the newer
dylib_use_command encoding with weak, re-export, upward and delayed-init
flags, plus LC_ID_DYLIB, LC_RPATH, LC_DYLD_ENVIRONMENT and code-signature
presence. Malformed tables are reported with the failing command instead
of being guessed.
Resolution follows dyld:
- @executable_path comes from each process root;
- @loader_path comes from the loading image, or from the owner of the
LC_RPATH entry being expanded;
- @rpath searches the loader's rpaths first, then up the load chain;
- dependencies load dependents-first in load-command order, once per
process, and a matching install name reuses an already loaded image;
- symlinks are followed inside the analyzed root, and escapes stop.
Every candidate path is reported with its outcome. Each edge resolves to
resolved, conditional (an outside-root candidate such as /usr/lib/swift
precedes), unresolved, or undetermined. Absolute and shared-cache system
paths are never checked against the host. Derived findings cover
unresolved required or weak loads, absent earlier @rpath candidates (with
library validation left unknown), and LC_DYLD_ENVIRONMENT.
verify:macos-bundle now also:
- checks every traced image against otool -l;
- asserts each resolution status on a clang-built fixture that adds a
missing Overrides rpath, an absolute rpath, and a delay-init library;
- compares the predicted load order of two process roots with what dyld
loads under DYLD_PRINT_LIBRARIES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Directory, ZIP, and DMG inventories can hold installers, disk-image
extras, and AppleDouble sidecars beside the app. Those components were
classified as the application's frameworks, libraries, JavaScript, and
metadata, and fed runtime families and bridge candidates. For these
formats, components outside every application root are now left out,
with a limitation that counts them. IPA projection is unchanged.
A selected directory whose name ends in .app is now its own root by
identity, so an empty or partial inventory still reports it. iOS is
claimed only for shallow roots that have an Info.plist, so an empty root
reports no platform.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The inventory snapshot captures reader provenance before close() runs,
so a `hdiutil info -plist` entry recorded during cleanup never reached
CLI or MCP results. Detach entries already share that timing. Record
neither instead of implying the query is observable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(artifacts): read AppKit connectors from flat keyed-archive NIBs
Flat AppKit NIBs archive NSNibOutletConnector and NSNibControlConnector
endpoints and the outlet or selector name under NSSource, NSDestination,
and NSLabel. The keyed-archive parser read only source, destination,
and label, so it skipped every such connector: XLD.app's 64 decoded NIBs
had no outlet or target-action edges. The NS-prefixed keys now follow the
same source and destination convention as compiled NIBArchive documents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(artifacts): read AppKit control connectors from the control to its target
NSNibControlConnector archives NSSource as the sending control and
NSDestination as the action target. The previous commit inverted them
like the unprefixed fields, so every flat AppKit action ran from the
target to the control. The action trace selects a control and follows
control -> action, and the graph keys the selector by its target, so
neither could resolve these actions. The NS-prefixed keys are now read
as source and destination for outlets and actions alike.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(artifacts): key AppKit connector endpoints by their node identity
An NSSource or NSDestination object with an identifier field was
referenced by that identifier, but its node is keyed by its authored
object ID or archive UID. The connector then pointed at a synthetic
unknown node. NS-prefixed endpoints now use the referenced object's
node identity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
project_apple_application_graph accepted only IPA inventories. It now
also projects macOS .app directories and ZIP or DMG inventories that
contain applications. It reports application roots, platforms, and
every nested bundle with its path-convention role, layout, parent,
Info.plist path, executable candidates, and signing paths:
- roles: app extensions, XPC services (including inside frameworks),
frameworks, login items, system and driver extensions, plug-ins,
resource bundles, and helper or nested applications;
- privileged helpers in Contents/Library/LaunchServices and launchd
plists in Contents/Library/LaunchAgents or LaunchDaemons;
- direct files of Contents/Helpers;
- symlinks by path. Their targets are not inventoried, so versioned
frameworks are described through their real Versions/<version>/
directories.
AppleDouble sidecars (._* and __MACOSX/) are excluded from roles. A DMG
whose contents could not be inventoried is reported as partial. Other
inventory formats remain input errors. The projection still reads no
plist or CMS semantics; it points callers at inspect_plist.
The result schema is additive. projection_id now covers the new fields,
so IPA projection IDs differ from earlier versions.
verify:macos-bundle compiles a source-owned app with clang (CLT only).
It packs it as a directory, a ditto ZIP, and an APFS DMG, and checks
identical anatomy through the CLI with stdio MCP parity. It runs in the
macOS CI job.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An APFS disk image attaches as an image disk plus a synthesized APFS
container, and hdiutil reports both as whole disks. Detaching the
container also ejects the image disk, so the reverse-order detach then
failed with "No such file or directory" and every APFS DMG inventory
ended in "DMG detach or mount-root cleanup failed". APFS is the default
format of `hdiutil create` on current macOS.
When a detach fails, ask `hdiutil info -plist` whether the device is
still attached. A device that is gone counts as detached; one that is
still listed, or whose state cannot be read, remains a cleanup failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(artifacts): project compiled AppKit actions from the control to its target
NSNibControlConnector archives the sending control as NSSource and the
action target as NSDestination; a nil target is the first responder. The
NIBArchive projection swapped them for control connectors, so every
compiled AppKit action ran from its target to the control. The action
trace, which follows control -> action, could not find them, and the
selector node was keyed by the control instead of the target class. When
the target was nil, the swapped source was null and the action was
dropped: Chess lost 29 menu actions, KeepingYouAwake 7.
Connectors now keep NSSource -> NSDestination. The NIBArchive test
encoder moves to a fixture module so a bundle test can build compiled
NIBs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(artifacts): report observed action routes when a compiled route is missing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(artifacts): report compiled connector records when a storyboard route is missing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(artifacts): write the storyboard fixture's action in Cocoa syntax
MacFixture.storyboard declared its button action with the iOS attribute,
<action selector="buildTapped:" destination="vc1">. Cocoa documents
name an action's receiver with target=. ibtool compiled the iOS spelling
into an NSNibControlConnector whose NSSource is the view controller
placeholder and whose NSDestination is the button. That is the reverse
of what Xcode-authored AppKit NIBs contain (Music, Chess,
KeepingYouAwake, and Transmission all archive NSSource as the control),
and it is what the old inversion had matched. With target= the compiled
fixture archives the button as NSSource. The temporary connector dump is
removed; the observed routes remain in the assertion message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(website): add local introduction and DX-Ball showcase
* feat(website): illustrate REA and DX-Ball reconstruction
Add two explanatory figures and an interactive comparison of recorded DX-Ball x86 instructions with maintained C. Preserve the full instruction listing, caller excerpt, constant reads, figure prompts and evidence provenance.
Validation: Chrome preview at local root and /rea/; five comparison steps; 22 clipboard checks; keyboard navigation; responsive widths 1440, 768, 390 and 320. Verify assembly against saved REA Evidence and C against the pinned public source. Existing execution and compiler results are cited, not rerun.
* docs(website): show the REA queries behind source recovery
Present compact function, caller and byte-read request excerpts alongside recorded evidence. Keep the overview and case narrative focused on REA, with provider provenance in contributor notes. Replace raster references with maintained SVG source diagrams while retaining the established layout and image-generation prompts.
Validation: local and /rea/ Chrome previews, five comparison steps, 22 clipboard checks, responsive widths and keyboard navigation passed. SVG XML and all 41 local links/assets passed checks.
Compiled UIKit NIBs and storyboards archive outlets and control actions
as UIRuntimeOutletConnection and UIRuntimeEventConnection with
UISource, UIDestination, and UILabel. The NIBArchive projection
recognized only AppKit *Connector classes with NS-prefixed keys, so an
iOS app produced no outlet or action edges: Stash's 64 decoded documents
yielded none. These connections are now projected with the same
source -> destination roles, an event connection carries its
UIEventMask as ui_event_mask, UIClassSwapper reports its UIClassName,
UIProxyObject is a placeholder, and connection records are no longer
emitted as unknown objects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>