Commit Graph

4 Commits

Author SHA1 Message Date
Levi Neuwirth a41596d329 Push 5 / P4: the decode conformance corpus, and the tag it could not read back
spec/vectors/decode_vectors.txt -- 37 committed byte strings across five
surfaces, each with its normative accept/reject verdict. The reference
implementation's fuzzers prove its own decoders self-consistent, which says
nothing about whether a foreign decoder agrees with the format. This is what one
is checked against. Gated in the conformance suite as [7d], and drift-locked:
the committed file must equal vectors::render(), so a wire-format change lands
in the diff.

It found a real defect on its first run. OperationKindTag::TransposeInterval
encoded to [30] and its own decoder REJECTED it -- Push 4a added the variant to
discriminant() and never to decode_canonical. OperationKindTag is what edit
barriers persist, so a barrier prohibiting TransposeInterval could be written and
never read back. Silent data loss on reopen.

Four things should have caught it. None did, and two made it worse:

  The round-trip test enumerated DISCRIMINANTS -- (0u8..30).map(decode_canonical)
  -- starting from bytes the decoder already knew, so it structurally could not
  notice a variant the decoder was missing. It now enumerates VARIANTS from one
  all_tags() list, with a completeness check in both directions.

  The distinctness test's hand-written variant list omitted it too. Same list now.

  operation_kind_tag_decode_rejects_malformed_bytes asserted that tag 30 is
  REJECTED, and layout-ir's decode_rejects_unknown_discriminants asserted the
  same at the barrier surface. Both were locking the bug in place and made it
  look deliberate. Both now name 31, and a new barrier test round-trips a barrier
  prohibiting every tag -- the persistence surface where this actually bites.

  The P2 decode fuzzer fed valid corpus bytes to the tag decoder and tallied the
  failure as a REJECTION, like any garbage input. It never asserted that an
  unmutated corpus entry decodes. Both fuzzers now do, as a pre-pass.

The harness had the same disease as the code. `check` collapsed "rejected" with
"accepted but does not re-encode", so a decoder that silently normalizes
non-canonical bytes PASSED the reject vectors it was written to catch. Verified:
removing the whole-state guard, and restoring the lenient compression codec, both
left the corpus green. `check` now returns Ok(injective) for accept and Err for
reject and never conflates them -- silently normalizing non-canonical bytes IS
accepting them. With that fixed, all four defect mutations fail the corpus, each
naming its class.

The corpus pins one vector per class this repo has shipped a bug in:
non-canonical-map-order (a guard catches it; no per-site check exists),
non-canonical-vec-order (only a per-site check catches it; a guard is blind),
lenient-sub-codec (a guard masked it in the manifest; the index had none), plus
trailing-bytes, truncated, unknown-discriminant, count-exceeds-remaining. A test
fails if one goes missing.

Gate: fmt clean, clippy 0, 30 targets / 1024 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8 (now including [7d]), zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:16:15 -04:00
Levi Neuwirth 92aaccf7e2 Pushes 1+3: fix the MUST-level violations, wire the types-only machinery
Two audit pushes whose code edits interleave line-by-line in the same
files (reduce.rs, bundle.rs, the DECISIONS logs), committed together so
the tree at every commit builds. Gate: 784 workspace tests pass, clippy
-D warnings clean, fmt clean.

Push 1 — the true MUST violations, all fixed:
- bundle: zstd read support on both read paths, output bounded by the
  declared uncompressed_length, typed decompression errors, explicit
  CompressedManifest rejection (zstd 0.13 workspace dep; write path
  stays uncompressed per the Phase-3 deferral).
- ops: system-derived counter collision check — mint registry seeded
  from the base graph, canonical-order pre-walk, halt via the new
  PendingReason::HaltedBySystemCollision (discriminant 4, additive)
  with transaction-atomicity and causal-dependent closure; neither
  input set occupies a collided counter. canonical_pitch_bytes made
  pub in core for the MUSCSPCH preimage.
- ops: Transpose skips tombstoned targets per the catalog; missing
  targets still refuse the whole operation.
- ops: marker re-anchoring recorded as a RepairRecord in the
  triggering operation's effect; ResolveConflict meta-conflicts name
  both resolvers; base-free pitch-id freshness; reserved effect
  vocabulary annotated.
- core: decomposition pre-pass honors authored attachments
  (resolve_decomposition, spec-default precedence); inversion
  tolerance typed as a TempoIntegration-class Tolerance.
