[GH-ISSUE #5590] [Bug] decode-interface-builder reports complete connection coverage while omitting bindings #3396

Closed
opened 2026-10-31 00:39:53 +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/5590

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

node scripts/rea.mjs decode-interface-builder "$app" --format json accepts a copied compiled AppKit keyed-property-list nib containing 13 connector records, but reports only nine connections and complete connection coverage with zero omissions. The four NSNibBindingConnector records survive as unknown object nodes with source, destination and label fields, but have no connection evidence or omission accounting. The CLI exits 0 with empty stderr.

Reproduction

An independent verifier ran this exact script from the built baseline checkout. On macOS, set TERMINAL_APP to the installed system Terminal.app bundle and SCRATCH to an existing disposable directory. This copies one resource, metadata and executable into a one-nib fixture; it never launches Terminal. The script replay reproduced the same counts as the initial run. Broader frequency was not measured.

: "${SCRATCH:?Set SCRATCH to an existing disposable directory}"
work="$(mktemp -d "$SCRATCH/repro.XXXXXX")"
export HOME="$work/home"
export XDG_CONFIG_HOME="$work/config"
export XDG_DATA_HOME="$work/data"
export XDG_CACHE_HOME="$work/cache"
export XDG_STATE_HOME="$work/state"
app="$work/Fixture.app"
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME"
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources/Base.lproj"
cp "$TERMINAL_APP/Contents/Info.plist" "$app/Contents/Info.plist"
cp "$TERMINAL_APP/Contents/MacOS/Terminal" "$app/Contents/MacOS/Terminal"
cp "$TERMINAL_APP/Contents/Resources/Base.lproj/TTGenericPickerPanel.nib" "$app/Contents/Resources/Base.lproj/TTGenericPickerPanel.nib"
node scripts/rea.mjs decode-interface-builder "$app" --format json > "$work/ib.json" 2> "$work/ib.stderr"
python3 - "$app" "$work/ib.json" <<'PYTHON'
import json, plistlib, sys
from collections import Counter
from pathlib import Path
relative = "Contents/Resources/Base.lproj/TTGenericPickerPanel.nib"
archive = plistlib.loads((Path(sys.argv[1]) / relative).read_bytes())
objects = archive["$objects"]
def class_name(value):
    if not isinstance(value, dict) or "$class" not in value:
        return None
    return objects[value["$class"].data].get("$classname")
print(dict(Counter(name for name in map(class_name, objects)
                   if name and "Connector" in name)))
result = json.loads(Path(sys.argv[2]).read_text())["normalized_result"]
print(next(document for document in result["documents"]
           if document["relative_path"] == relative))
print(next(facet for facet in result["graph"]["coverage"]
           if facet["facet"] == "connections:" + relative))
print("binding nodes:", [node["attributes"] for node in result["graph"]["nodes"]
      if node["attributes"].get("class_name") == "NSNibBindingConnector"])
PYTHON

The tested resource's SHA-256 is de6cadc04953efb8d12588f837a8b283f92aa6a9c89aec493a703f2a8b561f47. Other resource versions may have different counts. The copied original nib remained byte-identical to the system resource.

Expected behavior

Account for each serialized binding connector as connection evidence, including explicitly unsupported or unresolved evidence where appropriate, or report its omission with partial connection coverage and a reason. Full binding semantics and new binding-specific edge types are not required. Complete coverage with nine examined connections and zero omissions conceals the four unhandled bindings.

Source-derived contract: docs/native-investigation.md:383-396 advertises compiled nib objects and connections with unsupported archive forms explicit; src/contracts/artifactToolContracts.ts:92-95 describes a bounded connection graph with explicit unknown classes. src/domain/native/nativeInvestigationGraph.ts:100-108 states that coverage prevents missing graph parts from being read as absent, and docs/tool-design.md:51-58 requires coverage and preservation of unresolved facts. Generic limitations about recognized classes explain limited semantics but do not account for these four missing connections in the per-document facet.

Impact and scope

P2: a static UI investigation receives a complete connection inventory that omits four serialized dataflow bindings. Independently reproduced scope is this one shipping nib and a class-descriptor control. No target execution, input mutation, unresolved endpoint, graph truncation or budget exhaustion explains the original result. Wider application prevalence was not measured.

A verified manual workaround is available: the emitted unknown nodes preserve binding source/destination archive IDs, labels and class names, and endpoint objects are present. An analyst can recover these relationships from the same result or independently read the copied archive with Python plistlib, as above.

REA version

rea-agents 6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64. No optional native analysis engine or analysis database was needed.

Target and artifact context

One-nib app bundle assembled from read-only copies of Terminal.app metadata, Mach-O executable and Contents/Resources/Base.lproj/TTGenericPickerPanel.nib, a binary NSKeyedArchiver archive. HOME and all XDG roots were isolated. The app was never launched.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Independent Python plistlib class counts and public CLI results from the same copied input:

archive connectors: 7 NSNibOutletConnector + 2 NSNibControlConnector + 4 NSNibBindingConnector = 13
object_count: 77
connection_count: 9
connections:Contents/Resources/Base.lproj/TTGenericPickerPanel.nib:
  status: complete, examined: 9, omitted: 0, reason: null
objects coverage: complete, examined: 77, omitted: 0
archive_decode coverage: complete, examined: 1, omitted: 0
graph.truncated: false
connection nodes: 7 outlets, 2 actions

Binding objects at archive IDs 140, 152, 156 and 160 retain NSSource, NSDestination and NSLabel, with both endpoint objects present. Their labels are contentArray: representedObjects, content: arrangedObjects, contentValues: arrangedObjects.name and selectedObject: representedObject.

As an executed diagnostic control, changing only the binding class descriptor and its class-list entry in a second copy to NSNibOutletConnector made the same CLI report 13 connections, complete coverage with 13 examined and zero omitted, 11 outlets and two actions. This isolates class-dependent recognition; classifying bindings as outlets is not the proposed fix. The verifier also ran the existing Interface Builder graph test file: 23 tests passed. Those tests do not establish binding coverage.

Typed outcome or recovery expected

Proposed restoration check: rerun the original nib at default limits and account for all 13 connectors. Retain explicit evidence for the four bindings or report four omitted bindings with partial coverage and an explanatory reason. Preserve the seven outlet and two action observations and all existing binding fields. An otherwise equivalent archive containing only supported outlets/actions should retain complete coverage with zero omissions; marking every keyed archive partial would not restore this contract.

Analysis or suspected cause

Source-derived: src/domain/apple/interfaceBuilderKeyedArchive.ts:365-369 recognizes outlet, connection, segue and control connector class names, excluding NSNibBindingConnector before connection accounting. src/domain/apple/interfaceBuilderGraph.ts:479-485,500-530 derives the count and complete coverage from admitted records and omission counters.

The sibling NIBArchive producer accepts Connector classes in src/artifacts/apple/InterfaceBuilderAnalysis.ts:575-613, and src/domain/apple/interfaceBuilderGraph.ts:437-447 already supports unresolved evidence for unsupported connection kinds. These are source-derived corroboration, not separately reproduced NIBArchive behavior. Explicit unsupported or omitted connection accounting is sufficient; full binding support exceeds the demonstrated defect.

Additional context

Prepared with AI assistance and independently reproduced through the production CLI. The submission stage checked upstream source freshness and duplicates; it did not rerun the runtime reproduction. Freshly fetched origin/main at 080c0e2b2b82a7d9394b59d3d86630d9d85346a5 has no changes since the tested baseline in the cited implementation and contract files.

Related #5540 concerns incorrect reasons for object-limit omissions, and #5541 concerns NIBArchive boolean polarity. This report uses default limits with complete object/archive coverage and concerns missing keyed-property-list binding connector accounting. Merged #900 explicitly left bindings out of connections but did not report this false complete-coverage result. #2484 addresses recognized connectors with unresolved owners, whereas these bindings have present endpoints and never enter connection accounting.

NOT_ENOUGH_INFO: the last known good version and regression introduction have not been established. MCP transport and wider application prevalence were not tested.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5590 ### 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 `node scripts/rea.mjs decode-interface-builder "$app" --format json` accepts a copied compiled AppKit keyed-property-list nib containing 13 connector records, but reports only nine connections and complete connection coverage with zero omissions. The four `NSNibBindingConnector` records survive as unknown object nodes with source, destination and label fields, but have no connection evidence or omission accounting. The CLI exits 0 with empty stderr. ### Reproduction An independent verifier ran this exact script from the built baseline checkout. On macOS, set `TERMINAL_APP` to the installed system Terminal.app bundle and `SCRATCH` to an existing disposable directory. This copies one resource, metadata and executable into a one-nib fixture; it never launches Terminal. The script replay reproduced the same counts as the initial run. Broader frequency was not measured. ```sh : "${SCRATCH:?Set SCRATCH to an existing disposable directory}" work="$(mktemp -d "$SCRATCH/repro.XXXXXX")" export HOME="$work/home" export XDG_CONFIG_HOME="$work/config" export XDG_DATA_HOME="$work/data" export XDG_CACHE_HOME="$work/cache" export XDG_STATE_HOME="$work/state" app="$work/Fixture.app" mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME" mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources/Base.lproj" cp "$TERMINAL_APP/Contents/Info.plist" "$app/Contents/Info.plist" cp "$TERMINAL_APP/Contents/MacOS/Terminal" "$app/Contents/MacOS/Terminal" cp "$TERMINAL_APP/Contents/Resources/Base.lproj/TTGenericPickerPanel.nib" "$app/Contents/Resources/Base.lproj/TTGenericPickerPanel.nib" node scripts/rea.mjs decode-interface-builder "$app" --format json > "$work/ib.json" 2> "$work/ib.stderr" python3 - "$app" "$work/ib.json" <<'PYTHON' import json, plistlib, sys from collections import Counter from pathlib import Path relative = "Contents/Resources/Base.lproj/TTGenericPickerPanel.nib" archive = plistlib.loads((Path(sys.argv[1]) / relative).read_bytes()) objects = archive["$objects"] def class_name(value): if not isinstance(value, dict) or "$class" not in value: return None return objects[value["$class"].data].get("$classname") print(dict(Counter(name for name in map(class_name, objects) if name and "Connector" in name))) result = json.loads(Path(sys.argv[2]).read_text())["normalized_result"] print(next(document for document in result["documents"] if document["relative_path"] == relative)) print(next(facet for facet in result["graph"]["coverage"] if facet["facet"] == "connections:" + relative)) print("binding nodes:", [node["attributes"] for node in result["graph"]["nodes"] if node["attributes"].get("class_name") == "NSNibBindingConnector"]) PYTHON ``` The tested resource's SHA-256 is `de6cadc04953efb8d12588f837a8b283f92aa6a9c89aec493a703f2a8b561f47`. Other resource versions may have different counts. The copied original nib remained byte-identical to the system resource. ### Expected behavior Account for each serialized binding connector as connection evidence, including explicitly unsupported or unresolved evidence where appropriate, or report its omission with partial connection coverage and a reason. Full binding semantics and new binding-specific edge types are not required. Complete coverage with nine examined connections and zero omissions conceals the four unhandled bindings. Source-derived contract: `docs/native-investigation.md:383-396` advertises compiled nib objects and connections with unsupported archive forms explicit; `src/contracts/artifactToolContracts.ts:92-95` describes a bounded connection graph with explicit unknown classes. `src/domain/native/nativeInvestigationGraph.ts:100-108` states that coverage prevents missing graph parts from being read as absent, and `docs/tool-design.md:51-58` requires coverage and preservation of unresolved facts. Generic limitations about recognized classes explain limited semantics but do not account for these four missing connections in the per-document facet. ### Impact and scope P2: a static UI investigation receives a complete connection inventory that omits four serialized dataflow bindings. Independently reproduced scope is this one shipping nib and a class-descriptor control. No target execution, input mutation, unresolved endpoint, graph truncation or budget exhaustion explains the original result. Wider application prevalence was not measured. A verified manual workaround is available: the emitted unknown nodes preserve binding source/destination archive IDs, labels and class names, and endpoint objects are present. An analyst can recover these relationships from the same result or independently read the copied archive with Python plistlib, as above. ### REA version rea-agents 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64. No optional native analysis engine or analysis database was needed. ### Target and artifact context One-nib app bundle assembled from read-only copies of Terminal.app metadata, Mach-O executable and `Contents/Resources/Base.lproj/TTGenericPickerPanel.nib`, a binary NSKeyedArchiver archive. HOME and all XDG roots were isolated. The app was never launched. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Independent Python plistlib class counts and public CLI results from the same copied input: ```text archive connectors: 7 NSNibOutletConnector + 2 NSNibControlConnector + 4 NSNibBindingConnector = 13 object_count: 77 connection_count: 9 connections:Contents/Resources/Base.lproj/TTGenericPickerPanel.nib: status: complete, examined: 9, omitted: 0, reason: null objects coverage: complete, examined: 77, omitted: 0 archive_decode coverage: complete, examined: 1, omitted: 0 graph.truncated: false connection nodes: 7 outlets, 2 actions ``` Binding objects at archive IDs 140, 152, 156 and 160 retain `NSSource`, `NSDestination` and `NSLabel`, with both endpoint objects present. Their labels are `contentArray: representedObjects`, `content: arrangedObjects`, `contentValues: arrangedObjects.name` and `selectedObject: representedObject`. As an executed diagnostic control, changing only the binding class descriptor and its class-list entry in a second copy to `NSNibOutletConnector` made the same CLI report 13 connections, complete coverage with 13 examined and zero omitted, 11 outlets and two actions. This isolates class-dependent recognition; classifying bindings as outlets is not the proposed fix. The verifier also ran the existing Interface Builder graph test file: 23 tests passed. Those tests do not establish binding coverage. ### Typed outcome or recovery expected Proposed restoration check: rerun the original nib at default limits and account for all 13 connectors. Retain explicit evidence for the four bindings or report four omitted bindings with partial coverage and an explanatory reason. Preserve the seven outlet and two action observations and all existing binding fields. An otherwise equivalent archive containing only supported outlets/actions should retain complete coverage with zero omissions; marking every keyed archive partial would not restore this contract. ### Analysis or suspected cause Source-derived: `src/domain/apple/interfaceBuilderKeyedArchive.ts:365-369` recognizes outlet, connection, segue and control connector class names, excluding `NSNibBindingConnector` before connection accounting. `src/domain/apple/interfaceBuilderGraph.ts:479-485,500-530` derives the count and complete coverage from admitted records and omission counters. The sibling NIBArchive producer accepts Connector classes in `src/artifacts/apple/InterfaceBuilderAnalysis.ts:575-613`, and `src/domain/apple/interfaceBuilderGraph.ts:437-447` already supports unresolved evidence for unsupported connection kinds. These are source-derived corroboration, not separately reproduced NIBArchive behavior. Explicit unsupported or omitted connection accounting is sufficient; full binding support exceeds the demonstrated defect. ### Additional context Prepared with AI assistance and independently reproduced through the production CLI. The submission stage checked upstream source freshness and duplicates; it did not rerun the runtime reproduction. Freshly fetched `origin/main` at `080c0e2b2b82a7d9394b59d3d86630d9d85346a5` has no changes since the tested baseline in the cited implementation and contract files. Related #5540 concerns incorrect reasons for object-limit omissions, and #5541 concerns NIBArchive boolean polarity. This report uses default limits with complete object/archive coverage and concerns missing keyed-property-list binding connector accounting. Merged #900 explicitly left bindings out of connections but did not report this false complete-coverage result. #2484 addresses recognized connectors with unresolved owners, whereas these bindings have present endpoints and never enter connection accounting. NOT_ENOUGH_INFO: the last known good version and regression introduction have not been established. MCP transport and wider application prevalence were not tested.
Author
Owner

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

Cross-referencing with canonical issue #5540 ([Bug] decode-interface-builder misattributes object-limit omissions to the connection budget) and sibling #5541. All relate to decoding anomalies, connection budget attribution, and coverage reporting in decode-interface-builder. Cataloged under Cluster AX in consolidation tracker #4330.

<!-- gh-comment-id:6295984105 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5540 (`[Bug] decode-interface-builder misattributes object-limit omissions to the connection budget`) and sibling #5541. All relate to decoding anomalies, connection budget attribution, and coverage reporting in `decode-interface-builder`. Cataloged under Cluster AX in consolidation 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#3396
No description provided.