Skip to content

Pre-release follow-ups to #254 and #255 - #257

Merged
guijacquemet merged 3 commits into
mainfrom
claude/prerelease-followups
Nov 9, 2026
Merged

guijacquemet merged 3 commits into
mainfrom
claude/prerelease-followups

Conversation

@guijacquemet

@guijacquemet guijacquemet commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Pre-release follow-ups from the reviews of #254 and #255 and the owner decisions of 2026-11-10. Based on main 4e8e982. I checked each item against main first and reproduced it with a regression test that fails on 4e8e982 and passes here.

# Item Verdict on main Fix
1 A layout change must re-ask the replicate's "same way?" (revised after the review of f8b57e3, see below) STILL HOLDS for a change of the declared channel list (read from the code: main's reopened compared the member folders only, at the reopen, and no answer re-checked it, so a re-ordered unit kept its saved date). Experiment-v1 §10, N35 reopening: "The confirmation of a repeat is reopened (draft confirmed = false) only when channels, layout or grouping change: a new or removed unit or member, a unit's ordered channel list, or the pairing of folders into repeats." The candidate's layout (units, their ordered channel IDs and image type) is compared with the published one: at the reopen (published_answers.reopened) and whenever an answer changes it (reasked, via the described run's _answer). Only that repeat's confirmed_on is dropped; ID and shown-as name are kept; every other repeat keeps its date byte for byte (§4.4a). Headers that no longer fit are not a reopening: they are the run's refusal.
2 Save stays ALLOWED when the run's checks refuse (owner decision) STILL HOLDS (no line beside the button). Save was already allowed. save_row = HBox([save_button, save_note]). When the description can be saved but run_refusals is non-empty, run_stop_html shows "You can save, but the run (step 12) will stop. Refused: R_HEADER_CONTRADICTION (2x). Each reason is listed above, with the folder it names." Nothing is shown while the checks wait for the axis roles.
3 Gate the step-by-step "preview looks right" tick on _preview_drawn STILL HOLDS. show_preview() set preview_checked.disabled = False even when no plane was drawn, and a preview that failed later left the tick enabled and ticked. _render_preview now always ends in _gate_preview_tick(). The tick is enabled only when _preview_of(self.mapping) holds: a plane drawn with exactly the current roles, as the one-click _quick_preview_shown (now _preview_of(offer)) requires. A failed or stale preview takes an earlier tick back. Confirmed roles stay locked.
4 CLI reopen comparison used counts only STILL HOLDS. After a header-only rewrite, describe printed "No change." while the notebook reopened rep1. cli_describe._published reads the headers of the images to analyse (inspect_images, no pixel read) and calls observe_folders(discovery, excluded, headers), as published_state does. It also reports headers changed: … and gives channels_changed / renamed_folder reasons in words. The docstring and the user guide are updated.
5 Fingerprint before AND after the read for channel_strip and the native crop STILL HOLDS. A file rewritten during the read was shown. The Images found picture (header_preview) had the same before-only check, so it is fixed here too. check_unchanged runs after the read, with the fingerprint taken from preview.checked_fingerprint (in channel_strip against the fingerprint taken before the read).
6 Per-unit channel counts from EVERY image STILL HOLDS at the gate. unit_layouts gave a unit with a 3-channel and a 2-channel image channels == 3, and _channel_counts passed that on. In the notebook the experiment-wide axis check (different_channel_counts) blocks the axes today, but the gate no longer relies on that. UnitLayout.counts_agree is new, and so is channel_units.unit_channel_counts. A unit whose images disagree in count, or one of whose images has no known count, is left out, so the to-do shows unit_channel_count_unknown (its wording now says "every image … has the same number of channels"). Images that agree in count but not in names keep their count.
7 "The run (step 12) can start" before the run's checks ran STILL HOLDS. An unchanged reopen with the axes not yet confirmed said "Saved …. The run (step 12) can start." _saved_status says "can start" only when _run_allowed() holds (the checks ran and passed). Before the axis roles are confirmed it says "Saved …. Confirm the axis roles (step 2) first…".

Review of f8b57e3: the blocker and its fix (owner decision: follow §10 literally).

The vendored tests/spec_fixtures/docs/experiment-v1.md says:

