[PR #3793] [MERGED] fix(apple): report plist dates the decoder cannot represent as unknown #5903

Closed
opened 2026-10-31 01:10:19 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

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

Base: main ← Head: fix/plist-unrepresentable-dates


📝 Commits (1)

  • 4c28dd9 fix(apple): report plist dates the decoder cannot represent as unknown

📊 Changes

9 files changed (+156 additions, -13 deletions)

View changed files

📝 docs/native-investigation.md (+6 -0)
📝 src/artifacts/apple/InterfaceBuilderAnalysis.ts (+27 -6)
📝 src/artifacts/apple/KeyedArchiveReader.ts (+12 -4)
📝 src/domain/apple/plistValue.test.ts (+14 -0)
📝 src/domain/apple/plistValue.ts (+14 -3)
📝 src/native/parsers/plist.ts (+4 -0)
📝 tests/boundary/cli/nativePlistCli.test.ts (+4 -0)
📝 tests/boundary/filesystem/interfaceBuilderBundle.test.ts (+27 -0)
➕ tests/boundary/filesystem/keyedArchiveDateRange.test.ts (+48 -0)

📄 Description

Summary

A plist that plutil decodes without complaint made inspect-plist fail entirely when one <date> had a year after 9999, a negative year, or a binary timestamp outside the JavaScript Date range. The command returned unreadable_output with a "run rea doctor" remediation, which doesn't help: the bytes are valid. inspect-keyed-archive and decode-interface-builder rejected such archives the same way.

Now only that date is reported as unknown: { "$plist_type": "date", "iso8601": null }, with a counted limitation. Every other value is still returned.

Problem and expected behavior

plutil -convert xml1 writes such dates as 11507-08-17T05:20:00Z or -1168-02-16T14:13:20Z. The pinned plist decoder passes the text to new Date(...), which rejects these spellings because JavaScript needs +011507/-001168 for expanded years. Binary plists can also hold timestamps beyond ±8.64e15 ms. In both cases the decoder returns an Invalid Date. projectPlistValue then called toISOString(), which throws RangeError, and the caller reported the whole plist as unreadable.

Minimal trigger: a binary plist {d: <date 3e11 s after 2001>, s: "ok"}. plutil -p shows d => 11507-08-17 05:20:00 +0000, yet rea inspect-plist failed.

Change and scope

  • The shared plist value projection, projectPlistValue in src/domain/apple/plistValue.ts, owns the date representation. It now maps an Invalid Date to { "$plist_type": "date", "iso8601": null } and returns unknownDateCount, the same treatment non-finite reals already get through unknownRealCount.
  • unknownDateLimitation(count) gives one wording for all three consumers:
    • inspect_plist: parsePlistXml
    • inspect_keyed_archive: decodeKeyedArchiveBytes
    • decode_interface_builder: a per-archive limitation, like the existing __proto__ omission notes
  • An unknown date is still a value present in the archive, so it does not mark Interface Builder archive_decode coverage partial.
  • Not included: recovering the exact timestamp for XML years 10000–275760. JavaScript can represent those years, but the decoder only sees text it cannot parse. Recovering them would mean rewriting the decoder's input. They are reported as unknown rather than guessed.

Contract and boundary impact

  • Semantic owner and earliest changed stage: projectPlistValue, the shared plist value projection.
  • Contribution scope fit and linked maintainer decision: correctness fix within existing read-only parsing; not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. A date value may now carry iso8601: null with a limitation, where the call previously failed.
  • Provider, bridge, target-format, or platform compatibility: inspect_plist is macOS-only (plutil). Keyed archive and Interface Builder decoding are portable.
  • Evidence, artifact, provenance, or reconstruction contract: the unknown is explicit and counted. Valid dates are unchanged.
  • Target selection, authority, upstream behavior, and cleanup details: unchanged.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.

Evidence and regression coverage

  • Tests added or updated:
    • tests/boundary/cli/nativePlistCli.test.ts: the real-plutil XML and binary cases now include a year-11507 date.
    • tests/boundary/filesystem/keyedArchiveDateRange.test.ts (new): runs the built CLI inspect-keyed-archive on an XML archive holding an unrepresentable date and a valid one.
    • tests/boundary/filesystem/interfaceBuilderBundle.test.ts: the archive is decoded with a per-archive limitation instead of being reported as undecodable.
    • src/domain/apple/plistValue.test.ts: an Invalid Date as produced by binary decoders; no portable binary fixture exercises this path.
  • Base reproduction: against the unfixed sources, the keyed-archive CLI test and both plist CLI cases fail (Tests 3 failed | 8 passed).
  • User-visible CLI output for a binary plist with a year-11507 date, before:
    {"error":"Analysis failed","code":"unreadable_output",...,"details":{"operation":"inspect_plist","reason":"plutil XML conversion was not a readable plist"}}
    
    After:
    "value": {"d": {"$plist_type": "date", "iso8601": null}, "s": "ok"},
    "limitations": [..., "1 date value(s) are reported as { \"$plist_type\": \"date\", \"iso8601\": null } because the decoder could not represent them, such as XML dates with years beyond 9999 or negative years, or dates outside the JavaScript date range."]
    
  • Remaining proof gaps: exact timestamps for XML years 10000–275760 (see scope).

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:focused -- tests/boundary/filesystem/interfaceBuilderBundle.test.ts tests/boundary/filesystem/keyedArchiveDateRange.test.ts src/domain/apple/plistValue.test.ts tests/boundary/cli/nativePlistCli.test.ts tests/boundary/filesystem/keyedArchiveSelection.test.ts tests/boundary/filesystem/keyedArchiveScalarIdentity.test.ts tests/acceptance/applications/interfaceBuilderUIKitKeyedArchive.test.ts: 80 passed, 2 skipped (Xcode ibtool unavailable)
  • npm run check:fast: passed; npm run docs:check: passed
  • Real plists on macOS (Darwin 25.6, arm64), built with plutil. Binary plists with dates at 2001, year 501, year 11507, year −1168 and year 318888:
    • The first two are reported exactly as before.
    • The last three were unreadable_output before and now return the date as unknown, with the sibling string intact and the limitation.
    • The same date in XML and binary keyed archives (inspect-keyed-archive) and in a bundle nib (decode-interface-builder) now decodes with the limitation; the nib was previously listed as undecodable.
  • No regressions on 36 real apps in /Applications: inspect-plist 36/36 in 34.8 s and decode-interface-builder 36/36 in 37.1 s, against 36/36 in 37–41 s before. The fix only adds a getTime() check per date.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Inputs that previously failed now succeed with an explicit unknown.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS plutil; no Hopper/Ghidra involvement.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none. No new reads; a malformed date no longer aborts the analysis.

Author readiness

Human owner:

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above. (Found by testing real plutil output; no existing issue.)
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 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/3793 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/15/2026 **Status:** ✅ Merged **Merged:** 10/15/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/plist-unrepresentable-dates` --- ### 📝 Commits (1) - [`4c28dd9`](https://github.com/morluto/rea/commit/4c28dd99d75acec99a4c6d92e9b45c9abc341965) fix(apple): report plist dates the decoder cannot represent as unknown ### 📊 Changes **9 files changed** (+156 additions, -13 deletions) <details> <summary>View changed files</summary> 📝 `docs/native-investigation.md` (+6 -0) 📝 `src/artifacts/apple/InterfaceBuilderAnalysis.ts` (+27 -6) 📝 `src/artifacts/apple/KeyedArchiveReader.ts` (+12 -4) 📝 `src/domain/apple/plistValue.test.ts` (+14 -0) 📝 `src/domain/apple/plistValue.ts` (+14 -3) 📝 `src/native/parsers/plist.ts` (+4 -0) 📝 `tests/boundary/cli/nativePlistCli.test.ts` (+4 -0) 📝 `tests/boundary/filesystem/interfaceBuilderBundle.test.ts` (+27 -0) ➕ `tests/boundary/filesystem/keyedArchiveDateRange.test.ts` (+48 -0) </details> ### 📄 Description ## Summary A plist that `plutil` decodes without complaint made `inspect-plist` fail entirely when one `<date>` had a year after 9999, a negative year, or a binary timestamp outside the JavaScript Date range. The command returned `unreadable_output` with a "run `rea doctor`" remediation, which doesn't help: the bytes are valid. `inspect-keyed-archive` and `decode-interface-builder` rejected such archives the same way. Now only that date is reported as unknown: `{ "$plist_type": "date", "iso8601": null }`, with a counted limitation. Every other value is still returned. ## Problem and expected behavior `plutil -convert xml1` writes such dates as `11507-08-17T05:20:00Z` or `-1168-02-16T14:13:20Z`. The pinned `plist` decoder passes the text to `new Date(...)`, which rejects these spellings because JavaScript needs `+011507`/`-001168` for expanded years. Binary plists can also hold timestamps beyond ±8.64e15 ms. In both cases the decoder returns an Invalid Date. `projectPlistValue` then called `toISOString()`, which throws `RangeError`, and the caller reported the whole plist as unreadable. Minimal trigger: a binary plist `{d: <date 3e11 s after 2001>, s: "ok"}`. `plutil -p` shows `d => 11507-08-17 05:20:00 +0000`, yet `rea inspect-plist` failed. ## Change and scope - The shared plist value projection, `projectPlistValue` in `src/domain/apple/plistValue.ts`, owns the date representation. It now maps an Invalid Date to `{ "$plist_type": "date", "iso8601": null }` and returns `unknownDateCount`, the same treatment non-finite reals already get through `unknownRealCount`. - `unknownDateLimitation(count)` gives one wording for all three consumers: - `inspect_plist`: `parsePlistXml` - `inspect_keyed_archive`: `decodeKeyedArchiveBytes` - `decode_interface_builder`: a per-archive limitation, like the existing `__proto__` omission notes - An unknown date is still a value present in the archive, so it does not mark Interface Builder `archive_decode` coverage partial. - Not included: recovering the exact timestamp for XML years 10000–275760. JavaScript can represent those years, but the decoder only sees text it cannot parse. Recovering them would mean rewriting the decoder's input. They are reported as unknown rather than guessed. ## Contract and boundary impact - Semantic owner and earliest changed stage: `projectPlistValue`, the shared plist value projection. - Contribution scope fit and linked maintainer decision: correctness fix within existing read-only parsing; not applicable. - CLI and MCP/tool-catalog contract: no schema change. A date value may now carry `iso8601: null` with a limitation, where the call previously failed. - Provider, bridge, target-format, or platform compatibility: `inspect_plist` is macOS-only (plutil). Keyed archive and Interface Builder decoding are portable. - Evidence, artifact, provenance, or reconstruction contract: the unknown is explicit and counted. Valid dates are unchanged. - Target selection, authority, upstream behavior, and cleanup details: unchanged. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. ## Evidence and regression coverage - Tests added or updated: - `tests/boundary/cli/nativePlistCli.test.ts`: the real-plutil XML and binary cases now include a year-11507 date. - `tests/boundary/filesystem/keyedArchiveDateRange.test.ts` (new): runs the built CLI `inspect-keyed-archive` on an XML archive holding an unrepresentable date and a valid one. - `tests/boundary/filesystem/interfaceBuilderBundle.test.ts`: the archive is decoded with a per-archive limitation instead of being reported as undecodable. - `src/domain/apple/plistValue.test.ts`: an Invalid Date as produced by binary decoders; no portable binary fixture exercises this path. - Base reproduction: against the unfixed sources, the keyed-archive CLI test and both plist CLI cases fail (`Tests 3 failed | 8 passed`). - User-visible CLI output for a binary plist with a year-11507 date, before: ``` {"error":"Analysis failed","code":"unreadable_output",...,"details":{"operation":"inspect_plist","reason":"plutil XML conversion was not a readable plist"}} ``` After: ``` "value": {"d": {"$plist_type": "date", "iso8601": null}, "s": "ok"}, "limitations": [..., "1 date value(s) are reported as { \"$plist_type\": \"date\", \"iso8601\": null } because the decoder could not represent them, such as XML dates with years beyond 9999 or negative years, or dates outside the JavaScript date range."] ``` - Remaining proof gaps: exact timestamps for XML years 10000–275760 (see scope). For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:focused -- tests/boundary/filesystem/interfaceBuilderBundle.test.ts tests/boundary/filesystem/keyedArchiveDateRange.test.ts src/domain/apple/plistValue.test.ts tests/boundary/cli/nativePlistCli.test.ts tests/boundary/filesystem/keyedArchiveSelection.test.ts tests/boundary/filesystem/keyedArchiveScalarIdentity.test.ts tests/acceptance/applications/interfaceBuilderUIKitKeyedArchive.test.ts`: 80 passed, 2 skipped (Xcode `ibtool` unavailable) - `npm run check:fast`: passed; `npm run docs:check`: passed - Real plists on macOS (Darwin 25.6, arm64), built with `plutil`. Binary plists with dates at 2001, year 501, year 11507, year −1168 and year 318888: - The first two are reported exactly as before. - The last three were `unreadable_output` before and now return the date as unknown, with the sibling string intact and the limitation. - The same date in XML and binary keyed archives (`inspect-keyed-archive`) and in a bundle nib (`decode-interface-builder`) now decodes with the limitation; the nib was previously listed as undecodable. - No regressions on 36 real apps in `/Applications`: `inspect-plist` 36/36 in 34.8 s and `decode-interface-builder` 36/36 in 37.1 s, against 36/36 in 37–41 s before. The fix only adds a `getTime()` check per date. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Inputs that previously failed now succeed with an explicit unknown. - Real Hopper/Ghidra, browser, or OS coverage: macOS plutil; no Hopper/Ghidra involvement. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none. No new reads; a malformed date no longer aborts the analysis. ## Author readiness Human owner: <!-- @GitHub-handle --> - [ ] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [ ] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. (Found by testing real plutil output; no existing issue.) - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 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-31 01:10:19 +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#5903
No description provided.