[GH-ISSUE #5692] [Bug] decode-interface-builder labels an archived outlet-collection destination missing or external #3442

Closed
opened 2026-10-31 00:40:32 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 20, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5692

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

decode-interface-builder recognizes a compiled UIKit outlet collection named buttons but returns an unresolved outlet_to edge with destination_object_missing_or_external. The destination array and its single button member exist in the archive, and the button exists in REA's node set. Connection coverage reports complete with zero omissions. An otherwise identical ordinary outlet resolves to the button.

Reproduction

Run from a built source checkout on macOS with Xcode's iOS platform, ibtool, a C compiler, Node.js and jq. This compiles and inspects an inert app without launching it. The independent verifier ran the collection and ordinary-outlet control once at the baseline; submission repeated both against a fresh build of main.

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" "$work_dir/cache" \
  "$work_dir/state" "$work_dir/tmp" "$work_dir/Demo.app"
cat > "$work_dir/Main.storyboard" <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VC1-aa-aaa">
  <dependencies>
    <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
  </dependencies>
  <scenes>
    <scene sceneID="sc1-aa-aaa">
      <objects>
        <viewController id="VC1-aa-aaa" customClass="DemoViewController" sceneMemberID="viewController">
          <view key="view" contentMode="scaleToFill" id="vw1-aa-aaa">
            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
              <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="btn-1-aaa">
                <rect key="frame" x="20" y="40" width="100" height="44"/>
                <state key="normal" title="Go"/>
              </button>
            </subviews>
            <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
            <viewLayoutGuide key="safeArea" id="sa1-aa-aaa"/>
          </view>
          <connections>
            <outletCollection property="buttons" destination="btn-1-aaa" collectionClass="NSMutableArray" id="col-1-aaa"/>
          </connections>
        </viewController>
        <placeholder placeholderIdentifier="IBFirstResponder" id="fr1-aa-aaa" sceneMemberID="firstResponder"/>
      </objects>
    </scene>
  </scenes>
</document>
XML
xcrun ibtool --errors --warnings --compile "$work_dir/Demo.app/Main.storyboardc" \
  "$work_dir/Main.storyboard" --target-device iphone \
  --minimum-deployment-target 17.0 --module Demo
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' \
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
  '<plist version="1.0"><dict><key>CFBundleExecutable</key><string>Demo</string><key>CFBundleIdentifier</key><string>dev.rea.demo</string></dict></plist>' \
  > "$work_dir/Demo.app/Info.plist"
printf 'int main(void){return 0;}\n' > "$work_dir/demo.c"
cc "$work_dir/demo.c" -o "$work_dir/Demo.app/Demo"
xcrun ibtool --connections --output-format xml1 "$work_dir/Main.storyboard" \
  | plutil -p - | sed -n '/connections/,/^  }/p'
env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" \
  XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" \
  XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \
  node scripts/rea.mjs decode-interface-builder "$work_dir/Demo.app" --json \
  > "$work_dir/out.json"
jq '{outlet_edges: [.normalized_result.graph.edges[]
       | select(.relation == "outlet_to") | {from, to, resolution, reason,
         label: .evidence[0].description}],
     coverage: [.normalized_result.graph.coverage[]
       | select(.facet | startswith("connections:"))
       | {facet, status, reason, examined, omitted}]}' "$work_dir/out.json"

For the ordinary-outlet control, use a separate work directory and replace only the outletCollection element with <outlet property="buttons" destination="btn-1-aaa" id="col-1-aaa"/>. Repeat compilation and inspection. The source-side ibtool --connections result identifies the collection's button destination; REA resolves the ordinary-outlet control.

Expected behavior

Retain the relationship between a recognized outlet collection and its serialized members, either through an explicit collection node or direct member relationships. A local serialized collection and its button must not be diagnosed as missing or external. An explicit unsupported-collection limitation would be more truthful but would not restore the lost membership.

