Commit Graph

277 Commits

Author SHA1 Message Date
Levi Neuwirth 3af242d457 Draft the G2b contract: subset over normalization, and the raise this rung pays
Documentation only. Not dispatched - two items need ratification.

Resolves the open accidental_extensions pin as SUBSET, not normalization.
SetTuningContextOp carries a new type holding exactly the five wire-bearing
fields, and reduction leaves accidental_extensions untouched rather than
clearing it.

The deciding argument is that this costs no wire design at all:
ScoreTuningContext::enc already encodes exactly those five fields in exactly
that order, so the subset type's canonical encoding is byte-for-byte the
current one. It is a type-level narrowing, and canonical_value! still applies.
Normalization would instead make correctness depend on clearing a field at
every construction site, enforced by nothing the compiler or the codec can see
- canonical_value! compares bytes and never the originating value, so a field
that never reached the bytes is structurally invisible to it. That is the same
hand-maintained-discipline shape that went stale four times at Push 4a and six
times at G2a. Preserving is also semantically right: the operation carries no
information about the field, so clearing it would silently erase a live
registry extension the wire cannot even record.

Nothing in production populates accidental_extensions - every non-empty
reference in the tree is a test - and the text projection drops it too, so the
subset type ratifies the status quo rather than changing it.

Ten pins. The accept-set raise is charged here because minimal stamping is a
function of each payload's value and ScoreTuningContext is the only genesis
payload born at major 3. bundle.rs:56 does not merely document the cap, it
asserts that no operation payload embeds the tuning context - a sentence this
rung falsifies, so it must be rewritten rather than left beside a changed
number.

Awaiting ratification: the subset type's name, and epoch 10 for kind 34, which
is the first exercise of the G-minor ladder's own growth path. G-minor's
wildcard-free introduced_minor means kind 34 cannot compile without one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:15:07 -04:00
Levi Neuwirth b8601ec909 File P13-S15: the OperationKind golden lock stops at 29, and close P13-S14
Documentation only. No code change.

S15 records that operation_kind_wire_discriminants_are_golden declares
[(OperationKind, u8); 30] at payload.rs:1959, covering 0..=29 - so
TransposeInterval, CreateInstrument, SetCanvasLayoutDefaults and
SetSpellingPrecedence have no byte-level lock. Not a live incorrectness: all
four discriminants are currently correct and the .tex tables carry them
normatively. The gap is the absence of a guard.

Left open deliberately with no code change. The fix is mechanical, but a
golden-lock extension should land with its mutation evidence and nothing else
in the diff - and the mutation is to move one of the four and watch the
extended lock fail where it previously stayed green. The macro-guarded
OperationKindTag half is unaffected; this is the hand-written match, which is
the site Push 4a got wrong. Its sibling phase3_tag_discriminants_are_golden
wants the same check.

Closes S14 at ff9bd0f, and records the two things its filing did not
anticipate. The scope was never just OperationKind - OperationPayload,
ReanchorReason and PreconditionFailureReason all append - and the manifest
reaches OperationKindTag through edit_barriers with no envelope involved, which
supersedes the filing's "no companion bump" note. Op-block stamping did stay
projection-invisible as scoped; the bump came from the manifest attribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:10:17 -04:00
Levi Neuwirth ff9bd0fd06 G-minor: the chunk schema minor becomes a derived record
Implements the schema-minor MUST (binary_format.tex, Schema Versioning) that no
writer had ever honoured: a writer raises the chunk minor when it emits a
discriminant appended after the minor it otherwise declares, so an
unknown-discriminant decode failure is attributable to version skew rather than
corruption.

introduced_minor() lands on the five vocabularies with post-baseline variants -
OperationKind, OperationKindTag, OperationPayload, ReanchorReason, and
PreconditionFailureReason - each exhaustive with no wildcard arm, so a future
variant cannot compile without being assigned an epoch. The tag epochs live
inside operation_kind_tag_vocabulary! rather than beside it, because a sibling
match is the parallel list that macro exists to prevent. The sentinel is
Option<u16>, not 0, since 0 is a real baseline minor for V1-V3 and conflating
them would make the max read correctly only by accident.

An envelope's required minor is the max over every discriminant it actually
emits; a block's is the max over its envelopes; major and minor derive
independently. Baselines are not normalised - V0 keeps minor 1.

The manifest seam keeps epiphany-bundle opaque: no ops or layout-ir dependency,
and the aggregate version is supplied by the producer rather than derived, with
CommitContext carrying the previous one so unchanged barrier content preserves
it. The version rides the superblock slot that already exists; Manifest gains no
field, which would have been schema-major and would have defeated the rung.
bundle.rs's superblock check stays major-only - tightening it to full-version
equality is a conformance regression, and s11 locks that.

textproj carries the manifest SchemaVersion and never derives it, so
COMPANION_VERSION moves 0.9.0 to 0.10.0 with the corpus regenerated. Not because
of op-block stamping, which remains projection-invisible. A new normative
requirement records the carry-never-derive rule in the companion itself; its
rationale names layout-ir, which textproj genuinely lacks, rather than the
operation vocabulary, which it has.

Gate [7f] adds an independent oracle over decodable in-tree barrier fixtures,
requiring exact equality rather than >=. Equality is load-bearing: >= catches
under-stamping but not stale over-stamping after the sole maximum contributor is
removed. Undecodable blobs are reported not-checkable, never as a pass.

Also repairs binary_format.tex's stale claim that OperationKind and
OperationKindTag append at 30 with a history stopping at 29, while 30-33 are
taken and the normative tables already carry them.

Gate: 1399 tests, clippy 0, fmt clean, conformance 8/8 with [7f] at four
fixtures checked and one not-checkable, 102 decode vectors byte-identical
(verified, not assumed), 13 text-projection vectors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:04:34 -04:00
Levi Neuwirth 496dfd5640 T4 round 0: iced is eliminated, and the probe that cleared it proved nothing
Round 0 is the ladder's cheapest hard gate: for each candidate, a desk survey
and a demonstrated accessibility route -- a window exposing one node with a
role and a name, read back through the platform adapter. It is first precisely
so that a candidate with no accessibility story costs one round instead of
four.

C1 (egui/eframe 0.35.0) passes on a first-party route: egui-winit's accesskit
feature is literally dep:accesskit_winit, so the chain runs eframe ->
egui-winit -> accesskit_winit -> accesskit_unix and inherits that crate's
window-lifecycle handling. C2 (vello 0.9.0 + winit 0.30.13) passes on the
manual accesskit_winit route the contract names, wired into the same
ApplicationHandler that owns the vello renderer.

C3 (iced 0.14.0) FAILS, and the interesting part is that it first reported as
a pass. The probe registered a hand-built two-node tree through
accesskit_unix::Adapter, which takes no window handle at all -- only handlers
-- and registers with AT-SPI from process identity. It read back cleanly. It
also happened to label its button exactly as iced's own view() labelled a
button, so the transcript looked as though iced had produced it. Deleting iced
from that probe would produce the identical readback. Round 0 asks whether the
CANDIDATE exposes a route; a process-level side channel answers a different
question.

The verdict is recorded with dual attribution, because two distinct things
went wrong. The probe-design defect is the false positive above. The candidate
limitation -- which alone fails the round -- is that iced 0.14 ships no
accessibility integration anywhere (accesskit appears in no iced crate
manifest) and its stock runner hands application code neither the winit
ActiveEventLoop nor a pre-visibility Window, both of which every
accesskit_winit::Adapter constructor requires. That scoping matters and is
deliberate: iced_winit documents a conversion module for users implementing a
custom event loop, so a hand-built shell remains conceivable but unproven, and
would mean owning the shell. Upstream iced #552 is still open.

The evidence file keeps the verifier's factual READBACK: PASS beneath a
ROUND-0 RESULT: FAIL annotation rather than being rewritten. A corrected record
that erases the false positive teaches nobody why it was false.

Two findings carried forward. C1's frame node is unnamed -- its readback path
is application:'probe-egui' / frame:'' / button:'...' where C2 names its frame
-- which is non-disqualifying here but means a screen-reader user hears an
unnamed window, and round 3 must check it. And AT-SPI application registration
is gated behind two settings that are off by default; without both, probes
connect to the bus and enumerate zero applications, which would read as a
candidate failure rather than the environment absence it is.

The spike workspace lives outside the root workspace with its own lockfile;
the root gains one line, exclude = ["spikes"], and nothing else. Round 0's
probes depend on no epiphany crate, which is what makes this round independent
of the pinned source baseline the later rounds need.

Root gate unchanged: fmt clean, clippy -D warnings clean, 1371 tests passed 0
failed. Spike workspace gated separately: fmt clean, all probes build --locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 19:01:51 -04:00
Levi Neuwirth 0c8b8db1a5 Amend the G-minor contract: the [7f] oracle, and the PDFs the touch table missed
Documentation only. No implementation.

Rules pin 11 as (b) with (a) normative. textproj stays a preserving producer -
it carries the declared manifest version verbatim, never decodes barriers, and
the document author owns updating it when editing opaque barrier bytes.

On top of that, an independent testkit conformance oracle at gate [7f]. When
every barrier blob decodes it recomputes the exact aggregate and requires
equality, not >=: equality is what catches stale over-stamping after a
contributing barrier is removed, which >= cannot see. Both negative fixtures
are required - a tag-31 blob carrying baseline, and removal of the sole maximum
contributor. An undecodable blob is reported not-checkable and never as a pass,
with byte-and-version preservation left to s10/s13 rather than claimed here.
The gate must describe itself as validating known decodable in-tree artifacts,
not as evidence that textproj validates arbitrary edits.

Three new mutations. s16 is deliberately inverted: relaxing equality to >= makes
the over-stamp fixture go green, and that green is the evidence that equality is
load-bearing.

The touch table gains the testkit conformance files and the two tracked PDFs
whose .tex sources change. core_spec.pdf and operation_catalog.pdf stay
build-only for the undefined-references check; if either turns out byte-changed
that is a finding, since this contract changes neither source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:59:00 -04:00
Levi Neuwirth f94102d839 Contract G-minor: ten pins, one open ruling, fourteen mutations
Documentation only. No implementation.

Drafts the implementation contract for the schema-minor rung against the
ratified epoch ladder and the vocabulary audit. Ten design pins covering the
derivation (envelope max over emitted, block max over envelopes, major and
minor derived independently), the five vocabularies that need introduced_minor,
the manifest seam, and the text-projection consequence.

Three pins exist to prevent specific wrong edits. The baseline sentinel must
not be 0, because 0 is a real baseline minor for V1-V3 and conflating them
makes the max read correctly by accident. OperationKindTag's epochs go inside
operation_kind_tag_vocabulary!, not beside it, because a sibling match is the
parallel list the macro exists to prevent. And bundle.rs:301 must stay a
major-only comparison - tightening it to the full version would be a
conformance regression, and it is the most tempting wrong edit in the packet.

One pin requires ratification before implementation: epiphany-textproj cannot
detect changed barrier bytes, having no prior state to diff and no layout-ir
dependency to decode with. Recommended disposition is a documented preserving
producer, with a testkit conformance check as the alternative. The two differ
by a test rather than a design.

Fourteen tests, each with the mutation that must kill it, including the
rejected highest-discriminant policy, the over-stamp after a contributing
barrier is removed, and the bundle.rs:301 tightening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:50:45 -04:00
Levi Neuwirth 0def1563c8 Amend the G-minor plan: the seam ruling, the companion consequence, one wrong hash
Documentation only. No Rust, no .tex, no vectors, no implementation.

