[PR #5809] [CLOSED] fix(apple): preserve binary keyed archive date precision #7076

Closed
opened 2026-11-01 21:45:12 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5809
Author: @Faisal-Fayaz
Created: 10/21/2026
Status: ❌ Closed

Base: main ← Head: fix/keyed-archive-date-precision


📝 Commits (1)

  • 3c6e7b7 fix(apple): preserve binary keyed archive date precision

📊 Changes

14 files changed (+784 additions, -41 deletions)

View changed files

📝 docs/native-investigation.md (+21 -0)
➕ src/artifacts/apple/BinaryPlistObjectTable.ts (+67 -0)
📝 src/artifacts/apple/InterfaceBuilderAnalysis.ts (+44 -2)
📝 src/artifacts/apple/KeyedArchiveReader.ts (+27 -2)
➕ src/artifacts/apple/PlistDateLiterals.test.ts (+127 -0)
➕ src/artifacts/apple/PlistDateLiterals.ts (+149 -0)
📝 src/artifacts/apple/PlistNumberLiterals.ts (+6 -37)
📝 src/domain/apple/keyedArchive.ts (+1 -0)
➕ src/domain/apple/plistDate.ts (+41 -0)
📝 src/domain/apple/plistValue.test.ts (+25 -0)
📝 src/domain/apple/plistValue.ts (+17 -0)
➕ tests/boundary/filesystem/keyedArchiveDatePrecision.test.ts (+31 -0)
➕ tests/boundary/mcp/keyedArchiveDatePrecisionMcp.test.ts (+36 -0)
➕ tests/fixtures/keyedArchiveDates.ts (+192 -0)

📄 Description

Summary

Fixes #5770.

A binary Foundation keyed archive can hold a date whose instant is finer than a
millisecond. inspect_keyed_archive reported such a date as a different known
instant, because the pinned binary property-list decoder builds a JavaScript
Date from the Float64 seconds and truncates the source value before the
shared date projection can see it. The archive reader now reports the source
instant and discloses the precision it carries.

Problem and expected behavior

A binary property list stores a date as Float64 seconds after the 2001
reference date. The pinned decoder builds a JavaScript Date from those
seconds, so the instant is truncated to whole milliseconds before any
projection sees it.

Smallest trigger: a binary NSKeyedArchiver archive whose date object carries
0.1005 seconds after the 2001 reference date.

Before:

{"id":1,"kind":"scalar","status":"observed","value":{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100Z"}}

The reported instant is not the instant in the archive, and no limitation
discloses the precision loss.

Expected behavior: preserve the instant when the output contract can represent
it, and otherwise report the typed unknown date (iso8601: null) with an
applicable counted limitation, keeping the source bytes. A source instant must
never be reported as a different known instant.

Change and scope

The fix is at the producing boundary, the binary property-list decode:

  • src/artifacts/apple/BinaryPlistObjectTable.ts reads the binary object table
    once. The number metadata scanner now shares it instead of parsing the
    trailer and the offset table a second time.
  • src/artifacts/apple/PlistDateLiterals.ts reads every date object of the
    object table with its exact Float64 payload. The decoder's copy of the bytes
    carries a per-object sentinel, so a decoded date identifies its source object
    instead of a rounded value, and the decoded tree is resolved back to the
    source instants. The original archive bytes are never modified.
  • src/domain/apple/plistDate.ts carries the precise date value and its ISO
    8601 rendering: a whole-millisecond instant keeps its millisecond timestamp,
    and a finer instant adds the microsecond digits.
  • src/domain/apple/plistValue.ts projects the precise date and counts the
    values that carry finer precision.
  • src/artifacts/apple/KeyedArchiveReader.ts and
    src/artifacts/apple/InterfaceBuilderAnalysis.ts wire the binary date decode
    and its limitations. Interface Builder decodes the same binary property
    lists, so it shares the projection.

Follow the contribution scope.
Scope decision: not applicable. This is an ordinary fix inside the existing
inspect_keyed_archive and decode_interface_builder boundaries. It adds no
provider or capability, and it involves no target execution, injection,
mutation, or protection recovery.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the binary property-list decoder in
    src/artifacts/apple owns date identity; the shared date projection in
    src/domain/apple/plistValue.ts owns the reported value.
  • Contribution scope fit and linked maintainer decision: ordinary fix within
    existing boundaries; no maintainer scope decision required.
  • CLI and MCP/tool-catalog contract: inspect_keyed_archive keeps its result
    shape. A date value is still { "$plist_type": "date", "iso8601": ... }; the
    ISO 8601 timestamp now carries microsecond digits when the source instant
    needs them, and a result gains one counted limitation that names the values
    with finer source precision. No tool, schema, or catalog change.
  • Provider, bridge, target-format, or platform compatibility: no provider
    change. Binary and XML property lists are both covered; XML dates keep their
    whole-second precision.
  • Evidence, artifact, provenance, or reconstruction contract: the archive digest
    still identifies the original bytes, and the decode never modifies them.
  • Target selection, authority, upstream behavior, and cleanup details: not
    applicable; the change reads one selected archive.
  • Generated metadata (docs/public/product-catalog.json), package, or
    installation impact: none. The product catalog, package metadata, and
    skills/ outputs are unchanged.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated:

    • src/artifacts/apple/PlistDateLiterals.test.ts: source-level regressions
      for the sub-millisecond instant, the whole-millisecond control, two dates
      that share a rounded millisecond, a non-finite source date, a non-canonical
      date marker, and an archive whose date identity cannot be established.
    • tests/boundary/filesystem/keyedArchiveDatePrecision.test.ts: the built
      CLI over the reported archives.
    • tests/boundary/mcp/keyedArchiveDatePrecisionMcp.test.ts: the built stdio
      MCP server over the same archives.
    • src/domain/apple/plistValue.test.ts: the domain projection of a precise
      date.
  • Base reproduction or other evidence: the reported archives are used verbatim,
    including the sub-millisecond archive with SHA-256
    ab69ba6e8034c6f60b828ee323a5a3e81bae3e7657477e0ebfbfdb93d53ed2ce. On the
    base revision the built CLI reports 2001-01-01T00:00:00.100Z for it; with
    this change it reports 2001-01-01T00:00:00.100500Z. The regression fails on
    the base revision because the source instant is truncated before the
    projection, and the canonical millisecond control stays
    2001-01-01T00:00:00.100Z in both revisions.

  • User-visible CLI/MCP output:

    $ rea inspect-keyed-archive submillisecond.plist --json
    ...
    "objects":[{"id":1,"kind":"scalar","status":"observed",
      "value":{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100500Z"}}],
    "limitations":[...,
      "1 date value(s) carry source precision finer than a millisecond and are reported with microsecond precision; the original archive bytes retain the exact source seconds."]
    
  • Remaining proof gaps: the real Foundation verifier (npm run verify:keyed-archive)
    needs macOS and Xcode and was not run on this Linux host, so a
    Foundation-written binary archive with a sub-millisecond date is not verified.
    The Interface Builder path shares the decoder and the projection but has no
    separate binary date fixture.

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 build:cached — build passes.
  • npm run check:fast — typecheck and lint pass.
  • npm run lint — 0 errors; module boundaries verified.
  • npm run docs:check — pass.
  • npm run jscpd — 3.497% duplicated lines, below the 3.5% threshold.
  • npm run knip — no unused exports.
  • npx vitest run --project domain --project adapters --project composition --project services --project evaluation --project conformance — 769 files, 12698 tests pass.
  • npx vitest run --project boundary — 513 files pass. The 3 failing files are
    the firmware tests, which need binwalk 3.1.x while this host has 26.6.4.
  • npx vitest run --project mcp-boundary — the failures are the same binwalk
    version, a missing generated .cache/mcp-tool-catalog.json, and an
    HTTP-transport socket error in this sandbox. None of them touch the changed
    code.
  • npm run verify:keyed-archive — not run; it requires macOS.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Existing whole-millisecond dates
    keep the same timestamp, and the reported value shape is unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable. No macOS-only
    workflow was executed on this Linux host.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none. The change reads
    one selected archive and never modifies it.

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.
  • 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.

🔄 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/5809 **Author:** [@Faisal-Fayaz](https://github.com/Faisal-Fayaz) **Created:** 10/21/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/keyed-archive-date-precision` --- ### 📝 Commits (1) - [`3c6e7b7`](https://github.com/morluto/rea/commit/3c6e7b7ed7d430e79adf7b557b800361cd2916af) fix(apple): preserve binary keyed archive date precision ### 📊 Changes **14 files changed** (+784 additions, -41 deletions) <details> <summary>View changed files</summary> 📝 `docs/native-investigation.md` (+21 -0) ➕ `src/artifacts/apple/BinaryPlistObjectTable.ts` (+67 -0) 📝 `src/artifacts/apple/InterfaceBuilderAnalysis.ts` (+44 -2) 📝 `src/artifacts/apple/KeyedArchiveReader.ts` (+27 -2) ➕ `src/artifacts/apple/PlistDateLiterals.test.ts` (+127 -0) ➕ `src/artifacts/apple/PlistDateLiterals.ts` (+149 -0) 📝 `src/artifacts/apple/PlistNumberLiterals.ts` (+6 -37) 📝 `src/domain/apple/keyedArchive.ts` (+1 -0) ➕ `src/domain/apple/plistDate.ts` (+41 -0) 📝 `src/domain/apple/plistValue.test.ts` (+25 -0) 📝 `src/domain/apple/plistValue.ts` (+17 -0) ➕ `tests/boundary/filesystem/keyedArchiveDatePrecision.test.ts` (+31 -0) ➕ `tests/boundary/mcp/keyedArchiveDatePrecisionMcp.test.ts` (+36 -0) ➕ `tests/fixtures/keyedArchiveDates.ts` (+192 -0) </details> ### 📄 Description ## Summary Fixes #5770. A binary Foundation keyed archive can hold a date whose instant is finer than a millisecond. `inspect_keyed_archive` reported such a date as a different known instant, because the pinned binary property-list decoder builds a JavaScript `Date` from the Float64 seconds and truncates the source value before the shared date projection can see it. The archive reader now reports the source instant and discloses the precision it carries. ## Problem and expected behavior A binary property list stores a date as Float64 seconds after the 2001 reference date. The pinned decoder builds a JavaScript `Date` from those seconds, so the instant is truncated to whole milliseconds before any projection sees it. Smallest trigger: a binary `NSKeyedArchiver` archive whose date object carries `0.1005` seconds after the 2001 reference date. Before: ``` {"id":1,"kind":"scalar","status":"observed","value":{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100Z"}} ``` The reported instant is not the instant in the archive, and no limitation discloses the precision loss. Expected behavior: preserve the instant when the output contract can represent it, and otherwise report the typed unknown date (`iso8601: null`) with an applicable counted limitation, keeping the source bytes. A source instant must never be reported as a different known instant. ## Change and scope The fix is at the producing boundary, the binary property-list decode: - `src/artifacts/apple/BinaryPlistObjectTable.ts` reads the binary object table once. The number metadata scanner now shares it instead of parsing the trailer and the offset table a second time. - `src/artifacts/apple/PlistDateLiterals.ts` reads every date object of the object table with its exact Float64 payload. The decoder's copy of the bytes carries a per-object sentinel, so a decoded date identifies its source object instead of a rounded value, and the decoded tree is resolved back to the source instants. The original archive bytes are never modified. - `src/domain/apple/plistDate.ts` carries the precise date value and its ISO 8601 rendering: a whole-millisecond instant keeps its millisecond timestamp, and a finer instant adds the microsecond digits. - `src/domain/apple/plistValue.ts` projects the precise date and counts the values that carry finer precision. - `src/artifacts/apple/KeyedArchiveReader.ts` and `src/artifacts/apple/InterfaceBuilderAnalysis.ts` wire the binary date decode and its limitations. Interface Builder decodes the same binary property lists, so it shares the projection. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Scope decision: not applicable. This is an ordinary fix inside the existing `inspect_keyed_archive` and `decode_interface_builder` boundaries. It adds no provider or capability, and it involves no target execution, injection, mutation, or protection recovery. ## Contract and boundary impact - Semantic owner and earliest changed stage: the binary property-list decoder in `src/artifacts/apple` owns date identity; the shared date projection in `src/domain/apple/plistValue.ts` owns the reported value. - Contribution scope fit and linked maintainer decision: ordinary fix within existing boundaries; no maintainer scope decision required. - CLI and MCP/tool-catalog contract: `inspect_keyed_archive` keeps its result shape. A date value is still `{ "$plist_type": "date", "iso8601": ... }`; the ISO 8601 timestamp now carries microsecond digits when the source instant needs them, and a result gains one counted limitation that names the values with finer source precision. No tool, schema, or catalog change. - Provider, bridge, target-format, or platform compatibility: no provider change. Binary and XML property lists are both covered; XML dates keep their whole-second precision. - Evidence, artifact, provenance, or reconstruction contract: the archive digest still identifies the original bytes, and the decode never modifies them. - Target selection, authority, upstream behavior, and cleanup details: not applicable; the change reads one selected archive. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. The product catalog, package metadata, and `skills/` outputs are unchanged. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: - `src/artifacts/apple/PlistDateLiterals.test.ts`: source-level regressions for the sub-millisecond instant, the whole-millisecond control, two dates that share a rounded millisecond, a non-finite source date, a non-canonical date marker, and an archive whose date identity cannot be established. - `tests/boundary/filesystem/keyedArchiveDatePrecision.test.ts`: the built CLI over the reported archives. - `tests/boundary/mcp/keyedArchiveDatePrecisionMcp.test.ts`: the built stdio MCP server over the same archives. - `src/domain/apple/plistValue.test.ts`: the domain projection of a precise date. - Base reproduction or other evidence: the reported archives are used verbatim, including the sub-millisecond archive with SHA-256 `ab69ba6e8034c6f60b828ee323a5a3e81bae3e7657477e0ebfbfdb93d53ed2ce`. On the base revision the built CLI reports `2001-01-01T00:00:00.100Z` for it; with this change it reports `2001-01-01T00:00:00.100500Z`. The regression fails on the base revision because the source instant is truncated before the projection, and the canonical millisecond control stays `2001-01-01T00:00:00.100Z` in both revisions. - User-visible CLI/MCP output: ``` $ rea inspect-keyed-archive submillisecond.plist --json ... "objects":[{"id":1,"kind":"scalar","status":"observed", "value":{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100500Z"}}], "limitations":[..., "1 date value(s) carry source precision finer than a millisecond and are reported with microsecond precision; the original archive bytes retain the exact source seconds."] ``` - Remaining proof gaps: the real Foundation verifier (`npm run verify:keyed-archive`) needs macOS and Xcode and was not run on this Linux host, so a Foundation-written binary archive with a sub-millisecond date is not verified. The Interface Builder path shares the decoder and the projection but has no separate binary date fixture. 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 build:cached` — build passes. - `npm run check:fast` — typecheck and lint pass. - `npm run lint` — 0 errors; module boundaries verified. - `npm run docs:check` — pass. - `npm run jscpd` — 3.497% duplicated lines, below the 3.5% threshold. - `npm run knip` — no unused exports. - `npx vitest run --project domain --project adapters --project composition --project services --project evaluation --project conformance` — 769 files, 12698 tests pass. - `npx vitest run --project boundary` — 513 files pass. The 3 failing files are the firmware tests, which need binwalk 3.1.x while this host has 26.6.4. - `npx vitest run --project mcp-boundary` — the failures are the same binwalk version, a missing generated `.cache/mcp-tool-catalog.json`, and an HTTP-transport socket error in this sandbox. None of them touch the changed code. - `npm run verify:keyed-archive` — not run; it requires macOS. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Existing whole-millisecond dates keep the same timestamp, and the reported value shape is unchanged. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. No macOS-only workflow was executed on this Linux host. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none. The change reads one selected archive and never modifies it. ## 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 - [ ] 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. - [ ] 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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:45:12 +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#7076
No description provided.