§10: "N35 reopening. The confirmation of a repeat is reopened (draft confirmed = false) only when channels, layout or grouping change: a new or removed unit or member, a unit's ordered channel list, or the pairing of folders into repeats. New FOVs (or removed ones) inside an existing unit whose confirmed layout still holds do not reopen it; they are reported and recorded." And: "confirmed_on is kept byte-identical for every repeat whose confirmation is not reopened (§4.4a); the draft copies it from the published file, never from the clock."

§4.4a: "confirmed_on is a user fact. It is the calendar date of the user's confirmation action. A writer MUST NOT default, infer or refresh it (not from the write time, the read time, file times, another repeat or a template); a reader supplies no default (absent is E_MISSING_FIELD). A reconciliation that does not reopen the N35 confirmation (§10) MUST keep confirmed_on of every unchanged repeat byte-identical; a candidate that changes it without a reopening and a new user confirmation is refused (R_CONFIRMED_ON_CHANGED)."

§4.4, technical_repeats.<T>.channels: "array of channel IDs ... ordered: entry i is source channel Ci".

"A unit's ordered channel list" is the channels array of channel IDs in the description. So:

  1. Reopening is decided by the candidate against the published file. A header rename (DAPI→Hoechst) fixed by tagging channel 1 DNA again (Hoechst joins DNA.header_names) leaves the list unchanged: the date is kept, Save is enabled and publication accepts it; the run passes. On f8b57e3 this was the dead end (R_CONFIRMED_ON_CHANGED at repeats.rep1).
  2. Until it is fixed, the header contradiction is shown at once (R_HEADER_CONTRADICTION in Check and save as soon as the axis roles are confirmed; before them, the reviewer's "blocked by both" line with the header change). Nothing is hidden, nothing is ticked for the user.
  3. Changing the unit's channel order reopens that repeat: "same way?" is asked again (a layout with an untagged channel on the way declares no list and reopens nothing). description_transition._dates already agrees with §10: it refuses a new date only when repeat_layout (which holds each unit's ordered channels and image type) is unchanged, so the new date of the reopened repeat is accepted and the other repeat keeps its date. But re-ordering also moves channel positions, which §4.6 (c) makes R_CONFIRMED_ID_CHANGED for each channel moved ("some member folder, position or alias it held is removed or reassigned"), accepted only with an explicit recorded re-confirmation. The notebook makes such records only for removals and renames, so this candidate is refused, and Save is now disabled with that reason (on f8b57e3 it was enabled, then refused at publication, reproduced). The test shows that publication, given the §4.6 records, accepts the new date for that repeat only. A notebook flow to record a channel re-confirmation is a separate owner decision.
  4. The pre-save check runs publication's transition checks (run.description_file.transition_refusals, the same function _compare now uses, with the same re-confirmations as _publish). Save's enabled state matches what publication accepts. A refused date names its way out: answering "same way?" again for a repeat whose layout is as published takes the published date back (§10: "copied from the published file, never from the clock"), only on the user's click. Such a replicate is not folded away.

Behaviour change to note: on a reopen after a header-only change, the replicate keeps its date and its toggle stays ticked; R_HEADER_CONTRADICTION is shown at once. The command line's describe still reports the folder reconciliation's channels_changed as "must be confirmed again" and --publish refuses (fail-closed; its wording could follow in a later PR).

API: published_answers.reopened(draft, answers, published) (the reconciliation argument of a75915f is gone again); published_answers.layouts, published_layouts, reasked, kept_date; run.description_file.transition_refusals; description_candidate.transition_issue; check_candidate(..., published=None, confirmations=()). UnitLayout.counts_agree and unit_channel_counts are new.