- CONFORMANCE.md: the determinism conformance statement required by
  Appendix D — all seven declarations.

Push 3 — wiring the types-only machinery:
- layout-ir/engrave: to_constrained emits real constraints (successive
  notehead no-collision chains, per-glyph region containment, soft
  user-break constraints); ConstraintStrength{Required, Preferred}
  with strength-by-rule; Preferred violations surface as warnings, not
  failures; StubSolver reworked honest-but-renderable. SVG goldens
  byte-identical; snapshot constraint counts regenerated (0->90/15).
- layout-ir: to_logical projects user system/page breaks as anchored
  EngravingOverrides with paired UserOverride-sourced decisions
  (OverrideKind::SystemBreak/PageBreak carry TimeAnchor, ratified in
  the spec alongside).
- layout-ir/ops/editor-core: edit-barrier bridge — decode mirrors for
  the whole barrier tree (reject-never-normalize, NFC revalidation,
  MAX_CONDITION_DEPTH = 64), golden-locked blob codec for the
  ExtensionDeclaration fields, a barrier gate in apply and
  apply_transaction backed by a Score oracle and real containment
  contexts, and apply_unsafe recording the crossed extensions in
  extensions_requiring_tombstone() for the next bundle write.
- ops: ResolveEquivocation meta-operation per the newly ratified
  catalog entry — payload discriminant 3 (appended), set-level
  earliest-resolve-governs promotion, ResolveConflict-mirrored
  meta-conflicts, permutation-invariance fuzz; the missing golden
  locks on the OperationKind/OperationPayload wire tables added.
- ops/editor-core: validation modes — ValidationMode + a non-canonical
  advisory layer (validate.rs), an authoring gate before minting, and
  reduction pinned as replay mode by construction (canonical bytes
  untouched).
- bundle: the operation index (opindex.rs) — provisional golden-locked
  payload, binary-search locate, staleness defined as full-ChunkRef
  set equality against operation_roots, and the reject-and-rebuild
  discipline (a defective index is never bundle corruption).
- ops: re-anchoring rule table completed — the four-key "nearest"
  ordering computed from base-free ledger indices; markers re-anchor
  to the nearest live event in the same staff instance (replacing the
  Push-1 region-start stand-in); cue-source cascade; graphic-gesture
  Events/Range/Free rows; comment and analytical-annotation orphaning.
  Zero appended discriminants.

Spec enablers ratified with Push 3: catalog §ResolveEquivocation
(0.3.0 -> 0.4.0) and anchored break overrides; 16 new Pass-12 rows
filed (C1-C4, K5-K7, I4-I6, D1, E1-E5). The data-model payload
expansion (SlurKind, beam geometry, voltas, instrument bodies,
metadata) is deliberately staged to the Binary Format companion — the
positional graph codec has no value-level versioning, so filling those
structs is a schema-major break that should land once, with J.

Also carries the pre-existing editor-track increment: the atomic
tuplet overwrite (CascadeDeleteTuplets prunes decomposition
attachments naming the cascaded tuplet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 17:10:50 -04:00
Levi Neuwirth f105b53599 Item 6 (part 2): operation-block summaries (D-B, C/D integration)
Chapter 8's OperationEnvelopeBlock carries dvv_summary/min_stamp/max_stamp so a
reader can select or skip a block by causal frontier / stamp range without
decoding it. These are semantic (ops-computed); the bundle carries them opaquely.

Bundle (Agent D):
- OperationBlockSummary { dvv_summary: FrontierBytes, min_stamp, max_stamp } and
  Manifest.operation_block_summaries: BTreeMap<ChunkId, OperationBlockSummary>,
  keyed by the block's chunk id, encoded/decoded in canonical (ChunkId-ascending)
  order and accessible via Manifest::operation_block_summary. Optional and
  non-canonical; preserved across reopen by the manifest round-trip.
- Round-trip + selectability test.

Testkit (Agent F, the C/D integration point):
- roundtrip::operation_block_summary computes the summary from envelopes using
  ops (causal frontier + min/max OperationStamp canonical bytes).
- assert_operation_block_summary_survives_storage commits a real operation block
  + its summary, reopens, and selects the summary by block id without decoding
  the payload. Wired into acceptance + the conformance suite.

bundle DECISIONS updated (summary metadata now carried, not omitted); fixed a
stale "pending item 5" doc on criterion 4 (the whole-score codec has landed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:26:17 -04:00
Levi Neuwirth a2e9ec32f6 A B C D F 2026-06-19 12:42:31 -04:00