[GH-ISSUE #5770] [Bug] inspect-keyed-archive silently truncates sub-millisecond binary dates #3507

Closed
opened 2026-10-31 00:41:16 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched open and closed issues and open PRs for a related report.
  • The reproduction uses only synthetic archive data and no credentials.

Area

Evidence, artifacts, or reconstruction

Observed behavior

A valid binary keyed-archive date at 0.1005 seconds after the 2001 epoch is returned as the known observed timestamp 2001-01-01T00:00:00.100Z by both CLI and stdio MCP. There is no date-precision limitation. Python plistlib reads 2001-01-01T00:00:00.100500; plutil -lint accepts the input. The canonical 0.100 millisecond control remains correct.

Reproduction

From a built checkout, run this Python standard-library fixture generator and CLI replay. It writes disposable files, uses a real binary UID, and runs no target code. The final three fixtures were also verified through open_binary + inspect_keyed_archive over stdio MCP with the selected advertised SDK output schema.

python3 - <<'PYTHON'
import datetime, hashlib, json, pathlib, plistlib, struct, subprocess, tempfile
root=pathlib.Path(tempfile.mkdtemp(prefix='rea-keyed-date-'))
control=plistlib.dumps({'$archiver':'NSKeyedArchiver','$version':100000,'$objects':['$null',datetime.datetime(2001,1,1,0,0,0,100000)],'$top':{'root':plistlib.UID(1)}},fmt=plistlib.FMT_BINARY)
pattern=b'\x33'+struct.pack('>d',.1)
assert control.count(pattern)==1
offset=control.index(pattern)
for name in ('valid','invalid-date-marker','submillisecond'):
    data=bytearray(control)
    if name=='invalid-date-marker': data[offset]=0x30
    if name=='submillisecond': struct.pack_into('>d',data,offset+1,.1005)
    path=root/(name+'.plist');path.write_bytes(data)
    p=subprocess.run(['node','scripts/rea.mjs','inspect-keyed-archive',str(path),'--json'],capture_output=True,text=True,check=True)
    result=json.loads(p.stdout)['normalized_result']
    print(json.dumps({'case':name,'sha256':hashlib.sha256(data).hexdigest(),'object':result['objects'][1],'limitations':result['limitations']}))
PYTHON

Expected behavior

Preserve the instant if the output contract can represent it. Otherwise return the existing typed unknown date (iso8601: null) with an applicable counted limitation, retaining the source bytes. Do not assert a different known instant. The documented scalar projection uses explicit unknown dates when the instant cannot be represented.

Impact and scope

Confirmed silent timestamp loss on a reduced synthetic binary keyed archive. Production frequency and nonbinary paths are unmeasured. No target execution or artifact mutation occurred.

REA version

6.7.0, built source 3a0e91dd914145ecb9accbdeb68ecf7fe4d5133d.

Runtime and platform

Node.js 24.18.0; macOS 26.3 arm64; pinned plist dependency 5.0.0.

Target and artifact context

Self-authored binary NSKeyedArchiver document with $objects = ["$null", date] and $top.root encoded as a binary UID to object 1. The valid, malformed-marker, and sub-millisecond fixtures differ only in the selected date marker/payload. All bytes were unchanged after CLI/MCP inspection.

Interface

CLI and MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Both interfaces return object 1 as:

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

The valid sub-millisecond fixture has SHA-256 ab69ba6e8034c6f60b828ee323a5a3e81bae3e7657477e0ebfbfdb93d53ed2ce, marker 0x33 at offset 77, and Float64 payload hex 3fb9ba5e353f7cee. The only limitations concern class semantics, nil references, and paging; none discloses date precision. The valid millisecond control has payload 3fb999999999999a and the same reported ISO timestamp.

Typed outcome or recovery expected

Keep the canonical-millisecond control known. Mark only the unrepresentable source instant unknown with a counted date limitation, or report it losslessly.

Analysis or suspected cause

Source-derived: the pinned binary plist decoder constructs JavaScript Date directly from Float64 seconds. The source scalar precision disappears before the shared date projection can distinguish truncation from a canonical millisecond date. Fixes must follow source object identity rather than matching rounded values, since distinct dates can share the same rounded millisecond.

Additional context

Prepared with AI assistance. Related #5703 explicitly covers representable milliseconds in native inspect_plist's plutil conversion path and excludes microsecond precision; this report concerns direct keyed-archive binary decoding and its millisecond-limited Date conversion. #4295 covered scalar-vs-dictionary classification, not the timestamp value. Interface Builder uses the same decoder by source inspection; public Interface Builder reproduction is not claimed. Last known good: NOT_ENOUGH_INFO; no version range was tested.

Originally created by @morluto on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5770 ### Before submitting - [x] I searched open and closed issues and open PRs for a related report. - [x] The reproduction uses only synthetic archive data and no credentials. ### Area Evidence, artifacts, or reconstruction ### Observed behavior A valid binary keyed-archive date at `0.1005` seconds after the 2001 epoch is returned as the known observed timestamp `2001-01-01T00:00:00.100Z` by both CLI and stdio MCP. There is no date-precision limitation. Python `plistlib` reads `2001-01-01T00:00:00.100500`; `plutil -lint` accepts the input. The canonical `0.100` millisecond control remains correct. ### Reproduction From a built checkout, run this Python standard-library fixture generator and CLI replay. It writes disposable files, uses a real binary UID, and runs no target code. The final three fixtures were also verified through `open_binary` + `inspect_keyed_archive` over stdio MCP with the selected advertised SDK output schema. ```sh python3 - <<'PYTHON' import datetime, hashlib, json, pathlib, plistlib, struct, subprocess, tempfile root=pathlib.Path(tempfile.mkdtemp(prefix='rea-keyed-date-')) control=plistlib.dumps({'$archiver':'NSKeyedArchiver','$version':100000,'$objects':['$null',datetime.datetime(2001,1,1,0,0,0,100000)],'$top':{'root':plistlib.UID(1)}},fmt=plistlib.FMT_BINARY) pattern=b'\x33'+struct.pack('>d',.1) assert control.count(pattern)==1 offset=control.index(pattern) for name in ('valid','invalid-date-marker','submillisecond'): data=bytearray(control) if name=='invalid-date-marker': data[offset]=0x30 if name=='submillisecond': struct.pack_into('>d',data,offset+1,.1005) path=root/(name+'.plist');path.write_bytes(data) p=subprocess.run(['node','scripts/rea.mjs','inspect-keyed-archive',str(path),'--json'],capture_output=True,text=True,check=True) result=json.loads(p.stdout)['normalized_result'] print(json.dumps({'case':name,'sha256':hashlib.sha256(data).hexdigest(),'object':result['objects'][1],'limitations':result['limitations']})) PYTHON ``` ### Expected behavior Preserve the instant if the output contract can represent it. Otherwise return the existing typed unknown date (`iso8601: null`) with an applicable counted limitation, retaining the source bytes. Do not assert a different known instant. The [documented scalar projection](https://github.com/morluto/rea/blob/3a0e91dd914145ecb9accbdeb68ecf7fe4d5133d/docs/native-investigation.md) uses explicit unknown dates when the instant cannot be represented. ### Impact and scope Confirmed silent timestamp loss on a reduced synthetic binary keyed archive. Production frequency and nonbinary paths are unmeasured. No target execution or artifact mutation occurred. ### REA version 6.7.0, built source `3a0e91dd914145ecb9accbdeb68ecf7fe4d5133d`. ### Runtime and platform Node.js 24.18.0; macOS 26.3 arm64; pinned plist dependency 5.0.0. ### Target and artifact context Self-authored binary NSKeyedArchiver document with `$objects = ["$null", date]` and `$top.root` encoded as a binary UID to object 1. The valid, malformed-marker, and sub-millisecond fixtures differ only in the selected date marker/payload. All bytes were unchanged after CLI/MCP inspection. ### Interface CLI and MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Both interfaces return object 1 as: ```json {"id":1,"kind":"scalar","status":"observed","value":{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100Z"}} ``` The valid sub-millisecond fixture has SHA-256 `ab69ba6e8034c6f60b828ee323a5a3e81bae3e7657477e0ebfbfdb93d53ed2ce`, marker `0x33` at offset 77, and Float64 payload hex `3fb9ba5e353f7cee`. The only limitations concern class semantics, nil references, and paging; none discloses date precision. The valid millisecond control has payload `3fb999999999999a` and the same reported ISO timestamp. ### Typed outcome or recovery expected Keep the canonical-millisecond control known. Mark only the unrepresentable source instant unknown with a counted date limitation, or report it losslessly. ### Analysis or suspected cause Source-derived: the pinned binary plist decoder constructs JavaScript `Date` directly from Float64 seconds. The source scalar precision disappears before the shared date projection can distinguish truncation from a canonical millisecond date. Fixes must follow source object identity rather than matching rounded values, since distinct dates can share the same rounded millisecond. ### Additional context Prepared with AI assistance. Related #5703 explicitly covers representable milliseconds in native `inspect_plist`'s `plutil` conversion path and excludes microsecond precision; this report concerns direct keyed-archive binary decoding and its millisecond-limited `Date` conversion. #4295 covered scalar-vs-dictionary classification, not the timestamp value. Interface Builder uses the same decoder by source inspection; public Interface Builder reproduction is not claimed. Last known good: NOT_ENOUGH_INFO; no version range was tested.
bsakaguchi 2026-10-31 00:41:16 +00:00
  • closed this issue
  • added the
    bug
    triage
    labels
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#3507
No description provided.