Contract sources at the baseline: docs/native-investigation.md:383-396 advertises compiled storyboard/nib objects, outlets and connections; AGENTS.md, Architecture and Cleanup, requires preserved identity, omission, uncertainty and source evidence. src/domain/apple/interfaceBuilderKeyedArchive.ts:39-48 explicitly recognizes UIRuntimeOutletCollectionConnection. Apple's ibtool --connections independently identifies this fixture's collection and destination.

Impact and scope

P2: the graph loses a recoverable outlet-to-button relationship for this one-member collection and gives a misleading absence reason. Ordinary outlets and the member object still decode. This does not depend on repeated-key overwriting. Source-side ibtool supplies the relationship when the original storyboard is available; a practical compiled-only workaround was not established. Wider application prevalence and MCP transport were not tested.

REA version

rea-agents 6.7.0, independently verified source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked using npm run build:cached on fetched origin/main at 4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0: still reproduces. These are source-checkout observations.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Xcode 27.1 build 27A9275; ibtool 27.1 bundle 25098. No Hopper/Ghidra provider involved.

Target and artifact context

Self-built UIKit storyboard compiled for iPhone with minimum deployment target 17.0, inside a minimal app bundle with an inert Mach-O executable. HOME and XDG roots are isolated. Compiled bytes were inspected without instantiating UIKit objects or launching the app.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Compilation has no errors or warnings; both collection and control CLI calls exit 0. The collection's buttons edge contains:

{"to":null,"resolution":"unresolved","reason":"destination_object_missing_or_external","label":"outlet buttons"}

Its view-scene connection facet reports status: "complete", examined: 2, omitted: 0, reason: null; graph.truncated is false. The other connection is the internal view outlet and resolves. The ordinary buttons outlet control resolves with a non-null button destination, resolution: "observed", and reason: null.

The verifier independently read the compiled NIBArchive tables, preserving ordered fields: object 29 is UIRuntimeOutletCollectionConnection, with UILabel referencing string buttons, UISource referencing proxy object 6, and UIDestination referencing object 31, NSMutableArray. Object 31 has exactly one UINibEncoderEmptyKey reference to object 10, UIButton. Object 10 also appears in REA's node set. Source-side ibtool --connections reports the buttons collection from VC1-aa-aaa to btn-1-aaa.

Evidence provenance: observed. Local verification: independently reproduced at the baseline and replayed on main. No restoration is claimed.

Typed outcome or recovery expected

Proposed restoration check: decode the collection fixture with its button membership retained and no false missing-or-external diagnosis; preserve resolution of the ordinary-outlet control. Merely changing the reason or marking coverage partial does not restore member decoding. Also proposed, but not executed: a genuinely unavailable destination must retain an explicit unresolved outcome rather than gain an invented endpoint.

Analysis or suspected cause

Source-derived explanation at the baseline: src/artifacts/apple/InterfaceBuilderAnalysis.ts:514-521,588-614 excludes collection containers from projected object nodes, then uses the collection's UIDestination reference as a single endpoint without interpreting its members. src/domain/apple/interfaceBuilderGraph.ts:394-403 cannot find that array node and labels it missing or external. Those projection paths remain on the rechecked main. The one-member archive isolates this loss from the separate repeated-key array problem. No bundled dependency was identified as the cause.

Additional context

Prepared with AI assistance. This uncovered collection case still reproduces after merged #913's UIKit outlet/event projection. That PR's ordinary-outlet restoration does not retain this collection's member relationship. #2177 concerns UIKit field aliases in keyed-property-list archives; this fixture uses compiled NIBArchive tables. #5590 concerns unadmitted AppKit binding connectors; this connector is recognized but its collection membership is lost. #5540 concerns budget reasons, #5541 boolean polarity, and #5638 ordinary dictionaries mistaken for UID references. Their restoration signals differ. Open #3502 explicitly leaves repeated-key array handling outside its scope; this one-member case also needs collection endpoint interpretation.

