[GH-ISSUE #4271] [Bug] Colliding ZIP paths receive a false integrity diagnosis and ineffective retry advice #1687

Closed
opened 2026-10-16 21:09:05 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

extract-artifact refuses a valid ZIP whose regular members share one NFC logical path with category: integrity_mismatch, claims that the archive contradicts declared integrity, and recommends a retry that fails identically. Both members have correct CRCs and separately verified inventory content identities. Refusing this layout is legitimate under the existing no-overwrite extraction contract; the defect is the incorrect typed diagnosis and recovery advice. Byte-identical duplicate names reproduce the same problem, so this is one logical-path collision defect rather than separate Unicode and duplicate-entry issues.

Reproduction

Use the source checkout and runtime described under Runtime or environment. From the repository root, prepare the locked dependencies with npm ci and build with npm run build, using the repository's pinned Node/npm. The following Python 3 recipe exercises that checkout's public launcher in isolated home, configuration, cache and temporary directories. It needs unzip for a second reader's CRC check and creates regular stored members with Python's standard valid ZIP headers and timestamps. Both Unicode names carry the UTF-8 flag; the archive is read directly without first materializing its colliding names on the host filesystem.

import hashlib
import json
import os
from pathlib import Path
import subprocess
import tempfile
import zipfile
import zlib

launcher = str(Path("scripts/rea.mjs").resolve())
cases = {
    "collision": [("caf\u00e9.txt", b"alpha\n"),
                  ("cafe\u0301.txt", b"beta longer\n")],
    "distinct": [("first.txt", b"alpha\n"),
                 ("second.txt", b"beta longer\n")],
    "single-nfd": [("cafe\u0301.txt", b"alpha\n")],
}
with tempfile.TemporaryDirectory(prefix="archive-review-") as directory:
    root = Path(directory)
    for case, members in cases.items():
        archive = root / (case + ".zip")
        with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_STORED) as z:
            for name, data in members:
                z.writestr(zipfile.ZipInfo(name), data)
        with zipfile.ZipFile(archive) as z:
            assert z.testzip() is None
            assert len(z.infolist()) == len(members)
            for info, (name, expected) in zip(z.infolist(), members):
                observed = z.read(info)
                assert info.filename == name and observed == expected
                assert zlib.crc32(observed) & 0xffffffff == info.CRC
                if not name.isascii():
                    assert info.flag_bits & 0x800
        oracle = subprocess.run(["unzip", "-t", str(archive)], capture_output=True)
        assert oracle.returncode == 0
        for operation, options in [
            ("inspect-artifact", []),
            ("extract-artifact", []),
            ("extract-artifact", ["--integrity-policy", "record-and-continue"]),
        ]:
            run = root / (case + "-" + str(len(options)) + "-" + operation)
            for name in ["home", "config", "cache", "temporary"]:
                (run / name).mkdir(parents=True)
            env = dict(PATH=os.environ["PATH"], HOME=str(run / "home"),
                       XDG_CONFIG_HOME=str(run / "config"),
                       XDG_CACHE_HOME=str(run / "cache"),
                       TMPDIR=str(run / "temporary"))
            result = subprocess.run(
                ["node", launcher, operation, str(archive), "--json", *options],
                env=env, capture_output=True, text=True, timeout=60)
            reply = json.loads(result.stdout)
            row = dict(case=case, operation=operation, options=options,
                       exit=result.returncode, category=reply.get("category"),
                       details=reply.get("details"), stderr=result.stderr)
            if operation == "inspect-artifact" and result.returncode == 0:
                inventory = next(s["evidence"]["normalized_result"]
                    for s in reply["normalized_result"]["substeps"]
                    if s["operation"] == "inventory_artifact")
                occurrences = [o for o in inventory["occurrences"]
                               if o["logical_path"] != "."]
                assert len(occurrences) == len(members)
                assert len({o["occurrence_id"] for o in occurrences}) == len(members)
                assert all(o["hash_status"] == "verified" for o in occurrences)
                assert inventory["integrity_contradictions"] == []
                row["members"] = [dict(path=o["logical_path"],
                    size=o["declared_size"], hash_status=o["hash_status"])
                    for o in occurrences]
            if operation == "extract-artifact":
                files = [p for p in (run / "temporary").rglob("*") if p.is_file()]
                row["file_count"] = len(files)
                if result.returncode != 0:
                    assert list((run / "temporary").iterdir()) == []
                else:
                    actual = sorted((p.stat().st_size,
                        hashlib.sha256(p.read_bytes()).hexdigest()) for p in files)
                    expected = sorted((len(data), hashlib.sha256(data).hexdigest())
                                      for _, data in members)
                    assert actual == expected
            print(json.dumps(row, ensure_ascii=True))

