Skip to content

Description model, canonical writer and section digests (Stage 1, PR 4) - #187

Merged
guijacquemet merged 2 commits into
mainfrom
claude/description-writer-digests
Nov 7, 2026
Merged

guijacquemet merged 2 commits into
mainfrom
claude/description-writer-digests

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

What changed

Design PR 4 of the Stage 1 design (section 3 modules, section 5 row 4). All additive; no run, notebook or CLI uses it yet.

  • description.py: the Description model (Condition, Channel, Repeat, Member, Unit, Mask), built only from a document the strict reader accepted. description_from_bytes(data) reads with read_description_bytes and raises DescriptionError carrying every issue of the first failing phase. Arrays whose order is not semantic are held sorted (so model equality is file meaning); unit channels keep their order; optional fields stay None when not written, and absent is kept distinct from [].
  • description_write.py: canonical_bytes(model) per Spec experiment-v1 9.1 (UTF-8, LF, final newline, no comments, entries sorted in code point order, fields in the Spec's table order taken from description_rules, bare or basic-quoted keys, minimal escapes, empty masks omitted). Nothing is defaulted: confirmed_on is written as the model holds it (4.4a). section_digests(model) gives the SHA-256 of conditions, channels, repeats, masks with every display_name, target, treatment_key omitted; used_ids is not a section.
  • Registered the modules as pure in the code-rules layers; CHANGELOG and AGENTS.md repo map.

Why: groundwork for publication (PR 5), which writes the model and re-reads the exact bytes.

Decision for the owner (Spec ambiguity)

Spec 9.3 does not define the byte form of one section alone (the Spec's own refval.py flags this as AMBIGUITY and hashes the writer's output for a document holding only that section, with a dummy schema). Here a section digest is the text the file holds for that section (its table blocks, each followed by a blank line), nothing else; an empty section is the empty string. Digest values therefore differ from refval's. Nothing consumes them yet; the Spec should state the byte form before a consumer keys a cache on them.

Tests (tests/test_description_write.py, 158 tests)

  • parse(write(parse(F))) equals parse(F) (model and parsed file, unordered arrays sorted, empty optional table dropped) on all 36 valid and 12 transition fixtures; writing is idempotent; the bytes re-read with no error and the same warnings; layout checks of 9.1.
  • v18 model == v01 model and canonical bytes equal; v16 and v21 differ from v01 as models but have all four section digests of v01.
  • Semantic changes move the right digest: a condition source_folder moves only conditions; a unit's channel order moves only repeats; dropping a reference leaves channels and repeats; a longer used_ids moves none.
  • Key and string spelling; DescriptionError carries the phase and issues.

Result on the old code: the modules do not exist on main, so the test module fails at collection (ImportError); verified by running it against the tree of PR #186 (collection error).

Checks

python tools/check.py: 3679 passed, 4 skipped (case-insensitive file system x3, file-mode permissions x1; none are Spec tests), 2 warnings; Ruff and code rules clean. Branched from main (93b3c4c), independent of #186 (it only needs v01, v16, v18, v21, present in both pins).


Generated by Claude Code

Stage 1 design PR 4: Description model built from reader-accepted
documents, canonical bytes of experiment-v1 section 9.1, round trip and
idempotence on every valid and transition fixture, section digests
(v18 = v01; v16 and v21 digests equal v01). Not used by any run yet.

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

# Conflicts:
#	CHANGELOG.md
@guijacquemet
guijacquemet merged commit 0159b9f into main Nov 7, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/description-writer-digests branch November 7, 2026 13:31
guijacquemet pushed a commit that referenced this pull request Nov 7, 2026
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