Corrects G2a's introducing commit to 7df5ca1 - where kinds/tags 32-33 enter
payload.rs. 55eff00 is the later review-fix and introduces no discriminant.
The hashes are the evidence for the ladder's monotonicity, so citing a
follow-up commit would make it unverifiable at exactly the rung a reader is
most likely to re-derive.

Records the ruled seam. epiphany-bundle stays opaque with no ops or layout-ir
dependency; producers supply the aggregate manifest SchemaVersion explicitly
and CommitContext exposes the previous one so unchanged barrier content
preserves it. That is new plumbing rather than a read-through: Manifest has no
schema-version field, it lives in the superblock, and it must not gain one -
a field addition is schema-major regardless of type, which would defeat the
rung. Changed barrier bytes require an aware producer that computes the exact
epoch or refuses, because blindly retaining the previous aggregate
over-stamps once a contributing barrier is removed.

Manifest::SCHEMA becomes a baseline constant rather than the universally
emitted version. Three sites silently select it, not two - bundle.rs:220,
bundle.rs:724, and the re-exported helper manifest_chunk_hash at bundle.rs:1298,
which is public API and so a signature change. The major-only check at
bundle.rs:301 stays as-is and stays correct.

Consequently G-minor does bump the text companion: 0.9.0 to 0.10.0 with corpus
regeneration, because TextDocument must carry the manifest SchemaVersion that
the projection cannot derive from deliberately opaque bytes. Not because of
op-block stamping, which remains projection-invisible - section 2's "no
companion bump" correction is still true about op blocks and now carries a
forward pointer so it does not read as the whole answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:47:42 -04:00
Levi Neuwirth 2c7191482a Ratify the G-minor epoch ladder, and narrow the Manifest::SCHEMA rule
Documentation only. No Rust, no .tex, no vectors, no implementation.

Records the ratified schema-minor epochs (minors 2-9, one per additive event:
M2c, Push 3, Phase-3 tranche 1, Pass-12 G-pass, schema-major-2 repeat revision,
Push 4a, G1, G2a). These are schema-minor epochs, not companion semver.

The ladder is complete against spec/AUDIT_GMINOR_VOCABULARIES.md - every
post-baseline variant appears exactly once - and monotonic in the topological
order of the commits that introduced each variant, which is what makes a
declared minor prefix-closed. Verified against git rather than assumed,
including the two events sharing 2026-07-07.

Major baselines unchanged: V0 minor 1, V1-V3 minor 0. Baseline variants impose
no additive override; M2c's operation kinds stay baseline while its
PreconditionFailureReason append requires epoch 2.

Manifest::SCHEMA is narrowly superseded. Changed child ChunkRefs still never
raise the manifest minor, but emitted barrier tags do: a manifest naming a tag
in 24-33 takes that tag's epoch. Major stays 0. The derivation seam is left to
the implementation contract, with the case that must not be discovered late
written down - epiphany-bundle depends on neither ops nor layout-ir and
preserves barrier bytes verbatim, so a repack can carry a blob it cannot
decode, and "decode and inspect" does not cover it.

PLAN_GENESIS_OPS.md:257 no longer says "per-kind minor". That policy was
rejected; the paragraph now carries the ratified rule and records why.

Every gate in PLAN_GMINOR_SCHEMA_MINOR.md section 5 is discharged. The
implementation contract may now be drafted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:39:28 -04:00
Levi Neuwirth c90d662a0c Fold the vocabulary audit into the G-minor plan: the ladder is not ratifiable as written
Documentation only. No Rust, no .tex, no vectors, no implementation.

Records spec/AUDIT_GMINOR_VOCABULARIES.md (c63258d) as the governing
reachability inventory, superseding section 3's table where they differ. Section
3 stays as the reasoning that led there.

Section 4's tentative epoch ladder is marked incomplete rather than merely
unratified: it was drawn against OperationKind alone, and the audit placed
three families it never considered. OperationPayload 3 (Push 3) and
ReanchorReason 6 (Pass-12 G-pass) have no rung, and PreconditionFailureReason
10-15 spans four tranches of which M2c and the G-pass are unplaced.

Also records the distinction that makes the global epoch space and the
per-vocabulary baselines compatible: the epoch answers which revision
introduced a variant, the baseline answers whether it predated that
vocabulary's own lock, and only the second varies. M2c is the proof - baseline
for OperationKind, a genuine append for PreconditionFailureReason. So
introduced_minor is assigned per variant, per vocabulary.

The .tex correction at binary_format.tex:2373 is recorded as owed by the
implementation, not by this plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:29:59 -04:00
Levi Neuwirth c63258d32a Audit the G-minor reachable vocabularies: a ledger, a matrix, one stale sentence
Read-only packet. Walks each of the nine ChunkKind roles' payload encoders
transitively and records every discriminant vocabulary actually emitted,
with every variant classified baseline / post-baseline native / escape.

Two vocabularies outside the OperationKind/Tag/Payload trio have real native
appends: ReanchorReason (SameCanvasNearer = 6, Pass-12 G-pass) and
PreconditionFailureReason (10-15, four separate tranches). Fourteen further
escape-carrying enums checked and clean, recorded as evidence.

Two findings the plan's section 3 table did not name. The canonical base
reaches RepairKind, ReanchorReason, and SpellingNominal, and ReanchorReason
is one of only two vocabularies with an append. And the manifest reaches
OperationKindTag with no operation envelope in it at all, through
ExtensionDeclaration::edit_barriers -> EditBarrier::prohibited_operation_kinds
- the disposition-(c) crossing, realized in a role the plan never mentions.

binary_format.tex:2373 is recorded as stale narrative: it says OperationKind
and OperationKindTag "append at >= 30" and its history names only 24-29,
while 30-33 are taken. The normative tables at :1443 and :1526 are current
and authoritative; the .tex correction is deferred to the G-minor
implementation, which must touch that paragraph regardless.

CompressionAlgorithm is ruled out of scope for schema-minor epochs: it is
ChunkRef transport metadata, not a discriminant emitted by the payload
SchemaVersion governs, and it is deliberately excluded from chunk identity.
Recorded so it is not reopened.

The epoch space stays global, but baseline classification and introduction
events are per variant per vocabulary. The tentative ladder is incomplete -
Push 3, the Pass-12 G-pass, and M2c are unplaced - and must not be ratified
as written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:27:35 -04:00
Levi Neuwirth 9dc48ecb70 Audit contract amendments: a possible gate, and four scoping repairs
The gate was unsatisfiable. The tree is already dirty with the editor track's
Cargo.toml and spikes/ subtree -- which the contract itself acknowledged two
sections earlier -- so "exactly one new file" could only have been faked or
argued around. It is now a before/after status delta whose sole permitted
difference is the new audit file, with anything else, a cargo-touched
Cargo.lock included, reported rather than cleaned up.

Scope widened to every reachable discriminant vocabulary, open or append-only
or expanded by ratified revision. binary_format:2386 permits minor-additive
appends to the value unions it describes as closed, so "closed" was the wrong
exclusion boundary; only ChunkKind is genuinely out.

Role completeness now has four dispositions rather than two, because opacity
is a property of a layer and not of bytes. Normatively typed bytes opaque only
to the immediate layer -- operation envelopes, manifest barrier blobs -- must
be crossed into and their producer encoders walked; treating op blocks as
opaque would drop the one vocabulary G-minor exists for. Producer-owned opaque
bytes such as ExtensionData get an explicit "no core derivation possible,
schema carried from the producer", since cannot-see-inside and nothing-inside
are different findings.

The escape checklist is now sixteen named entries and the count is deleted
rather than corrected: BarrierScope and BarrierCondition are separate enums,
and a prose pairing of them is exactly how the miscount happened.

Baseline enumeration is mandatory, in a separate vocabulary ledger classifying
every variant as baseline, post-baseline native, or escape/reserved. The matrix
references it instead of repeating large baseline sets, and a disagreement
between the two parts is a defect in the audit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:08:55 -04:00
Levi Neuwirth 6145efecd9 Contract the G-minor vocabulary audit: a matrix, read-only
The gating work before G-minor can be contracted. Deliverable is one file,
spec/AUDIT_GMINOR_VOCABULARIES.md, holding a reachability matrix keyed
chunk role -> encoded payload type -> discriminant vocabulary ->
post-baseline variants -> introduction event -> derivation site.

Method is pinned to walking the encoder rather than the type, because a field
can exist and never reach the wire -- ScoreTuningContext::accidental_extensions
is the standing proof -- and the matrix's purpose is to say what is emitted.

Inclusions cover nested additive variants at any depth and, the sharpest edge,
later native additions to enums that also carry a Registered escape: the escape
is out of scope while a new native variant on the same enum is not, and
binary_format names fifteen such carriers. ChunkKind is excluded as closed.

The trap most likely to corrupt the matrix: the spec's "append at >= N"
phrasing names the next free slot, not the baseline boundary. OperationPayload
is documented as appending at >= 4 while discriminant 3 is itself an append,
and OperationKind at >= 30 while 24-29 were appends too. Post-baseline
membership must come from revision history, not from those numbers.

Epoch numbers are explicitly not ratified here; column 5 records the
introduction event. Completeness is enforced by requiring a per-role
accounting including roles that produce no rows, and negative results recorded
for every escape-carrying enum -- an unlisted role being indistinguishable
from an overlooked one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:02:32 -04:00
Levi Neuwirth 82ac636982 Revise the G-minor scope: the recommended policy could not work
All five findings verified against the tree; the recommended policy is
rejected and replaced with the ratified one.

Policy (b), minor = highest discriminant emitted, cannot represent an
operation block. An envelope also emits the outer OperationPayload
discriminant, and ResolveEquivocation is appended at 3 while carrying no
OperationKind at all -- so the ambiguity is inside one role and one block, not
between roles. Generalizing to "highest from any vocabulary" is worse: an old
kind 23 would numerically mask a new payload 3. binary_format itself enumerates
four independent minor-additive vocabularies. The rung's gating work is
therefore an audit of every append-only discriminant reachable from each
affected payload, which the first draft never scoped.

Ratified instead: a global additive epoch with content-minimal stamping, an
envelope's minor being the max across outer payload, primitive kind, and every
nested additive variant actually emitted. The maintenance objection is
answered by co-locating introduced_minor with each discriminant in an
exhaustive match with no wildcard, so an unassigned variant cannot compile --
the operation_kind_tag_vocabulary! reasoning. Per-major counters are rejected
too: mixed blocks do not compose after max_major.

Two of my conclusions were wrong. Op-block minors do not reach the text
projection -- block schemas are discarded there, and all seven accepted-corpus
schema forms belong to extension chunks or canonical bases -- so no companion
bump. I had also miscounted them as six by grepping lines rather than
occurrences. And the manifest-ID promise is not threatened: it is conditional
on the same manifest body, and a changed ChunkRef is a different body. Real
address churn, not a broken guarantee.

"Is the canonical base exempt" was the wrong binary question. It never emits
the op-kind discriminant, so it holds its minor until MaterializedState's own
bytes emit a later-added variant. Manifest::SCHEMA stays put; existing bundles
need no migration. Construction-site count corrected 62 -> 66.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:59:56 -04:00
Levi Neuwirth 42a8cc6dc5 T4 pin 7: the fixture recipe is approved, and the spike tree opens with it
Pin 7 of the spike contract holds that staff-by-measure dimensions do not pin a
workload, and the workload is what gets tessellated. This is that recipe: one
region, one staff instance per staff, a fixed seven-event bar of six eighths
plus a quarter, and integral per-measure quotas for rests, accidentals, ledger
notes and slurs. Nothing is sampled. The generator still takes a seed, but its
only role is replica minting as in the testkit fixtures -- content is fixed by
construction, which is strictly stronger than a fixed seed over a free
selection algorithm.

Three review rounds, and nearly every finding was the same mistake in a new
place: a claim about the pipeline inferred from an adjacent fact instead of
read from the code that emits.

Ties are not curves. The Curve doc says slurs engrave to one "and ties and
other span curves will follow" -- future tense; constrained.rs has a Slur arm
and no Tie arm, so ties trace as zero-extent anchors. The casting lines first
cited only transform curves produced upstream. Ties are now zero for the
primary ladder, and the decisive reason is not the miscount: every structural
anchor is emitted at the default x, which is the clef column, so casting files
all of them into the first system regardless of musical position. At the top
rung that would pile 2,400 anchors into the most likely damage target and
distort the per-system rebuild timing the whole spike turns on.

The census counts the parser's output, not the source string. H and V each
become a PathCommand::LineTo, so counting M/L/C/Z tokens undercounts exactly
the commands a tessellator consumes -- a sharp is 71, not 57. The census is now
defined as render_data(name).outline.len(), which measures the API candidates
actually see and removes the whole error class.

Ledger lines are emitted per staff step, not per off-staff note. A note one
space outside the staff emits none at all; the first ledger sits two steps out.
So off-staff pitches are pinned by absolute step rather than by "ledger
position", which would have produced about half the ledger ink it implied.

Also corrected: an all-eighths bar cannot yield a quarter rest (rest_glyph maps
value to glyph exactly), 4/4 uses timeSig4 which is single-subpath rather than
the multi-subpath coverage first claimed, barlines are glyphs and not strokes,
and the accidental cadence is one fixed cycle with bounded edge imbalance --
not equal thirds, since no rung's measure count divides by three.

Two-voice engraving is NOT unproven -- rs4_two_voice_counterpoint passes
against the real engraver. The narrower gap is collision handling, rest
displacement and deliberate stem-direction separation, and the ladder stays at
one voice as an isolation choice rather than out of doubt.

The remaining gate is separate and still open: no candidate code exists until
the generator emits its per-rung census, glyph histogram, SHA-256 of each
rung's canonical layout bytes, and the F4 engrave preflight -- and that output
is approved. Recipe approval is not generator approval.

Docs only. No Cargo manifest yet, so the root workspace is untouched; the
exclude line lands with the generator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 17:50:12 -04:00
Levi Neuwirth a229a977a2 Scope G-minor: the schema minor is content-addressed, and that changes the rung
The scoping question was whether the chunk header's minor reaches a pinned
corpus. It does, and worse: chunk_content_hash pushes SchemaVersion's canonical
bytes -- major AND minor -- into the preimage, so raising a minor changes the
ChunkId and propagates to the manifest that names it. Not a semantic break,
since readers gate on the major only, but a content-address-moving change
landing on the structure req:format:manifest-id makes a two-writer promise
about.

The text projection also projects the minor as a document surface, and the
committed corpus carries six literal (schema 0 1) occurrences, so op-block
minors rising means another companion bump on the G1/G2a precedent.

The policy is the real work and is left for ratification: one minor per tranche
(a retroactive judgement needing a hand-maintained table -- a seventh such site
on a track whose defining lesson is that they go stale), minor as the highest
discriminant emitted (derivable, self-describing, nothing to maintain, but
redefines the term and is per-vocabulary), or a per-major append counter. I
recommend the second and say why the recommendation is not a default.

Four further questions logged, including whether the canonical base is exempt:
its major is role-pinned to 0 and its byte-identity test pins the payload, not
the header, so a base whose minor rises keeps its payload and moves its id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:49:29 -04:00
Levi Neuwirth 55eff00778 G2a review fixes: a false deviation, and two tests that proved less than claimed
Four findings, all confirmed against the tree.

The accept-set "deviation" was not real, and I propagated it. bundle.rs:1322
has asserted max_supported_major(OperationEnvelopeBlock) == 2 since before
this packet, so the gate was always satisfiable; the privacy observation about
the symbol not being re-exported is true but irrelevant, because the assertion
never needed to live outside the crate. I checked the premise and not the
conclusion, then reported it as verified. No re-export is needed here or by
G-minor. The test now also asserts what the contract actually asked for -- that
a staged block carrying either kind stamps major 0, exercising the writer-side
derivation rather than the bare kind.

s3 asserted effects only, though the contract required chain growth too. A
mutant returning Applied while skipping WriteChain::record for an unchanged
value passed it, and the damage would surface only later as an undo restoring
the base instead of reporting supersession. The two identical writes now sit in
different transactions and a strict undo of the first must report the second as
superseding; mutation (b) kills exactly that half while the effects half stays
green.

s9 claimed no mutation was needed because the test is itself a reject-path
exercise. That reasoning was wrong: being a reject-path test does not show the
rejection is caused by the mislabeling rather than something incidental. The
mutation -- a parse arm that silently accepts a mismatched shape -- was both
performable and killing.

core_spec grouped SetCanvasLayoutDefaults and CreateInstrument as leaves with a
single layout. True only of the first. Instrument has distinct major-0/1/2
layouts and stamps major 2 unconditionally because its major-2 appends are
mandatory, so it is not major 1 for the opposite reason. Split.

All ten contract mutations now have observed kill evidence: s1, s2, s4, s8 and
s10's row-28 half were run here alongside the four recorded earlier. s8 killed
only the two pinned literal-byte vectors while 217 round-trip tests stayed
green -- the 3b-i property, demonstrated rather than asserted.

Gate: fmt clean, clippy 0, 1371 passed / 0 failed, conformance 8/8 and 9/9,
labels 6/6, core_spec PDF 0 undefined refs, goldens byte-identical,
epiphany-bundle diff empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:37:43 -04:00
Levi Neuwirth 61890a046f Editor T4 opens: the toolkit spike gets a contract that cannot be argued with
Ruling A grants the architecture -- direct vector canvas over ResolvedLayoutIR,
viewport-culled, per-system damage -- and deliberately leaves the toolkit open
for this spike to decide under six recorded criteria. All three prerequisites
are discharged (W1 dd33b34, W2 24f8c80, W3 f639919), so the tranche opens.

The contract's problem is not what to build but how to keep the answer honest.
Six criteria across three candidate stacks is unbounded work with a dozen
places for the verdict to be decided by whoever writes the report. So the
structure is an elimination ladder, cheapest disqualifier first, with every
deciding number pinned before it is measured: the fixture ladder F1-F4 and its
musical-content recipe, the deciding rung, the equivalence band, the tie-break
and its three operationally defined axes, and the oracles -- committed, with
their commit recorded, before any candidate renders against them.

Five review rounds shaped it, and most of what they caught was mine.

Measurement. Timed work must be completed GPU work, not submission, or a
candidate that returns after enqueueing looks fast. One common configuration
across all candidates: offscreen, 1920x1080, identical MSAA and format,
--release --locked, both adapters with the integrated one deciding. Freehand
latency runs from scheduled event injection to completion of the first frame
containing that event, backlog included; timing from renderer pickup would let
a candidate buffer the trace, render late, drop nothing, and report fast update
work while lagging visibly.

Damage. The first draft compared raw PrimitiveIndices, which are positions in
flat arrays -- so a pitch change that preserves primitive count moves geometry
while every vector stays identical, and an inserted accidental renumbers
indices so untouched systems look changed. It is now fingerprints over
dereferenced primitives, unowned included. And byte equality alone lets a full
redraw masquerade as per-system damage, so the oracle is paired with a reuse
assertion over the scene cache, at every engraving-valid rung, before the
ranking round rather than inside it.

Outcomes. A check that ran and got the wrong answer is FAIL, not NOT RUN;
conflating them let a broken implementation buy an escalation instead of
recording negative evidence. Capability and timing are separate cells.
Eligibility is a disqualifying set tracked apart from criterion cells, so a
user waiver actually reaches the tie-break instead of leaving a decision with
no effect. Both no-winner paths are total: disqualification, and ranking-loss
wipeout when nobody renders the deciding rung.

Maintenance inputs are reproducible rather than selectable: upstreams pinned
per candidate before any figure is fetched, composites taking the worse value
per axis, maintainer identity machine-observable, the issue snapshot archived,
and unanswered issues contributing infinity rather than their current age --
which would otherwise score twenty ignored issues opened yesterday as
excellent responsiveness.

What the spike is not: the app. No command registry, no editing, no document
layer, no goldens. Ruling D creates epiphany-editor-app fresh after this
verdict. And it does not reopen Ruling A -- a finding that no candidate can
implement the granted architecture escalates as an amendment request and does
not silently promote the SVG path back.

Docs only; no code, no crate, no gate movement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 17:26:23 -04:00
Levi Neuwirth 7df5ca17ce Genesis G2a: two settings setters, and the vocabulary's documentation catches up
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
2026-07-28 17:07:46 -04:00
Levi Neuwirth 4a4988ce88 The latency wall is document-lifetime, not one sitting
Review found the mitigation attached to the last landing reading the wrong
code path. EditorSession::open does start with an empty applied log, but that
is the score-only probe constructor. The savable-document path is Ruling B's:
reopen is full replay, stored envelopes load as a committed partition, and
materialization reduces committed plus session operations together. Nothing
resets the depth this bench varies until the checkpoint and pruning machinery
assigned to T4b can write a new canonical_base.

So consequence (e) is withdrawn rather than corrected in place. The ~4,500-edit
wall is a budget on a document's whole accumulated history, and there is no
session reset to lean on -- neither as reassurance about the number nor as
support for the sequencing argument, which rests on paint dominance and does
not need it. T4b's trigger is correspondingly firmer than it read yesterday.

Also: two comments still described the gated core portion as reduce plus
engrave, from before envelope construction was added as a third stage. The
sum they document has included it since the last landing.