Expected behavior

A refusal caused by two regular occurrences mapping to the same destination logical path should identify the conflicting path using the existing path-failure contract (details.reason: path), publish no output tree, and avoid claiming corrupt archive bytes or suggesting that an integrity-policy retry resolves the collision. Extraction may continue to refuse the entire layout. No disambiguating names, overwrite mode, subset selector, or successful materialization of both occurrences at one pathname is required.

The public extraction schema has no entry or artifact-ID selector: it extracts every regular child of the active artifact. Inventory intentionally retains duplicate same-kind paths as distinct occurrences, and NFC normalization is intentional. Those inventory facts do not imply that every layout is materializable. The integrity policy concerns declared-versus-observed contradictions and need not bypass collision refusals or internal inventory/write verification.

Impact and scope

P3, supported: this changes the typed diagnosis and recovery guidance for a bounded refusal. Extraction may legitimately continue refusing this layout. Every failed extraction left its owned destination empty, and no data loss or incorrect successful output was observed. The emitted integrity-policy retry fails identically; a safe external-extraction workaround remains unverified.

REA version

Source build at 461eb16128, freshly fetched from public main and rebuilt with locked dependencies before the exact reproduction and focused checks. The independently verified original source baseline was 62bf09809e. Both source launchers report 6.5.0; published-package behavior was not reproduced.

Runtime and platform

Node.js v24.18.0 and npm 11.16.0; macOS 27.0.1 arm64. Python 3 standard-library zipfile and unzip -t supply independent archive/content validation. They do not establish safe external extraction.

Target and artifact context

A tiny self-authored stored ZIP with two ordinary regular members whose valid UTF-8 names differ by Unicode composition. Inventory retains both physical occurrences with verified content identities at the intended NFC logical path. No application target is involved.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction, during artifact materialization after successful inventory.

Relevant output or evidence

The collision inspection exits 0 with two distinct occurrence IDs, logical path café.txt for both members, sizes 6 and 12, hash_status: verified, and an empty integrity_contradictions array. Their observed SHA-256 values are b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060 and 73b349007b6b3acf28463c3e759f8be279163402a49080194de9c7912a5c79a7, matching independent content calculations. Python reads both physical entries correctly, and unzip -t exits 0.

Both collision extraction calls exit 1, emit no stderr, and leave their isolated temporary directories empty. Their errors contain the following fields, and the message says that the bytes contradict declared integrity and recommends --integrity-policy record-and-continue or a fresh copy. The retry fails with the same fields and message.

{
  "code": "artifact_operation_failed",
  "category": "integrity_mismatch",
  "details": {
    "operation": "extract_artifact",
    "reason": "integrity",
    "detail": "Extracted content size disagrees with inventory: café.txt"
  }
}

Executed additional controls show the same bounded diagnosis error: reversing member order reports Extracted content exceeds the inventoried size; different equal-length contents report Extracted content disagrees with inventory; equal-content members report Regular artifact entry is missing from inventory. Exact duplicate raw names with different contents or identical contents also produce an integrity failure. These are supporting variants of the same logical-path collapse, not additional reports. Noncolliding names and a single decomposed name extract successfully with independently checked bytes. A file/directory path collision correctly reports reason path. A separate bad-CRC control reports reason format under both integrity policies.

