Repository navigation
PP-11c: conversion integrity (copy SHA-256, metadata epochs, exact CLI sizes) - #126
Merged
Merged
Conversation
…only) conversion.py was at 587 of 600 lines. Move plan_conversion and PlannedCopy to conversion_plan.py, the comparison with an earlier copy tree to conversion_reuse.py and the .partial writing to conversion_write.py. No behaviour changes; the private helpers used across modules lose their underscore, and the tests patch write_named_copy and os.replace where they are now used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
… sizes) - #96: plan_conversion fingerprints each image before and after its header is read (preflight(bind_headers=True)) and refuses an image whose two fingerprints differ (source_changed_during_check, nothing written); that fingerprint is the source's in the record and is checked before and after the copy is written. The copy's pixel type comes from the same header (write_named_copy no longer reopens it) and a plane of another type is refused, never cast. - #87: the conversion record (version 2) gives each copy's SHA-256 (content_hash, one pass after writing); reuse, review_conversion and RunInputs.open (conversion_check, once at open for the recorded images) verify it. A version 1 record is refused with a message to convert again. - #88: CopyEntry records the copy's channel names and significant bits and same_plan() compares them. - #92: same_plan() compares the unknown sizes' reasons, not only their roles. - #80: parse_size is exact (Fraction): a size that is not a whole number of bytes above 0 ("1.5", "0.5B") is refused while the command line is read. Docs: contract §9.1 and §17, plan question 59 (16)-(18) and §10, user guide, AGENTS.md module map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…ithout hashing A copy whose SHA-256 cannot be computed (permission denied, say) is not known to be unchanged, so reuse, review_conversion and RunInputs treat it as changed instead of raising. The record-version check no longer hashes the recorded value (a list there raised TypeError instead of ConversionRecordError). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
marked this pull request as ready for review
November 2, 2026 16:16
…bricks will refuse - content_hash: os.walk does not enter a link to a folder, so an OME-Zarr copy holding one was hashed without the files behind it (a change there kept the SHA-256). Such a folder now raises OSError, so the copy counts as changed (the conversion writes no links). - copy_changed_after_conversion: the run goes on, but RunInputs now refuses a run on a changed or added copy, so the warning no longer suggests analysing it; it says the bricks will refuse it and to convert again (contract §17, user guide). Regression tests fail on 792a6c1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
pushed a commit
that referenced
this pull request
Nov 2, 2026
docs/plan.md: main's items (16) to (18) of question 59 come first, then question 60 of this PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Contributor
Author
|
Independent review: merged as 6ac81ad, a merge commit with head 418bbbd. The branch was auto-deleted and has been restored on 418bbbd. What I checked in the code, with my own scripts as well as the tests
Findings, fixed in 418bbbd (both regression tests fail on 792a6c1)
Gate (own worktree, Python 3.12, §9 figures (files touched in src)
Issues: only #80 closed automatically, because GitHub links just the first number after "Fixes". I am closing #87, #88, #92 and #96 by hand, naming this merge. Generated by Claude Code |
This was referenced Nov 2, 2026
guijacquemet
pushed a commit
that referenced
this pull request
Nov 2, 2026
Conflicts resolved hunk by hunk: - conversion_plan.py: PP-11c's plan_conversion (epoch binding, dtype, _copied) kept as merged; PP-11b's copy_folder_issues, ConversionPreview and preview_conversion added after it, with their imports. - conversion.py: PP-11c's split kept; PP-11b's ConversionResult.unknown_sizes and the call to copy_folder_issues (its _folder_issues removed) applied. - pyproject.toml: PP-11c's layers plus notebook_convert. - AGENTS.md, contract §17, plan §10 and question 59: both PRs' text (PP-11c marked implemented in #126; PP-11b's notebook row replaces "Not yet"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
pushed a commit
that referenced
this pull request
Nov 2, 2026
…rrangement test_an_ome_zarr_copy_changed_in_place_is_not_reused (from #126) wrote a 'zyx' store into an experiment confirmed ZYX -> ZXY: the same letters under contradicted roles, which issue #72 refuses (ome_zarr_axes_mismatch). The store is now 'tzyx' with one time point, matched by its declared roles, so the test checks the reuse of a changed chunk as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
This was referenced Nov 2, 2026
guijacquemet
pushed a commit
that referenced
this pull request
Nov 2, 2026
docs/plan.md: main's PP-11b and PP-11c items first, then this PR's links item; this PR's questions 60 and 61 become 61 and 62, after main's question 60 (OME-Zarr axes and calibration, #129). docs/contract.md: both additions to the error row kept (main's OME-Zarr codes, this PR's link codes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the five closures an independent review of 8db1f4a found premature (issues reopened by the owner). Binding spec: design v0.4 §1, §5, §6, §9; contract §17; plan question 59.
Fixes #80, #87, #88, #92, #96
(#96: the correctness part, mixed metadata epochs, is fixed. See "#96: header reads that remain" below for the performance part. If the owner wants that part kept open, change
Fixes #96toRefs #96before merging.)Summary (by module)
Move only, first commit (f8cb39a):
conversion.py(587/600 lines) split intoconversion_plan(plan_conversion, PlannedCopy; orchestration),conversion_reuse(compare with an earlier copy tree; io) andconversion_write(.partialwriting; io). Helpers used across modules lose their leading underscore. Tests now patchwrite_named_copy/os.replaceinconversion_write.[Sad Catapella B6][Performance] TIFF-to-Zarr conversion reopens headers to read physical calibration already inspected #96, one metadata epoch:
preflight(bind_headers=True)takes each image's fingerprint before and after reading its header (Preflight.header_fingerprints).plan_conversionrefuses any image whose two fingerprints differ or that went missing (source_changed_during_check, an error naming the files, nothing written). That bound fingerprint is recorded as the source's and checked before and after the copy is written, as before. The copy's pixel type now comes from the same header:write_named_copy(..., dtype=)no longer callsplane_layoutto reopen the header. A plane whose type differs from the header's is refused (CopyWriteError, soconversion_failed). It is never cast.[Sad Catapella B5][Scientific integrity] Reused TIFF-converted Zarr copies are not validated against their stored pixels #87, the copies' SHA-256 (owner decision): new
content_hash.content_sha256. A file is hashed as is. An OME-Zarr folder is hashed from its files' relative paths and SHA-256s in path order, withlabelsleft out (as for fingerprints). Each copy is hashed in one pass after it is written and moved into place, and the hash is recorded ascopy_sha256. Three places check it:conversion_reuse): needs the plan, the fingerprint and the SHA-256 to match;review_conversion(check/runon a copy tree):copy_changed_after_conversionalso covers a same-size change with the time put back;RunInputs.open(newconversion_check): checks the record SHA-256, the record version, and the fingerprint and SHA-256 of each copy the run recorded.A copy that cannot be read counts as changed. Sources keep size+mtime (design §1).
[Sad Catapella B5][Correctness] Channel-label copy freshness check accepts missing or truncated OMERO metadata #88:
CopyEntry.channel_namesandsignificant_bitshold what the copy contains: as written for converted images, from the header for copied ones.same_plan()compares both.[Sad Catapella B6][Scientific calibration] Reused OME-Zarr copies are not checked for numeric pixel-scale correctness #92:
same_plan()comparesunknown_sizesin full (roles and reasons). The copy hash also covers the calibration written into the copy.[Sad Catapella B3][Validation] ConversionSettings accepts noninteger numeric inputs that fail downstream #80:
cli_run.parse_sizeis exact (Fraction, ASCII digits only). The result must be a whole number of bytes above 0:1.5GiBgives 1610612736, while1.5,0.5B,0.5and1.0000000001KiBare refused. Previously9007199254740993lost precision through float.--memory-limitis only onrunandconvert(checkandmasksdon't take it). argparse callsparse_sizewhile reading the command line, so a bad size exits with 2 before any preflight or I/O. A test proves this for both commands.Decisions
record_versiongoes from "1" to "2". The new per-entry fields arecopy_sha256,channel_namesandsignificant_bits, and an entry withoutcopy/copy_sha256is invalid. A version 1 record is refused everywhere it is read (convert,review_conversion→conversion_record_unreadable,RunInputs.open), and the message says to convert the originals again into a new folder. It is never silently accepted. This is documented in contract §17 and the user guide (copies made by v0.4.0rc3 or earlier).RunInputscosts: it hashes once, atopen, the copies of every image inmanifest.files, which is one full read of those copies. Excluded images' copies are not read, sinceRunInputsnever reads them. I rejected hashing on every read because it would read a copy whole once per plane. Reads afteropenkeep the per-read fingerprint check. Measured on this machine: 256 MiB hashed in 0.38 s with a warm cache, so a cold read is bounded by disk speed.check/runon a copy tree pay the same one extra read (inreview_conversion). This is documented in contract §9.1 and §17 and the user guide.source_changed_during_check. I did not reuseconversion_source_changed, which is the run's warning about sources changed after the conversion.#96: header reads that remain
write_named_copy'splane_layout(source, …), which reopened the header only to get the pixel type.preflight(once per image). The calibration and layout come from it, throughcopy_layout, which is pure and does not read the file.check_planesat plan time, once per image to convert. This is the run's own memory and block check inplanes.py. Its block layout needs the TIFF page and strip structure, whichTiffMetadatadoes not hold, andplanes.pyis far over its size baseline. If the file changed, the write-time fingerprint check refuses it.iter_planes) opens the file and parses its header to read the pixels. This is unavoidable, and it happens between the two checks against the bound fingerprint.convertruns one preflight of its own (for--axeschecking) beforeconvert_experiment's bound preflight.§9 figures (lines, origin/main → this branch)
conversion 587 → 289; conversion_plan new 185; conversion_reuse new 76; conversion_write new 141; content_hash new 87; conversion_check new 67; conversion_record 386 → 463; conversion_issues 136 → 148; run_inputs 575 → 575; ome_tiff_copy 152 → 158; cli_run 452 → 464; run/preflight 370 → 399.
tools/code_rules_baseline.jsonis unchanged and the# noqacount is unchanged (nonoqalines in the diff). The new modules are declared inpyproject.tomllayers.Notes for review
tests/test_convert_integrity.pyfail when run against main's source (checked withPYTHONPATH=<main>/src). The 5 that pass on main are the valid-size parse cases that main also handled and two refusals that main already refused. Failures on main, by issue:dtypekeyword is missing.record_versionis "1"; a same-size, mtime-restored copy change is reused; it is not warned about by the run;RunInputs.opendoes not raise; an OME-Zarr chunk changed in place is reused; there is no version 1 refusal.1.5,0.5B,0.5,1.0000000001KiBand fullwidth1are accepted; 2**53+1 is rounded; the commands run preflight with--memory-limit 1.5.metadata.inspect_tiffto rewrite the source right after its header is read.claude/pp-11b-convert-card) has not merged yet. The publicconvert_experimentsignature is unchanged.Independent review (418bbbd)
os.walkdoes not enter a link to a folder, so the files behind one were left out of the copy's SHA-256 (a change there kept the hash).content_sha256now raisesOSErrorfor such a folder, and the copy counts as changed. The conversion itself writes no links.copy_changed_after_conversion: the run continues with this warning, butRunInputsnow refuses a run on a changed or added copy. The warning no longer suggests analysing such a copy: it says the bricks will refuse it and to convert again (contract §17, user guide).test_discovery.py:665, root).Tests
ruff check .,ruff format --check .,python tools/check.py --rules: pass.pytestwith.[dev,notebook,zarr]: 1778 passed, 1 skipped (at 792a6c1; 1780 after the review) (the skip:tests/test_discovery.py:665, running as root ignores file modes, expected), 1 warning (the existingreading array from closed fileintest_planes).https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Generated by Claude Code