Verified in an isolated worktree at HEAD rather than in the working tree,
which still carries the genesis tranche's in-flight work: fmt clean, clippy 0
with and without golden-gate, gate OK with every verdict unchanged (the edits
are documentation only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 16:27:59 -04:00
Levi Neuwirth 986c9cc5a1 The edit-latency bench measures the log a session actually writes
Review found the first version generating every envelope with an empty causal
context. Production gives only the root that shape: each later edit carries
active_prior_context(), the head's context extended by the head, so it covers
the whole active prefix. Reduction orders on those edges, so a context-free log
exercises a different algorithm -- and a cheaper one. Counters were wrong the
same way: EditorSession mints at authored.len(), so the root is counter zero,
which is also what extend_context recognises as the start of a contiguous run.

Remeasured on a session-shaped log, reduce is roughly three times its former
self at depth ten thousand -- 54 ms, not 17 -- and the wall moves from about ten
thousand edits to between three and five thousand. That is the number T4b is
sequenced against, so the first table would have mis-sequenced it. Depths three
and five thousand now bracket the crossing; sampling only decades hid it.

Two findings survive the correction and one is weakened. Reduce is still the
only depth-scaling stage, and is superlinear at about n^1.4 -- which does not
contradict the reduction bench's subquadratic result at fifty thousand
envelopes, because that log is generated across three replicas with a different
causal shape, and two logs of equal length are not equal work. Engrave is still
flat, at 260 to 327 microseconds, and is still the larger half of the core's
portion at shallow depth, so criterion 2's "uninformative while reduction
dominates" holds only past roughly depth five hundred. But "render dominates at
realistic depths" is now bounded: paint leads by four and a half times at depth
one hundred, is level by one thousand, and is left behind after. T4 before T4b
still stands -- the canvas removes what dominates a session's first thousand-odd
edits -- but the two are no longer comfortably separated.

The gate now includes envelope construction, which the requirement names first
and the first version silently dropped. It is forty nanoseconds and never moves
a verdict; a gate that omits a named component is a proxy for the requirement
rather than the requirement.

The 98% claim is replaced by both figures with their denominators named: what a
direct-IR canvas avoids is 83% of the full measured per-edit pipeline, and 99.8%
of the render path alone. The unqualified number was supported by neither.

One row changed marking for a reason worth recording. Depth four thousand passes
clean at 12.99 ms, but that is 78% of budget, and a load-contaminated run
measured it at 22.77 ms -- above the five thousand row, which is impossible
clean. A Pass row that fails whenever the machine is busy teaches people to
ignore the gate, so the last gated Pass is three thousand and four thousand's
clean number is kept as data in the table instead.

Also recorded: depth is per session, not per document. EditorSession::open
starts with an empty applied log, so reopening resets it and the reduced score
becomes the new pristine base. That is what keeps a four-figure wall from being
catastrophic -- though note entry mints one operation per note, so it is
reachable in a sitting.

Verified in an isolated worktree at HEAD rather than in the working tree, which
currently carries the genesis tranche's in-flight G2 work: fmt clean, clippy 0
with and without golden-gate, workspace tests green, gate OK across all five
rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 16:03:27 -04:00
Levi Neuwirth 4043627898 Contract G2a round five: the sixth hand-maintained site, and locking Registered
The literal-site count said four and named three plus two generator bounds. The
omitted one is OperationKind::discriminant() -- the hand-written match that
Push 4a got wrong in the first place, and the one the contract elsewhere notes
the tag macro does not guard. All six are now enumerated, with the count after
row 29's derivation stated as five so the next tranche inherits an accurate
number rather than this one's.

s10's layout-stub half asserted kinds 30-33, which would have survived deleting
the required Registered append -- PAYLOAD_FREE excludes it by design and the
generator's doc promises every variant, so the gap between them is exactly
where the bug would live. It now asserts PAYLOAD_FREE union {Registered}, with
removal of Registered as the mutation. Future built-ins follow structurally
with no test change; the one payload-bearing variant that cannot stays locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:47:17 -04:00
Levi Neuwirth 2b86e8033a Contract G2a round four: three generator sites, and a grep I read and ignored
All three confirmed. Two of them contain SetMetadata and appeared in the very
grep the touch table claimed to be derived from, so the derivation claim is
struck rather than repaired: the evidence was on screen and went unused, which
is worse than not having looked.

generators.rs:647 and layout_stub.rs:951 are both already stale by two
tranches -- neither emits TransposeInterval or CreateInstrument -- and both
feed corpora other suites treat as exhaustive, so a missing kind is untested
everywhere downstream while every suite stays green. layout_stub's doc comment
claims every variant while its bound omits two. Its fix is structural rather
than additive: derive the built-ins from PAYLOAD_FREE and append Registered,
which payload.rs:1991 already does. New test s10 asserts both generators emit
all four appended kinds.

textproj_kind.rs:597 is a fourth hand-maintained literal count.

The pattern is worth naming: operation_kind_tag_vocabulary! exists because
Push 4a added TransposeInterval to a hand-written match and nothing else, and
its own doc says four hand-maintained lists stayed green. The macro made the
compile-enforced half safe, and these three lists went stale at that same
append anyway, because nothing forces an rng bound or a literal count to move.
Hence: prefer deriving over extending wherever a list can be derived, and
report the sites that cannot be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:43:18 -04:00
Levi Neuwirth 5eac1c9bc7 Contract G2a round three: Push-4a listing debt, and the ladder order
TransposeInterval is missing from both Core normative listings, which carry the
older Transpose and never gained its successor at kind 30. So the vocabulary
has drifted from its normative listings for two tranches, not one, and both
listings need four additions rather than two. The contract's grep list gains
TransposeInterval, and names the signature worth hunting: a spelled-out count
that disagrees with the enumeration beside it.

for_major does not return {major, 0} unconditionally -- V0 is {0, 1}, and only
V1 through V3 carry minor 0. Corrected in the contract, the plan, and P13-S14.
The finding is unchanged: the function accepts only a major, so no per-kind
additive minor can reach it.

Ladder order is now explicit as G2a -> G-minor -> G2b, with the reason. The
sweep is scoped to kinds 24-33, which is what exists once G2a lands; running
G2b first appends kind 34 and would either grow the sweep mid-flight or ship 34
carrying the defect the rung exists to retire.

Core is five live-text edits plus one historical annotation, not five edits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:29:32 -04:00
Levi Neuwirth 47fb4266c4 Ruling A criterion 2 stops being an assumption
The staged interactive-edit latency bench: reduce / engrave / scene-build /
paint measured separately, gating the core's portion against
req:perf:single-system-edit-latency's 16.7 ms frame. Criterion 2 asserts that a
toolkit verdict is uninformative while reduction dominates, and that sentence
had never been measured; the sequencing question it governs -- T4's spike now,
or T4b's incrementality first -- was resting on it.

The stage split is not invented here. It is the seam EditorSession::materialize
already walks, read off its private render_score and reproduced stage for
stage, so the bench measures the pipeline rather than a model of it. Only reduce
and engrave are gated: the requirement bounds "the core's portion" and says in
its own words that edit-to-pixel latency is a product-layer obligation, so
charging the SVG serializer and resvg against a core budget would be a category
error. They are measured and printed because the ruling asks for the stages
separately, and because today's is the path Ruling A demotes -- the number is
the baseline a canvas must beat, not a budget to defend.

Four findings, in the order they matter. Reduce is the only stage that scales
with log depth, near-linearly, and it breaks the frame at roughly ten thousand
edits -- 17.26 ms against 16.7, a three percent miss, so an order of magnitude
rather than a threshold. Engrave is flat and small at ~280 microseconds, and at
shallow depth it is the larger half of the core's portion, which qualifies
criterion 2 rather than confirming it: reduction does not dominate until about
depth five hundred. Paint is the largest single cost at every realistic depth --
2.12 ms at depth one hundred is four and a half times the entire core portion.
And scene-build is 3.5 microseconds of IR work plus about 130 of SVG
serialization, which the no-feature run separates: a canvas consuming the IR
directly skips some ninety-eight percent of today's per-edit cost, none of it in
the core.

The sequencing answer is therefore that T4 before T4b stands, for the opposite
reason to the one assumed. The dominant cost at the depths real sessions reach
is the render path Ruling A already demoted, not reduction. T4b's trigger is a
session ten thousand edits deep, and the bench now watches for it as the one
Xfail row.

Two things the bench had to survive being wrong about, both mine. The depth-1000
row was drafted Xfail on the assumption Fact 8 would already bite; it passes
with eightfold margin, the gate's XPASS notice said so, and the row is promoted
here rather than left stale -- which is the whole point of that mechanism. And
the first edit log alternated transposition direction per operation, which is
degenerate when the pitch-list length is even: every edit to a given pitch
pushed the same way, drifting it twenty-five semitones by depth 1000 and would
have been two hundred and fifty by depth 10000. That inflated engrave by a
factor of two and paint by nearly three -- a score-content change wearing a
log-depth costume. Alternating per pass instead bounds drift to one semitone.
The residual content effect is documented rather than hidden: paint is
non-monotonic in depth because pass-count parity decides how many accidentals
the score carries, and reading its dip at depth 10000 as a scaling win would be
a mistake.

Stated limitation: the testkit's largest fixture is three staves by ten
measures, so the engrave and scene-build columns are lower bounds and this
cannot prove the budget holds on the hundred-page orchestral score the
requirement contemplates. It shows where the time goes at the scale we can
build, and a row that misses at this size misses by more at a real one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 15:04:36 -04:00
Levi Neuwirth c39e58a755 Contract G2a round two: the schema-minor debt, and a test that could not fail
All four findings verified against the tree before fixing; all four hold.

The schema-minor MUST (binary_format.tex:2330) has never been implemented.
SchemaVersion::for_major accepts no minor and returns {major, 0}, and both
staging paths derive only the major, so kinds 24-27, 28-29, 30, and 31 already
carry no additive-version record -- and the requirement's own rationale is
exactly what the gap defeats: a reader meeting an appended discriminant cannot
tell a stale vocabulary from damaged bytes. Filed as P13-S14 and ruled a
separate rung after G2a, sweeping 24-33 in one retroactive pass rather than
blocking G2a on a debt eight kinds deep or paying for two partial sweeps. G2a
now says explicitly that it extends the violation by two, knowingly, and
forbids working around the absence.

s7 could not fail. WorkingSnapshot::restore reassigns the whole graph
independently of every write chain, so omitting a chain leaves stale history
while the field still rolls back -- the prescribed assertion passed under its
own mutation. Two framings of this test were wrong; the third asserts against
a later undo's predecessor, and the contract now requires the mutation be run
rather than reasoned about. s8's mutation was impossible as written: each
payload has one field, so there are no adjacent fields to swap. Swapping
discriminants 32/33 in both halves is the self-consistent mutation that leaves
round-trips green and kills correctly-named literal vectors.

The normative repair surface doubles: eleven sites across four documents, five
of them G1 debt. Core's normative OperationKind and OperationKindTag listings
are missing CreateInstrument as well as both new kinds; the catalog's
value-restoration family list is normative for undo and omitting a family is a
silent semantic gap; two spelled-out payload counts move. Since two independent
reviews each found sites the other missed, the list is a floor and the contract
now prescribes grepping the load-bearing phrases.

Also: core_spec said "two edits" and prescribed more, and the split-cost
accounting counted only Text Projection when G2b repeats the Binary Format and
Operation Catalog work too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 14:48:10 -04:00
Levi Neuwirth f639919ee1 Editor T4-pre W3: the text run carries both its string and its ink
The last T4 prerequisite, and the one that had to be a ruling rather than a
packet: a canvas, an exporter, a hit test, and an accessibility tree must agree
about text, and what they agree on is decided by where the shaper runs relative
to the canonical boundary.

The census reframes the tranche. Three of the five categories the plan names --
lyrics, chord symbols, rehearsal marks -- carry no text in the model at all:
LyricLine holds only event references, ChordSymbol and Marker only an anchor.
They are blocked on a core-track schema major, not on this decision. What the
primitive does gate is the text the model already has: score metadata,
instrument and staff names, and text-line spanners. That is a smaller v1 than
the plan implied and a real one, and bidi and fallback are exercised through
synthetic fixtures that need no model work.

The ruling is a fourth resolved primitive carrying the source string and the
canonical shaped result together. The alternative that discards the string
renders a title as anonymous outlines and is unreadable to a screen reader; the
alternative that discards the shaped result lets two consumers draw the same
bytes differently, which contradicts the definition of canonical_bytes as the
rendering fingerprint. Both halves stay, and the apparent trade between
deterministic geometry and accessibility turns out not to exist.

Two drafts were wrong in opposite directions and the errors are recorded rather
than quietly fixed, because each came from asserting a constraint instead of
reading the requirement that governs it. Draft 1 held that shaping before the
canonical boundary poisons cross-implementation byte equality -- but layout
determinism is byte-equal only within one implementation at a fixed version;
across implementations it is reference-suite thresholds, and the spec says so in
both the determinism table and req:solver:cross-implementation-conformance.
Draft 1 had imported the score layer's guarantee into the layout layer, where
the spec deliberately weakens it. Revision 2 then over-corrected, banning host
fonts outright on the grounds that an OS font update breaks fixed-version
stability -- but that requirement defines identical inputs to include font
metrics referenced by version and content hash, so an updated font is a changed
input. The rule that survives is narrower than either: no ambient or unresolved
lookup, and a host face may participate only once resolved to an exact
content-hashed asset every consumer can obtain.

The identity is specified rather than gestured at, because bytes that do not
determine ink are worse than bytes that admit they don't. A face is pinned by a
hash over the font file, not its metrics -- GlyphCatalogIdentity's metrics_hash
covers bounding boxes, advances and anchors, which pins spacing and not shape --
together with face index, variation coordinates and synthetic weight/slant.
Segments carry font-internal glyph ids, source ranges, direction, script,
language and em size; glyph offsets have alignment already applied, so a
consumer places by origin alone; positions quantize on the same 1/1024 grid as
every other primitive. The cluster map indexes UTF-8 byte offsets with caret
stops at grapheme boundaries carrying bidi affinity, and the Unicode
segmentation version is always part of the identity -- otherwise two
implementations could agree on every pixel and still differ inside the
fingerprint, where no visual test would ever see it.

One consequence lands on the exporter: SVG cannot honour "no consumer reshapes"
with <text>, which carries characters and lets the viewer's shaper choose the
glyphs, so a ligature or positional form silently draws something the layout did
not resolve. Conformant text export emits explicit glyphs as paths through the
same face, reusing the mode render-svg already has for music.

The reservation is re-ordered to follow shaping rather than precede it -- with a
canonical shaper in the pipeline, reserved_box becomes a solver policy over
measured bounds, not an estimate of them. Paint-time re-spacing stays forbidden.

Two findings for the core track, named so their absence is a decision. Score
text authored through operations is not NFC-validated: the envelope's NFC-
checked string reader covers only directly encoded strings such as transaction
labels, while SetMetadataOp, CreateStaffOp, CreateInstrumentOp and the cross-
cutting values embed the core codec's bytes, which preserve non-NFC strings by
design. And the .tex amendment adding the primitive changes the layout
fingerprint but needs no bundle or wire schema-major move, following strokes and
curves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 14:38:06 -04:00
Levi Neuwirth 4a4a766d4b Contract G2a review fixes: the normative surfaces, and a G1 debt
All four findings verified independently against the tree before fixing; all
four hold, and the first is worse than filed.

P1 (normative wire docs) is not a G2a omission but a G1 regression. 3b09595
touched operation_catalog.tex and text_projection.tex and no other .tex, so it
shipped four falsehoods: binary_format's payload-layout and tag tables stop at
kind 30 while 31 is assigned in code, and req:binfmt:kind-discriminants says
each row pins the payload's byte layout; binary_format:2432 still asserts there
is no CreateInstrument; core_spec:12186 still says Canvas.layout_defaults and
Instrument.range reach only the non-canonical snapshot; and the catalog gained
a CreateInstrument section with no version bump against its own convention.
G2a now repairs all four and adds its own two surfaces, with explicit version
and changelog dispositions for binary_format (0.11.0 -> 0.12.0), the catalog
(0.9.0 -> 0.10.0, covering G1 retroactively), and two narrow core_spec edits.

The core_spec:5114 amendment is pinned rather than left to a subagent. The
K8 paragraph has a load-bearing half the ruling does not touch -- root and
canvas remain structural givens no operation mints -- and only the
outside-the-operation-set clause is superseded.

P1 (source) adds ops/src/lib.rs re-exports and the textproj parse.rs literal
HEADER fixture, whose tracking test is a tripwire that fires by design.

P2: s8 and s9 gain the mutations they lacked, and s2's was fabricated --
set_metadata contains no comparison to reverse, it records and overwrites
unconditionally, so the mutation is now setter-local first-write-wins. s7's
framing was backwards: WorkingSnapshot is transaction rollback, so omitting a
chain retains a failed transaction's write rather than losing history, and the
wrong framing would have sent the test hunting the wrong symptom.

Records the G2b holdout: ScoreTuningContext's Codec deliberately drops
accidental_extensions while OperationSet::accept stores envelopes as objects,
so a naive full-value SetTuningContext diverges between a live session and the
same document reloaded -- and canonical_value! cannot see it, because its
check compares bytes and never the originating value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 14:35:53 -04:00
Levi Neuwirth be40eb2d7d Contract G2a: the split, and the two setters that move no wire bound
G2 was scoped as one packet of three LWW setters. Reading the frozen codec
walks rather than the type labels shows they do not sit at the same major:
SpellingPrecedence has never been versioned, CanvasLayoutDefaults is versioned
in the containing Canvas walk and not the leaf, and only ScoreTuningContext is
born at v3. So the accept-set raise — a one-way door — is charged to one
surface, not amortised across nine as the ruling's framing implied. G2 splits:
G2a is the two major-0 setters and touches epiphany-bundle not at all; G2b is
SetTuningContext alone, carrying the raise and the S13 close.

Withdraws plan trap 5. SetMetadata already answers it: Score::empty seeds
metadata exactly as it seeds tuning_context, the base ingest seeds the LWW
chain from it, and restoring that seed is correct for both never-authored and
authored-to-default. These are always-valued fields, not map keys, so the
Predecessor::Base/::Write distinction that matters for spellings and breaks
does not apply here.

Records the G1 lesson as a trap in its own right: an OperationKind variant is
not containable to core+ops, and the G2a contract budgets all three downstream
literal sites up front instead of discovering them mid-dispatch.

Corrects S13's amortisation claim, and states the closure argument properly —
the canonical base is a MaterializedState that embeds no graph values for any
field, so S13 closes on the metadata precedent, not on the base. Notes the
consequence: after G2b, pruning would discard authored state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 14:18:49 -04:00
Levi Neuwirth 3b09595196 Genesis G1: CreateInstrument, and the from-empty spine reaches a note
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
2026-07-24 21:02:12 -04:00
Levi Neuwirth 24f8c8099a Editor T4-pre W2: the glyph outlines become a shared typed seam
A canvas tessellator needs typed vector paths, and the outlines were private to
render-svg as SVG `d` strings. They move to a new epiphany-glyphs crate, which
implements layout-ir's GlyphCatalog with a real render_data.

The seam was already designed and merely unpopulated: PathCommand,
GlyphRenderData, and GlyphCatalog::render_data all existed, and BravuraCatalog
returned None by deliberate documented honesty -- reporting Some would claim
render data that does not exist. Like W1, this packet fills a structure rather
than building one.

Typed paths are parsed from the `d` strings rather than regenerated, because
Bravura.otf is not vendored -- tools/ carries only the extractor and its OFL
notice, and the generated header pins source hashes verified at extraction
time. Equivalence is therefore proven rather than assumed: parse every bundled
glyph, re-emit, compare byte-for-byte. All thirty-seven round-trip exactly, so
the sanctioned coordinate-comparison fallback was not needed. The real grammar
is wider than the contract's summary claimed -- absolute V/H lineto shorthand
appears in twenty-three of thirty-seven glyphs, and numbers carry nought to
three fractional digits with trailing zeros stripped, never a fixed four. The
parser lowers V/H to LineTo and the emitter reconstructs the shorthand from
geometry.

Nothing rendered moves. The outline data is byte-identical to its previous home
(the only edit is BravuraOutline's visibility), render-svg keeps emitting the
stored strings and re-exports bundled_glyph_count and smufl_codepoint
unchanged, and BRAVURA_METRICS is untouched -- it feeds metrics_hash and hence
GlyphCatalogIdentity, which is inside the resolved layout's canonical bytes.
Verified: all eight reference fixtures produce byte-identical canonical layouts,
and all five GUI goldens are unchanged.

The cross-table test locks what glyph.rs asserted only in prose: every glyph's
declared metrics bbox contains its drawn ink, re-derived through true
cubic-bezier extrema rather than control points. Worst-case deviation across the
whole table is exactly zero, at noteheadBlack's left edge. Stated honestly, this
is a rounding-chain lock rather than an independent cross-check -- the same
extractor emits both tables and deliberately rounds the metric box outward so it
always contains the ink -- but it catches a unit-conversion error, a
regeneration mismatch, or a future inward rounding, none of which had a test
before.

Five of six mutations killed, all coordinator-re-verified independently. The
sixth is an honest survivor and a defect in the contract rather than the work:
it asked for proof that re-emitting from the typed form breaks the goldens,
which the round-trip pin makes a no-op. The goldens were re-proven live a
different way -- perturbing one glyph's stored path by one digit flips one pixel
of 279,992 and fails all five.

Gate: 35 suites / 1351 tests / 0 failed, conformance 9/9 with golden-gate and
8/8 without, requirement labels 6/6, clippy 0, cargo tree showing exactly one
dependency and no third-party crate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 18:54:30 -04:00
Levi Neuwirth 4e30fe8475 Contract G1: one operation, and the two things that make from-empty correct
The ladder is ratified, so this contracts its first rung. CreateInstrument is
the single missing link between Score::empty and a note -- CreateStaff already
demands a live Instrument and nothing can create one -- so one kind satisfies
the ruling's acceptance criteria 1 and 3. Instrument holds no outbound entity
references, so the operation needs no referential preconditions at all, only
mint and idempotence.

Enumerating every CreateStaff site turned up two surfaces PLAN_GENESIS_OPS
under-counted: v0.rs's V0OperationKind and migrate.rs in both directions. The
table now lists fifteen, each anchored to its CreateStaff analogue, and all
twenty-two line citations were checked against the working tree.

It also turned up a gap the plan missed. TypedObjectId::Instrument liveness is
already seeded from the base, but there is no instrument_values map, so
byte-identical re-carry against a BASE instrument has nothing to compare. Pin 8
adds one mirroring staff_values at all six of its sites and calls out the
snapshot/restore pair as the silent one -- missing it surfaces only under
undo/replay, which is why i3 tests that case specifically.

Pinned against the traps this project keeps hitting: schema_major is
unconditionally 2 (Instrument's major-2 appends are mandatory, not
Option-hidden, so the value-dependent arm shape would be wrong); the accept-set
stays 2 and epiphany-bundle is untouched, since the raise belongs to G2 alone;
kind 31 and tag 31 coincide by accident, not by rule, and only the tag half is
compile-enforced; and the decode vector is pinned to literal bytes because
round-trip locking cannot see a self-consistent reorder.

Ten tests, each with the mutation that must kill it, plus an explicit note that
i6 documents a designed asymmetry rather than guarding a bug -- base-free
reduction skips referential preconditions because it has no universe to check
against, and a later reader must not "fix" it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-24 18:04:30 -04:00
Levi Neuwirth a35f534235 Editor T4-pre W2: the glyph-asset contract, and three parallelism claims get finer
The W2 contract charters the shared typed glyph-asset seam Ruling A names as a
T4 prerequisite. Scoping it turned up the same shape W1 had: the seam is
already designed and merely unpopulated. PathCommand, GlyphRenderData, and
GlyphCatalog::render_data all exist in layout-ir, and BravuraCatalog returns
None by deliberate documented honesty -- reporting Some would claim render data
that does not exist. So the packet fills a seam rather than building one.

Two findings reshape it from the sketch carried in the W1 contract. Bravura.otf
is not in the tree -- tools/ holds only the extractor script and OFL.txt, and
the generated header pins source hashes verified at extraction time -- so
"have the generator emit typed paths alongside the d strings" cannot be
executed here. The contract replaces it with a dependency-free in-crate parser
over exactly the grammar the generator emits, and proves equivalence by
round-trip: parse every bundled d, re-emit, compare byte-for-byte, with a
sanctioned coordinate-sequence fallback that must be reported if used. And the
metrics table is conformance identity -- metrics_hash hashes (name, metrics)
pairs with values participating, and GlyphCatalogIdentity is encoded into the
resolved layout's canonical bytes -- so it is out of bounds entirely.

The test worth watching is the cross-table one. glyph.rs claims the metrics and
the outlines agree because both came from the same Bravura release; that is
asserted in prose and tested nowhere. The contract requires comparing each
glyph's real outline extent against its declared bbox, reporting the worst-case
deviation, and treating a failure as a finding rather than a reason to widen
the tolerance -- it would mean engraving reserves the wrong space for that
glyph, which is the bug class that twice bit the vertical metric.

Three parallelism claims are corrected in the same pass, all mine and all too
coarse. The plan and the ruling both said T1b's lease/save/single-writer
machinery could be contracted in parallel with the genesis work. That was
written before the ladder existed, and it is now per-rung rather than
unconditional: G1 needs no accept-set raise and never enters epiphany-bundle,
so T1b's bundle work runs beside it, while G2 spends the raise in bundle.rs
where T1b's single-writer enforcement also lands, so those two must not fly
together. Ruling B's blocker note also still described the identity disposition
as blocking; it is ruled, and now points at where.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 17:50:02 -04:00
Levi Neuwirth 439e1e2e14 Scope the genesis operation tranche: a ladder, and one very short first rung
The ruling names nine surfaces. Each op touches roughly thirteen places -- the
kind variant, four hand-written matches, the decode arm and its validation, the
reduction arm, both text-projection arms, validate, vectors, the fuzz
generators, and the catalog. Nine at thirteen is not one dispatch, so this plans
a ladder rather than a contract.

The load-bearing finding is that G1 is one operation. Score::empty gives
Canvas::default() and empty vectors, and the chain to a note is
CreateInstrument -> CreateStaff -> CreateRegion -> CreateStaffInstance ->
CreateVoice -> InsertEvent, of which only the first is missing. Every later
arrow already exists with graph-aware preconditions. So a single new kind
satisfies the ruling's acceptance criteria 1 and 3 and unblocks T1b.

It is also cheap. Instrument's major-2 appends are mandatory rather than
Option-hidden, so CreateInstrument is unconditionally born at major 2 -- exactly
where CreateStaff already sits, and the op-block accept-set is already 2. No
raise, no wire change. Checked every carried type's fields for the same
property: eight of the nine are major 0 or 2, and SetTuningContext alone drags
OperationEnvelopeBlock to 3, so the raise is spent once in G2, where S13 closes.

The tranche also designs no wire layouts. All nine carried types already have
Codec impls and already ship inside Score; CanonicalValue delegates to them
without introducing a layout, and its generated decode_canonical already
rejects non-canonical encodings, so each payload inherits strict-form
enforcement on the same seam the decode-vector corpus uses.

Traps recorded, including one that would bite quietly: OperationKind's
discriminant match is hand-written and is a DIFFERENT space from the
macro-guarded tag space -- they are not aligned, and Push 4a's four-green-lists
failure is why the tag half is a macro now. Also that bundle.rs documents the
accept-set cap with the tuning-context rationale in prose, so the comment must
move with the number.

Four questions need ratification before a G1 contract: delete/modify coverage
per family, the decomposition_attachments derived-not-authored call, the
measure/meter invariant, and the ladder shape itself.

All fifteen line citations verified against the working tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-24 15:25:25 -04:00
Levi Neuwirth ec17d06e77 The identity cursor is ruled, and three records stop contradicting each other
RULING_GENESIS_PERSISTENCE.md §3 was the tranche's stated blocker. Ruled:
identity stays on Score and stays canonically encoded, byte-equality claims
confine to MaterializedState, and from-empty reduction derives next_counter
from the log.

Scoping it turned up that the section understated the problem. Verified in the
tree: epiphany-ops has no `.identity` reference at all, so reduction never
advances the cursor; invariant 11 checks only the reserved replica, never the
counter against ids present; and every mint from score.identity today is under
cfg(test). Score::identity is an authoring cursor reduction never touches, and
this tranche is what activates the hazard -- under from-empty the cursor sits at
the seed while the log already holds that replica's ids at 0..N. Divergent bytes
were the lesser problem, and none of the three options originally listed fixed
the larger one.

The manifest option, previously recommended, is rejected on evidence:
req:format:manifest-id promises two conforming writers derive identical
ManifestIds, which a replica-scoped field in a shipped content-addressed
structure cannot honour. The two wire options each cost schema major 4 on the
role 3b-i just froze at 3, and neither corrects the cursor.

Also reconciles two records against 011c68a. DECISIONS.md flatly prohibited a
SetTuningContext operation, which the ruling now requires; the prohibition is
marked superseded and re-scoped to what it was aimed at -- no tuning-only fix,
no wire widening to compensate -- both of which still hold. PASS13-S13 moves
from blocked-on to resolved-by, naming which of the four dispositions was taken.

Flagged for the tranche, not fixed here: bundle.rs documents the
OperationEnvelopeBlock cap of 2 with the tuning-context rationale in prose, so
that comment becomes false when the cap moves.

Gate: requirement_labels 6/6. No .md here is include_str'd or compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-24 15:17:55 -04:00
Levi Neuwirth 011c68a831 The operation set absorbs genesis
Ratified 2026-07-24, resolving Ruling B blocker (i). Pass-12 K8 is reversed:
every mutable field of Score becomes operation-authored, and a document is
Score::empty(identity) plus its envelope log. The instrument, staff, staff
instance, voice, event chain is authorable end to end.

The decision removes machinery rather than adding it. Every alternative kept
genesis outside the operation set and then had to pay for that: a new chunk
role, a manifest field, an immutability rule, and a merge or fail-closed rule
for a canonical payload with no CRDT semantics. Genesis state is edited —
instruments get added, page geometry changes, temperaments are chosen — and
each alternative made those edits single-writer, unmergeable, or impossible.
Concurrency is a first-order product commitment, so the exception was not
worth institutionalising in the format.

Scope is nine surfaces over two templates already proven in reduce.rs: three
LWW settings setters on the SetMetadata pattern (canvas.layout_defaults,
tuning_context, spelling_precedence) and six entity mint families on the
CreateStaff pattern (instruments, staff_groups, parts, analysis_layers, views,
and StaffInstance.measures), each with graph-aware referential preconditions.
Delete and modify coverage is left to the tranche contract rather than assumed,
since CreateStaff itself ships today with no DeleteStaff.

Measures are ruled authored rather than derived. TimeAnchor::Measure carries a
measure id that cross-cutting structures anchor to, so deriving measures from
the metric grid would make their identity a function of the meter and every
time-signature change would orphan the anchors pointing into them. The cost
accepted is that measure/meter consistency becomes an authoring obligation
backed by a graph invariant.

Three constraints are written in rather than left implicit. Pruning may not be
implemented until the canonical base carries graph values: a prune installs a
MaterializedState base whose effects are outcomes, not payloads, so nothing
rebuilds the score afterward — silent and total, and free to prohibit now
because no prune exists to break. The from-empty path must reduce through
new_onto with an empty Score rather than base-free, because the base-free mode
skips graph-aware preconditions by design and would silently lose referential
enforcement from the first operation. And the OperationEnvelopeBlock accept-set
raise 2 to 3 is spent once, so the new kinds land as one batch — this is a
different major from Push 4b's schema major 3, the Score and Snapshot role wire
that tranche 3b-i froze, and there is no free ride between them.

The analysis is corrected in place rather than rewritten, so the evidence the
ruling rests on stays readable. Two amendments: Measure is a ninth uncovered
surface the original table missed by scoring canvas.regions at container
granularity, and identity is promoted from a stated question to a blocking one
— IdentityContext is replica-scoped yet lives on Score and is encoded, so under
from-empty reduction two replicas with an identical log produce Scores
differing in an encoded field while the music is identical. That disposition
blocks specification of the tranche and is deliberately not ruled here.

Execution belongs to the Push-4b-class coordinated track; the editor track
consumes it. T1b's lease, save, and single-writer machinery does not depend on
the tranche landing and may be contracted in parallel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 15:11:36 -04:00
Levi Neuwirth dd33b34f08 Editor T4-pre W1: the resolved layout stops discarding its own partition
ResolvedLayoutIR carried a page/system tree and three flat primitive arrays
with nothing joining them, so a canvas wanting to re-tessellate one damaged
system had to infer ownership spatially — ambiguous exactly where it matters,
at cross-system slurs and boundary-straddling strokes. The partition was
never missing, though: casting-off computes it (system_of_slot, stroke_system,
curve_system) and the fold into ResolvedLayoutIR dropped it. This publishes
it instead of inferring it, so the answer is exact.

ResolvedSystem gains PrimitiveIndices — u32 index lists into the layout's flat
glyphs/strokes/curves — and ResolvedLayoutIR gains an unowned bucket of the
same shape. No primitive is split, merged, reordered, or renumbered; the flat
arrays are exactly what they were. The partition is total and disjoint: for
each array, every system's list plus the unowned bucket covers 0..len exactly
once, tested directly rather than assumed from construction.

Unowned is a first-class bucket with a real producer. The stub solver resolves
no per-system geometry — one default-rect system per region — so it publishes
every primitive unowned rather than fabricating an attribution it never
computed. Cross-system primitives need no special case: casting-off already
splits a spanning stroke or curve into per-system segments carrying
SYSTEM_CONTINUATION_SYNTHESIS provenance, and each segment is owned by the
system it was split into, not by the source's.

Ownership is excluded from the canonical encoding, for the reason vertical_band
already is: it draws nothing, so two layouts differing only in it are the same
rendered layout and hash alike. Encoding it would make the fingerprint more
fragile than the rendering it fingerprints — a casting-off refactor that
re-partitions without moving a pixel would become a byte-level break for
something no renderer and no conformance claim can observe. Verified against
main: all eight reference fixtures produce byte-identical canonical layouts,
and all five GUI goldens are untouched.

One derivation, structurally. The quality census consumed its own copy of the
glyph-to-system rule, and so did vertical_raw's system_of_glyph closure; both
now read the published glyph_system. That left CastLayout::system_of_slot with
no consumer outside the casting pass, so it is removed from the published
struct — a future consumer cannot grow a third copy of the rule, because the
raw material is no longer there. The per-glyph answer travels; the derivation
does not.

Six mutations, each killed and coordinator-re-verified independently: dropping
an index from a system's list, coercing unowned onto system 0, an off-by-one
system index, publishing a constant attribution, attributing a continuation to
its source's system, and encoding ownership into the canonical bytes. The
fourth is the one that proves the single-derivation claim rather than asserting
it — it kills three quality tests, two of which survived it before
vertical_raw was migrated.

Gate: 34 suites / 1341 tests / 0 failed, conformance 9/9 with golden-gate and
8/8 without, requirement labels 6/6 at 212/282/282, clippy 0, five goldens
byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 14:47:31 -04:00
Levi Neuwirth 962eab3ac5 Editor T4-pre: the IR readiness contract, and the genesis-persistence table
Two documents, both prerequisites rather than implementations.

CONTRACT_EDITOR_T4PRE_IR.md charters the layout-IR readiness tranche Ruling A
names as its prerequisite: per-system primitive ownership (W1), the shared
typed glyph-asset seam (W2, contracted but not dispatched), and the text-run
primitive decision (W3, an analysis packet). Its central finding reframes the
work — casting-off already computes the ownership partition and the fold into
ResolvedLayoutIR drops it on the floor, so W1 is "stop discarding it", exact
rather than heuristic. Eight design pins, six named mutations, and a
byte-neutrality gate that keeps the tranche compatible with the Push-4b
freeze.

The contract survived a source-level review that found one real defect: the
blast radius audited ResolvedLayoutIR literals but not ResolvedSystem's, and
pin 1 adds a field to both — editor-core's two-system hit-test fixture is a
full literal with no spread, so the packet could not compile the workspace
without it. Amended, along with the SystemId claim (no *production*
constructor; testkit's random cache generator has one), the stub solver's
total provenance aliasing, and the stroke-continuation citation.

ANALYSIS_GENESIS_PERSISTENCE.md is the field-by-field Score table Ruling B
blocker (i) requires before it can be resolved. It decides nothing; it
establishes what is true. Eight of Score's twenty fields have no operation
that can produce them — canvas.layout_defaults (the printed page geometry),
instruments, staff_groups, parts, tuning_context, spelling_precedence,
analysis_layers, views, plus identity — and decomposition_attachments can be
pruned but never authored. Each is reachable only through a base the reducer
is handed, and pruning is licensed to replace that base with a
MaterializedState carrying none of them.

Three findings follow: the genesis root is unreachable from an empty base
(CreateStaff preconditions a live instrument; nothing creates an instrument),
the canonical base cannot carry what the base-only fields hold, and there is
no canonical wire path from bundle bytes to a Score at all. They are one
problem from three sides — the format has no representation for graph state
that no operation authors.

Four dispositions are laid out with costs: a canonical genesis block, closing
the op-coverage gap, promoting the canonical base to carry graph values, or
scope-limiting T1b. The recommendation is the last now and the third as the
destination, because only that one makes pruning safe for every field rather
than for an enumerated list, and T4b needs the same checkpoint mechanism
regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 14:47:08 -04:00
Levi Neuwirth 91d44dbe65 P13-S13 defers to the genesis-persistence ruling
Ratified 2026-07-24. S13 is re-pointed rather than resolved: the tuning context
is not a special case, it is field 10 of an eight-field gap that
spec/ANALYSIS_GENESIS_PERSISTENCE.md tables across the whole Score —
canvas.layout_defaults, instruments, staff_groups, parts, tuning_context,
spelling_precedence, analysis_layers, views, plus identity, with
decomposition_attachments prunable-but-never-authored. Each is reachable only
through a base the reducer is handed, and pruning is licensed to replace that
base with a MaterializedState carrying none of them.

Independently re-verified against the working tree before recording: every one
of those fields' sole reduce.rs mention is a read-only base-seed read, and
epiphany-ops has no tuning-context payload at all.

So the entry drops the operation-vs-canonical-base framing it was filed with.
That framing was right about the axis and wrong about the scope: it maps onto
the analysis's dispositions B and C at eight times the size, and a
tuning-specific fix is exactly the shape the analysis argues against — "any
option that enumerates fields must be re-audited against this table every time
a field is added to Score". S13 is now marked blocked-on rather than open, and
whichever disposition the genesis ruling picks resolves it as a side effect.

The accept-set cost this entry surfaced is kept, repositioned as evidence
against enumerated per-field fixes rather than as a tuning-only tradeoff:
blocks stamp minimally, and bundle.rs caps OperationEnvelopeBlock at major 2
precisely because no operation payload embeds the tuning context, so a
SetTuningContext-style operation would drag a role accept-set raise along with
the kind append — for one field of eight.

The wire layouts stay frozen and correct under every disposition. This was
never about how the tuning context encodes, only about which carrier embeds it,
so epiphany-core's DECISIONS entry now records the matching prohibition: no
SetTuningContext operation, and no widening of ScoreTuningContext's layout to
compensate.

Docs only; no code path touched. Gate unchanged: fmt clean, 1336 passed /
0 failed, requirement labels 6/6 at 212/282/282.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-24 14:23:36 -04:00
Levi Neuwirth 6d37f39826 Text-projection parity: smufl and overrides project, and a persistence gap surfaces
Schema major 3 put `smufl` and `overrides` on the binary wire and left the text
surface alone. This closes that: `TextValue for ScoreTuningContext` projects
five fields in `fn enc` order, with four new leaf impls (SmuflVersion,
SmuflVersionRequirement, TuningScope, TuningOverride) mirroring the four Codec
impls 3b-i froze.

The rule that moved them is the one this projection always followed — the text
projection is the same canonical surface the binary codec is. What changed was
not the rule but a premise: the old doc comment excluded all three fields
because "no schema major 3 has been opened", and 3b-i opened it.
`accidental_extensions` was staged out of that major and stays in-memory only,
so it is still correctly absent — the staging line now falls in the same place
on both surfaces, which is what the inverted tests assert.

No header-version bump and no document vector moved. This is a value-projection
change, not a document-format one: epiphany-textproj has no reference to a
tuning context anywhere, and the canonical base projects as a single opaque
byte atom (req:textproj:base-snapshot-inline), not structurally. So
req:textproj:header-version's sole accepted version (0 7 0) is untouched, all
13 document vectors are unchanged, and req:textproj:roundtrip was never
violated — the tuning context is not part of the projected document.

Both tests were renamed and inverted, following 3b-i's treatment of the binary
pair rather than rewriting the tranche-2/3a decision entries. Mutation-verified:
making `overrides` project as an always-empty vector — the exact regression this
fixes — fails both.

Two stale-pointer classes fixed, both of the kind this pass was hunting: the
comment in codec.rs naming the old text-test names and claiming the text
surface was unchanged; and ScoreTuningContext's per-field docs in graph.rs,
which still read "In memory only this tranche" for `smufl` and `overrides` —
stale since 3b-i, and contradicting the struct-level wire note they point at.
The struct-level doc was already correct.

Filed P13-S13, found while scoping and deliberately not fixed here: the tuning
context has NO canonical persistence path. Its only *persisted* carrier is the
acceleration snapshot, which Chapter 8 makes non-canonical and regenerable; no
canonical carrier embeds it at all. No operation authors it (the ops
vocabulary's only `tuning` references are the per-pitch TuningReference::Inherit)
and MaterializedState does not carry it. So a per-voice tuning override survives
a snapshot round-trip but cannot be authored, replicated, or merged, which makes
req:tuning:tuning-resolution-order's scopes 2-4 unreachable from any exchangeable
document. Not a regression — nothing ever worked; major 3 made the gap visible
by giving the value a wire form and no way to reach it.

The candidate prices both routes. Ratification is about the carrier, not the
encoding — the layouts are frozen and correct either way — but the operation
route is not quite the free schema-minor it first looks: blocks stamp minimally,
and bundle.rs caps OperationEnvelopeBlock at major 2 precisely because no
operation payload embeds the tuning context. An authoring operation carrying a
TuningOverride would stamp v3, dragging a role accept-set raise along with the
kind append.

Gate: fmt clean, clippy 0, 1336 passed / 0 failed, doc 0, conformance 8/8,
requirement labels 6/6 at 212/282/282.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-24 14:02:31 -04:00
Levi Neuwirth 85d8af612f Editor T3: the note-entry caret [W1+W2]
The seam (editor-core): a session-local caret — (voice, position, entry
duration), never in the op log, undo leaves it advanced, a deleted voice
clears it — with enter_nominal/enter_pitch/enter_rest funneling to one
insertion core that reuses the pencil's make-room verbatim and advances
only on success. Octave inference is nearest-in-staff-steps to the
preceding note with a downward tie-break — proven unreachable for real
letter pairs (candidate octaves sit an odd 7 steps apart), so the
comparator is tested directly. midi_note_to_pitch (sharps-spelled v1
policy) is the testable core of MIDI entry; x_at_position mirrors
invert_x for the GUI's caret line, extrapolation-tested on non-uniform
anchors after the uniform fixture proved blind to the wrong-segment
mutation.

The GUI (editor-gui): N-toggled entry mode, mutually exclusive with
pencil via a pure toggle_exclusive; mode-gated letters A-G, R for rests,
arrow-key caret movement, the duration palette doing double duty; the
caret drawn full staff height at x_at_position from the staff's own
rendered strokes. G5 locks the entry loop: C-D-E-F entered at the caret,
each advancing position value-asserted as an exact rational before any
pixel, baseline user-reviewed. Twelve mutations killed across the pair,
coordinator re-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:55:05 -04:00
Levi Neuwirth ca0dcee10c Editor T3: contract for the note-entry caret
The input-method-agnostic entry seam: session-local caret, naturals-only
letters with nearest-octave inference (downward tie-break), the pencil's
make-room semantics, the x_at_position forward map, and the testable core
of MIDI entry — the device listener stays T4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:55:05 -04:00
Levi Neuwirth d96d79b8ba The decode vector corpus reaches the core score wire
The cross-implementation corpus (req:binfmt:decode-vectors) covered ops and
bundle but nothing from epiphany-core — the oldest and most load-bearing wire
in the repo had no vectors at all. 65 -> 93 vectors, 5 -> 19 surfaces.

This delivers a revision the companion had already ordered. binary_format.tex
asked for the corpus to extend to the representative layouts "which remain
round-trip locked rather than literal-byte locked" — a description, written in
advance, of the exact defect found in tranche 3b-i: swapping smufl and
overrides in BOTH halves of ScoreTuningContext's codec silently moved a
permanently frozen layout and passed all 1283 tests and 8/8 conformance. An
encoder and decoder that agree on the wrong order round-trip perfectly. Only
literal bytes can see it.

That mutation is now the mandatory regression vector
(core.score_tuning_context/overrides_before_smufl), and re-applying the swap
was verified to fail three ways: the corpus reports "declared reject, but was
ACCEPTED", the committed corpus goes stale, and the conformance suite fails.
Both the full workspace suite and conformance now catch what previously passed.

Almost no new machinery was needed: CanonicalValue was already public and
already did decode + finish() + re-encode + reject-on-mismatch, and already
covered four of the five representative layouts. RationalTime joins it (the
fifth, and the exemplar of the spec's own warning that an outer guard can mask
a lenient inner codec — it reduces to lowest terms on decode, so only
hand-written bytes reach that path), along with the five schema-major-3 types.
check() therefore calls real production API, not a harness-side wrapper: a
leaf reject vector exercises the code an external implementer must match.
Codec and Reader stay pub(crate); the only new public item is `pub mod
vectors`.

Two things deliberately shaped: core is appended LAST in the testkit chain, so
the regenerated corpus diff is purely additive — 56 insertions, 0 deletions,
which PROVES no existing vector's bytes moved and therefore that no existing
wire form changed. And the per-major Score vectors assert canonical_bytes()
equality only at major 3: majors 0-2 migrate by design, rewriting the bytes,
and their canonicality at their own major is already established by the frozen
per-major encoder's re-encode guard. Comparing there would fail on every
vector and invite "fixing" it by gutting the vector's meaning.

Also rebuilds binary_format.pdf, which commit 68b08ad should have carried and
did not — every other binary_format.tex commit includes it. The rebuild picks
up both that tranche's Schema Major 3 section and this one. latexmk -xelatex
twice: 0 errors, 0 undefined references, 60 pages.

Gate: fmt clean, clippy 0, 1321 passed / 0 failed, doc 0, conformance 8/8 with
gate [7d] reporting 93 vectors, requirement labels 6/6 at 212/282/282 (no new
req: labels).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:38:15 -04:00
Levi Neuwirth 62117d3426 Editor T2 W4b: slur/tie replay on paste, and the clipboard reaches the GUI
Paste now re-mints contained slurs and ties as fresh CreateCrossCutting
ops — fragment ordinals mapped to freshly-minted destination EventIds,
ids from new mint_slur_id/mint_tie_id under the same three-source
high-water discipline, inserts ordered before cross-cuttings because the
reducer preconditions live anchors — all inside the single paste
transaction, with the emitted-stream test tightened to the exact member
count and its kill re-proven against the per-op-commit mutation. Tie
replay is unconditional: pitch_pairing None is the model's own
ascending-PitchId default, not a gap (investigated, not assumed). The GUI
gains Ctrl/Cmd+C / Ctrl/Cmd+V and toolbar Copy/Paste over egui 0.29's
actual clipboard surfaces (copied_text out, Event::Paste in — verified
against vendored source), pasting over the selection anchor. Goldens
byte-identical; all mutations killed and coordinator re-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:27:08 -04:00
Levi Neuwirth 56950f8d7c Push 4b: ji-adaptive-5limit resolves, and the blocker was never HarmonicContext
The last fail-closed entry in the built-in tuning catalog now resolves, leaving
only the compatibility-mapping registry open in Push 4b.

The recorded blocker was wrong. Five places in tuning.rs claimed adaptive
"needs HarmonicContext, which does not exist in Rust". But
req:tuning:adaptive-default-version makes version 1 a pure function of
(position, anchor pitch class) — it ignores concurrent, recent, hints,
parameters, and mode. And two of HarmonicContext's four specified fields are
UNIMPLEMENTABLE: key_context and hints are typed on KeyContext / ContextHint,
which core_spec.tex:4111 leaves undefined deliberately, "so defining them now
would freeze a type surface on a chapter with no consumer". The minimal
one-field shape was not a preference; it was the only implementable one. Each
remaining field arrives with the first function that consumes it.

The real blocker was structural and small: locate_voice returned
(RegionId, StaffId) while key_sequence lives on StaffInstance, so the resolver
threw away the only object that could answer "what key is prevailing here".

No new pitch math. ji_static_5limit_ratios already took a runtime anchor — the
three ji-static-5limit-{C,G,D} built-ins are that one function at anchors
0, 7, 2 — so adaptive v1 is that same call with a derived anchor. Verified as
an identity: adaptive at anchor 0 is bit-identical to ji-static-5limit-C and at
anchor 7 to ji-static-5limit-G, across all seven naturals.

A missing tonal centre is NOT an error. core_spec.tex:3452 mandates C
(chromatic position 0) when none is supplied, so that is a defined default, not
a fail-closed case; a test pins it against a future "fail closed" refactor.
Fail-closed applies to exactly two things: an unregistered
AdaptiveTuningFunctionId (hard error, no fallback) and a TimeAnchor that cannot
be unambiguously ordered against the onset (AnchorNotOrderable, naming the kind
that defeated it). If any KeySignatureChange in a sequence is unorderable the
whole derivation fails, rather than risk skipping the true prevailing signature.

Anchor arithmetic is (7 * fifths).rem_euclid(12), never %: fifths reaches -7 and
% yields a negative pitch class. ChromaticPitchClass is a checked 0..=11 newtype,
so the sign bug cannot degrade into a silently wrong anchor — under mutation it
panics outright. Verified by hand across the whole -7..=7 range, including the
enharmonic collisions (6 and -6 both F#/Gb = 6).

Nothing reaches the wire: TuningResolution is catalog-computed and appears in
neither codec.rs nor textvalue_graph.rs, so TuningResolution::Adaptive and
HarmonicContext are in-memory only and schema major 3 is untouched. Zero Codec
impls added; no vector or golden moved.

Gate: fmt clean, clippy 0, 1311 passed / 0 failed, doc 0, conformance 8/8,
requirement labels 6/6 at 212/282/282. Independently verified: the C default,
static systems ignoring context, statelessness across reordered resolution, and
the 0..=11 invariant. Two mutations killed — rem_euclid -> % and a silent
fallback for an unregistered id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:17:46 -04:00
Levi Neuwirth 1dfa8b4f3a Editor T2 W4a: the clipboard fragment projection and copy/paste
Ruling E made concrete: a versioned s-expression fragment — values only,
reusing epiphany-core's own TextValue productions, no ids, no envelopes —
with named untrusted-input caps (1 MiB / 4096 events / depth 64) checked
before parsing and clean unknown-major rejection. copy_selection reports
closure drops (boundary slurs/ties) on CopyOutcome; a partially-selected
tuplet refuses; incidental rubber-band geometry (staff lines, stems) is
skipped, mirroring alter_selection's precedent. paste_at and
paste_over_selection mint fresh operations — InsertEvent + carried
RespellPitch — per-voice-ordinal lanes, make-room overwrite, one atomic
transaction; a second lane's refusal rolls the whole paste back
byte-identically. Contained slur/tie capture lands in the format; replay
on paste is W4b. Six worker mutations killed; coordinator review added a
seventh test pinning the emitted stream (one DeclareTransaction descriptor
plus members — descriptor-precedence at peers is canonical surface) after
its absence let a per-op-commit mutation survive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:36:13 -04:00
Levi Neuwirth f50f0ee52d Editor T2 W2: rubber-band selection in the GUI
Drag-to-select over the score view: a pure DragRect tracks the gesture, a
4.0pt movement threshold (under egui's 6.0pt click tolerance) keeps still
long-holds resolving as the clicks users intend — egui reclassifies them as
drags by elapsed time alone — and resolve_release dispatches rubber-band
vs click/toggle as a pure, unit-tested decision. Ctrl/Cmd-click toggles
membership; every member draws the existing highlight with the anchor
accented on top; pencil mode takes precedence and never starts a drag. The
T1a baselines are byte-identical: selection is overlay-side and never
touches the raster. Both mutations killed and coordinator re-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:35:54 -04:00
Levi Neuwirth 51f7ac9f61 Push 4b tranche 3b-ii: one SmuflVersion, and a live ordering bug dies with the homonym
Closes P13-S12. epiphany-layout-ir defined its own SmuflVersion storing the
minor LITERALLY ({1,4} for SMuFL 1.4), so derived Ord sorted 1.3 and 1.4
BEFORE 1.12 — backwards versus SMuFL's real release order — on a direct field
of GlyphCatalogIdentity, which is layout-conformance identity. That bug was
live. The crate graph fixes the direction: layout-ir depends on core, so
core's fraction-normalized minor_centi type is necessarily the survivor.
layout-ir deletes its own and re-exports core's, keeping
epiphany_layout_ir::SmuflVersion resolving for downstream at zero churn.

No wire change: no schema major, no Codec, no decode vector. LayoutCache is a
regenerable major-0 role. The one line that moves bytes is encode_catalog's
.minor -> .minor_centi (04 00 -> 28 00 at offset 2, verified by encoding a
default identity: 60 bytes, unchanged elsewhere).

The tranche also closes S12's OTHER half, which had gone unnoticed: the
ratified shape reached Rust and the binary companion but core_spec.tex never
defined SmuflVersion at all — the exact "undefined leaf" S12 was filed about.
It is now declared once, with the hundredths rule as normative prose and the
release table as a rationale note, cross-referenced from both Chapter 9 sites
so the glyph catalog's version and the tuning context's are visibly one type.
No new req: label; counts stay 212/282/282.

Two ledger claims corrected, both verified false before dispatch rather than
after: PASS13_CANDIDATES.md's S12 entry and epiphany-core/DECISIONS.md's S12
bullet both promised this move would land "with golden regen". Nothing is
pinned to the catalog identity — every ResolvedLayoutIR::canonical_bytes()
assertion is RELATIVE (stability, determinism, and a sensitivity check that
mutates metrics_hash, never smufl_version), and the committed SVG/PNG goldens
embed no identity, only a fixed comment string. There was nothing to
regenerate, and conformance gate [9] never moved.

The fix is locked by a test that names the bug: layout-ir asserts the real
release order through GlyphCatalogIdentity.smufl_version. Mutation-verified
independently by making from_decimal store one-digit minors literally — it
fails with "minor_centi: 20 did not sort before minor_centi: 3", which is
precisely the deleted type's behaviour.

Gate: fmt clean, clippy 0, 1283 passed / 0 failed (+1, the new test), doc 0,
conformance 8/8, requirement labels 6/6 at 212/282/282. No golden, vector,
baseline, or editor-track file changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 19:30:52 -04:00
Levi Neuwirth 68b08adb5e Push 4b tranche 3b-i: the score wire opens schema major 3
ScoreTuningContext gains `smufl` and `overrides` on the canonical wire.
`accidental_extensions` is deliberately STAGED to a later major: under
req:binfmt:frozen-layout a field addition is a major, so freezing a large
subtree whose consumer (the engraver) does not exist yet buys nothing and
risks a major-4 to fix a mis-shaped field. The three shapes 3a ratified
(Cents/CanonicalF64, AnchorPoint, SmuflVersion) stay reversible until
something exercises them.

Scope discovery: no operation payload embeds the tuning context, anywhere.
So the wire reached is the acceleration full-Score snapshot ONLY — the
canonical operation layer is untouched, no payload is born at v3, no frozen
v2 op-payload decoder is needed, and OperationEnvelopeBlock's accept-set
stays at 2 while Snapshot rises to 3. Major 3 is the first data-model bump
under which a chunk role's max does not move in lockstep. The canonical
base embeds no tuning context and stays major 0, byte-identical.

Migration: the live codec becomes v3; the 3-field form is frozen as
enc/dec_tuning_context_v2 and consumed by decode_v2_score (new, with its
byte-exact inverse encode_v2_score) and by the v0/v1 decoders — AND by
encode_v0_score/encode_v1_score, which the contract missed and which would
have silently corrupted the frozen forms through the strict-canonicality
re-encode check.

The frozen bytes are now pinned by a golden, because nothing else pinned
them. The cross-implementation decode corpus covers only the operation and
bundle surfaces — there is no epiphany-core vectors module — so every
existing test round-trips enc against dec and stays green under a
SELF-CONSISTENT reordering: swapping smufl and overrides in both halves
passed the entire workspace suite and 8/8 conformance, silently moving a
permanently frozen layout. schema_major_3_tuning_context_wire_bytes_are_frozen
asserts the exact encoding of the default (48 B) and a loaded (82 B)
context; it kills that swap. The S12 normalization is visible in the
literal: 1.4 stores minor_centi 40 (0x28), 1.12 stores 12.

Gate: fmt clean, clippy 0, 1282 passed / 0 failed, doc 0, conformance 8/8,
requirement labels 6/6 at 212/282/282 (unchanged — no new req: labels).
Mutations verified independently: breaking the v0 reroute kills 6 tests;
the wire-order swap kills the new golden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:35:55 -04:00
Levi Neuwirth 456792debd Editor T2 W3: the goldens become conformance gate [9/9]
A golden-gate feature on epiphany-testkit: resvg + epiphany-render-svg as
optional regular dependencies (dev-dependencies cannot be optional; the
MSRV test job builds examples with --all-targets, so the raster stack must
stay out of the featureless closure — proven by cargo tree, 0 vs 1). Gate 9
re-derives the three T1a golden states headlessly and compares decoded RGBA
against the committed baselines; compare-only, failing loudly with a pointer
to the editor-gui diagnostic tests. Without the feature the suite's output
is byte-identical to before; with it, 9/9. The conformance CI job's
invocation gains the feature; CONFORMANCE.md documents gate [9] and the
split. Two mutations killed and coordinator re-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:20:14 -04:00
Levi Neuwirth d27ea23ae1 Editor T2 W1: the selection set with an anchor
Option<Selection> becomes a paint-ordered set with an anchor member.
click replaces, toggle_at adds/removes (anchor = last added), select_within
takes the hit-test within(rect) in paint order deduped by score object —
a ledgered note's synthesized ledger strokes collapse into its notehead's
member, the representative id preferring the non-synthesized occurrence so
relayout cannot wrongly drop it. Single-target intents retarget to the
anchor, byte-identical at N=1. delete_selection batches atomically via one
transaction (a refused member rolls back the whole batch);
alter_selection instead mints ONE TransposeIntervalOp naming every target —
the format's native atomic multi-target primitive
(req:opcat:transpose-interval-atomic) — preserving N=1 op-stream
compatibility outright. Anchor fallback across relayout: next surviving
member after the anchor's original position, wrapping. Four mutations
killed and coordinator re-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:08:38 -04:00