Repository navigation
Draft file and draft-to-description (Stage 1) - #221
Merged
Merged
Conversation
Add morphobricks.draft.json, the versioned, strictly read file holding the user's answers so far with base_sha256 and the pinned Spec commit, saved atomically under the output lock, and the pure step that builds a Description from a fully confirmed draft (refusing with every missing confirmation). publish_draft publishes and then deletes the draft under one hold of the lock; any refusal writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Scma6W3LC11s3qjXHZuGDm
…aft-file # Conflicts: # CHANGELOG.md
This was referenced Nov 8, 2026
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.
What
<output>/morphobricks.draft.json: a versioned, strictly read JSON file with the user's answers so far,base_sha256(hex of the published bytes, or"absent") and the pinned Spec commit. Saved atomically underoutput_lock(run/draft_file.save_draft). A malformed, unknown-version or extra-key file is refused withDraftFileError(code:malformed,unknown_version,unknown_key,R_DRAFT_BASE_MISSING,R_FILE_SYMLINK), never repaired.load_draft): refused withR_PUBLISHED_FILE_CHANGEDwhen the published file is no longer the recorded base (also when it is gone or the base wasabsentand a file appeared);R_DRAFT_BASE_MISSINGwhen the file has no usable base. An answer for a folder the draft no longer has is refused (unknown_key), never moved.draft_answers.description_from_draft, pure, no clock): refuses withDraftIncompletelisting every missing confirmation once (id_pending,repeat_not_confirmed,confirmed_on_missing,control_unresolved,legacy_folder_missing,unit_channels_missing,channel_undeclared); otherwise returns theDescription. Nothing is adopted from a proposal.publish_draft: under one hold of the lock, resume, build, publish with every check ofpublish_description(symlink, base, transitions, strict reader on the exact bytes), then delete the draft. Any refusal writes nothing.run/description_file.publish_lockedispublish_descriptionfor a caller that already holds the (non re-entrant) lock;publish_descriptionnow calls it.Why
The Stage 1 design (section "Draft / confirmed lifecycle", module table) specifies the draft file and the publication step, but no merged PR built them. #220 (CLI
describe) lists this gap: its--publishcannot complete a draft. This PR is built onorigin/main, not on #220.Format
One JSON object (UTF-8, two-space indent, sorted keys, LF), every key required:
format(morphobricks-description-draft),version(1),base_sha256,spec_commit(equal totests/spec_fixtures/PIN.toml, tested), and six tables of answers only, keyed by the draft's stable keys (source folder labels, the repeat pairing label):conditions: folder ->{id?, display_name?, reference?, target?, treatment_key?}technical_repeats: folder (singlefor none) -> IDrepeats: pairing label ->{id?, confirmed_on?, display_name?}(confirmed whenconfirmed_onis present; that day is the caller's, §4.4a)units: condition folder -> source repeat -> technical folder ->{channels, image_type?, display_name?}channels: ID ->{display_name?, header_names?}masks: ID ->{source_type, representation, source_channels?, display_name?}Folders, counts and proposals are not stored: the draft is derived again from discovery (or the legacy design) on each resume.
save_draftapplies the answers to the draft and reads the exact bytes back with the strict reader first, so a file that resume would refuse is never written.Regression evidence
New tests:
tests/test_draft_format.py,tests/test_draft_file.py,tests/test_draft_answers.py(108 tests, helpers intests/draft_file_support.py): round trip and deterministic bytes, 36 strict refusals (non-UTF-8, BOM, duplicate key,NaN, wrongformat, unknown versions2/"1"/true/1.0/missing, extra keys at four levels, missing keys, wrong types, bad dates, missing and malformed base), symlink, base mismatch (changed, replaced, gone), incomplete draft refused with every missing item listed, complete draft -> description ->publish_descriptionpath -> draft deleted, refusals (incomplete, reader refusal, changed base, held lock) leaving draft and published file byte-identical, legacy confirmation carried and not asked again, date is the user's and never the clock.On
origin/maincode (new source absent): 3 collection errors, 0 of the 108 tests run (ModuleNotFoundError: No module named 'morphobricks_preprocessing.draft_answers',ImportError: cannot import name 'DRAFT_FILENAME'). With the change: 108 passed.A real defect found by the tests while writing:
used_ids.channelswas empty in the built description; the strict reader refused it (E_EMPTY); fixed (declared channels are inused_ids).Checks
python tools/check.py: Ruff and format clean (323 files); pytest 4454 passed, 4 skipped, 0 failed; code rules pass (new modules have layers inpyproject.toml, files under 600 lines, no baseline grew). The 4 skips are not in this change (3 case-insensitive-name tests, 1 file-mode test run as root).Will conflict on CHANGELOG with other open PRs; keep both sides.
Needs an owner decision
Draft.confirm_idrefuses an ID already inused_ids, so a draft over a published base can only carry the published IDs as answers ifused_idspassed to the draft holds the retired IDs only (this is whatdescription_from_draftassumes:used_ids= retired IDs + every ID confirmed now). How a reconciliation (reconcile) result becomes a draft file (answers copied from the published file,confirmed_onkept) is not built here.spec_commiton resume. It is recorded and read strictly (40-digit hex) but not compared: refusing a draft written under another Spec commit would discard the user's answers, accepting it may apply other rules. Decide whether a mismatch refuses, warns, or is only recorded.masksanswer is not a missing confirmation here (the Spec makesmasksoptional and the draft does not know the discovered mask types). Decide whether an undeclared mask type blocks publication in the notebook.DraftError(repeat_group_conflict) at build time; there is no pairing API yet to create one, so this only guards a future one.Generated by Claude Code