[PR #1843] [MERGED] fix(plist): decode formatted XML dictionary keys #4852

Closed
opened 2026-10-31 00:56:02 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1843
Author: @Aliciawque
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/13/2026
Merged by: @morluto

Base: main ← Head: fix/plist-key-element-decoding


📝 Commits (2)

  • 4dad5b3 fix(plist): decode formatted XML dictionary keys
  • 9dbea03 chore(plist): merge main into formatted key decoding branch

📄 Description

Summary

Follow up the XML dictionary-key handling accepted in #885 and #894. Valid key formatting currently makes an otherwise readable Foundation archive fail entirely instead of retaining ordinary values and reporting the existing __proto__ omission.

No separate issue was opened: this PR includes an executed reproducer of the existing contract regression. Open and closed plist/XML contributions were checked for overlap.

Problem and expected behavior

On main 0b4ce83618bb800627264ae6221e2e35140417b5, each of these legal XML representations bypasses the regular-expression substitution:

  • <key >__proto__</key >
  • A newline before the key tag's closing >
  • <key>__proto__<!-- trailing comment --></key>
  • <key><![CDATA[__proto__]]><!-- trailing comment --></key>

The compiled inspect-keyed-archive CLI exits 1 with artifact_operation_failed, reason format, and the plist decoder's prototype-key rejection. The same failure reaches the real SDK MCP workflow. The exact-tag control succeeds. Python's standard plist reader independently accepts the inert reproducer files with equivalent dictionaries.

Expected: use the same key interpretation as the pinned plist decoder, retain unrelated evidence and report the omission count already required by #885/#894.

Change and scope

Replace the key-matching regex with the existing pinned xmldom parser's element identity and source locations. Reserve the placeholder against decoded first-child key values, then insert a CDATA placeholder before each rejected first child. The plist decoder remains responsible for value interpretation.

Source edits operate on the producer's line-normalized XML, without serializing the DOM. This keeps character references such as &#13;, &#x85; and &#x2028; intact rather than turning their decoded values into normalized newlines. Source locations are reached with one forward scan, without allocating an offset for every newline. Existing OpenStep autodetection is preserved.

This does not expand plist syntax, change the decoder's first-child key semantics, change binary-plist handling, or introduce a new omission policy.

Contract and boundary impact

  • Semantic owner and earliest changed stage: shared plist XML preprocessing, before the pinned decoder sees dictionary keys.
  • CLI and MCP/tool-catalog contract: no schema, tool or parameter changes; the existing successful archive result and limitation become reachable for the valid representations above.
  • Provider, bridge, target-format, or platform compatibility: portable XML parsing; existing OpenStep app metadata remains accepted. No provider protocol changes.
  • Evidence, artifact, provenance, or reconstruction contract: ordinary values, original archive digest and the existing exact omission count remain intact.
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata, package, or installation impact: none; xmldom is already a direct pinned dependency. No generated outputs or new dependency are committed.

Evidence and regression coverage

  • Four newly covered key forms fail against pristine production source; existing controls pass.

  • Both new compiled CLI and SDK MCP tests fail against pristine source, then pass with the fix. Their inert fixture asserts the original byte digest, root identity, nested ordinary values, character-reference preservation, placeholder collision handling and two reported omissions.

  • Domain regressions cover source positions across literal newline forms, OpenStep compatibility, first-child semantics and newline-dense input.

  • Independent review exercised 500 deterministic differential cases, 16 malformed-input comparisons, typed values, entities, comments/processing instructions, Unicode offsets and actual OpenStep app-bundle resolution.

  • Representative final result: object 1 retains kept: "a\rb\u0085c\u2028d", the ordinary __rea_prototype_key__ field and nested: { value: 7 }; limitations report 2 dictionary entries keyed __proto__ were omitted because REA results cannot represent that key.

  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity and source provenance remain preserved.

  • Omitted or unavailable information remains visible.

Validation performed

Linux, pinned Node 24.18.0 and npm 11.16.0:

  • npm ci — passed.
  • npm run test:focused -- src/domain/propertyListKeys.test.ts src/artifacts/apple/KeyedArchiveReader.test.ts tests/boundary/cli/keyedArchivePrototypeKeyCli.test.ts tests/boundary/mcp/keyedArchivePrototypeKeyMcp.test.ts tests/boundary/filesystem/binaryTarget.test.ts tests/boundary/filesystem/interfaceBuilderBundle.test.ts tests/boundary/providers/native/keyedArchiveEncoding.test.ts tests/boundary/providers/native/nativeProvider.test.ts — 138 passed, 25 platform-dependent skips across eight files.
  • npm run check — passed; 753 existing lint warnings, no errors.
  • npm run docs:check — passed.
  • npm run verify:test-discovery — all 838 files discovered exactly once; this is discovery, not full-suite execution.
  • git diff --cached --check — passed.
  • npm run test:fast — 4,457 passed, 3 skipped, 11 failed across two Hopper lease/cleanup files. All 11 failures reproduce with pristine production source in this cloud environment: Unix-domain socket binding is denied with listen EPERM, with two cleanup cases failing downstream. This is not a full-suite pass; no socket restriction was bypassed.

Remote CI status

At publication, CI run 38145874828 for head 4dad5b36f153e48a624bbf5239b9637f85fd0fee reports action_required: maintainer approval is needed before execution. The run has zero jobs and the commit has zero check runs. This draft is not claiming remote CI passed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: no real provider or macOS/Windows run. The portable compiled CLI and SDK MCP paths ran on Linux; recorded native-provider tests do not establish real macOS behavior.
  • Package or release metadata impact: none.
  • Security/privacy/process review: only synthetic source-owned plist data is added. The existing reserved-key omission remains explicit. No input file is rewritten and no process authority is expanded.
  • Resource tradeoff: XML receives an additional parser pass. Independent 32 MiB newline-dense measurements were approximately 1.08 s / 158 MiB maximum RSS before and 2.55 s / 174 MiB after; these are local observations, not universal bounds. Sparse-key newline regressions avoid retaining a per-line offset table.

Review checklist

  • Concrete problem and one focused outcome with a Conventional Commit title.
  • Related accepted behavior linked; reason for no separate issue stated above.
  • Tests cover changed observable behavior and meaningful preservation paths.
  • Owning documentation updated; contracts and generated metadata unchanged.
  • Representative CLI/MCP result and verification gaps included.
  • Final diff checked for secrets, unrelated cleanup and unsupported claims.

AI-assisted implementation and independent review; all validation claims above refer to executed checks.


🔄 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/1843 **Author:** [@Aliciawque](https://github.com/Aliciawque) **Created:** 10/11/2026 **Status:** ✅ Merged **Merged:** 10/13/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/plist-key-element-decoding` --- ### 📝 Commits (2) - [`4dad5b3`](https://github.com/morluto/rea/commit/4dad5b36f153e48a624bbf5239b9637f85fd0fee) fix(plist): decode formatted XML dictionary keys - [`9dbea03`](https://github.com/morluto/rea/commit/9dbea03e4006e7dbf063dba0e2d83735e1e36da5) chore(plist): merge main into formatted key decoding branch ### 📄 Description ## Summary Follow up the XML dictionary-key handling accepted in #885 and #894. Valid key formatting currently makes an otherwise readable Foundation archive fail entirely instead of retaining ordinary values and reporting the existing `__proto__` omission. No separate issue was opened: this PR includes an executed reproducer of the existing contract regression. Open and closed plist/XML contributions were checked for overlap. ## Problem and expected behavior On main `0b4ce83618bb800627264ae6221e2e35140417b5`, each of these legal XML representations bypasses the regular-expression substitution: - `<key >__proto__</key >` - A newline before the key tag's closing `>` - `<key>__proto__<!-- trailing comment --></key>` - `<key><![CDATA[__proto__]]><!-- trailing comment --></key>` The compiled `inspect-keyed-archive` CLI exits 1 with `artifact_operation_failed`, reason `format`, and the plist decoder's prototype-key rejection. The same failure reaches the real SDK MCP workflow. The exact-tag control succeeds. Python's standard plist reader independently accepts the inert reproducer files with equivalent dictionaries. Expected: use the same key interpretation as the pinned plist decoder, retain unrelated evidence and report the omission count already required by #885/#894. ## Change and scope Replace the key-matching regex with the existing pinned xmldom parser's element identity and source locations. Reserve the placeholder against decoded first-child key values, then insert a CDATA placeholder before each rejected first child. The plist decoder remains responsible for value interpretation. Source edits operate on the producer's line-normalized XML, without serializing the DOM. This keeps character references such as `&#13;`, `&#x85;` and `&#x2028;` intact rather than turning their decoded values into normalized newlines. Source locations are reached with one forward scan, without allocating an offset for every newline. Existing OpenStep autodetection is preserved. This does not expand plist syntax, change the decoder's first-child key semantics, change binary-plist handling, or introduce a new omission policy. ## Contract and boundary impact - Semantic owner and earliest changed stage: shared plist XML preprocessing, before the pinned decoder sees dictionary keys. - CLI and MCP/tool-catalog contract: no schema, tool or parameter changes; the existing successful archive result and limitation become reachable for the valid representations above. - Provider, bridge, target-format, or platform compatibility: portable XML parsing; existing OpenStep app metadata remains accepted. No provider protocol changes. - Evidence, artifact, provenance, or reconstruction contract: ordinary values, original archive digest and the existing exact omission count remain intact. - Process execution, authorization, cleanup, or containment impact: none. - Generated metadata, package, or installation impact: none; xmldom is already a direct pinned dependency. No generated outputs or new dependency are committed. ## Evidence and regression coverage - Four newly covered key forms fail against pristine production source; existing controls pass. - Both new compiled CLI and SDK MCP tests fail against pristine source, then pass with the fix. Their inert fixture asserts the original byte digest, root identity, nested ordinary values, character-reference preservation, placeholder collision handling and two reported omissions. - Domain regressions cover source positions across literal newline forms, OpenStep compatibility, first-child semantics and newline-dense input. - Independent review exercised 500 deterministic differential cases, 16 malformed-input comparisons, typed values, entities, comments/processing instructions, Unicode offsets and actual OpenStep app-bundle resolution. - Representative final result: object 1 retains `kept: "a\rb\u0085c\u2028d"`, the ordinary `__rea_prototype_key__` field and `nested: { value: 7 }`; limitations report `2 dictionary entries keyed __proto__ were omitted because REA results cannot represent that key.` - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity and source provenance remain preserved. - [x] Omitted or unavailable information remains visible. ## Validation performed Linux, pinned Node 24.18.0 and npm 11.16.0: - `npm ci` — passed. - `npm run test:focused -- src/domain/propertyListKeys.test.ts src/artifacts/apple/KeyedArchiveReader.test.ts tests/boundary/cli/keyedArchivePrototypeKeyCli.test.ts tests/boundary/mcp/keyedArchivePrototypeKeyMcp.test.ts tests/boundary/filesystem/binaryTarget.test.ts tests/boundary/filesystem/interfaceBuilderBundle.test.ts tests/boundary/providers/native/keyedArchiveEncoding.test.ts tests/boundary/providers/native/nativeProvider.test.ts` — 138 passed, 25 platform-dependent skips across eight files. - `npm run check` — passed; 753 existing lint warnings, no errors. - `npm run docs:check` — passed. - `npm run verify:test-discovery` — all 838 files discovered exactly once; this is discovery, not full-suite execution. - `git diff --cached --check` — passed. - `npm run test:fast` — 4,457 passed, 3 skipped, 11 failed across two Hopper lease/cleanup files. All 11 failures reproduce with pristine production source in this cloud environment: Unix-domain socket binding is denied with `listen EPERM`, with two cleanup cases failing downstream. This is not a full-suite pass; no socket restriction was bypassed. ### Remote CI status At publication, [CI run 38145874828](https://github.com/morluto/rea/actions/runs/38145874828) for head `4dad5b36f153e48a624bbf5239b9637f85fd0fee` reports `action_required`: maintainer approval is needed before execution. The run has zero jobs and the commit has zero check runs. This draft is not claiming remote CI passed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: no real provider or macOS/Windows run. The portable compiled CLI and SDK MCP paths ran on Linux; recorded native-provider tests do not establish real macOS behavior. - Package or release metadata impact: none. - Security/privacy/process review: only synthetic source-owned plist data is added. The existing reserved-key omission remains explicit. No input file is rewritten and no process authority is expanded. - Resource tradeoff: XML receives an additional parser pass. Independent 32 MiB newline-dense measurements were approximately 1.08 s / 158 MiB maximum RSS before and 2.55 s / 174 MiB after; these are local observations, not universal bounds. Sparse-key newline regressions avoid retaining a per-line offset table. ## Review checklist - [x] Concrete problem and one focused outcome with a Conventional Commit title. - [x] Related accepted behavior linked; reason for no separate issue stated above. - [x] Tests cover changed observable behavior and meaningful preservation paths. - [x] Owning documentation updated; contracts and generated metadata unchanged. - [x] Representative CLI/MCP result and verification gaps included. - [x] Final diff checked for secrets, unrelated cleanup and unsupported claims. AI-assisted implementation and independent review; all validation claims above refer to executed checks. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:56:02 +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#4852
No description provided.