The unchanged draft recipe also passed on the freshly rebuilt current-main source: nine public CLI calls, both policy settings and successful distinct/single-NFD controls. All 37 existing focused tests passed across four files. These passing checks do not catch this duplicate-path diagnostic.

  • Expected source: src/contracts/artifactToolContracts.ts:30-39,86-89 defines complete extraction without a selector and promises never to overwrite; src/domain/analysisErrorPresentation.ts:418-432 distinguishes conflicting-path guidance from declared-integrity guidance; tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts:82-126 establishes typed path refusal and absent output when a destination cannot preserve colliding names; src/domain/artifactIdentity.ts:11-12 owns intended NFC logical spelling. Closed #3152 explicitly preserves extraction reason path while admitting duplicate inventory occurrences. src/artifacts/ArtifactPaths.test.ts:30-37 explicitly permits repeated same-kind inventory entries, and tests/boundary/filesystem/artifactZipIntegrity.test.ts:265-322 retains duplicate ZIP source identities.
  • Failure source: The executed source-launcher reproduction above returns the integrity error for the valid two-member fixture while independent readers confirm the original contents. src/artifacts/extraction/ArtifactExtraction.ts:217-219,298-328 indexes selected occurrences by logical path and compares the first physical member against the surviving same-path item, then deletes that path; src/artifacts/SafeOutputTree.ts:230-253 detects the resulting size/hash mismatch. These source locations explain the observation without prescribing a repair.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Pinned source: extraction input and result contract.

Pinned source: materialization selection and traversal.

Pinned source: output byte verification.

Typed outcome or recovery expected

Run the unchanged valid collision fixture through the public launcher with both integrity-policy settings. A typed path refusal (details.reason: path) identifying the collision, with no published output and no false declared-integrity or ineffective retry advice, restores this contract. Preserve NFC normalization and inventory multiplicity. The noncolliding and single-decomposed-name controls must still extract the expected bytes, genuine corruption must remain a failure, and internal inventory/write contradictions must remain guarded. Reordered, equal-length, equal-content, and exact-duplicate variants should not masquerade as damaged source bytes. The existing extraction, duplicate-reader and cleanup checks should remain green.

Additional context

This review verified the diagnostic defect only. It observed neither a successful extraction with the wrong occurrence nor source/output data loss. The supported public input offers no individual occurrence selection, so no claim of a broken selected-artifact workflow is made. The record-and-continue failure is included because it disproves the emitted recovery advice, not because the flag must suppress materialization checks. The registry's comment about rejecting exact collisions is narrower in executable behavior: repeated same-kind entries are intentionally accepted by its tests.

Closed issue #3152 explicitly requires duplicate-member inventory to complete while extraction continues to refuse with reason path, excludes caller selection, and distinguishes collisions from declared-hash contradictions. Its closing inventory change permits repeated same-kind entries in the shared registry; the extraction path map remains unchanged. This is a follow-up diagnosis regression after inventory became multiplicity-aware, rather than another request to admit duplicates or choose a winning member. The existing subset/destination feature request #3490 describes a correct case-collision refusal and proposes new selection capabilities. Issue #4124 rejects leading ./ producer paths during inventory, and #3953 misclassifies missing ZIP headers beyond EOF. Their triggers and restoration signals differ from the valid same-logical-path extraction diagnosis here. No alternate extractor was used to materialize this fixture, and no safe external-extraction workaround is established by the CRC-only unzip -t check.

Fresh closed-report comparisons distinguish #1725 (reader physical-entry identity), #3956 (duplicate-path comparison counts), #1318 (false success after an inventoried file disappears), #1470 (case-distinct inventory admission), #1488 (NFC joins), and #1860 (engine JAR admission). They do not report this current false diagnosis after successful duplicate inventory. NOT_ENOUGH_INFO: packaged-release reproduction, MCP transport, other platforms/filesystems, a practical safe workaround and repaired-build restoration were not tested. Historical causality is source-derived; no old runtime was executed.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: A valid but colliding layout receives a false machine-readable integrity diagnosis and ineffective recovery guidance. Refusal itself is permitted by the existing complete-extraction/no-overwrite contract; all failing runs cleaned their output, and no data loss or incorrect successful output was observed. Restoring a truthful typed path refusal requires no consequential extraction behavior change.
  • Workaround status: unverified
  • Workaround basis: No supported option correcting the diagnosis and no safe external extraction preserving both original occurrences was demonstrated. The emitted integrity-policy retry was executed and fails identically. CRC validation by unzip does not prove extraction on a destination that may collapse Unicode spellings; editing or repacking the input is not a demonstrated workaround for the unchanged archive.
Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4271 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior `extract-artifact` refuses a valid ZIP whose regular members share one NFC logical path with `category: integrity_mismatch`, claims that the archive contradicts declared integrity, and recommends a retry that fails identically. Both members have correct CRCs and separately verified inventory content identities. Refusing this layout is legitimate under the existing no-overwrite extraction contract; the defect is the incorrect typed diagnosis and recovery advice. Byte-identical duplicate names reproduce the same problem, so this is one logical-path collision defect rather than separate Unicode and duplicate-entry issues. ### Reproduction Use the source checkout and runtime described under Runtime or environment. From the repository root, prepare the locked dependencies with `npm ci` and build with `npm run build`, using the repository's pinned Node/npm. The following Python 3 recipe exercises that checkout's public launcher in isolated home, configuration, cache and temporary directories. It needs `unzip` for a second reader's CRC check and creates regular stored members with Python's standard valid ZIP headers and timestamps. Both Unicode names carry the UTF-8 flag; the archive is read directly without first materializing its colliding names on the host filesystem. ```python import hashlib import json import os from pathlib import Path import subprocess import tempfile import zipfile import zlib launcher = str(Path("scripts/rea.mjs").resolve()) cases = { "collision": [("caf\u00e9.txt", b"alpha\n"), ("cafe\u0301.txt", b"beta longer\n")], "distinct": [("first.txt", b"alpha\n"), ("second.txt", b"beta longer\n")], "single-nfd": [("cafe\u0301.txt", b"alpha\n")], } with tempfile.TemporaryDirectory(prefix="archive-review-") as directory: root = Path(directory) for case, members in cases.items(): archive = root / (case + ".zip") with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_STORED) as z: for name, data in members: z.writestr(zipfile.ZipInfo(name), data) with zipfile.ZipFile(archive) as z: assert z.testzip() is None assert len(z.infolist()) == len(members) for info, (name, expected) in zip(z.infolist(), members): observed = z.read(info) assert info.filename == name and observed == expected assert zlib.crc32(observed) & 0xffffffff == info.CRC if not name.isascii(): assert info.flag_bits & 0x800 oracle = subprocess.run(["unzip", "-t", str(archive)], capture_output=True) assert oracle.returncode == 0 for operation, options in [ ("inspect-artifact", []), ("extract-artifact", []), ("extract-artifact", ["--integrity-policy", "record-and-continue"]), ]: run = root / (case + "-" + str(len(options)) + "-" + operation) for name in ["home", "config", "cache", "temporary"]: (run / name).mkdir(parents=True) env = dict(PATH=os.environ["PATH"], HOME=str(run / "home"), XDG_CONFIG_HOME=str(run / "config"), XDG_CACHE_HOME=str(run / "cache"), TMPDIR=str(run / "temporary")) result = subprocess.run( ["node", launcher, operation, str(archive), "--json", *options], env=env, capture_output=True, text=True, timeout=60) reply = json.loads(result.stdout) row = dict(case=case, operation=operation, options=options, exit=result.returncode, category=reply.get("category"), details=reply.get("details"), stderr=result.stderr) if operation == "inspect-artifact" and result.returncode == 0: inventory = next(s["evidence"]["normalized_result"] for s in reply["normalized_result"]["substeps"] if s["operation"] == "inventory_artifact") occurrences = [o for o in inventory["occurrences"] if o["logical_path"] != "."] assert len(occurrences) == len(members) assert len({o["occurrence_id"] for o in occurrences}) == len(members) assert all(o["hash_status"] == "verified" for o in occurrences) assert inventory["integrity_contradictions"] == [] row["members"] = [dict(path=o["logical_path"], size=o["declared_size"], hash_status=o["hash_status"]) for o in occurrences] if operation == "extract-artifact": files = [p for p in (run / "temporary").rglob("*") if p.is_file()] row["file_count"] = len(files) if result.returncode != 0: assert list((run / "temporary").iterdir()) == [] else: actual = sorted((p.stat().st_size, hashlib.sha256(p.read_bytes()).hexdigest()) for p in files) expected = sorted((len(data), hashlib.sha256(data).hexdigest()) for _, data in members) assert actual == expected print(json.dumps(row, ensure_ascii=True)) ``` ### Expected behavior A refusal caused by two regular occurrences mapping to the same destination logical path should identify the conflicting path using the existing path-failure contract (`details.reason: path`), publish no output tree, and avoid claiming corrupt archive bytes or suggesting that an integrity-policy retry resolves the collision. Extraction may continue to refuse the entire layout. No disambiguating names, overwrite mode, subset selector, or successful materialization of both occurrences at one pathname is required. The public extraction schema has no entry or artifact-ID selector: it extracts every regular child of the active artifact. Inventory intentionally retains duplicate same-kind paths as distinct occurrences, and NFC normalization is intentional. Those inventory facts do not imply that every layout is materializable. The integrity policy concerns declared-versus-observed contradictions and need not bypass collision refusals or internal inventory/write verification. ### Impact and scope P3, supported: this changes the typed diagnosis and recovery guidance for a bounded refusal. Extraction may legitimately continue refusing this layout. Every failed extraction left its owned destination empty, and no data loss or incorrect successful output was observed. The emitted integrity-policy retry fails identically; a safe external-extraction workaround remains unverified. ### REA version Source build at 461eb161282237ad3135f6d8f8fbbc560285548e, freshly fetched from public main and rebuilt with locked dependencies before the exact reproduction and focused checks. The independently verified original source baseline was 62bf09809ef7bb224028916424f8f07652a20f4f. Both source launchers report 6.5.0; published-package behavior was not reproduced. ### Runtime and platform Node.js v24.18.0 and npm 11.16.0; macOS 27.0.1 arm64. Python 3 standard-library zipfile and unzip -t supply independent archive/content validation. They do not establish safe external extraction. ### Target and artifact context A tiny self-authored stored ZIP with two ordinary regular members whose valid UTF-8 names differ by Unicode composition. Inventory retains both physical occurrences with verified content identities at the intended NFC logical path. No application target is involved. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction, during artifact materialization after successful inventory. ### Relevant output or evidence The collision inspection exits 0 with two distinct occurrence IDs, logical path `café.txt` for both members, sizes 6 and 12, `hash_status: verified`, and an empty `integrity_contradictions` array. Their observed SHA-256 values are `b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060` and `73b349007b6b3acf28463c3e759f8be279163402a49080194de9c7912a5c79a7`, matching independent content calculations. Python reads both physical entries correctly, and `unzip -t` exits 0. Both collision extraction calls exit 1, emit no stderr, and leave their isolated temporary directories empty. Their errors contain the following fields, and the message says that the bytes contradict declared integrity and recommends `--integrity-policy record-and-continue` or a fresh copy. The retry fails with the same fields and message. ```json { "code": "artifact_operation_failed", "category": "integrity_mismatch", "details": { "operation": "extract_artifact", "reason": "integrity", "detail": "Extracted content size disagrees with inventory: café.txt" } } ``` Executed additional controls show the same bounded diagnosis error: reversing member order reports `Extracted content exceeds the inventoried size`; different equal-length contents report `Extracted content disagrees with inventory`; equal-content members report `Regular artifact entry is missing from inventory`. Exact duplicate raw names with different contents or identical contents also produce an integrity failure. These are supporting variants of the same logical-path collapse, not additional reports. Noncolliding names and a single decomposed name extract successfully with independently checked bytes. A file/directory path collision correctly reports reason `path`. A separate bad-CRC control reports reason `format` under both integrity policies. The unchanged draft recipe also passed on the freshly rebuilt current-main source: nine public CLI calls, both policy settings and successful distinct/single-NFD controls. All 37 existing focused tests passed across four files. These passing checks do not catch this duplicate-path diagnostic. - Expected source: `src/contracts/artifactToolContracts.ts:30-39,86-89` defines complete extraction without a selector and promises never to overwrite; `src/domain/analysisErrorPresentation.ts:418-432` distinguishes conflicting-path guidance from declared-integrity guidance; `tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts:82-126` establishes typed path refusal and absent output when a destination cannot preserve colliding names; `src/domain/artifactIdentity.ts:11-12` owns intended NFC logical spelling. Closed #3152 explicitly preserves extraction reason `path` while admitting duplicate inventory occurrences. `src/artifacts/ArtifactPaths.test.ts:30-37` explicitly permits repeated same-kind inventory entries, and `tests/boundary/filesystem/artifactZipIntegrity.test.ts:265-322` retains duplicate ZIP source identities. - Failure source: The executed source-launcher reproduction above returns the integrity error for the valid two-member fixture while independent readers confirm the original contents. `src/artifacts/extraction/ArtifactExtraction.ts:217-219,298-328` indexes selected occurrences by logical path and compares the first physical member against the surviving same-path item, then deletes that path; `src/artifacts/SafeOutputTree.ts:230-253` detects the resulting size/hash mismatch. These source locations explain the observation without prescribing a repair. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Pinned source: [extraction input and result contract](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/src/contracts/artifactToolContracts.ts#L30-L89). Pinned source: [materialization selection and traversal](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/src/artifacts/extraction/ArtifactExtraction.ts#L200-L328). Pinned source: [output byte verification](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/src/artifacts/SafeOutputTree.ts#L230-L253). ### Typed outcome or recovery expected Run the unchanged valid collision fixture through the public launcher with both integrity-policy settings. A typed path refusal (`details.reason: path`) identifying the collision, with no published output and no false declared-integrity or ineffective retry advice, restores this contract. Preserve NFC normalization and inventory multiplicity. The noncolliding and single-decomposed-name controls must still extract the expected bytes, genuine corruption must remain a failure, and internal inventory/write contradictions must remain guarded. Reordered, equal-length, equal-content, and exact-duplicate variants should not masquerade as damaged source bytes. The existing extraction, duplicate-reader and cleanup checks should remain green. ### Additional context This review verified the diagnostic defect only. It observed neither a successful extraction with the wrong occurrence nor source/output data loss. The supported public input offers no individual occurrence selection, so no claim of a broken selected-artifact workflow is made. The `record-and-continue` failure is included because it disproves the emitted recovery advice, not because the flag must suppress materialization checks. The registry's comment about rejecting exact collisions is narrower in executable behavior: repeated same-kind entries are intentionally accepted by its tests. Closed issue [#3152](https://github.com/morluto/rea/issues/3152) explicitly requires duplicate-member inventory to complete while extraction continues to refuse with reason `path`, excludes caller selection, and distinguishes collisions from declared-hash contradictions. Its closing inventory change permits repeated same-kind entries in the shared registry; the extraction path map remains unchanged. This is a follow-up diagnosis regression after inventory became multiplicity-aware, rather than another request to admit duplicates or choose a winning member. The existing subset/destination feature request #3490 describes a correct case-collision refusal and proposes new selection capabilities. Issue #4124 rejects leading `./` producer paths during inventory, and #3953 misclassifies missing ZIP headers beyond EOF. Their triggers and restoration signals differ from the valid same-logical-path extraction diagnosis here. No alternate extractor was used to materialize this fixture, and no safe external-extraction workaround is established by the CRC-only `unzip -t` check. Fresh closed-report comparisons distinguish #1725 (reader physical-entry identity), #3956 (duplicate-path comparison counts), #1318 (false success after an inventoried file disappears), #1470 (case-distinct inventory admission), #1488 (NFC joins), and #1860 (engine JAR admission). They do not report this current false diagnosis after successful duplicate inventory. NOT_ENOUGH_INFO: packaged-release reproduction, MCP transport, other platforms/filesystems, a practical safe workaround and repaired-build restoration were not tested. Historical causality is source-derived; no old runtime was executed. ### Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: A valid but colliding layout receives a false machine-readable integrity diagnosis and ineffective recovery guidance. Refusal itself is permitted by the existing complete-extraction/no-overwrite contract; all failing runs cleaned their output, and no data loss or incorrect successful output was observed. Restoring a truthful typed path refusal requires no consequential extraction behavior change. - Workaround status: unverified - Workaround basis: No supported option correcting the diagnosis and no safe external extraction preserving both original occurrences was demonstrated. The emitted integrity-policy retry was executed and fails identically. CRC validation by unzip does not prove extraction on a destination that may collapse Unicode spellings; editing or repacking the input is not a demonstrated workaround for the unchanged archive.
Author
Owner

@morluto commented on GitHub (Oct 16, 2026):

Fixed on main in 862a0b263. Artifact extraction preflights colliding materialization paths, preserving duplicate inventory occurrences and returning a path refusal before acquiring output resources.

Collision, inventory and cleanup controls cover the reported false integrity diagnosis and ineffective retry.

Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.

<!-- gh-comment-id:6206032530 --> @morluto commented on GitHub (Oct 16, 2026): Fixed on `main` in [862a0b263](https://github.com/morluto/rea/commit/862a0b2631785492f8dc20c8acd396646c545fd1). Artifact extraction preflights colliding materialization paths, preserving duplicate inventory occurrences and returning a path refusal before acquiring output resources. Collision, inventory and cleanup controls cover the reported false integrity diagnosis and ineffective retry. Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.
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#1687
No description provided.