`project` and `parse` for every value an operation payload can embed.
The codec macros now emit a `TextValue` impl beside the `Codec` impl, from the
same invocation: 116 types whose field order cannot disagree between the binary
form and the text, at zero call-site churn. That is the companion's own rationale
applied to code -- a rule cannot drift from the listing it reads, and two listings
of one struct is the drift P13-I1 already cost us. `struct_codec!` rebuilds through
a struct literal and `cstyle_enum_codec!` matches exhaustively, so a field or
variant added later fails to compile rather than silently vanishing from the text.
The other 44 types have hand-written codecs and so need hand-written projections.
Their field order is verified by a mechanical diff of the identifier sequence in
each `fn enc` against the one in each `project`; all 44 agree. This matters because
a `project`/`parse` pair that agrees with itself on a *wrong* order round-trips
perfectly -- neither the compiler nor any round-trip test can see it. The
neighbouring blind spot, a mistyped constructor symbol, is closed by
`textvalue_names.rs`, which recovers each type's Rust name from its derived `Debug`
and compares it against the symbol actually emitted.
Strictness turned out to need only one of its two layers, and mutation testing is
what established that. Every per-site check is live: the set/map strictly-increasing
walk, `RationalTime`'s lowest-terms compare before construction, the catalog-id NFC
intern-and-compare, and `EventArena`'s ascending-`EventId` walk. Every whole-value
`ensure_canonical` guard was dead -- `Tempo::new`, `ReferencePitch::new`,
`SpellingPrecedence::new` and `EventOrderingDAG::try_new` reject rather than adjust,
so an accepted value re-projects to exactly its input and the guard could never
fire. A probe confirmed `try_new` returns its input map unchanged. Helper and all
four call sites removed: a check that cannot fail invites weakening the real one.
Also moves `catalog_name` out of the grammar *test* and into
`operation_kind_tag_vocabulary!`, where the discriminant and decoder already live.
It was a hand-maintained list parallel to an enum -- the exact shape that has cost
this project four bugs.
Method note recorded in DECISIONS: the work list came from compiler errors, but the
compiler reports only the frontier. `AnchorOffset`, `VoiceSelector`, `PowerOfTwo`,
`OctaveOffset` and `NonZeroU16` were each hidden behind a type that had not compiled
yet, so the list must be iterated to a fixpoint.
Gate green -- clippy 0, 1109 tests, doc 0, conformance 8/8, no golden churn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The value model of the companion, and the half of it that has to say no.
`Sexp` carries only what a lexer can honestly know: list, symbol, integer, byte
string, string. There is no `Bool` variant, because `true` is spelled exactly like
a symbol and nothing but the expected type tells them apart; no `Ratio` and no
`Option` variant, because both are lists. That is `req:textproj:schema-directed`
expressed as a type rather than asserted in a comment.
Integers are `BigInt`. `RationalTime` promotes to a `BigRational` when arithmetic
overflows its inline range, so the text's integers cannot be bounded by `i64`.
`read_sexp` is strict per `req:textproj:strict-parse`: it accepts exactly what
`Sexp::write` emits. Leading zeros, negative zero, upper-case hex, an odd hex digit
count, an unknown escape, a raw tab, a doubled space, a space after `(`, and any
trailing input are rejections, never normalizations. `BTreeSet` and `BTreeMap`
parse strictly increasing and refuse to absorb a duplicate -- sorting the input
would be normalizing it.
Every guard mutation-verified, which caught two that were not guards. The
upper-case-hex branch and the space-before-element branch change no verdict: the
first is already rejected by the odd-digit count or by trailing input, the second
by `value`, which knows no value beginning with U+0020. Both survived deletion
with the rejection suite green -- it proved only that *something* rejected them.
They are diagnostics, so their contract is the message, and
`the_reader_names_the_real_problem` now holds them to it. A check that looks like a
guard but is not is worse than no check: it invites weakening the real one.
Gate green -- clippy 0, 1048 tests, doc 0, conformance 8/8, no golden churn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>