Science rules: no answer that was refused before is accepted now. Nothing is confirmed automatically (a tick is only ever taken back; a date is set only by the user's click, and for a repeat that is not reopened that click keeps the published date). No refusal is hidden: Save is now disabled, with the reason, for everything publication refuses, and the run's refusals are shown at once. Nothing is guessed (a count that cannot be known stays unknown).

Release branch: the P21/P29 with_description wiring is not touched. A trial merge of origin/claude/release-0.5.0 (76e09c3) on top of this branch (re-checked on 789e2bb) conflicts in CHANGELOG.md, in the cli_describe.py module docstring (both rewrite the "reads the folder structure" sentence, an easy resolution) and in notebook_folder.py, which this PR does not touch (that conflict is between main and the release branch). notebook_check_save.py auto-merges.

Verification (head 789e2bb)

  • python tools/check.py: Ruff, format and code rules clean; 5074 passed, 4 skipped (all 4 are file-system or permission skips: case-insensitive names x3, root ignores file modes x1).
  • Spec modules plus test_parity_v0_4_0: 1673 passed, 0 skipped, no golden or fixture changes.
  • The review fix's tests fail on f8b57e3 and pass here: (a) test_a_a_rename_resolved_through_header_names_keeps_the_dates_saves_and_runs, (b) test_b_a_swap_resolved_by_the_unit_s_channel_order_reopens_only_that_repeat, (c) test_c_a_date_publication_would_refuse_never_reaches_a_save_click, (d) test_d_a_header_change_shows_its_refusal_at_once_and_reopens_nothing plus the reviewer's two row-fold tests (updated: the refusal is still visible at once, without a re-ask); test_published_answers gains three library tests of reopened/reasked/kept_date.
  • The item tests of a75915f were shown to fail on 4e8e982 there; not re-run against 4e8e982 for this head.
  • Cards rendered with embed_minimal_html in Playwright Chromium (1180 px, deviceScaleFactor 2): (a) rename resolved, dates kept, Save enabled; (b) re-ordered, re-asked, then answered with Save disabled by R_CONFIRMED_ID_CHANGED; (c) Save disabled by R_CONFIRMED_ON_CHANGED with its way out; plus the earlier cards.

Generated by Claude Code

- A header change (channels_changed) reopens the replicate's "same way?"
  (experiment-v1 section 10: "a unit's ordered channel list"):
  published_answers.reopened takes the reconciliation.
- describe (CLI) reads the image headers and compares them with
  observe_folders(..., metadata=), as the notebook does.
- Save stays allowed when the run's checks refuse (owner decision,
  2026-11-10); a line beside the button says the run will stop and why.
- The step-by-step preview tick is gated on _preview_drawn of the
  current roles; a failed preview takes an earlier tick back.
- channel_strip, the native crop and the Images found picture check the
  bound fingerprint before and after the read.
- A unit's channel count comes from every image (counts_agree,
  unit_channel_counts): disagreement leaves it unknown.
- "The run can start" only once the run's checks have run and passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
After a header change, R_HEADER_CONTRADICTION appeared only once the reopened replicate was
answered again: a refusal hidden until the user acts. While answers are missing, Check and save
now says the run is blocked by both: the unanswered question and the run's refusal of the saved
description (codes and folders), or, before the axis roles are confirmed, the header change the
folder check found. Also drop planes read across a rewrite from the preview cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc

@guijacquemet guijacquemet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review: changes requested, not merged.

Pushed f8b57e3 (coordinator decision): a refusal is no longer hidden behind the reopened "same way?". While answers are missing, Check and save shows "The run (step 12) is blocked by both:" with (1) the reopened question and (2) either the run's refusal of the saved description (codes, messages and folders), or, before the axis roles are confirmed, the header change the folder check found. Tests: the extended test_a_header_rewritten_under_the_same_files_keeps_its_row_open and the new test_a_header_change_before_the_axes_are_confirmed_is_shown_as_blocking. Both fail on a75915f. Also fixed: channel_strip cached the planes of a refused read under the old fingerprint, so a file restored with the same size and mtime would have been served from those torn planes. It now discards them, with a regression test.

Blocker (item 1 against §4.4a): suppose the header change is fixed in the description and not by touching the files. For example, a rename DAPI→Hoechst is fixed by adding Hoechst to DNA.header_names. Then the unit's channels list and repeat_layout stay the same. But published_answers.reopened has already dropped rep1's confirmed_on, because the reconciliation against the published file still says channels_changed. The user's new answer is dated today. check_candidate says can_run and Save is enabled, but publication refuses R_CONFIRMED_ON_CHANGED at repeats.rep1, because description_transition._dates compares descriptions only. Unticking and ticking again gives today's date again, so there is no way out except changing the channel list or the files. The same flow saves on main 4e8e982 with the dates kept (reproduced with the first save dated 2026-01-01). It is fail-closed, so nothing refused before is now accepted, but it is a dead end that appears exactly when a user picks the correct fix. The spec has to settle this. Either a header-only channels_changed is not a reopening (the declared ordered channel list did not change, so §4.4a keeps the date), or publication must accept a new date for repeats that the reconciliation reopened. Either way it needs an owner decision and a test of the whole save path.

