Skip to content

[Sad Catapella B5][Scientific integrity] Reused TIFF-converted Zarr copies are not validated against their stored pixels #87

Closed

Description

@guijacquemet

Baseline / classification

Adversarial audit Batch 5 at d7a538e56d41ac1093ba79adcad397c21b3dfd32. Confirmed missing verification in copy-reuse predicate; a missing Zarr chunk can be interpreted as fill value rather than a read error. End-to-end fixture not run.

Evidence

  • run_experiment() unchanged-copy predicate checks only whether the copy folder exists, conversion settings match, axis units match and channel labels match. It never checks level-0 shape, dtype, chunks, chunk presence/content or a copy integrity digest.
  • _load_previous() validates the CSV hash and source TIFF signatures, not the converted Zarr chunks.
  • OmeZarrWriter._open() creates Zarr arrays with a fill value of zero (see _create_array()). A deleted, previously nonzero chunk can therefore read as zeros from a sparse Zarr array rather than failing loudly.
  • Tests check that deleting the entire copy directory triggers re-conversion, not an interior 0/ chunk.

Reproducer to implement

Convert a TIFF with a nonzero level-0 chunk. Save table/manifest. Delete or overwrite one level-0 Zarr chunk while leaving .zattrs, .zarray and the source TIFF unchanged. Rerun with convert_tiffs=True. The current predicate can treat the source as unchanged and reuse its prior histogram; the reused copy's pixel values no longer match the histogram. Validate also truncation, dtype/shape edits and changes to non-level-0 pyramids.

Impact

A manifest and histogram table can claim to describe the exported data while that persisted exported data has been changed or lost. Downstream consumers of converted copies can silently analyze zero-filled/mismatching pixels.

Fix

Record and validate copy integrity (versioned per-chunk hashes or a trustworthy immutable manifest/index, including metadata), or rebuild/verify copies that cannot be trusted; the chosen trade-off should be documented and benchmarked for large stores. Ensure successful reuse implies level-0 data exactly matches recorded source histograms. Do not silently interpret missing expected chunk payload as a real zero image. Test corrupt/missing chunks, changed metadata, intact unchanged copies and rollback. No Numba benefit for I/O verification; hashing codecs may already be accelerated.

Activity

  1. guijacquemet commented on Nov 2, 2026

    @guijacquemet
    ContributorAuthor

    Obsolete in v0.4.0: convert_tiffs, zarr_export and OME-Zarr copy reuse were removed in PP-2 (#66, commit 3c2802d; plan question 46). Named-axis copies of images with axis errors are now the OME-TIFF convert step (PP-11, #124, merged as 8db1f4a). Closing as not planned (owner-approved triage, 2026-11-02).

    PP-11 reuses a copy only after checking its fingerprint against the conversion record, and RunInputs refuses a run whose conversion record changed or disappeared.


    Generated by Claude Code

  2. guijacquemet commented on Nov 2, 2026

    @guijacquemet
    ContributorAuthor

    Reopening: an independent review of commit 8db1f4a found the closure premature. Copy reuse checks the copy's fingerprint, but a fingerprint is only size + mtime (fingerprint.py), and the conversion record's SHA-256 covers the JSON record, not the image copies. A same-size change with the mtime preserved is not caught. Owner decision: record a SHA-256 of each copy when it is written; reuse, review_conversion and RunInputs on a copy tree verify it. Tracked in PR PP-11c.


    Generated by Claude Code

  3. guijacquemet commented on Nov 2, 2026

    @guijacquemet
    ContributorAuthor

    Fixed by PR #126, merged as 6ac81ad (fix commits 4312b5a, 792a6c1 and 418bbbd). I checked each point in the code before closing:

    • Every copy's SHA-256 (copy_sha256, record version 2) is computed from the copy on disk after it is written.
    • The SHA-256 is checked at reuse, in review_conversion (check/run) and in RunInputs.open, which checks every recorded image. Excluded images are never read.
    • A same-size change with the modification time put back is caught, and so is an OME-Zarr chunk changed in place.
    • An OME-Zarr copy that holds a folder link counts as changed: it is never hashed in part.
    • A version 1 record is refused everywhere it is read.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions