SetCanvasLayoutDefaults and SetSpellingPrecedence, kinds and tags 32/33, on the
SetMetadata LWW pattern: advisory last-writer-wins, no conflict, no idempotence
short-circuit, seeded from the base so a value-restoring undo of the first
operational write reaches the pre-operational value. Both carried types already
had Codec impls, so the packet designs no wire layout -- canonical_value!
delegation and one push_lp_bytes each, inheriting strict canonical-form
rejection rather than writing it.
Both stamp schema major 0 unconditionally, staying in the catch-all arm. The
op-block accept-set is untouched at 2; epiphany-bundle has an empty diff. The
raise to 3 belongs to G2b, and the chunk schema minor -- a MUST no writer has
ever honoured -- belongs to G-minor, filed as P13-S14 and deliberately not
fixed here.
Most of this commit is documentation the vocabulary had outgrown. G1 shipped
five normative falsehoods because its contract declared the wire surfaces out
of scope, and Push 4a left TransposeInterval out of both of core_spec's
normative listings. So binary_format's payload-layout and tag tables now reach
33 rather than 30, its snapshot-only bullet records that it is empty and why,
two spelled-out payload counts move from eight to nine, and core_spec's
listings gain all four missing kinds. The Pass-12 K8 doctrine is narrowed
rather than reversed: the root and canvas remain structural givens no operation
mints, and only the claim that the score's contents arrive outside the
operation set is superseded.
Two generators were stale by two tranches and fed corpora other suites treat as
exhaustive. layout_stub's is now derived from PAYLOAD_FREE with an explicit
Registered append instead of a hand-written match, so future built-ins follow
structurally; s10 asserts PAYLOAD_FREE union {Registered} so deleting that
append cannot pass.
Companion 0.8.0 -> 0.9.0, binary format 0.11.0 -> 0.12.0, catalog 0.9.0 ->
0.10.0 (covering G1 retroactively). 102 decode vectors, regenerated.
Coordinator verification: 1371 passed / 0 failed, clippy 0 warnings, fmt clean,
conformance 8/8 and 9/9, requirement_labels 6/6, four PDFs at 0 undefined
references, goldens byte-identical, accept-set confirmed still 2. Mutations
s3/s5/s7/s10 re-run independently and each observed to kill its test.
Two findings beyond the contract. max_supported_major is not reachable outside
epiphany-bundle -- mod bundle is private and the symbol is absent from its
pub use -- so the gate's "assert it in code" was unsatisfiable without touching
a crate the contract forbids; verified by reading the unedited source instead.
And core_spec carries two P12-K8 references, not one; the contract named only
the first, so the second is annotated here for consistency.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Score::empty plus operations alone now materializes a note-bearing Score. The
chain CreateInstrument -> CreateStaff -> CreateRegion -> CreateStaffInstance ->
CreateVoice -> InsertEvent needed exactly one new link: CreateStaff already
demanded a live Instrument and nothing could create one.
Instrument is a root with no outbound references, so the operation carries no
referential preconditions -- only mint and byte-identical re-carry, on the
CreateStaff template. It designs no wire layout: Instrument joins
canonical_value! and the payload is one push_lp_bytes over the existing Codec,
so strict canonical-form rejection is inherited rather than written. Kind 31 and
tag 31 agree; schema_major is unconditionally 2 (Instrument's major-2 appends
are mandatory, not Option-hidden); bundle.rs is untouched and the op-block
accept-set stays 2, since that raise belongs to G2.
Two cross-cutting items the ruling required. Reduction now writes identity for
the first time, deriving next_counter from the log rather than trusting the
seed -- and the implementation is broader than contracted, covering minted
entity ids as well as operation ids, which is right: both burn counters. And the
from-empty path is pinned to reduce_operation_set_onto, since the base-free mode
skips referential preconditions by design; a test documents that asymmetry as
designed rather than as a bug to fix.
The contract's parallel-safety claim was WRONG and this commit corrects it.
Extending OperationKind is not containable to core+ops: Rust exhaustiveness
forces an arm in editor-core's barriers.rs, and because testkit depends on
editor-core, that one missing arm blocked conformance and requirement_labels
too. Three more downstream sites had 31 or a kind-count baked in as a literal --
layout-ir's barrier decode test, testkit's grammar vocabulary count, and the
textproj corpus generator. The subagent found the first two, reverted its
out-of-bounds edit, and reported rather than working around; the user authorized
the boundary crossing. Each literal now carries a comment saying it must move
with every tag append.
The text projection needed a companion bump, which the contract never
anticipated. Adding create-instrument to the kind production while holding
0.7.0 would leave two incompatible grammars claiming one version -- precisely
what the single-version gate exists to prevent -- so COMPANION_VERSION is now
0.8.0, the first kind appended since the header was gated. Cached projections do
not migrate and are not expected to: a TextProjection chunk is a non-canonical
accelerator, so a stale one is regenerated. The negative "wrong version" vector
had to flip, since 0.8.0 was the version it used as its future-and-therefore-
rejected example; it now names 0.7.0, which tests the deferred migrate-on-read
posture better anyway. Test headers that were literals now assert against the
constant.
Gate, all observed: fmt clean; clippy --workspace --all-targets 0 warnings;
1359 passed / 0 failed; requirement_labels 6/6; conformance 8/8 and 9/9 with
golden-gate, 96 decode vectors and 13 textproj vectors, every verdict agreed.
max_supported_major(OperationEnvelopeBlock) verified still 2. Both PDFs rebuilt.
Mutations i1, i3 and i5 re-run independently rather than taken on report: the
spine collapses to TargetMissing without the instrument, an unseeded
instrument_values misreports a base re-carry as RecreateContentMismatch, and a
seed-returning cursor yields 0 where 12 is required.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Thirteen drift-locked document vectors -- four accept, nine reject, one per
implemented rejection class -- gated as conformance step [7e] beside [7d]'s 65
decode vectors.
The checker distinguishes three outcomes, and that distinction is the whole
point. `Rejected`, `AcceptedCanonical`, and `AcceptedNonCanonical` are separate
verdicts, and every one of the four wrong pairings is a failure: an accept vector
that merely normalizes fails, and a reject vector that is accepted -- whether as
canonical or after normalization -- fails. The decode corpus learned this the
expensive way, where collapsing "rejected" with "accepted but normalized" let a
corpus pass against the exact decoders it was written to catch.
Accept vectors assert the byte-checkable equation `project(serialize(parse(T)))
== T`, which quantifies over TEXTS. The comments say plainly that it must not be
replaced with bundle identity, and name the three deliberate binary losses --
duplicate blobs collapsing under derived-ordering, regenerated physical layout,
and dropped accelerators -- so the next reader does not "fix" it into a bundle
comparison and then chase a phantom.
Reach is asserted exactly, not merely non-zero: two documents each carrying an
extension, a canonical base, a custom profile, a lineage, and more than one
envelope; and every reject class must have exactly one vector the parser really
refused. A mislabelled reject drops its class to zero rather than collecting
credit from its label.
The corpus was verified by breaking the implementation, not the checker: making
`parse_document` silently tolerate a missing trailing LF is caught three ways,
and the suite reports "declared reject, but was ACCEPTED and normalized".
Review fixed one defect in the semantic harness. It claimed to check
`semantics(parse(project(B))) == semantics(B)` but compared `parse(project(B))`
against `document_from_bundle(B)` -- and `project_bundle` *is*
`project_text_document(document_from_bundle(..))`, so both sides flowed through
one function and any bug in it cancelled out. Dropping an envelope there passed.
It now compares against the envelopes the test itself serialized in, which is
independent of the projection path; the same mutation fails.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`epiphany-textproj` projects a bundle to its canonical text and reads it back:
the header, identity, profile, extension, canonical-base and blob productions,
plus the pipeline that joins them to the operation layer already in place.
Free functions, not `TextValue` impls. `epiphany-bundle` does not depend on
`epiphany-core`, so the trait and the manifest types are both foreign to this
crate and the orphan rule forbids the impls. That looked like it would force a new
dependency edge until the productions were read properly: no document line
contains a `value` position, so none is needed. The bundle stays independent of
the music model, as designed.
Blobs are handled on both sides and neither is the obvious one. The emit side
filters `blob_roots` through a real reachability predicate that today provably
returns empty -- never the field wholesale, which is the plausible wrong answer.
The parse side rejects any blob line outright, while still parsing the production
in isolation so both halves are ready together. A trip-wire source-scans
epiphany-core and epiphany-ops for `BlobId` and names both obligations when it
fires; it asserts it actually scanned files, so it cannot pass vacuously.
Two rejections were added on the agents' own reading of the spec rather than the
brief, and both are right. Envelopes must already be in canonical reduction order
(text_projection.tex:458) -- without that, two texts differing only in envelope
order would denote one document. And a hand-built `TextDocument` carrying blobs is
refused rather than silently dropped, since serializing it would produce a bundle
whose next projection loses those bytes.
Section order is enforced as a sequence, not a set: a repeated or out-of-order
section is a rejection, never something the parser sorts back into place.
`derived-ordering` sorts and de-duplicates blob lines and extension chunk roots by
*rendered* form, proved against fixtures that are unsorted in memory rather than
already canonical -- the blind spot that let five outbound normalizations ship
untested in the operation layer.
Serialization stages payloads and lets `commit` assign every offset, hash and
chunk id, which is exactly what `req:textproj:derive-or-carry` assumes; nothing is
hashed by hand. The emitted bundle deliberately carries no accelerators.
And the three directions compose: `project(serialize(parse(T))) == T` holds
end-to-end over the companion's own worked example. Eight checks re-mutated
independently of the agents' reports, each killed by its own named test; all eight
document productions diffed against the grammar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>