[PR #1226] [MERGED] fix(artifacts): report non-archive keyed-archive selections as unsupported targets #2736

Closed
opened 2026-10-17 21:09:40 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1226
Author: @kaoru0822-kitauji
Created: 10/9/2026
Status: ✅ Merged
Merged: 10/9/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/keyed-archive-kind-refusal


📝 Commits (1)

  • 8c5088c fix(artifacts): report non-archive keyed-archive selections as unsupported targets

📊 Changes

3 files changed (+99 additions, -14 deletions)

View changed files

📝 src/artifacts/apple/KeyedArchiveReader.ts (+16 -2)
📝 src/domain/apple/keyedArchive.ts (+32 -4)
📝 tests/boundary/filesystem/keyedArchiveSelection.test.ts (+51 -8)

📄 Description

Follow-up to #1070, which made keyed-archive selection mistakes invalid input but left this case as a decode failure.

Problem

inspect_keyed_archive reports a selected file that is intact but is not an NSKeyedArchiver archive as a damaged artifact, and tells the caller to get a fresh copy. This is easy to hit, because most .plist and many .nib files in a real app bundle are not keyed archives:

$ rea inspect-keyed-archive /System/Applications/Chess.app Contents/Resources/Defaults.plist --format json
{ "code": "artifact_operation_failed", "category": "execution_failure",
  "message": "Artifact is invalid or has changed. Get a fresh copy and try again.",
  "remediation": { "action": "Artifact is invalid or has changed. Get a fresh copy and try again." },
  "details": { "reason": "format",
    "detail": "Cannot decode selected Foundation keyed archive: Expected NSKeyedArchiver with an $objects array" } }

A compiled NIBArchive (/Applications/KeepingYouAwake.app Contents/Resources/MainMenu.nib) gets the same message and remediation. The useful hint, "decode it with decode_interface_builder", appears only in details.detail. In a sample of four real apps (Transmission, XLD, Chess, KeepingYouAwake), 13 of 16 .nib/.plist resources produced this misleading failure. The other three were real keyed archives, which decode fine. With this branch, all 13 report unsupported_target, and the three archives still decode.

Change

Only files that provably are another kind are reclassified:

Selected file Before After
NIBArchive magic artifact_operation_failed / format, "get a fresh copy" unsupported_target; remediation: decode it with decode_interface_builder
Parsed plist whose root is not a dictionary, has no $archiver, or whose $archiver is not "NSKeyedArchiver" same unsupported_target; remediation: use inspect_plist
$archiver: "NSKeyedArchiver" but no $objects array, unparsable plist bytes, encoding errors, limits format / limit unchanged

details.path is the selected file (<bundle>/<path>, or the active plist itself). The reason names the observed mismatch, for example it has no $archiver key or its $archiver is "NSArchiver".

After the change:

{ "code": "unsupported_target", "category": "unsupported_target",
  "message": "Unsupported target for inspect_keyed_archive at /System/Applications/Chess.app/Contents/Resources/Defaults.plist: Selected property list is not an NSKeyedArchiver archive: it has no $archiver key",
  "remediation": { "action": "Inspect an ordinary property list with inspect_plist; inspect_keyed_archive decodes NSKeyedArchiver archives only." },
  "details": { "operation": "inspect_keyed_archive", "path": "/System/Applications/Chess.app/Contents/Resources/Defaults.plist", "reason": "…" } }

The domain marks these cases with a KeyedArchiveKindError, and the reader maps it to AnalysisUnsupportedTargetError. ArtifactProvider already passes that error through unchanged. Schemas are unchanged.

Verification

  • tests/boundary/filesystem/keyedArchiveSelection.test.ts: the plain-plist, other-archiver and NIBArchive cases fail on main and pass here. A new case confirms that a damaged NSKeyedArchiver archive (no $objects) is still a format failure.
  • All keyed-archive suites (tests/boundary/filesystem/keyedArchive*, keyedArchiveEncoding, keyedArchiveNumberMetadataMcp, artifactMcp.inlineInventory, nativeArtifactGoldens, appleXmlDeclarations): 10 files, 96 passed.
  • npm run check:changed: typecheck, lint, and 393 affected tests passed.
  • Real CLI on macOS, using the apps above: the plain plist and NIBArchive results are shown above. A real keyed archive (XLD.app Contents/Resources/ARChecker.nib, a binary plist with 89 objects) still decodes.

No Hopper or Ghidra verification was needed; neither provider is involved.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1226 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/9/2026 **Status:** ✅ Merged **Merged:** 10/9/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/keyed-archive-kind-refusal` --- ### 📝 Commits (1) - [`8c5088c`](https://github.com/morluto/rea/commit/8c5088c8cdf32a9f96f6c479c6e70a71fa360168) fix(artifacts): report non-archive keyed-archive selections as unsupported targets ### 📊 Changes **3 files changed** (+99 additions, -14 deletions) <details> <summary>View changed files</summary> 📝 `src/artifacts/apple/KeyedArchiveReader.ts` (+16 -2) 📝 `src/domain/apple/keyedArchive.ts` (+32 -4) 📝 `tests/boundary/filesystem/keyedArchiveSelection.test.ts` (+51 -8) </details> ### 📄 Description Follow-up to #1070, which made keyed-archive *selection* mistakes invalid input but left this case as a decode failure. ## Problem `inspect_keyed_archive` reports a selected file that is intact but is not an `NSKeyedArchiver` archive as a damaged artifact, and tells the caller to get a fresh copy. This is easy to hit, because most `.plist` and many `.nib` files in a real app bundle are not keyed archives: ``` $ rea inspect-keyed-archive /System/Applications/Chess.app Contents/Resources/Defaults.plist --format json { "code": "artifact_operation_failed", "category": "execution_failure", "message": "Artifact is invalid or has changed. Get a fresh copy and try again.", "remediation": { "action": "Artifact is invalid or has changed. Get a fresh copy and try again." }, "details": { "reason": "format", "detail": "Cannot decode selected Foundation keyed archive: Expected NSKeyedArchiver with an $objects array" } } ``` A compiled `NIBArchive` (`/Applications/KeepingYouAwake.app Contents/Resources/MainMenu.nib`) gets the same message and remediation. The useful hint, "decode it with decode_interface_builder", appears only in `details.detail`. In a sample of four real apps (Transmission, XLD, Chess, KeepingYouAwake), 13 of 16 `.nib`/`.plist` resources produced this misleading failure. The other three were real keyed archives, which decode fine. With this branch, all 13 report `unsupported_target`, and the three archives still decode. ## Change Only files that provably are another kind are reclassified: | Selected file | Before | After | | --- | --- | --- | | `NIBArchive` magic | `artifact_operation_failed` / `format`, "get a fresh copy" | `unsupported_target`; remediation: decode it with `decode_interface_builder` | | Parsed plist whose root is not a dictionary, has no `$archiver`, or whose `$archiver` is not `"NSKeyedArchiver"` | same | `unsupported_target`; remediation: use `inspect_plist` | | `$archiver: "NSKeyedArchiver"` but no `$objects` array, unparsable plist bytes, encoding errors, limits | `format` / `limit` | unchanged | `details.path` is the selected file (`<bundle>/<path>`, or the active plist itself). The reason names the observed mismatch, for example `it has no $archiver key` or `its $archiver is "NSArchiver"`. After the change: ``` { "code": "unsupported_target", "category": "unsupported_target", "message": "Unsupported target for inspect_keyed_archive at /System/Applications/Chess.app/Contents/Resources/Defaults.plist: Selected property list is not an NSKeyedArchiver archive: it has no $archiver key", "remediation": { "action": "Inspect an ordinary property list with inspect_plist; inspect_keyed_archive decodes NSKeyedArchiver archives only." }, "details": { "operation": "inspect_keyed_archive", "path": "/System/Applications/Chess.app/Contents/Resources/Defaults.plist", "reason": "…" } } ``` The domain marks these cases with a `KeyedArchiveKindError`, and the reader maps it to `AnalysisUnsupportedTargetError`. `ArtifactProvider` already passes that error through unchanged. Schemas are unchanged. ## Verification - `tests/boundary/filesystem/keyedArchiveSelection.test.ts`: the plain-plist, other-archiver and NIBArchive cases fail on main and pass here. A new case confirms that a damaged `NSKeyedArchiver` archive (no `$objects`) is still a `format` failure. - All keyed-archive suites (`tests/boundary/filesystem/keyedArchive*`, `keyedArchiveEncoding`, `keyedArchiveNumberMetadataMcp`, `artifactMcp.inlineInventory`, `nativeArtifactGoldens`, `appleXmlDeclarations`): 10 files, 96 passed. - `npm run check:changed`: typecheck, lint, and 393 affected tests passed. - Real CLI on macOS, using the apps above: the plain plist and NIBArchive results are shown above. A real keyed archive (`XLD.app Contents/Resources/ARChecker.nib`, a binary plist with 89 objects) still decodes. No Hopper or Ghidra verification was needed; neither provider is involved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:09:40 +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#2736
No description provided.