[GH-ISSUE #5703] [Bug] inspect-plist silently drops fractional seconds from binary plist dates #3453

Closed
opened 2026-10-31 00:40:38 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

inspect-plist exits 0 for a valid binary plist containing a date with 100 milliseconds and a whole-second control. It reports both as 2020-05-05T01:02:03.000Z. Python's standard-library plistlib independently reads the fractional source value as 2020-05-05T01:02:03.100000. The result discloses XML conversion but gives no limitation for the lost date precision.

Reproduction

Run from a built source checkout on macOS. The fixture uses only Python's standard library and isolated REA state.

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \
  "$work_dir/cache" "$work_dir/state" "$work_dir/tmp"
python3 -I - "$work_dir" <<'PYTHON'
import datetime, pathlib, plistlib, sys
work = pathlib.Path(sys.argv[1])
values = {
    "fraction": datetime.datetime(2020, 5, 5, 1, 2, 3, 100000),
    "whole": datetime.datetime(2020, 5, 5, 1, 2, 3),
}
fixture = work / "dates.plist"
fixture.write_bytes(plistlib.dumps(values, fmt=plistlib.FMT_BINARY))
print({key: value.isoformat()
       for key, value in plistlib.loads(fixture.read_bytes()).items()})
PYTHON
env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" \
  XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" \
  XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \
  node scripts/rea.mjs inspect-plist "$work_dir/dates.plist" --json \
  > "$work_dir/result.json"
jq '.normalized_result | {value, limitations}' "$work_dir/result.json"
plutil -convert xml1 -o - "$work_dir/dates.plist"

The independent baseline verifier reproduced this reduced fixture. I reran it after building current main, then repeated the CLI invocation to check its exit status and stderr. Both current-main invocations returned the same wrong timestamps; the status-checked invocation exited 0 with empty stderr.

Expected behavior

Keep the fractional date distinguishable from the control, with a timestamp equivalent to 2020-05-05T01:02:03.100Z. This value fits JavaScript's millisecond representation. If the source instant cannot be established, report an explicit unknown with an applicable limitation instead of asserting a different known instant.

Source-derived contract at the baseline: src/contracts/native/nativeToolContracts.ts:111-117 promises normalized plist JSON; AGENTS.md:13,31 requires preservation of source values or explicit unknowns when normalization loses information; docs/native-investigation.md:679-687 defines typed date timestamps and counted limitations for unrepresentable dates. No whole-second exception is documented for binary dates.

Impact and scope

P2: valid fractional binary dates silently become different timestamps, which can mislead timestamp comparisons. Independently decoding this fixture with Python plistlib preserves the source dates and provides a practical workaround outside REA. Only the CLI workflow was exercised; downstream use, MCP behavior, and frequency in application plists were not tested. This report requires millisecond precision for the reduced fixture, not microsecond precision or fractional date literals in XML input.

REA version

rea-agents 6.7.0; independently verified at source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Still reproduces after npm run build:cached on fetched main 52c84f6f58fa3cc7b39326654c6d3f261f957957, also reporting 6.7.0. The lockfile did not change between these revisions.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1; arm64. Native macOS provider with system plutil; no Hopper or Ghidra involved.

Target and artifact context

Self-authored binary plist containing two date values, generated and independently decoded with Python plistlib. No app bundle or external service is needed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed on rebuilt current main:

Python: {'fraction': '2020-05-05T01:02:03.100000', 'whole': '2020-05-05T01:02:03'}

REA's normalized result subset:

{
  "value": {
    "fraction": {"$plist_type": "date", "iso8601": "2020-05-05T01:02:03.000Z"},
    "whole": {"$plist_type": "date", "iso8601": "2020-05-05T01:02:03.000Z"}
  },
  "limitations": [
    "plutil cannot express this plist as JSON, so it was decoded from plutil's XML conversion; data and date values are typed objects such as { \"$plist_type\": \"data\", \"base64\": ... }."
  ]
}

The direct XML conversion emits <date>2020-05-05T01:02:03Z</date> for both values. REA provenance records JSON conversion exit 1, then XML conversion exit 0. Evidence provenance: observed; local verification: reproduced.

Typed outcome or recovery expected

Preserve the fractional timestamp, or identify the affected value as unavailable with a counted limitation. Keep the whole-second control known and correct. A precision note alongside the same asserted wrong instant would leave the failure unresolved.

Analysis or suspected cause

Source-derived reasoning: current main src/native/NativeMacOSProvider.ts:875-925 falls back from failed plutil JSON conversion to XML conversion, then calls parsePlistXml. src/native/parsers/plist.ts:162-172 decodes and projects that XML after the date fraction has disappeared. The direct plutil output confirms loss at conversion before the shared date projection. This is not attributed to a bundled decoder dependency.

Additional context

Prepared with AI assistance and independently verified. No last known good version or regression range was established. Related closed #4834 and merged #4839 concern negative-zero identity on the same conversion path, not date fractions. Merged #3793 handles unrepresentable dates; this fixture uses a representable date. Closed #4852 concerns invalid XML scalar text, which this fixture does not contain.

Related sibling reports: https://github.com/morluto/rea/issues/5701, https://github.com/morluto/rea/issues/5702. They concern XML-fallback rejection and Unicode string rewriting, respectively, and have different restoration checks.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5703 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior `inspect-plist` exits 0 for a valid binary plist containing a date with 100 milliseconds and a whole-second control. It reports both as `2020-05-05T01:02:03.000Z`. Python's standard-library `plistlib` independently reads the fractional source value as `2020-05-05T01:02:03.100000`. The result discloses XML conversion but gives no limitation for the lost date precision. ### Reproduction Run from a built source checkout on macOS. The fixture uses only Python's standard library and isolated REA state. ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \ "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" python3 -I - "$work_dir" <<'PYTHON' import datetime, pathlib, plistlib, sys work = pathlib.Path(sys.argv[1]) values = { "fraction": datetime.datetime(2020, 5, 5, 1, 2, 3, 100000), "whole": datetime.datetime(2020, 5, 5, 1, 2, 3), } fixture = work / "dates.plist" fixture.write_bytes(plistlib.dumps(values, fmt=plistlib.FMT_BINARY)) print({key: value.isoformat() for key, value in plistlib.loads(fixture.read_bytes()).items()}) PYTHON env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" \ XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" \ XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs inspect-plist "$work_dir/dates.plist" --json \ > "$work_dir/result.json" jq '.normalized_result | {value, limitations}' "$work_dir/result.json" plutil -convert xml1 -o - "$work_dir/dates.plist" ``` The independent baseline verifier reproduced this reduced fixture. I reran it after building current main, then repeated the CLI invocation to check its exit status and stderr. Both current-main invocations returned the same wrong timestamps; the status-checked invocation exited 0 with empty stderr. ### Expected behavior Keep the fractional date distinguishable from the control, with a timestamp equivalent to `2020-05-05T01:02:03.100Z`. This value fits JavaScript's millisecond representation. If the source instant cannot be established, report an explicit unknown with an applicable limitation instead of asserting a different known instant. Source-derived contract at the baseline: `src/contracts/native/nativeToolContracts.ts:111-117` promises normalized plist JSON; `AGENTS.md:13,31` requires preservation of source values or explicit unknowns when normalization loses information; `docs/native-investigation.md:679-687` defines typed date timestamps and counted limitations for unrepresentable dates. No whole-second exception is documented for binary dates. ### Impact and scope P2: valid fractional binary dates silently become different timestamps, which can mislead timestamp comparisons. Independently decoding this fixture with Python `plistlib` preserves the source dates and provides a practical workaround outside REA. Only the CLI workflow was exercised; downstream use, MCP behavior, and frequency in application plists were not tested. This report requires millisecond precision for the reduced fixture, not microsecond precision or fractional date literals in XML input. ### REA version `rea-agents` 6.7.0; independently verified at source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Still reproduces after `npm run build:cached` on fetched main `52c84f6f58fa3cc7b39326654c6d3f261f957957`, also reporting 6.7.0. The lockfile did not change between these revisions. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1; arm64. Native macOS provider with system `plutil`; no Hopper or Ghidra involved. ### Target and artifact context Self-authored binary plist containing two date values, generated and independently decoded with Python `plistlib`. No app bundle or external service is needed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed on rebuilt current main: ```text Python: {'fraction': '2020-05-05T01:02:03.100000', 'whole': '2020-05-05T01:02:03'} ``` REA's normalized result subset: ```json { "value": { "fraction": {"$plist_type": "date", "iso8601": "2020-05-05T01:02:03.000Z"}, "whole": {"$plist_type": "date", "iso8601": "2020-05-05T01:02:03.000Z"} }, "limitations": [ "plutil cannot express this plist as JSON, so it was decoded from plutil's XML conversion; data and date values are typed objects such as { \"$plist_type\": \"data\", \"base64\": ... }." ] } ``` The direct XML conversion emits `<date>2020-05-05T01:02:03Z</date>` for both values. REA provenance records JSON conversion exit 1, then XML conversion exit 0. Evidence provenance: observed; local verification: reproduced. ### Typed outcome or recovery expected Preserve the fractional timestamp, or identify the affected value as unavailable with a counted limitation. Keep the whole-second control known and correct. A precision note alongside the same asserted wrong instant would leave the failure unresolved. ### Analysis or suspected cause Source-derived reasoning: current main `src/native/NativeMacOSProvider.ts:875-925` falls back from failed `plutil` JSON conversion to XML conversion, then calls `parsePlistXml`. `src/native/parsers/plist.ts:162-172` decodes and projects that XML after the date fraction has disappeared. The direct `plutil` output confirms loss at conversion before the shared date projection. This is not attributed to a bundled decoder dependency. ### Additional context Prepared with AI assistance and independently verified. No last known good version or regression range was established. Related closed #4834 and merged #4839 concern negative-zero identity on the same conversion path, not date fractions. Merged #3793 handles unrepresentable dates; this fixture uses a representable date. Closed #4852 concerns invalid XML scalar text, which this fixture does not contain. Related sibling reports: https://github.com/morluto/rea/issues/5701, https://github.com/morluto/rea/issues/5702. They concern XML-fallback rejection and Unicode string rewriting, respectively, and have different restoration checks.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #3371 (CDATA in XML keyed-archive keys truncates root and field names) and sibling plist decoding reports #3373, #4042, #4714, #4849, #4852, #5701, #5702, #5705.

Consolidated under Cluster K (XML Keyed-Archive Deserialization & CDATA Handling / Plist Decoder) in tracker #4330.

<!-- gh-comment-id:6304470253 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #3371 (`CDATA in XML keyed-archive keys truncates root and field names`) and sibling plist decoding reports #3373, #4042, #4714, #4849, #4852, #5701, #5702, #5705. Consolidated under **Cluster K (XML Keyed-Archive Deserialization & CDATA Handling / Plist Decoder)** in tracker #4330.
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#3453
No description provided.