[PR #1480] [MERGED] fix(artifacts): keep case-distinct archive names in inventory #2918

Closed
opened 2026-10-17 21:11:48 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

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

Base: main ← Head: cursor/apk-path-identity-07c3


📝 Commits (1)

  • 312263a fix(artifacts): keep case-distinct archive names in inventory

📊 Changes

11 files changed (+425 additions, -95 deletions)

View changed files

📝 docs/android-analysis.md (+7 -2)
📝 src/artifacts/ArtifactPaths.ts (+157 -11)
📝 src/artifacts/SafeOutputTree.ts (+33 -2)
📝 src/artifacts/inventory/scanCanonical.ts (+16 -7)
📝 src/artifacts/inventory/scanReader.ts (+14 -2)
📝 src/domain/analysisErrorPresentation.ts (+20 -3)
📝 src/domain/android/androidApplication.ts (+9 -28)
📝 src/domain/zipPackageFormat.ts (+2 -1)
📝 tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts (+55 -15)
📝 tests/boundary/providers/native/artifactProvider.failureDetail.test.ts (+67 -24)
📝 tests/composition/android/application.test.ts (+45 -0)

📄 Description

Summary

Fixes #1470. Shrunk release APKs can contain ZIP names that differ only in case, such as res/2F.xml and res/2f.xml. Inventory treated that as an unsafe path, so inspect-artifact failed and project-android-application-graph had no evidence. Both names now stay in the inventory, with a limitation that a case-insensitive destination cannot store both.

Problem and expected behavior

ArtifactPathRegistry folded every ZIP segment with English case folding during inventory. ZIP names are case-sensitive, and Android resource shrinking uses both cases. The failure was reported as a path the analyst should correct, and --integrity-policy record-and-continue did not apply.

Inventory should keep both logical names. Extraction should fail only when the destination directory cannot keep both spellings.

Change and scope

Logical registration is exact spelling. Exact duplicates and same-spelling file/directory prefixes still fail. After the scan, each affected occurrence and the inventory record that English (en-US) Unicode case folding collides those names. That note is not a filesystem observation and is not an integrity contradiction.

SafeOutputTree is the materialization boundary. It still refuses a second write of one logical path. If creating a segment fails and the parent listing already holds another English-case spelling, extraction names both paths, points back at the inventory, and rolls the tree back. Case-sensitive volumes write both files.

The Android projection no longer compares component formats with android-manifest, android-resources, dex, or jvm-class. Inventory never emits those labels. Classification stays on the path rules. Every projection states that ZIP inventory cannot see the APK Signing Block, so an empty signing array does not mean unsigned, and that a bridge basis comes from the native path and is repeated for every managed component.

.aab is no longer classified as apk. An app bundle with ZIP magic is a zip. JADX and the application graph already require an APK.

Not included: an APK Signing Block parser, split-APK sets, a JADX-to-graph join, or DEX/binary-XML magic detection.

Contract and boundary impact

  • Semantic owner and earliest changed stage: logical archive identity in ArtifactPathRegistry during inventory; destination collision in SafeOutputTree during extraction.
  • CLI and MCP/tool-catalog contract: no schema change. Path failures still use artifact_operation_failed / path. Unsafe paths still tell the analyst to correct the artifact. A destination case collision returns the spelling pair and says the inventory retains both names.
  • Provider, bridge, target-format, or platform compatibility: .aab resolves as zip instead of apk.
  • Evidence, artifact, provenance, or reconstruction contract: case-distinct members are inventoried. Inventory and occurrence limitations record the English case-folding collision. Android projection limitations add the signing-block and bridge-basis notes. Coverage remains inventory completeness.
  • Process execution, authorization, cleanup, or containment impact: none. Extraction rollback is unchanged.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: archive safety (logical names, destination-listing message, extraction of Main.js and main.js), path-failure presentation, and an APK with res/2F.xml and res/2f.xml that inventories and projects.
  • Base reproduction or other evidence: the registry previously rejected a.js after A.js. That case now inventories both entries.
  • User-visible CLI/MCP output (if applicable): inspect-artifact succeeds for case-distinct ZIP names and includes the case-folding limitation. Extraction on a case-insensitive volume fails with Destination filesystem cannot store both <path> and <other>; inventory retains both logical names.
  • Remaining proof gaps: Windows and macOS refusal of a case-insensitive destination was not executed. The collision message is tested as a pure function of the requested path and a directory listing. Linux CI covers writing both spellings.

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:local -- tests/composition/android/application.test.ts — 6 passed
  • npm run test:focused -- tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts tests/boundary/providers/native/artifactProvider.failureDetail.test.ts — 8 passed
  • pre-push npm run check:fast — typecheck and lint passed

Real JADX was not required. The graph change consumes inventory evidence only.

Compatibility, safety, and release

  • Breaking changes or migration steps: none for a supported contract. An .aab path that was previously labeled apk is now zip, so Android package tools and the application graph refuse it as a non-APK.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable. Case-insensitive extraction refusal was not run on Windows or macOS.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: extraction still uses an exclusive create and rolls back on failure. Case folding is no longer a reason to reject a logical archive name.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • 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.
Open in Web Open in Cursor 

🔄 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/1480 **Author:** [@morluto](https://github.com/morluto) **Created:** 10/10/2026 **Status:** ✅ Merged **Merged:** 10/10/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `cursor/apk-path-identity-07c3` --- ### 📝 Commits (1) - [`312263a`](https://github.com/morluto/rea/commit/312263a2159c204a7e82b1aeae170c3fbb1e13c4) fix(artifacts): keep case-distinct archive names in inventory ### 📊 Changes **11 files changed** (+425 additions, -95 deletions) <details> <summary>View changed files</summary> 📝 `docs/android-analysis.md` (+7 -2) 📝 `src/artifacts/ArtifactPaths.ts` (+157 -11) 📝 `src/artifacts/SafeOutputTree.ts` (+33 -2) 📝 `src/artifacts/inventory/scanCanonical.ts` (+16 -7) 📝 `src/artifacts/inventory/scanReader.ts` (+14 -2) 📝 `src/domain/analysisErrorPresentation.ts` (+20 -3) 📝 `src/domain/android/androidApplication.ts` (+9 -28) 📝 `src/domain/zipPackageFormat.ts` (+2 -1) 📝 `tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts` (+55 -15) 📝 `tests/boundary/providers/native/artifactProvider.failureDetail.test.ts` (+67 -24) 📝 `tests/composition/android/application.test.ts` (+45 -0) </details> ### 📄 Description <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary Fixes #1470. Shrunk release APKs can contain ZIP names that differ only in case, such as `res/2F.xml` and `res/2f.xml`. Inventory treated that as an unsafe path, so `inspect-artifact` failed and `project-android-application-graph` had no evidence. Both names now stay in the inventory, with a limitation that a case-insensitive destination cannot store both. ## Problem and expected behavior `ArtifactPathRegistry` folded every ZIP segment with English case folding during inventory. ZIP names are case-sensitive, and Android resource shrinking uses both cases. The failure was reported as a path the analyst should correct, and `--integrity-policy record-and-continue` did not apply. Inventory should keep both logical names. Extraction should fail only when the destination directory cannot keep both spellings. ## Change and scope Logical registration is exact spelling. Exact duplicates and same-spelling file/directory prefixes still fail. After the scan, each affected occurrence and the inventory record that English (en-US) Unicode case folding collides those names. That note is not a filesystem observation and is not an integrity contradiction. `SafeOutputTree` is the materialization boundary. It still refuses a second write of one logical path. If creating a segment fails and the parent listing already holds another English-case spelling, extraction names both paths, points back at the inventory, and rolls the tree back. Case-sensitive volumes write both files. The Android projection no longer compares component formats with `android-manifest`, `android-resources`, `dex`, or `jvm-class`. Inventory never emits those labels. Classification stays on the path rules. Every projection states that ZIP inventory cannot see the APK Signing Block, so an empty signing array does not mean unsigned, and that a bridge basis comes from the native path and is repeated for every managed component. `.aab` is no longer classified as `apk`. An app bundle with ZIP magic is a `zip`. JADX and the application graph already require an APK. Not included: an APK Signing Block parser, split-APK sets, a JADX-to-graph join, or DEX/binary-XML magic detection. ## Contract and boundary impact - Semantic owner and earliest changed stage: logical archive identity in `ArtifactPathRegistry` during inventory; destination collision in `SafeOutputTree` during extraction. - CLI and MCP/tool-catalog contract: no schema change. Path failures still use `artifact_operation_failed` / `path`. Unsafe paths still tell the analyst to correct the artifact. A destination case collision returns the spelling pair and says the inventory retains both names. - Provider, bridge, target-format, or platform compatibility: `.aab` resolves as `zip` instead of `apk`. - Evidence, artifact, provenance, or reconstruction contract: case-distinct members are inventoried. Inventory and occurrence limitations record the English case-folding collision. Android projection limitations add the signing-block and bridge-basis notes. Coverage remains inventory completeness. - Process execution, authorization, cleanup, or containment impact: none. Extraction rollback is unchanged. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: archive safety (logical names, destination-listing message, extraction of `Main.js` and `main.js`), path-failure presentation, and an APK with `res/2F.xml` and `res/2f.xml` that inventories and projects. - Base reproduction or other evidence: the registry previously rejected `a.js` after `A.js`. That case now inventories both entries. - User-visible CLI/MCP output (if applicable): `inspect-artifact` succeeds for case-distinct ZIP names and includes the case-folding limitation. Extraction on a case-insensitive volume fails with `Destination filesystem cannot store both <path> and <other>; inventory retains both logical names.` - Remaining proof gaps: Windows and macOS refusal of a case-insensitive destination was not executed. The collision message is tested as a pure function of the requested path and a directory listing. Linux CI covers writing both spellings. 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:local -- tests/composition/android/application.test.ts` — 6 passed - `npm run test:focused -- tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts tests/boundary/providers/native/artifactProvider.failureDetail.test.ts` — 8 passed - pre-push `npm run check:fast` — typecheck and lint passed Real JADX was not required. The graph change consumes inventory evidence only. ## Compatibility, safety, and release - Breaking changes or migration steps: none for a supported contract. An `.aab` path that was previously labeled `apk` is now `zip`, so Android package tools and the application graph refuse it as a non-APK. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. Case-insensitive extraction refusal was not run on Windows or macOS. - Package or release metadata impact: none. - Security, privacy, process, or containment review: extraction still uses an exclusive create and rolls back on failure. Case folding is no longer a reason to reject a logical archive name. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [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. - [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. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-44e94f43-7d92-4380-94b2-c70d5fe807c3?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-44e94f43-7d92-4380-94b2-c70d5fe807c3&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:11:48 +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#2918
No description provided.