NOT_ENOUGH_INFO: the last known good version and regression introduction have not been established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5692 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `decode-interface-builder` recognizes a compiled UIKit outlet collection named `buttons` but returns an unresolved `outlet_to` edge with `destination_object_missing_or_external`. The destination array and its single button member exist in the archive, and the button exists in REA's node set. Connection coverage reports complete with zero omissions. An otherwise identical ordinary outlet resolves to the button. ### Reproduction Run from a built source checkout on macOS with Xcode's iOS platform, `ibtool`, a C compiler, Node.js and jq. This compiles and inspects an inert app without launching it. The independent verifier ran the collection and ordinary-outlet control once at the baseline; submission repeated both against a fresh build of main. ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" "$work_dir/cache" \ "$work_dir/state" "$work_dir/tmp" "$work_dir/Demo.app" cat > "$work_dir/Main.storyboard" <<'XML' <?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VC1-aa-aaa"> <dependencies> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/> </dependencies> <scenes> <scene sceneID="sc1-aa-aaa"> <objects> <viewController id="VC1-aa-aaa" customClass="DemoViewController" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="vw1-aa-aaa"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="btn-1-aaa"> <rect key="frame" x="20" y="40" width="100" height="44"/> <state key="normal" title="Go"/> </button> </subviews> <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="sa1-aa-aaa"/> </view> <connections> <outletCollection property="buttons" destination="btn-1-aaa" collectionClass="NSMutableArray" id="col-1-aaa"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="fr1-aa-aaa" sceneMemberID="firstResponder"/> </objects> </scene> </scenes> </document> XML xcrun ibtool --errors --warnings --compile "$work_dir/Demo.app/Main.storyboardc" \ "$work_dir/Main.storyboard" --target-device iphone \ --minimum-deployment-target 17.0 --module Demo printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' \ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \ '<plist version="1.0"><dict><key>CFBundleExecutable</key><string>Demo</string><key>CFBundleIdentifier</key><string>dev.rea.demo</string></dict></plist>' \ > "$work_dir/Demo.app/Info.plist" printf 'int main(void){return 0;}\n' > "$work_dir/demo.c" cc "$work_dir/demo.c" -o "$work_dir/Demo.app/Demo" xcrun ibtool --connections --output-format xml1 "$work_dir/Main.storyboard" \ | plutil -p - | sed -n '/connections/,/^ }/p' env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" \ XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" \ XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs decode-interface-builder "$work_dir/Demo.app" --json \ > "$work_dir/out.json" jq '{outlet_edges: [.normalized_result.graph.edges[] | select(.relation == "outlet_to") | {from, to, resolution, reason, label: .evidence[0].description}], coverage: [.normalized_result.graph.coverage[] | select(.facet | startswith("connections:")) | {facet, status, reason, examined, omitted}]}' "$work_dir/out.json" ``` For the ordinary-outlet control, use a separate work directory and replace only the `outletCollection` element with `<outlet property="buttons" destination="btn-1-aaa" id="col-1-aaa"/>`. Repeat compilation and inspection. The source-side `ibtool --connections` result identifies the collection's button destination; REA resolves the ordinary-outlet control. ### Expected behavior Retain the relationship between a recognized outlet collection and its serialized members, either through an explicit collection node or direct member relationships. A local serialized collection and its button must not be diagnosed as missing or external. An explicit unsupported-collection limitation would be more truthful but would not restore the lost membership. Contract sources at the baseline: `docs/native-investigation.md:383-396` advertises compiled storyboard/nib objects, outlets and connections; `AGENTS.md`, Architecture and Cleanup, requires preserved identity, omission, uncertainty and source evidence. `src/domain/apple/interfaceBuilderKeyedArchive.ts:39-48` explicitly recognizes `UIRuntimeOutletCollectionConnection`. Apple's `ibtool --connections` independently identifies this fixture's collection and destination. ### Impact and scope P2: the graph loses a recoverable outlet-to-button relationship for this one-member collection and gives a misleading absence reason. Ordinary outlets and the member object still decode. This does not depend on repeated-key overwriting. Source-side `ibtool` supplies the relationship when the original storyboard is available; a practical compiled-only workaround was not established. Wider application prevalence and MCP transport were not tested. ### REA version `rea-agents` 6.7.0, independently verified source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked using `npm run build:cached` on fetched `origin/main` at `4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0`: still reproduces. These are source-checkout observations. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Xcode 27.1 build 27A9275; ibtool 27.1 bundle 25098. No Hopper/Ghidra provider involved. ### Target and artifact context Self-built UIKit storyboard compiled for iPhone with minimum deployment target 17.0, inside a minimal app bundle with an inert Mach-O executable. HOME and XDG roots are isolated. Compiled bytes were inspected without instantiating UIKit objects or launching the app. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Compilation has no errors or warnings; both collection and control CLI calls exit 0. The collection's `buttons` edge contains: ```json {"to":null,"resolution":"unresolved","reason":"destination_object_missing_or_external","label":"outlet buttons"} ``` Its view-scene connection facet reports `status: "complete"`, `examined: 2`, `omitted: 0`, `reason: null`; `graph.truncated` is false. The other connection is the internal `view` outlet and resolves. The ordinary `buttons` outlet control resolves with a non-null button destination, `resolution: "observed"`, and `reason: null`. The verifier independently read the compiled NIBArchive tables, preserving ordered fields: object 29 is `UIRuntimeOutletCollectionConnection`, with `UILabel` referencing string `buttons`, `UISource` referencing proxy object 6, and `UIDestination` referencing object 31, `NSMutableArray`. Object 31 has exactly one `UINibEncoderEmptyKey` reference to object 10, `UIButton`. Object 10 also appears in REA's node set. Source-side `ibtool --connections` reports the `buttons` collection from `VC1-aa-aaa` to `btn-1-aaa`. Evidence provenance: observed. Local verification: independently reproduced at the baseline and replayed on main. No restoration is claimed. ### Typed outcome or recovery expected Proposed restoration check: decode the collection fixture with its button membership retained and no false missing-or-external diagnosis; preserve resolution of the ordinary-outlet control. Merely changing the reason or marking coverage partial does not restore member decoding. Also proposed, but not executed: a genuinely unavailable destination must retain an explicit unresolved outcome rather than gain an invented endpoint. ### Analysis or suspected cause Source-derived explanation at the baseline: `src/artifacts/apple/InterfaceBuilderAnalysis.ts:514-521,588-614` excludes collection containers from projected object nodes, then uses the collection's `UIDestination` reference as a single endpoint without interpreting its members. `src/domain/apple/interfaceBuilderGraph.ts:394-403` cannot find that array node and labels it missing or external. Those projection paths remain on the rechecked main. The one-member archive isolates this loss from the separate repeated-key array problem. No bundled dependency was identified as the cause. ### Additional context Prepared with AI assistance. This uncovered collection case still reproduces after merged #913's UIKit outlet/event projection. That PR's ordinary-outlet restoration does not retain this collection's member relationship. #2177 concerns UIKit field aliases in keyed-property-list archives; this fixture uses compiled NIBArchive tables. #5590 concerns unadmitted AppKit binding connectors; this connector is recognized but its collection membership is lost. #5540 concerns budget reasons, #5541 boolean polarity, and #5638 ordinary dictionaries mistaken for UID references. Their restoration signals differ. Open #3502 explicitly leaves repeated-key array handling outside its scope; this one-member case also needs collection endpoint interpretation. NOT_ENOUGH_INFO: the last known good version and regression introduction have not been established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #5540 (decode-interface-builder misattributes object-limit omissions to the connection budget) and sibling NIB decoding reports #5541, #5590, #5693, #5695.

Consolidated under Cluster AX (Compiled Interface Builder / NIB Decoding & Object Limit Anomalies) in tracker #4330.

<!-- gh-comment-id:6304471321 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5540 (`decode-interface-builder misattributes object-limit omissions to the connection budget`) and sibling NIB decoding reports #5541, #5590, #5693, #5695. Consolidated under **Cluster AX (Compiled Interface Builder / NIB Decoding & Object Limit Anomalies)** in tracker #4330.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3442
No description provided.