Other items: 2, 3, 4, 5, 6 and 7 are correct and their tests prove them. Every reopened() caller is updated. Spec modules plus test_parity_v0_4_0: 1673 passed, 0 skipped, no fixture or golden changes. python tools/check.py: ruff clean, 5069 passed, 4 skipped (file-system/permission skips).


Generated by Claude Code

Experiment-v1 section 10 reopens a repeat when "a unit's ordered channel list"
changes: the declared list in the description, not the image headers. A
header rename fixed through header_names (tagging the channel again) no
longer drops the date, so it saves instead of being refused
R_CONFIRMED_ON_CHANGED at publication. Headers that no longer fit stay the
run's refusal (R_HEADER_CONTRADICTION), shown at once.

- published_answers.reopened compares the candidate's layouts (units with
  their ordered channels and image type) with the published ones; reasked
  drops the date of a repeat an answer just re-laid-out (a complete,
  non-published layout), so "same way?" is asked again; ticking a repeat
  whose layout is as published takes the published date back (section 10).
- check_candidate runs publication's transition checks with the same
  re-confirmations (run.description_file.transition_refusals), so Save is
  disabled, with the reason, for a candidate publication would refuse.
- A replicate whose date differs from the published one is not folded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
@guijacquemet
guijacquemet merged commit 8c573d6 into main Nov 9, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/prerelease-followups branch November 9, 2026 23:46
guijacquemet pushed a commit that referenced this pull request Nov 10, 2026
Brings the audit batch 33/34a/34b fixes and the pre-release follow-ups into the v0.5.0
release candidate, keeping the P21/P29 description_in_use wiring.

Conflicts:
- notebook_folder.py: the folder check passes both with_description (P21/P29) and
  bind_headers=True (audit batch 33).
- cli_describe.py: the module docstring says both that describe reads image headers when it
  compares a published description and that a non-NFC folder label is refused; the imports
  of both sides are kept.
- CHANGELOG.md: main's Unreleased entries are folded into the 0.5.0 section, after the
  P21/P29 fix; the 0.5.0 introduction names #254, #255 and #257.

Also: the P30 test's answers (added on this branch) now answer the image type as
"channels", since an unanswered image type is an open question since audit batch 33
(unit_image_type_unanswered). tools/check.py: ruff clean, 415 files formatted,
5105 passed, 4 skipped (environment), code rules passed.

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 10, 2026
RELEASE_REF (and the test asserting it) now names 0ea3254, the merge of origin/main
(#254, #255, #257) into claude/release-0.5.0, which carries the P21/P29 fix.

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 10, 2026
Only docs/conformance.md: the measured commit (ba415de, whose RELEASE_REF is the merge
0ea3254), the counts (5109 collected, 5105 passed, 4 skipped; Spec modules 1671 passed,
0 skipped), tools/check.py (415 files, 5105 passed), the P30 answers now stating the image
type, and the fixes merged from main: audit batches 33/34a (#254), 34b (#255) and the
pre-release follow-ups (#257).

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 10, 2026
1. A header refusal lifted by an answer about another cell. Tagging any channel of any
   folder recomputed every label's header_names from every saved tag (channel_answers.synced).
   After images were rewritten with other header names (for example two channels swapped),
   tagging one channel of an unchanged folder made the R_HEADER_CONTRADICTION of the changed
   folders vanish: Check and save said the images agree, the save published it and the run
   wrote the table. Now a tag confirms the header name of the cell tagged and nothing else:
   synced() takes the cells of the answer, drops the names no tagged cell has any more and
   adds a name only for those cells. The refusal stays until the channel it names is tagged
   again (the resolution #257 documents).

2. A "same way?" kept for other folders or another layout in a draft. With nothing published,
   the answer saved in morphobricks.draft.json was kept when a folder joined or left the
   replicate before the next folder check (carry_over keeps repeats by label), and when a tag
   or an image type changed after it (reasked ran only against a published file); it was then
   published as the user's confirmation. Now the draft file's date is kept only when every
   unit the repeat has now has its whole saved channel list and type and no unit with a saved
   answer left it (published_answers.resumed), and an answer that changes a repeat's declared
   layout reopens it with or without a published file; a repeat tagged back to the layout it
   was answered for keeps its date. The names card says which replicate is asked again.

Tests: tests/test_notebook_final_review.py (10; 8 fail before the fix), and two existing
tests follow the new rule (the alias of a cell not tagged by the answer; "same way?" asked
again after the image type of a folder changed).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants