[GH-ISSUE #2177] fix(apple): decode_interface_builder omits keyed-archive UIKit outlets #721

Closed
opened 2026-10-15 18:27:42 +00:00 by bsakaguchi · 0 comments
Owner

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

Summary

rea decode-interface-builder reports no outlet for a valid, rooted UIKit keyed-archive nib containing a UIRuntimeOutletConnection. Its connection coverage reports complete, with zero examined connections and zero omissions. The archive contains a view outlet from a UIViewController to a UIView, and Apple's native UINib consumer connects those exact decoded objects. The same loss occurs for XML and binary archives written by the native UIKit connector's encoder.

Runtime or environment

The source build was tested at c22111eca2, and the independently installed published package was rea-agents 6.4.0. Both CLI runs used Node.js 24.21.0 on macOS 27.0.1 arm64. This is a supported installed-package runtime; the contributor pin is Node.js 24.18.0. Native validation used Foundation and UIKit through a fixture compiled for Mac Catalyst with Xcode 27.0, build 27A266a. The fixture app contains a freshly compiled Mach-O executable and a small nib. No existing application was analyzed or launched.

Steps to reproduce

With REA available as rea, Python 3, and a C compiler on a supported macOS host, run this script. It creates an isolated app fixture, calls the public CLI, prints the connection result and raw archive references, and removes its fixture afterward. These steps were executed against both tested builds. The XML fixture was separately accepted by native Foundation and UINib; it is not an in-memory helper input with an empty root table.

import json
import pathlib
import plistlib
import subprocess
import tempfile

uid = lambda value: {"CF$UID": value}
archive = {
    "$archiver": "NSKeyedArchiver",
    "$version": 100000,
    "$objects": [
        "$null",
        {"$class": uid(4)},
        {"$class": uid(5)},
        {"$class": uid(6), "UISource": uid(1),
         "UIDestination": uid(2), "UILabel": uid(7)},
        {"$classname": "UIViewController",
         "$classes": ["UIViewController", "UIResponder", "NSObject"]},
        {"$classname": "UIView",
         "$classes": ["UIView", "UIResponder", "NSObject"]},
        {"$classname": "UIRuntimeOutletConnection",
         "$classes": ["UIRuntimeOutletConnection", "UIRuntimeConnection", "NSObject"]},
        "view",
        {"$class": uid(11), "NS.objects": [uid(1), uid(2), uid(3)]},
        {"$class": uid(11), "NS.objects": [uid(3)]},
        {"$class": uid(11), "NS.objects": [uid(1), uid(2)]},
        {"$classname": "NSArray", "$classes": ["NSArray", "NSObject"]},
    ],
    "$top": {
        "root": uid(8), "UINibConnectionsKey": uid(9),
        "UINibObjectsKey": uid(10), "UINibTopLevelObjectsKey": uid(10),
    },
}
with tempfile.TemporaryDirectory(prefix="rea-uikit-outlet-") as temporary:
    app = pathlib.Path(temporary) / "Fixture.app"
    app.mkdir()
    (app / "Info.plist").write_bytes(
        plistlib.dumps({"CFBundleExecutable": "Fixture"}))
    subprocess.run(["cc", "-x", "c", "-", "-o", str(app / "Fixture")],
                   input="int main(void) { return 0; }", text=True, check=True)
    (app / "Main.nib").write_bytes(plistlib.dumps(archive, sort_keys=False))
    output = subprocess.check_output(
        ["rea", "decode-interface-builder", str(app), "--format", "json"], text=True)
    result = json.loads(output)["normalized_result"]
    print(json.dumps({
        "connection_count": result["documents"][0]["connection_count"],
        "outlets": [node["name"] for node in result["graph"]["nodes"]
                    if node["kind"] == "outlet"],
        "coverage": [facet for facet in result["graph"]["coverage"]
                     if facet["facet"] == "connections:Main.nib"],
    }, indent=2))
    raw_output = subprocess.check_output(
        ["rea", "inspect-keyed-archive", str(app), "Main.nib", "--format", "json"],
        text=True)
    raw = json.loads(raw_output)["normalized_result"]
    print(json.dumps({
        "connector": [obj for obj in raw["objects"] if obj["id"] == 3],
        "references": [edge for edge in raw["references"] if edge["source"] == 3],
    }, indent=2))

Expected behavior

The decoded graph should contain one outlet named view, owned by the object at archive UID 1 and pointing to the object at UID 2. The outlet identity should retain its association with the connector at UID 3, and the connection coverage should account for that outlet. These are archive-table identities; the fixture does not encode authored objectID fields or establish any native code-handler address.

The public contract includes keyed property lists inside an active Apple app bundle and describes an object, hierarchy, and connection graph. The native consumer independently establishes this fixture's UIKit applicability: NSKeyedUnarchiver materializes UIRuntimeOutletConnection, and UINib.instantiateWithOwner:options: returns the two top-level objects with the controller's view equal to the decoded view. Re-archiving the actual decoded UIKit connector emits UISource, UIDestination, and UILabel again. This expectation does not rely on the sibling NIBArchive decoder accepting those keys.

Actual behavior

Both public CLI runs exit successfully and print this connection summary:

{
  "connection_count": 0,
  "outlets": [],
  "coverage": [
    {
      "facet": "connections:Main.nib",
      "status": "complete",
      "reason": null,
      "examined": 0,
      "omitted": 0
    }
  ]
}

The connector remains present as an object node with its raw UI-prefixed fields, but the graph contains no outlet node or outlet_to edge. The archive-decode coverage also reports complete. Repeating the public CLI check with the native connector's own XML and binary output produces the same missing-outlet result in both builds.

Discriminating controls locate all parts of the loss. A modeled AppKit connector using NSSource, NSDestination, and NSLabel, and a modeled unprefixed variant, each produce one outlet named view with the UID 1 owner and UID 2 destination. Adding only a recognized source alias to the UIKit fixture produces an outlet named archive:3 with an unresolved destination. Adding both endpoint aliases resolves the destination but still leaves the outlet named archive:3, because UILabel remains unread. These aliases are artificial parser controls, not claims about native UIKit encoding. A source-less control produces no fabricated outlet.

Evidence

  • Expected source: src/contracts/artifactToolContracts.ts:78-85 includes keyed property lists in the static Interface Builder connection-graph contract; native Foundation and UIKit observations establish the supported archive form and the outlet's actual field semantics.
  • Failure source: the successful public CLI observations above, traced through src/artifacts/ArtifactProvider.ts:103-127, src/artifacts/apple/InterfaceBuilderAnalysis.ts:88-152, and src/domain/apple/interfaceBuilderKeyedArchive.ts:346-384; the keyed decoder skips the connector when its source lookup returns null, and its endpoint and label lookups also omit the UI-prefixed keys. src/domain/apple/interfaceBuilderGraph.ts:419-460 derives the zero count and complete coverage from the resulting empty connection list.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The inline native consumer and producer procedure below supplies the qualification source and regenerates both UIKit-encoded archive forms from the same portable input. Native validation observed connector=UIRuntimeOutletConnection and outlet_view_identity=same for the portable XML fixture and both native-produced forms. Public CLI observations establish the REA result; native fixture observations establish archive applicability and semantics. No MCP runtime call, iOS device run, Xcode ibtool producer run, or corpus-prevalence measurement is claimed.

Native consumer and producer qualification

For the native check, use a disposable directory and save the Objective-C source below as native-consume.m. The first Python fixture above defines the complete portable archive dictionary. Reuse that definition and replace its temporary-app execution block with pathlib.Path("Minimal.nib").write_bytes(plistlib.dumps(archive, sort_keys=False)) to retain the identical native input in that directory. The consumer decodes the actual UIKit class and checks the connected object identity, then asks those native objects to encode their own XML or binary archive. This check was executed on the measured Mac Catalyst host; it creates no window and launches no app bundle.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
int main(int argc, char **argv) {
 @autoreleasepool {
  @try {
   NSString *output=[NSString stringWithUTF8String:argv[1]];
   NSData *bytes=[NSData dataWithContentsOfFile:output];
   NSError *err=nil;
   NSKeyedUnarchiver *u=[[NSKeyedUnarchiver alloc] initForReadingFromData:bytes error:&err];
   u.requiresSecureCoding=NO;
   NSArray *roots=[u decodeObjectForKey:@"root"];
   printf("Foundation decoded root=%s count=%lu connector=%s\n", NSStringFromClass([roots class]).UTF8String,(unsigned long)roots.count,NSStringFromClass([roots[2] class]).UTF8String);
   NSArray *instantiated=[[UINib nibWithData:bytes bundle:nil] instantiateWithOwner:nil options:nil];
   UIViewController *decodedOwner=instantiated[0];
   UIView *decodedView=instantiated[1];
   BOOL wired=decodedOwner.view==decodedView;
   printf("UINib decoded top=%lu owner=%s destination=%s outlet_view_identity=%s\n",(unsigned long)instantiated.count,NSStringFromClass([decodedOwner class]).UTF8String,NSStringFromClass([decodedView class]).UTF8String,wired?"same":"different");
   // Ask the actual UIKit connector's own encoder for its serialized keys.
   NSKeyedArchiver *native=[[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
   native.outputFormat=argc > 2 ? NSPropertyListBinaryFormat_v1_0 : NSPropertyListXMLFormat_v1_0;
   [native encodeObject:roots forKey:@"root"];
   [native encodeObject:@[roots[2]] forKey:@"UINibConnectionsKey"];
   [native encodeObject:@[roots[0],roots[1]] forKey:@"UINibObjectsKey"];
   [native encodeObject:@[roots[0],roots[1]] forKey:@"UINibTopLevelObjectsKey"];
   [native finishEncoding];
   [native.encodedData writeToFile:[output stringByAppendingString:@".native.xml"] atomically:YES];
   printf("UIKit-native connector rearchive bytes=%lu\n",(unsigned long)native.encodedData.length);
   return wired?0:2;
  } @catch(NSException *e) { fprintf(stderr,"%s: %s\n",e.name.UTF8String,e.reason.UTF8String); return 3; }
 }
}
rea_fixture_sdk=$(xcrun --sdk macosx --show-sdk-path)
xcrun clang -fobjc-arc -target arm64-apple-ios14.0-macabi \
  -isysroot "$rea_fixture_sdk" \
  -iframework "$rea_fixture_sdk/System/iOSSupport/System/Library/Frameworks" \
  -framework Foundation -framework UIKit -framework CoreGraphics \
  native-consume.m -o native-consume
./native-consume Minimal.nib
mv Minimal.nib.native.xml NativeXML.nib
./native-consume Minimal.nib binary
mv Minimal.nib.native.xml NativeBinary.nib
./native-consume NativeXML.nib
./native-consume NativeBinary.nib

Native runs accepted the portable XML and both native-produced forms, returned the actual UIRuntimeOutletConnection class, and printed outlet_view_identity=same. The encoded archives retain the UI-prefixed endpoint and label fields. The public CLI loss was then checked on those outputs. This qualification establishes the archive and outlet semantics on the stated host, without asserting an ibtool producer, iOS device behavior or application prevalence.

Additional context

The raw inspect-keyed-archive result is a verified manual workaround for this bounded outlet loss. It preserves connector UID 3, its class descriptor, resolved UISource reference to UID 1, resolved UIDestination reference to UID 2, and the UILabel reference to the scalar view at UID 7. It reports the UIKit class as unknown-class and supplies no automatic outlet interpretation. A caller can recover the same owner, destination, and label from those original references using the separately established UIKit semantics. The incorrect graph still cannot be used as an automatic outlet graph without that manual reconstruction.

The related merged UIKit projection change in morluto/rea#913 handles NIBArchive object tables. Its tested keyed-archive path still has this failure, so that merge does not establish restoration for this input encoding. The present report concerns outlet field decoding and its coverage consequence. It does not assert that event connections will become actions merely by recognizing their endpoint keys.

Restoration check

Run the portable rooted fixture and both native-produced forms through the public CLI. Each should yield the archived outlet, with the correct connector identity, label, owner, and destination, and coverage that accounts for the connection. For the portable fixture, require connection_count: 1, an outlet named view associated with connector UID 3, a source relationship from object UID 1, and an observed outlet_to relationship to object UID 2. Preserving only the source while leaving an unresolved destination or fallback label does not restore the contract. Preserve the existing NS-prefixed and unprefixed control behavior, and keep malformed or source-less records from becoming fabricated resolved connections.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: For a native-valid UIKit keyed-archive nib, the decoder silently loses a recoverable outlet and reports complete connection coverage. This is a bounded static graph correctness defect, reproduced in the source and published builds. Existing application prevalence was not measured.
  • Workaround status: available
  • Workaround basis: The public raw-archive inspector was executed on the same rooted input in both builds and preserves the connector, endpoint UID references, and outlet label without truncation. Those facts allow manual recovery of the same outlet using the natively verified UIKit semantics; the workaround does not automatically repair the Interface Builder graph.
Originally created by @coygeek on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2177 ## Summary `rea decode-interface-builder` reports no outlet for a valid, rooted UIKit keyed-archive nib containing a `UIRuntimeOutletConnection`. Its connection coverage reports `complete`, with zero examined connections and zero omissions. The archive contains a `view` outlet from a `UIViewController` to a `UIView`, and Apple's native `UINib` consumer connects those exact decoded objects. The same loss occurs for XML and binary archives written by the native UIKit connector's encoder. ## Runtime or environment The source build was tested at c22111eca2c4bc84a084f12711f405b3e7422a40, and the independently installed published package was rea-agents 6.4.0. Both CLI runs used Node.js 24.21.0 on macOS 27.0.1 arm64. This is a supported installed-package runtime; the contributor pin is Node.js 24.18.0. Native validation used Foundation and UIKit through a fixture compiled for Mac Catalyst with Xcode 27.0, build 27A266a. The fixture app contains a freshly compiled Mach-O executable and a small nib. No existing application was analyzed or launched. ## Steps to reproduce With REA available as `rea`, Python 3, and a C compiler on a supported macOS host, run this script. It creates an isolated app fixture, calls the public CLI, prints the connection result and raw archive references, and removes its fixture afterward. These steps were executed against both tested builds. The XML fixture was separately accepted by native Foundation and `UINib`; it is not an in-memory helper input with an empty root table. ```python import json import pathlib import plistlib import subprocess import tempfile uid = lambda value: {"CF$UID": value} archive = { "$archiver": "NSKeyedArchiver", "$version": 100000, "$objects": [ "$null", {"$class": uid(4)}, {"$class": uid(5)}, {"$class": uid(6), "UISource": uid(1), "UIDestination": uid(2), "UILabel": uid(7)}, {"$classname": "UIViewController", "$classes": ["UIViewController", "UIResponder", "NSObject"]}, {"$classname": "UIView", "$classes": ["UIView", "UIResponder", "NSObject"]}, {"$classname": "UIRuntimeOutletConnection", "$classes": ["UIRuntimeOutletConnection", "UIRuntimeConnection", "NSObject"]}, "view", {"$class": uid(11), "NS.objects": [uid(1), uid(2), uid(3)]}, {"$class": uid(11), "NS.objects": [uid(3)]}, {"$class": uid(11), "NS.objects": [uid(1), uid(2)]}, {"$classname": "NSArray", "$classes": ["NSArray", "NSObject"]}, ], "$top": { "root": uid(8), "UINibConnectionsKey": uid(9), "UINibObjectsKey": uid(10), "UINibTopLevelObjectsKey": uid(10), }, } with tempfile.TemporaryDirectory(prefix="rea-uikit-outlet-") as temporary: app = pathlib.Path(temporary) / "Fixture.app" app.mkdir() (app / "Info.plist").write_bytes( plistlib.dumps({"CFBundleExecutable": "Fixture"})) subprocess.run(["cc", "-x", "c", "-", "-o", str(app / "Fixture")], input="int main(void) { return 0; }", text=True, check=True) (app / "Main.nib").write_bytes(plistlib.dumps(archive, sort_keys=False)) output = subprocess.check_output( ["rea", "decode-interface-builder", str(app), "--format", "json"], text=True) result = json.loads(output)["normalized_result"] print(json.dumps({ "connection_count": result["documents"][0]["connection_count"], "outlets": [node["name"] for node in result["graph"]["nodes"] if node["kind"] == "outlet"], "coverage": [facet for facet in result["graph"]["coverage"] if facet["facet"] == "connections:Main.nib"], }, indent=2)) raw_output = subprocess.check_output( ["rea", "inspect-keyed-archive", str(app), "Main.nib", "--format", "json"], text=True) raw = json.loads(raw_output)["normalized_result"] print(json.dumps({ "connector": [obj for obj in raw["objects"] if obj["id"] == 3], "references": [edge for edge in raw["references"] if edge["source"] == 3], }, indent=2)) ``` ## Expected behavior The decoded graph should contain one outlet named `view`, owned by the object at archive UID 1 and pointing to the object at UID 2. The outlet identity should retain its association with the connector at UID 3, and the connection coverage should account for that outlet. These are archive-table identities; the fixture does not encode authored `objectID` fields or establish any native code-handler address. The public contract includes keyed property lists inside an active Apple app bundle and describes an object, hierarchy, and connection graph. The native consumer independently establishes this fixture's UIKit applicability: `NSKeyedUnarchiver` materializes `UIRuntimeOutletConnection`, and `UINib.instantiateWithOwner:options:` returns the two top-level objects with the controller's `view` equal to the decoded view. Re-archiving the actual decoded UIKit connector emits `UISource`, `UIDestination`, and `UILabel` again. This expectation does not rely on the sibling `NIBArchive` decoder accepting those keys. ## Actual behavior Both public CLI runs exit successfully and print this connection summary: ```json { "connection_count": 0, "outlets": [], "coverage": [ { "facet": "connections:Main.nib", "status": "complete", "reason": null, "examined": 0, "omitted": 0 } ] } ``` The connector remains present as an object node with its raw UI-prefixed fields, but the graph contains no outlet node or `outlet_to` edge. The archive-decode coverage also reports `complete`. Repeating the public CLI check with the native connector's own XML and binary output produces the same missing-outlet result in both builds. Discriminating controls locate all parts of the loss. A modeled AppKit connector using `NSSource`, `NSDestination`, and `NSLabel`, and a modeled unprefixed variant, each produce one outlet named `view` with the UID 1 owner and UID 2 destination. Adding only a recognized source alias to the UIKit fixture produces an outlet named `archive:3` with an unresolved destination. Adding both endpoint aliases resolves the destination but still leaves the outlet named `archive:3`, because `UILabel` remains unread. These aliases are artificial parser controls, not claims about native UIKit encoding. A source-less control produces no fabricated outlet. ## Evidence - Expected source: `src/contracts/artifactToolContracts.ts:78-85` includes keyed property lists in the static Interface Builder connection-graph contract; native Foundation and UIKit observations establish the supported archive form and the outlet's actual field semantics. - Failure source: the successful public CLI observations above, traced through `src/artifacts/ArtifactProvider.ts:103-127`, `src/artifacts/apple/InterfaceBuilderAnalysis.ts:88-152`, and `src/domain/apple/interfaceBuilderKeyedArchive.ts:346-384`; the keyed decoder skips the connector when its source lookup returns null, and its endpoint and label lookups also omit the UI-prefixed keys. `src/domain/apple/interfaceBuilderGraph.ts:419-460` derives the zero count and complete coverage from the resulting empty connection list. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The inline native consumer and producer procedure below supplies the qualification source and regenerates both UIKit-encoded archive forms from the same portable input. Native validation observed `connector=UIRuntimeOutletConnection` and `outlet_view_identity=same` for the portable XML fixture and both native-produced forms. Public CLI observations establish the REA result; native fixture observations establish archive applicability and semantics. No MCP runtime call, iOS device run, Xcode `ibtool` producer run, or corpus-prevalence measurement is claimed. ### Native consumer and producer qualification For the native check, use a disposable directory and save the Objective-C source below as `native-consume.m`. The first Python fixture above defines the complete portable `archive` dictionary. Reuse that definition and replace its temporary-app execution block with `pathlib.Path("Minimal.nib").write_bytes(plistlib.dumps(archive, sort_keys=False))` to retain the identical native input in that directory. The consumer decodes the actual UIKit class and checks the connected object identity, then asks those native objects to encode their own XML or binary archive. This check was executed on the measured Mac Catalyst host; it creates no window and launches no app bundle. ```objective-c #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> int main(int argc, char **argv) { @autoreleasepool { @try { NSString *output=[NSString stringWithUTF8String:argv[1]]; NSData *bytes=[NSData dataWithContentsOfFile:output]; NSError *err=nil; NSKeyedUnarchiver *u=[[NSKeyedUnarchiver alloc] initForReadingFromData:bytes error:&err]; u.requiresSecureCoding=NO; NSArray *roots=[u decodeObjectForKey:@"root"]; printf("Foundation decoded root=%s count=%lu connector=%s\n", NSStringFromClass([roots class]).UTF8String,(unsigned long)roots.count,NSStringFromClass([roots[2] class]).UTF8String); NSArray *instantiated=[[UINib nibWithData:bytes bundle:nil] instantiateWithOwner:nil options:nil]; UIViewController *decodedOwner=instantiated[0]; UIView *decodedView=instantiated[1]; BOOL wired=decodedOwner.view==decodedView; printf("UINib decoded top=%lu owner=%s destination=%s outlet_view_identity=%s\n",(unsigned long)instantiated.count,NSStringFromClass([decodedOwner class]).UTF8String,NSStringFromClass([decodedView class]).UTF8String,wired?"same":"different"); // Ask the actual UIKit connector's own encoder for its serialized keys. NSKeyedArchiver *native=[[NSKeyedArchiver alloc] initRequiringSecureCoding:NO]; native.outputFormat=argc > 2 ? NSPropertyListBinaryFormat_v1_0 : NSPropertyListXMLFormat_v1_0; [native encodeObject:roots forKey:@"root"]; [native encodeObject:@[roots[2]] forKey:@"UINibConnectionsKey"]; [native encodeObject:@[roots[0],roots[1]] forKey:@"UINibObjectsKey"]; [native encodeObject:@[roots[0],roots[1]] forKey:@"UINibTopLevelObjectsKey"]; [native finishEncoding]; [native.encodedData writeToFile:[output stringByAppendingString:@".native.xml"] atomically:YES]; printf("UIKit-native connector rearchive bytes=%lu\n",(unsigned long)native.encodedData.length); return wired?0:2; } @catch(NSException *e) { fprintf(stderr,"%s: %s\n",e.name.UTF8String,e.reason.UTF8String); return 3; } } } ``` ```sh rea_fixture_sdk=$(xcrun --sdk macosx --show-sdk-path) xcrun clang -fobjc-arc -target arm64-apple-ios14.0-macabi \ -isysroot "$rea_fixture_sdk" \ -iframework "$rea_fixture_sdk/System/iOSSupport/System/Library/Frameworks" \ -framework Foundation -framework UIKit -framework CoreGraphics \ native-consume.m -o native-consume ./native-consume Minimal.nib mv Minimal.nib.native.xml NativeXML.nib ./native-consume Minimal.nib binary mv Minimal.nib.native.xml NativeBinary.nib ./native-consume NativeXML.nib ./native-consume NativeBinary.nib ``` Native runs accepted the portable XML and both native-produced forms, returned the actual `UIRuntimeOutletConnection` class, and printed `outlet_view_identity=same`. The encoded archives retain the UI-prefixed endpoint and label fields. The public CLI loss was then checked on those outputs. This qualification establishes the archive and outlet semantics on the stated host, without asserting an `ibtool` producer, iOS device behavior or application prevalence. ## Additional context The raw `inspect-keyed-archive` result is a verified manual workaround for this bounded outlet loss. It preserves connector UID 3, its class descriptor, resolved `UISource` reference to UID 1, resolved `UIDestination` reference to UID 2, and the `UILabel` reference to the scalar `view` at UID 7. It reports the UIKit class as `unknown-class` and supplies no automatic outlet interpretation. A caller can recover the same owner, destination, and label from those original references using the separately established UIKit semantics. The incorrect graph still cannot be used as an automatic outlet graph without that manual reconstruction. The related merged UIKit projection change in [morluto/rea#913](https://github.com/morluto/rea/pull/913) handles `NIBArchive` object tables. Its tested keyed-archive path still has this failure, so that merge does not establish restoration for this input encoding. The present report concerns outlet field decoding and its coverage consequence. It does not assert that event connections will become actions merely by recognizing their endpoint keys. ## Restoration check Run the portable rooted fixture and both native-produced forms through the public CLI. Each should yield the archived outlet, with the correct connector identity, label, owner, and destination, and coverage that accounts for the connection. For the portable fixture, require `connection_count: 1`, an outlet named `view` associated with connector UID 3, a source relationship from object UID 1, and an observed `outlet_to` relationship to object UID 2. Preserving only the source while leaving an unresolved destination or fallback label does not restore the contract. Preserve the existing NS-prefixed and unprefixed control behavior, and keep malformed or source-less records from becoming fabricated resolved connections. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: For a native-valid UIKit keyed-archive nib, the decoder silently loses a recoverable outlet and reports complete connection coverage. This is a bounded static graph correctness defect, reproduced in the source and published builds. Existing application prevalence was not measured. - Workaround status: available - Workaround basis: The public raw-archive inspector was executed on the same rooted input in both builds and preserves the connector, endpoint UID references, and outlet label without truncation. Those facts allow manual recovery of the same outlet using the natively verified UIKit semantics; the workaround does not automatically repair the Interface Builder graph.
bsakaguchi 2026-10-15 18:27:42 +00:00
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#721
No description provided.