Repository navigation
Conversion record version 4: refuse version 3, require source_relative_path where written - #291
Conversation
…e_path where written PR #287 added the entry key source_relative_path (an OME-Zarr copy's source path) under record version 3. v0.5.1 reads version 3 and ignores the key, so it looked for every source at the copy's path and warned conversion_source_changed. Owner decision of 12 November 2026: version 4 before v0.5.2. RECORD_VERSION is "4", the only version written and read. A version 3 record is refused like 1 and 2 (the PP-06 pattern): convert, review_conversion, RunInputs and the notebook's earlier projections all go through read_record, which names the version and the fix (convert the originals again into a new folder). source_relative_path stays absent from OME-TIFF and byte-for-byte entries, and is required, as text other than the copy's path, on an entry converted or projected into an OME-Zarr copy; any other entry with the key is refused. Contract section 17 and 9.1, user guide, AGENTS.md and CHANGELOG Unreleased updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
A projected copy's record entry names its source twice: the entry's own path (or, for an OME-Zarr copy, its source_relative_path) and its projection's source_relative_path. The writer makes them equal, but the reader accepted two different paths, so a run would check one source (review_conversion uses the entry's) and record the other as projected in its manifest (RunProjection uses the projection's). CopyEntry now refuses the difference; the record is then conversion_record_unreadable, naming the fix. Regression test: test_a_projected_entry_names_one_source (OME-TIFF and OME-Zarr), which fails on 06df6e6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
earlier_projections skipped a projected copy tree whose record cannot be used (a version 3 tree of v0.5.1 among them) without a word, so the user saw no Continue button and did not know why; writing the same projection again into that folder is then refused. The Axes step now shows one warning line per such folder, naming it, the reason and the fix (another output folder). earlier_projections is unchanged; unusable_projections is new. Regression test: test_copies_written_earlier_that_cannot_be_used_are_said_not_hidden, which fails on 06df6e6. User guide and CHANGELOG (both commits of this review) updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Independent review of 06df6e6, with two fixes (head now 51c1c65)Verdict: approve, with the two fixes below pushed as their own commits. Verified myself (fresh clone, Python 3.13.18,
Fix 1: 4862550, a projected entry names one source. A projected entry names its source twice: its Fix 2: 51c1c65, the notebook said nothing about a refused tree. Generated by Claude Code |
…e branch The one conflict is CHANGELOG.md: #291's Unreleased block becomes the subsection "Conversion record version 4 (#291)" of v0.5.2, before the notebook redesign; the next commit moves it into the release notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Commit A' of the release PR, after main (ac2dc27, #291) was merged in. - CHANGELOG v0.5.2: Breaking gains conversion record version 4 (version 3, every v0.5.1 folder of copies, refused with the fix: convert the originals again into a new folder); the summary, the OME-Zarr copy's line (no longer "record version 3 kept"), Migration (users and the bricks' table) follow; #291's notes stay as their own subsection. - README upgrading paragraph and AGENTS.md status: the conversion record. - docs/colab-release-check.md: projected copies written by v0.5.1 are not offered and one line says why (item 17); a v0.5.1 folder of copies is refused naming the fix (item 21). - docs/conformance.md: the v0.5.2 record measured on this tree (check.py 5476 passed, 4 environment skips; Spec modules 1799 passed, 0 skipped, 25 modules); the record version question is the owner's decision now. - RELEASE_REF back to v0.5.1's d023ff9 (and its test), as in the first commit A; the next commit points it at this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
RELEASE_REF = 6a151b8 (RELEASE_TAG v0.5.2), with its PreprocessingNotebook() call recorded in RELEASE_CALLS and the test that asserts the ref: the same two-commit pattern as v0.5.0 (3b616a0, c74d717), v0.5.1 (d023ff9, a4154ad) and the first v0.5.2 pair (1244527, c4173dc), which #291 replaced. A commit cannot hold its own SHA, so the notebook installs the preparation commit, whose package code is the release's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…s) into the v0.5.2 release branch Two conflicts. CHANGELOG.md: #292's Unreleased block becomes the subsection "Axes confirmation and the image type on Channels (#292)" of v0.5.2, before #291's; the next commit moves it into the release notes. docs/colab-release-check.md: the release branch's walk-through is kept as it was; the next commit adds #292's items to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Owner decision, 12 November 2026: "Version 4 before v0.5.2".
Why
#287 added the entry key
source_relative_path(an OME-Zarr copy's source path) but kept the record at version 3. v0.5.1 reads version 3 and ignores the key, so it looks for each source at the copy's path and wrongly warnsconversion_source_changed. Spec ADR 0002 AR10 means no backward compatibility, so the version goes up and version 3 is refused.Change
RECORD_VERSION = "4". This is the only version written or read.Version 3 is refused the same way PP-06 (7068ec7) refused 1 and 2. The refusal is one message that names the version and the fix: "it is a record of version 3, and this version of preprocessing reads version 4 only (older records are not converted); convert the originals again into a new folder".
Readers. Every reader goes through
conversion_record.read_record→ConversionRecord.from_dict, so they all now refuse version 3. This was checked by greppingRECORD_FILENAME,RECORD_VERSION,read_recordandrecord_version. Each reader is covered by a test:convert(reuse of an earlier copy tree,conversion_record_unreadable)review_conversion(check/run,conversion_record_unreadable)RunInputs.open, throughconversion_check.conversion_problem, which is also whatRunInputs.projectionrelies on (RunInputsError)earlier_projections, which skips a refused tree and does not offer itsource_relative_pathstays optional at the record level, but is required exactly where it is written. No v4 writer writes it on every entry: onlyome_zarr_copy.as_zarr_copysets it, for entriesconvertedorprojectedinto an OME-Zarr copy. OME-TIFF converted entries, projected entries and byte-for-byte entries (copied, including OME-Zarr sources and excluded images) keep their source's path and never carry it. Making it required on every entry would change every non-Zarr record for no gain. Instead the reader now:relative_path, on aconverted/projectedentry whoserelative_pathends in.ome.zarr;So a v4 record can no longer be read with the key missing. Before this PR that produced the same false warning.
Contract §9.1/§17, user guide, AGENTS.md and the CHANGELOG
Unreleasedsection are updated. Tests that asserted"3"now assert"4". Docstrings inconversion_record.pywere shortened to keep the file within the 600-line code rule.Tests (all fail on main 682e14e)
tests/test_conversion_record_v4.py:from_dict/to_dictand the same bytes fromwrite_record;checkgivesconversion_record_unreadableand noconversion_source_changed, andconvertinto the folder is refused;source_relative_path, or withNone,"", its own path or a number, is refused rather than warned as missing;_ZARR_COPY_SUFFIXmatchesZARR_COPY_SUFFIX.tests/test_convert_integrity.py::test_runinputs_refuses_a_version_3_record_naming_the_fix.On main, 12 of the 12 new tests fail. On this branch they all pass.
Output evidence
Main 682e14e and this branch ran the same script under a frozen clock, on the same source files (mtimes kept) and at the same absolute paths. As a noise baseline, main was also compared against itself.
uint16tree including an OME-Zarr image, andfloat32tree):run_uint166/6 files andrun_float326/6 files are byte-identical to main.record_version"3"→"4"; the other differences are per-run copymtime_ns/inventory_sha256/copy_sha256, which also differ between two runs of main.source_relative_pathis unchanged: it is present on 3/3 and 2/2 OME-Zarr entries and on 0 OME-TIFF entries, as on main.conversion_record_sha256,projection.conversion_record.sha256, the report'sconversion.sha256) and themanifest_sha256that follows from it.conversion_source_changed("3 / 2 source images changed or went missing"). This is the bug.conversion_record_unreadable("record of version 4"). The record is refused, not misread.conversion_record_unreadable("record of version 3").Gate
python tools/check.pyon 06df6e6 (Python 3.13,.[dev,notebook,zarr]): ruff check and format pass; 5473 passed, 4 skipped; code rules pass; exit 0.The 4 skips come from the environment:
test_cli_log.py:166,test_folder_rules.py:199andtest_folder_rules.py:230: "this file system tells names apart by case";test_discovery.py:665: "running with permissions that ignore file modes" (root).Coordination
This PR is kept minimal so that release PR #290 can be rebuilt after it merges. #290's branch was not touched, and no tag was made.
🤖 Generated with Claude Code
https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc