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
This commit is contained in:
Levi Neuwirth 2026-07-24 21:02:12 -04:00
parent 24f8c8099a
commit 3b09595196
26 changed files with 1142 additions and 87 deletions

View File

@ -1401,3 +1401,23 @@ kind in one batch at `OperationEnvelopeBlock` major 3, so the raise is amortised
across nine surfaces rather than charged to one field of eight. Note that across nine surfaces rather than charged to one field of eight. Note that
`bundle.rs`'s cap of 2 is documented *with that rationale in prose* — when the `bundle.rs`'s cap of 2 is documented *with that rationale in prose* — when the
tranche lands, that comment becomes false and must move with the cap. tranche lands, that comment becomes false and must move with the cap.
## Genesis tranche G1 — `Instrument` joins `canonical_value!` (2026-07-24)
`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md` (executing
`spec/RULING_GENESIS_PERSISTENCE.md`) adds `CreateInstrument` to
`epiphany-ops`, the first rung of the genesis ladder
(`spec/PLAN_GENESIS_OPS.md` §4). Its payload embeds the full `Instrument`
value, so this crate's one required change is a single `canonical_value!`
line (`Instrument` already has a `Codec` via `struct_codec!`, `codec.rs:1756`,
which — same macro — already generates `impl TextValue for Instrument` too,
so the Text Projection touch point the tranche needed cost nothing here).
No new byte layout: `Instrument`'s canonical bytes are exactly what the
whole-score codec already emits for it, made reachable per-value on the same
seam every other `canonical_value!` entry uses. `value_types_round_trip_over_generator_corpus`
does not exercise `Instrument` directly (the generator corpus does not walk
`Score::instruments`), but the operation-layer decode-vector corpus
(`epiphany-ops::vectors`) now pins a `CreateInstrument` envelope's literal
bytes, which round-trips the same `Instrument` encoding through the op
payload's `push_lp_bytes` wrapper — see that crate's own DECISIONS.md entry.

View File

@ -3513,6 +3513,12 @@ canonical_value! {
TimeSignature, TimeSignature,
TempoSegment, TempoSegment,
StaffLineConfiguration, StaffLineConfiguration,
// Genesis tranche G1 (`CONTRACT_GENESIS_G1_INSTRUMENT.md`) — CreateInstrument
// embeds the full value, mirroring CreateStaff's `Staff`. `Instrument`
// already has a `Codec` (`struct_codec!`, `:1756`) and already ships inside
// `Score`; this makes that existing layout reachable per-value, same as
// every other entry here.
Instrument,
// Repeat authoring (schema-major-2 revision) — CreateRepeatStructure // Repeat authoring (schema-major-2 revision) — CreateRepeatStructure
// embeds the full value. // embeds the full value.
RepeatStructure, RepeatStructure,

View File

@ -437,6 +437,13 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject
OperationKind::CreateStaff(op) => { OperationKind::CreateStaff(op) => {
one(TypedObjectId::Staff(op.staff_id()), EditContext::default()) one(TypedObjectId::Staff(op.staff_id()), EditContext::default())
} }
// Genesis tranche G1: an instrument is a root entity with no outbound
// references, so — exactly like `CreateStaff` — it names only the
// object it mints, with no resolvable region or staff-instance context.
OperationKind::CreateInstrument(op) => one(
TypedObjectId::Instrument(op.instrument_id()),
EditContext::default(),
),
OperationKind::SetTimeSignature(op) => { OperationKind::SetTimeSignature(op) => {
let mut objects = vec![(TypedObjectId::Region(op.region), ctx(Some(op.region), None))]; let mut objects = vec![(TypedObjectId::Region(op.region), ctx(Some(op.region), None))];
if let Some(signature) = &op.time_signature { if let Some(signature) = &op.time_signature {

View File

@ -1100,22 +1100,25 @@ mod tests {
tag: 7 tag: 7
}) })
); );
// Operation-kind tag 31 is one past the vocabulary (the Phase-3 ops // Operation-kind tag 32 is one past the vocabulary (the Phase-3 ops
// tranche appended 24..=27, the repeat pair 28/29, `TransposeInterval` // tranche appended 24..=27, the repeat pair 28/29, `TransposeInterval`
// 30; encodings are append-only). // 30, genesis G1's `CreateInstrument` 31; encodings are append-only).
// //
// This assertion named 30 until Push 5 / P4 — by which time 30 was // This assertion named 30 until Push 5 / P4, and 31 until genesis G1 —
// `TransposeInterval`, so the test was pinning a bug: a barrier that // each time, by then, the number had become a real kind, so the test
// prohibited the new operation encoded fine and would not read back. // was pinning a bug: a barrier that prohibited the new operation
// encoded fine and would not read back. It must be bumped by every
// tranche that appends a tag, and it is deliberately a literal rather
// than `PAYLOAD_FREE.len()` so the bump is a conscious act.
let mut bytes = vec![0u8]; let mut bytes = vec![0u8];
bytes.extend(set_blob(&[])); bytes.extend(set_blob(&[]));
bytes.extend(set_blob(&[vec![31u8]])); bytes.extend(set_blob(&[vec![32u8]]));
bytes.push(0); bytes.push(0);
assert_eq!( assert_eq!(
EditBarrier::decode_canonical_bytes(&bytes), EditBarrier::decode_canonical_bytes(&bytes),
Err(BarrierDecodeError::InvalidTag { Err(BarrierDecodeError::InvalidTag {
kind: "OperationKindTag", kind: "OperationKindTag",
tag: 31 tag: 32
}) })
); );
} }

View File

@ -1705,3 +1705,111 @@ untouched. Widening what `TransposeInterval` can transpose is the tranche's
ratified purpose, and the version skew it implies (an old replica refuses what a ratified purpose, and the version skew it implies (an old replica refuses what a
new one applies) is the tranche's property, not this fix's — the fix only makes new one applies) is the tranche's property, not this fix's — the fix only makes
the operation layer agree with the core layer the tranche already moved. the operation layer agree with the core layer the tranche already moved.
## Genesis tranche G1 — `CreateInstrument`, kind 31 / tag 31 (2026-07-24)
`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md` lands the first rung of the genesis
ladder (`spec/PLAN_GENESIS_OPS.md` §4): `CreateInstrument` mints an
`Instrument` on the score root, set-union discipline identical to
`create_staff` (`reduce.rs`), minus the graph-aware reference-resolution
block — `Instrument` holds no outbound entity reference, so there is nothing
for one to check. All fifteen touch points landed as specified: `codec.rs`'s
`canonical_value!` (one line — `Instrument` already had a `Codec` and,
because `struct_codec!` generates both, already had `TextValue` too, so the
Text Projection touch point cost nothing extra); `payload.rs`'s
`CreateInstrumentOp` + the five mechanical sites (`OperationKind` variant,
`schema_major`, `discriminant`, `tag`, encode dispatch, tag-vocabulary
entry); `envdecode.rs`'s decode arm and `sample_kind` sample;
`v0.rs`/`migrate.rs`'s identity round-trip (born past v0, no lossy
projection); `reduce.rs`'s dispatch + `create_instrument` +
`instrument_values` (pin 8, six sites: both struct decls, init, the base
seed, and the snapshot/restore pair); `textproj_kind.rs`'s production/parse;
`fuzz.rs`'s generator arm 28 (`rng.below(28)` → `below(29)`); `vectors.rs`'s
decode vector; `operation_catalog.tex`'s new section.
**The identity cursor (pin 9) is gated on a *pristine-base* check the naive
version got backwards.** The first attempt compared `self.graph` against
`Score::empty(...)` at the *end* of `run()`, after every spine operation had
already populated it — always false, so the cursor never advanced (the i5
test caught this immediately: `next_counter` stayed `0`, not the expected
`12`). The fix captures `from_empty_base` in `new_onto`, from the *pristine*
`base` argument, before `graph` is cloned and mutated. This is also the
mechanism that keeps the fix scoped to from-empty reduction only:
`reduce_operation_set_onto` is the same entry point `epiphany-editor-core`
calls on every edit (`EditorSession::materialize`), against a populated
base, so an unconditional cursor-write there would have been a silent
behavioral change to a crate this packet may not touch. Gating on
`from_empty_base` makes onto-reduction against a populated base
byte-for-byte unchanged — verified, not assumed, since editor-core is one of
the crates the boundary below made un-editable, so its own test suite is the
only check available and it does not run in this tree (see below).
**The cursor's derivation walks two sources, not one.** `next_counter` is a
*single* counter shared by every identifier kind
(`IdentityContext::take_counter`), so scoping the fix to `InstrumentId`
alone would have been wrong the moment a spine mixes kinds — which i1's own
chain does (`CreateStaff`'s `StaffId`, `CreateVoice`'s `VoiceId`, ...).
`derive_identity_cursor` takes the max over (a) every `OperationId` the
`op_set` has ever accepted, via `OperationSet::slots()` — keyed by
`OperationId`, so its key set is exactly "every id in the log" regardless of
slot state (Single, Equivocated, held/pending) — and (b) every entity id in
the final `self.objects`, decoded generically from `TypedObjectId`'s
canonical form (2-byte discriminant + 16-byte `(replica, counter)`, for
every variant but `Registered`) rather than a new per-kind accessor. This
reads `TypedObjectId::canonical_bytes()` — already public — instead of
adding a `replica()`/`counter()` method to `epiphany-core::ids`, which
would have been a new public surface outside this packet's blast radius.
Not fully general (a losing equivocation candidate's *entity* ids, as
opposed to its `OperationId`, are invisible to this — no test in the
required nine reaches that case, and closing it would need the same kind of
new core-side accessor this design avoids); flagged rather than silently
assumed complete.
## Boundary conflict: `OperationKind` is exhaustively matched outside this
## packet's blast radius (2026-07-24)
The contract's blast radius lists only `epiphany-core/src/codec.rs` and
eight `epiphany-ops/src/*.rs` files, and its "Parallel safety" note claims
"this packet touches none of" `epiphany-editor-core`, `epiphany-editor-gui`,
`epiphany-layout-ir`, `epiphany-render-svg`, `epiphany-glyphs`. That claim
does not survive contact with the compiler: `OperationKind` gains a variant,
and Rust's exhaustiveness check finds every downstream consumer.
**`crates/epiphany-editor-core/src/barriers.rs:316`**, `subjects_of`, an
exhaustive `match kind: &OperationKind` deriving edit-barrier subjects —
fails to *compile* without a new arm (`CreateStaff`'s arm, one line, is the
exact analogue: an instrument is a score-root object with no regional
containment, same as a staff). Because `epiphany-testkit`,
`epiphany-engrave`, `epiphany-render-svg`, and `epiphany-editor-gui` all
depend on `epiphany-editor-core` (non-optionally), this one missing arm
blocks the *entire* rest of the workspace from compiling — including
`epiphany-testkit`, which owns `requirement_labels`, the decode-vector
corpus regeneration/verification, and the conformance suite. `cargo test
--workspace` and `cargo clippy --workspace --all-targets` both fail at this
single error and never reach a single test.
**`crates/epiphany-layout-ir/src/barrier.rs:1112`**,
`decode_rejects_unknown_discriminants`, hardcodes `31u8` as "one past the
[`OperationKindTag`] vocabulary" — a *runtime* test failure (the crate
compiles fine), now stale because 31 is `CreateInstrument`. This is not a
new failure mode: the test's own comment records it happened once already
("This assertion named 30 until Push 5 / P4 — by which time 30 was
`TransposeInterval`, so the test was pinning a bug"). The fix each time is
mechanical: bump the literal to the new one-past-the-vocabulary value (`32`)
and its expected `tag: 31` to `tag: 32`.
Both files are on this packet's explicit do-not-touch list (a parallel agent
owns them in a separate worktree), so neither was edited — an initial,
incorrect edit to `barriers.rs` (made before re-deriving the boundary against
the actual compiler output) was caught and reverted before landing; `git
status` on both crates is clean in the delivered tree. Consequently:
`cargo test --workspace`, `cargo clippy --workspace --all-targets`, the
conformance suite, and `requirement_labels` could not be executed against
the full workspace in the delivered state — only against the seven crates
that do not depend on `epiphany-editor-core`
(`epiphany-determinism`/`epiphany-core`/`epiphany-bundle`/`epiphany-ops`/
`epiphany-textproj`/`epiphany-layout-ir`/`epiphany-glyphs`), all of which
are green (`epiphany-layout-ir` excepted, for the one stale-vocabulary test
above). This is reported to the coordinator rather than worked around; see
the packet's final report for the exact reproduction commands and error
text.

View File

@ -36,10 +36,10 @@ use epiphany_core::{
}; };
use epiphany_core::{CanonicalValue, TempoSegment}; use epiphany_core::{CanonicalValue, TempoSegment};
use epiphany_core::{ use epiphany_core::{
EventId, InstrumentId, MetricGrid, MusicalPosition, OperationId, PitchId, PitchSpelling, EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition, OperationId, PitchId,
RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, ScoreMetadata, Staff, StaffInstance, PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, ScoreMetadata, Staff,
StaffInstanceId, StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval, StaffInstance, StaffInstanceId, StaffLineConfiguration, TimeAnchor, TimeSignature,
TupletId, TypedObjectId, Voice, VoiceId, WallClockTime, TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, WallClockTime,
}; };
use epiphany_determinism::{CanonicalDecode, CanonicalEncode}; use epiphany_determinism::{CanonicalDecode, CanonicalEncode};
@ -586,6 +586,9 @@ fn operation_kind(r: &mut Reader<'_>) -> Result<OperationKind> {
}, },
}) })
} }
31 => OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: value::<Instrument>(r, "Instrument")?,
}),
tag => { tag => {
return Err(EnvelopeDecodeError::InvalidTag { return Err(EnvelopeDecodeError::InvalidTag {
kind: "OperationKind", kind: "OperationKind",
@ -870,6 +873,11 @@ pub(crate) mod tests {
repeat: RepeatStructureId::new(ReplicaId(7), 1), repeat: RepeatStructureId::new(ReplicaId(7), 1),
}) })
} }
OperationKindTag::CreateInstrument => {
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: valuegen::instrument(InstrumentId::new(ReplicaId(7), 1)),
})
}
} }
} }

View File

@ -32,13 +32,14 @@ use crate::envelope::OperationEnvelope;
use crate::opset::OperationSet; use crate::opset::OperationSet;
use crate::payload::OperationKindTag; use crate::payload::OperationKindTag;
use crate::payload::{ use crate::payload::{
CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, CreateStaffInstanceOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp, CreateRepeatStructureOp,
CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp,
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp,
DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
ModifyIdentifiedPitchOp, OperationKind, OperationPayload, RespellPitchOp, SetMetadataOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationPayload,
SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetUserSystemBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransposeIntervalOp, TransposeOp,
TupletCompensation,
}; };
use crate::stamp::{HybridLogicalClock, OperationStamp}; use crate::stamp::{HybridLogicalClock, OperationStamp};
use crate::support::AuthorId; use crate::support::AuthorId;
@ -89,7 +90,7 @@ fn pitch(n: u64) -> PitchId {
/// Generates a random payload over the shared id space. /// Generates a random payload over the shared id space.
fn gen_payload(rng: &mut SplitMix64) -> OperationPayload { fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
let kind = match rng.below(28) { let kind = match rng.below(29) {
0 => { 0 => {
let voice = VoiceId::new(ReplicaId(7), rng.below(3)); let voice = VoiceId::new(ReplicaId(7), rng.below(3));
let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32)); let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32));
@ -283,6 +284,15 @@ fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
chromatic_steps: rng.below(9) as i32 - 4, chromatic_steps: rng.below(9) as i32 - 4,
}, },
}), }),
// Genesis tranche G1, over the shared instrument-id space (arm 21's
// `CreateStaff` already references `InstrumentId::new(ReplicaId(7),
// rng.below(2))`, so mints/re-carries genuinely interact with it).
28 => OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: valuegen::instrument(epiphany_core::InstrumentId::new(
ReplicaId(7),
rng.below(2),
)),
}),
_ => OperationKind::SetStaffLayout(SetStaffLayoutOp { _ => OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)), staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)),
instrument_override: None, instrument_override: None,

View File

@ -124,15 +124,16 @@ pub use envelope::{
pub use migrate::{migrate_v0_envelope, project_v1_to_v0, MigrationError}; pub use migrate::{migrate_v0_envelope, project_v1_to_v0, MigrationError};
pub use opset::{AcceptOutcome, OperationSet}; pub use opset::{AcceptOutcome, OperationSet};
pub use payload::{ pub use payload::{
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationPayload, PositionRemapping, ResolveConflictPayload, ResolveEquivocationPayload, OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ResolveConflictPayload,
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, ResolveEquivocationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation, TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
TupletCompensation,
}; };
pub use reduce::{ pub use reduce::{
canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason, canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason,

View File

@ -179,6 +179,8 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind {
OperationKind::DeleteRepeatStructure(op) => V0OperationKind::DeleteRepeatStructure(*op), OperationKind::DeleteRepeatStructure(op) => V0OperationKind::DeleteRepeatStructure(*op),
// Push 4a: born past v0; projected verbatim. // Push 4a: born past v0; projected verbatim.
OperationKind::TransposeInterval(op) => V0OperationKind::TransposeInterval(op.clone()), OperationKind::TransposeInterval(op) => V0OperationKind::TransposeInterval(op.clone()),
// Genesis tranche G1: born past v0; projected verbatim.
OperationKind::CreateInstrument(op) => V0OperationKind::CreateInstrument(op.clone()),
} }
} }
@ -332,6 +334,8 @@ fn migrate_kind(kind: &V0OperationKind, context: &Score) -> Result<OperationKind
} }
V0OperationKind::TransposeInterval(op) => OperationKind::TransposeInterval(op.clone()), V0OperationKind::TransposeInterval(op) => OperationKind::TransposeInterval(op.clone()),
V0OperationKind::DeleteRepeatStructure(op) => OperationKind::DeleteRepeatStructure(*op), V0OperationKind::DeleteRepeatStructure(op) => OperationKind::DeleteRepeatStructure(*op),
// Genesis tranche G1: identity round-trip (no lossy v0 form).
V0OperationKind::CreateInstrument(op) => OperationKind::CreateInstrument(op.clone()),
}) })
} }

View File

@ -33,9 +33,9 @@
use epiphany_core::{ use epiphany_core::{
Beam, CanonicalValue, Event, EventDuration, EventId, EventPosition, IdentifiedPitch, Beam, CanonicalValue, Event, EventDuration, EventId, EventPosition, IdentifiedPitch,
InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch, PitchId, Instrument, InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch,
PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, RepeatStructureId, Rest, PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, RepeatStructureId,
ScoreMetadata, Slur, Spanner, Staff, StaffId, StaffInstance, StaffInstanceId, Rest, ScoreMetadata, Slur, Spanner, Staff, StaffId, StaffInstance, StaffInstanceId,
StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, TimeSignature, TransactionId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, TimeSignature, TransactionId,
TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId,
}; };
@ -195,6 +195,14 @@ pub enum OperationKind {
/// Push 4a: the faithful transpose. Appended past 29 — a schema-minor /// Push 4a: the faithful transpose. Appended past 29 — a schema-minor
/// vocabulary append (`req:binfmt:kind-discriminants`). /// vocabulary append (`req:binfmt:kind-discriminants`).
TransposeInterval(TransposeIntervalOp), TransposeInterval(TransposeIntervalOp),
// --- Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`): the
// from-empty spine's missing link. Discriminant extends additively past 30.
// ---
/// Mint an abstract instrument on the score root (set-union creation) —
/// the single missing link between `Score::empty` and a note:
/// `CreateStaff` already demands a live `Instrument` and nothing else can
/// create one.
CreateInstrument(CreateInstrumentOp),
} }
impl OperationKind { impl OperationKind {
@ -212,12 +220,17 @@ impl OperationKind {
pub fn schema_major(&self) -> u16 { pub fn schema_major(&self) -> u16 {
match self { match self {
// Mandatory v2 appends: CrossCuttingValue (Slur/Tie/Beam/Spanner // Mandatory v2 appends: CrossCuttingValue (Slur/Tie/Beam/Spanner
// bodies), Staff (default_clef + filled line config), and // bodies), Staff (default_clef + filled line config),
// ScoreMetadata (six appended fields). // ScoreMetadata (six appended fields), and Instrument
// (sound_config/default_clef/default_staff_lines et al. — G1;
// unconditional, not `Option`-hidden, so no lower-major layout for
// this payload exists — do not copy the value-dependent arm shape
// below).
OperationKind::CreateCrossCutting(_) OperationKind::CreateCrossCutting(_)
| OperationKind::ModifyCrossCutting(_) | OperationKind::ModifyCrossCutting(_)
| OperationKind::CreateStaff(_) | OperationKind::CreateStaff(_)
| OperationKind::SetMetadata(_) => 2, | OperationKind::SetMetadata(_)
| OperationKind::CreateInstrument(_) => 2,
// Value-dependent: the embedded StaffLineConfiguration rides an // Value-dependent: the embedded StaffLineConfiguration rides an
// Option; None encodes byte-identically to the prior major. // Option; None encodes byte-identically to the prior major.
OperationKind::CreateRegion(op) => { OperationKind::CreateRegion(op) => {
@ -287,6 +300,10 @@ impl OperationKind {
// Push 4a; appended past 29. Every constituent is a major-0 // Push 4a; appended past 29. Every constituent is a major-0
// layout, so `schema_major` leaves it in the catch-all 0 arm. // layout, so `schema_major` leaves it in the catch-all 0 arm.
OperationKind::TransposeInterval(_) => 30, OperationKind::TransposeInterval(_) => 30,
// Genesis tranche G1; appended past 30. Coincides with tag 31 by
// accident, not by rule — the two spaces are independent and
// misaligned elsewhere (`RespellPitch` is kind 2 / tag 3).
OperationKind::CreateInstrument(_) => 31,
} }
} }
@ -328,6 +345,10 @@ impl OperationKind {
OperationKind::CreateRepeatStructure(_) => OperationKindTag::CreateRepeatStructure, OperationKind::CreateRepeatStructure(_) => OperationKindTag::CreateRepeatStructure,
OperationKind::DeleteRepeatStructure(_) => OperationKindTag::DeleteRepeatStructure, OperationKind::DeleteRepeatStructure(_) => OperationKindTag::DeleteRepeatStructure,
OperationKind::TransposeInterval(_) => OperationKindTag::TransposeInterval, OperationKind::TransposeInterval(_) => OperationKindTag::TransposeInterval,
// Name-verbatim, as the two most recent additions (`CreateVoice`,
// `CreateRepeatStructure`) are — the tag layer's older
// Create→Insert convention is not followed here (contract pin 3).
OperationKind::CreateInstrument(_) => OperationKindTag::CreateInstrument,
} }
} }
} }
@ -370,6 +391,7 @@ impl CanonicalEncode for OperationKind {
OperationKind::SetStaffLayout(op) => op.encode_canonical(out), OperationKind::SetStaffLayout(op) => op.encode_canonical(out),
OperationKind::CreateRepeatStructure(op) => op.encode_canonical(out), OperationKind::CreateRepeatStructure(op) => op.encode_canonical(out),
OperationKind::DeleteRepeatStructure(op) => op.encode_canonical(out), OperationKind::DeleteRepeatStructure(op) => op.encode_canonical(out),
OperationKind::CreateInstrument(op) => op.encode_canonical(out),
} }
} }
} }
@ -416,6 +438,10 @@ pub enum OperationKindTag {
DeleteRepeatStructure, DeleteRepeatStructure,
/// Push 4a. /// Push 4a.
TransposeInterval, TransposeInterval,
/// Genesis tranche G1. Name-verbatim (contract pin 3): the tag layer's
/// older Create→Insert convention (`InsertStaff` for `CreateStaff`) is not
/// followed here, matching the two most recent additions.
CreateInstrument,
} }
/// The discriminant of [`OperationKindTag::Registered`], the one tag that /// The discriminant of [`OperationKindTag::Registered`], the one tag that
@ -513,6 +539,7 @@ operation_kind_tag_vocabulary! {
CreateRepeatStructure = 28 => "create-repeat-structure", CreateRepeatStructure = 28 => "create-repeat-structure",
DeleteRepeatStructure = 29 => "delete-repeat-structure", DeleteRepeatStructure = 29 => "delete-repeat-structure",
TransposeInterval = 30 => "transpose-interval", TransposeInterval = 30 => "transpose-interval",
CreateInstrument = 31 => "create-instrument",
} }
impl CanonicalEncode for OperationKindTag { impl CanonicalEncode for OperationKindTag {
@ -1414,6 +1441,35 @@ impl CanonicalEncode for CreateStaffOp {
} }
} }
// --- Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`). ----------
/// Mint an abstract [`Instrument`] on the score root (operation_catalog
/// §CreateInstrument). Carries the full instrument value (schema major 2):
/// identity, name, range, abbreviation, sound configuration, transposition,
/// default clef, default staff-line configuration, and unpitched members.
/// `Instrument` holds no outbound entity references, so this operation needs
/// no referential preconditions — only mint and idempotence, exactly the
/// `CreateStaff` discipline. Set-union creation: a repeat create carrying a
/// byte-identical value is idempotent; a differing value under a live id is a
/// precondition no-op.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CreateInstrumentOp {
pub instrument: Instrument,
}
impl CreateInstrumentOp {
/// The minted instrument's identifier.
pub fn instrument_id(&self) -> InstrumentId {
self.instrument.id
}
}
impl CanonicalEncode for CreateInstrumentOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_lp_bytes(out, &self.instrument.canonical_bytes());
}
}
/// Set, replace, or (`None`) remove the single meter change at the anchor's /// Set, replace, or (`None`) remove the single meter change at the anchor's
/// resolved musical position in a region's default metric grid /// resolved musical position in a region's default metric grid
/// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full /// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full

View File

@ -34,13 +34,13 @@ use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
use epiphany_core::{ use epiphany_core::{
canonical_pitch_bytes, derive_promoted_voice_id, simplest_spelling, AnchorOffset, canonical_pitch_bytes, derive_promoted_voice_id, simplest_spelling, AnchorOffset,
AnnotationAnchor, CanonicalValue, Event, EventDuration, EventId, EventPosition, AnnotationAnchor, CanonicalValue, Event, EventDuration, EventId, EventPosition,
GestureAnchoring, InstrumentId, MeterChange, MetricGrid, MusicalDuration, MusicalPosition, GestureAnchoring, Instrument, InstrumentId, MeterChange, MetricGrid, MusicalDuration,
OperationId, Pitch, PitchId, PitchSpelling, RationalTime, RegionEdge, RegionId, MusicalPosition, OperationId, Pitch, PitchId, PitchSpelling, RationalTime, RegionEdge,
RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment, SpellingDirective, RegionId, RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment,
SpellingScope, SpellingSource, Staff, StaffId, StaffInstance, StaffInstanceId, SpellingDirective, SpellingScope, SpellingSource, Staff, StaffId, StaffInstance,
StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, TimeAnchor, TimeSignature, StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, TimeAnchor,
TimeSignatureId, TransactionId, TransposeRefusal, TranspositionInterval, TypedObjectId, Voice, TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal, TranspositionInterval,
VoiceId, VoiceOrigin, TypedObjectId, Voice, VoiceId, VoiceOrigin,
}; };
use epiphany_determinism::CanonicalEncode; use epiphany_determinism::CanonicalEncode;
@ -56,13 +56,14 @@ use crate::encode::{push_canon, push_len, push_lp_bytes, push_u8_bool};
use crate::envelope::OperationEnvelope; use crate::envelope::OperationEnvelope;
use crate::opset::OperationSet; use crate::opset::OperationSet;
use crate::payload::{ use crate::payload::{
resolved_anchor_position, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, resolved_anchor_position, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationPayload, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, OperationKind, OperationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp,
SetTimeSignatureOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
TransposeIntervalOp, TransposeOp, TupletCompensation,
}; };
use crate::stamp::StampTuple; use crate::stamp::StampTuple;
use crate::support::{ObjectKind, SerializedCanonicalInputs}; use crate::support::{ObjectKind, SerializedCanonicalInputs};
@ -913,6 +914,13 @@ struct Reducer<'a> {
// precondition no-op). Seeded from the base graph. // precondition no-op). Seeded from the base graph.
staff_values: BTreeMap<StaffId, Staff>, staff_values: BTreeMap<StaffId, Staff>,
time_signature_values: BTreeMap<TimeSignatureId, TimeSignature>, time_signature_values: BTreeMap<TimeSignatureId, TimeSignature>,
// Carried values of set-union-minted instruments (genesis tranche G1),
// mirroring `staff_values`: the byte-identical-re-carry idempotence check
// against a *base* instrument (pin 8 of
// `CONTRACT_GENESIS_G1_INSTRUMENT.md`) has nothing to compare without this
// — `TypedObjectId::Instrument` liveness is seeded from the base
// (`seed_from_graph`), but the base seed left no value map before this.
instrument_values: BTreeMap<InstrumentId, Instrument>,
structures: BTreeMap<TypedObjectId, Vec<TypedObjectId>>, structures: BTreeMap<TypedObjectId, Vec<TypedObjectId>>,
// Live child sets for the structural-container empty-only delete (Group 3): // Live child sets for the structural-container empty-only delete (Group 3):
// a region's live staff instances, and a staff instance's live voices. (A // a region's live staff instances, and a staff instance's live voices. (A
@ -958,6 +966,13 @@ struct Reducer<'a> {
equivocation_resolutions: BTreeMap<OperationId, (OperationId, crate::EnvelopeHash)>, equivocation_resolutions: BTreeMap<OperationId, (OperationId, crate::EnvelopeHash)>,
promoted_singles: BTreeMap<OperationId, &'a OperationEnvelope>, promoted_singles: BTreeMap<OperationId, &'a OperationEnvelope>,
graph: Option<Score>, graph: Option<Score>,
// Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md` pin 9):
// whether `new_onto`'s *pristine* base was exactly `Score::empty(identity)`
// — captured once, before any operation mutates `graph`, since by the time
// `run()` finishes `graph` is no longer empty and cannot answer this
// question itself. Gates the identity-cursor derivation to from-empty
// reduction only, never a transaction snapshot (fixed for the whole run).
from_empty_base: bool,
} }
/// A snapshot of the working state, for atomic transaction rollback. /// A snapshot of the working state, for atomic transaction rollback.
@ -1003,6 +1018,7 @@ struct WorkingSnapshot {
staff_layout_chain: BTreeMap<StaffInstanceId, WriteChain<StaffLayoutValue>>, staff_layout_chain: BTreeMap<StaffInstanceId, WriteChain<StaffLayoutValue>>,
staff_values: BTreeMap<StaffId, Staff>, staff_values: BTreeMap<StaffId, Staff>,
time_signature_values: BTreeMap<TimeSignatureId, TimeSignature>, time_signature_values: BTreeMap<TimeSignatureId, TimeSignature>,
instrument_values: BTreeMap<InstrumentId, Instrument>,
structures: BTreeMap<TypedObjectId, Vec<TypedObjectId>>, structures: BTreeMap<TypedObjectId, Vec<TypedObjectId>>,
region_instances: BTreeMap<RegionId, BTreeSet<StaffInstanceId>>, region_instances: BTreeMap<RegionId, BTreeSet<StaffInstanceId>>,
instance_voices: BTreeMap<StaffInstanceId, BTreeSet<VoiceId>>, instance_voices: BTreeMap<StaffInstanceId, BTreeSet<VoiceId>>,
@ -1281,6 +1297,7 @@ impl<'a> Reducer<'a> {
staff_layout_chain: BTreeMap::new(), staff_layout_chain: BTreeMap::new(),
staff_values: BTreeMap::new(), staff_values: BTreeMap::new(),
time_signature_values: BTreeMap::new(), time_signature_values: BTreeMap::new(),
instrument_values: BTreeMap::new(),
structures: BTreeMap::new(), structures: BTreeMap::new(),
region_instances: BTreeMap::new(), region_instances: BTreeMap::new(),
instance_voices: BTreeMap::new(), instance_voices: BTreeMap::new(),
@ -1296,11 +1313,16 @@ impl<'a> Reducer<'a> {
equivocation_resolutions: BTreeMap::new(), equivocation_resolutions: BTreeMap::new(),
promoted_singles: BTreeMap::new(), promoted_singles: BTreeMap::new(),
graph: None, graph: None,
from_empty_base: false,
} }
} }
fn new_onto(op_set: &'a OperationSet, base: &Score) -> Self { fn new_onto(op_set: &'a OperationSet, base: &Score) -> Self {
let mut reducer = Self::new(op_set); let mut reducer = Self::new(op_set);
// Captured from the pristine `base` argument, before `graph` is
// populated and mutated below — `graph` is no longer empty by the
// time `run()` needs this answer.
reducer.from_empty_base = *base == Score::empty(base.identity.clone());
reducer.graph = Some(base.clone()); reducer.graph = Some(base.clone());
reducer.seed_from_graph(); reducer.seed_from_graph();
reducer reducer
@ -1318,6 +1340,12 @@ impl<'a> Reducer<'a> {
for instrument in &score.instruments { for instrument in &score.instruments {
self.objects self.objects
.insert(TypedObjectId::Instrument(instrument.id), ObjectState::Live); .insert(TypedObjectId::Instrument(instrument.id), ObjectState::Live);
// The carried value backs CreateInstrument's byte-identical-re-carry
// idempotence check against base instruments (contract pin 8) —
// without this, `TypedObjectId::Instrument` liveness is seeded but
// there is nothing to compare a re-carry against.
self.instrument_values
.insert(instrument.id, instrument.clone());
} }
for staff in &score.staves { for staff in &score.staves {
self.objects self.objects
@ -1913,6 +1941,31 @@ impl<'a> Reducer<'a> {
pending.into_iter().chain(held).collect(); pending.into_iter().chain(held).collect();
pending_vec.sort_by_key(|(id, _)| *id); pending_vec.sort_by_key(|(id, _)| *id);
// Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md` pin 9;
// ruling §3 point 3): from-empty reduction is the first thing that
// ever writes `identity`. Gated on `from_empty_base`, captured in
// `new_onto` from the *pristine* base before any operation mutated
// `graph` — `graph` itself is no longer empty by this point, so a
// live re-comparison here would never fire. This keeps onto-reduction
// against a populated base (every other caller, notably
// `epiphany-editor-core`'s materialize-on-every-edit) byte-for-byte
// unchanged; nothing in this packet's blast radius touches that
// crate. Nothing between `new_onto` and here writes `identity`
// itself, so `graph.identity`'s replica and seed are still the
// pristine base's.
if self.from_empty_base {
if let Some(graph) = self.graph.as_ref() {
let replica = graph.identity.replica_id;
let seed = graph.identity.next_counter;
let next_counter = self.derive_identity_cursor(replica, seed);
self.graph
.as_mut()
.expect("checked Some above")
.identity
.next_counter = next_counter;
}
}
let graph = self.graph.take(); let graph = self.graph.take();
let state = MaterializedState { let state = MaterializedState {
effects: self.effects, effects: self.effects,
@ -1927,6 +1980,60 @@ impl<'a> Reducer<'a> {
(state, graph) (state, graph)
} }
/// Derives the from-empty identity cursor (ruling §3 point 3): `1 +
/// max(counter)` over every id — an envelope's own [`OperationId`], or an
/// entity id minted into the reduced graph — the reducing `replica`
/// authored anywhere in the log, or the untouched `seed` when that
/// replica authored none.
///
/// `next_counter` is a *single* monotonic counter shared by every
/// identifier kind (`IdentityContext::take_counter`), so this cannot be
/// scoped to `Instrument` alone: any kind minted in the same log —
/// `CreateStaff`'s `StaffId`, `InsertEvent`'s `EventId`, and so on —
/// shares the same sequence, and a later mint from the returned identity
/// must not re-issue a counter the log already used for *any* kind.
fn derive_identity_cursor(&self, replica: ReplicaId, seed: u64) -> u64 {
let mut max_counter: Option<u64> = None;
let mut bump = |counter: u64| {
max_counter = Some(max_counter.map_or(counter, |m| m.max(counter)));
};
// Every operation id ever accepted into this set, in any slot state
// (Single, Equivocated, promoted or not): the set is keyed by
// `OperationId`, so its key set is exactly "every id in the log" —
// including pending/held/excluded envelopes, which still burned a
// counter at authoring time even though this reduction did not apply
// them.
for (id, _) in self.op_set.slots() {
if id.replica == replica {
bump(id.counter);
}
}
// Every entity id minted into the reduced graph, across every kind —
// not read via a per-kind accessor, but generically from the object
// universe every mint already populates. `TypedObjectId`'s canonical
// form is a 2-byte discriminant then the 16-byte `(replica, counter)`
// payload for every variant except `Registered` (an extension id with
// no such convention, and so skipped here); reading it this way
// avoids a new public replica/counter accessor on `TypedObjectId` in
// `epiphany-core`, which is out of this packet's blast radius.
for obj in self.objects.keys() {
let bytes = obj.canonical_bytes();
if bytes.len() == 18 {
let id_replica = ReplicaId(u64::from_be_bytes(
bytes[2..10].try_into().expect("8 bytes"),
));
if id_replica == replica {
let counter = u64::from_be_bytes(bytes[10..18].try_into().expect("8 bytes"));
bump(counter);
}
}
}
match max_counter {
Some(m) => seed.max(m + 1),
None => seed,
}
}
fn record_anomaly(&mut self, kind: IntegrityAnomalyKind) { fn record_anomaly(&mut self, kind: IntegrityAnomalyKind) {
let a = IntegrityAnomaly::new(kind); let a = IntegrityAnomaly::new(kind);
self.anomalies.entry(a.id).or_insert(a); self.anomalies.entry(a.id).or_insert(a);
@ -2635,6 +2742,7 @@ impl<'a> Reducer<'a> {
OperationKind::SetStaffLayout(op) => self.set_staff_layout(env, op), OperationKind::SetStaffLayout(op) => self.set_staff_layout(env, op),
OperationKind::CreateRepeatStructure(op) => self.create_repeat_structure(env, op), OperationKind::CreateRepeatStructure(op) => self.create_repeat_structure(env, op),
OperationKind::DeleteRepeatStructure(op) => self.delete_repeat_structure(env, op), OperationKind::DeleteRepeatStructure(op) => self.delete_repeat_structure(env, op),
OperationKind::CreateInstrument(op) => self.create_instrument(env, op),
}, },
OperationPayload::ResolveConflict(op) => self.resolve_conflict(env, op), OperationPayload::ResolveConflict(op) => self.resolve_conflict(env, op),
OperationPayload::UndoTransaction(op) => self.undo_transaction(env, op), OperationPayload::UndoTransaction(op) => self.undo_transaction(env, op),
@ -3854,6 +3962,56 @@ impl<'a> Reducer<'a> {
OperationEffect::Applied OperationEffect::Applied
} }
// --- Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`). -------
/// Set-union creation of an `Instrument` on the score root
/// (operation_catalog §CreateInstrument): fresh id mints; a
/// byte-identical re-carry is idempotent; a differing value under a live
/// id is a precondition no-op — exactly `create_staff`'s discipline
/// (contract pin 6). Unlike `create_staff`, there is no graph-aware
/// reference-resolution block: `Instrument` holds no outbound entity
/// references (contract preamble), so this operation needs no referential
/// preconditions at all.
fn create_instrument(
&mut self,
env: &OperationEnvelope,
op: &CreateInstrumentOp,
) -> OperationEffect {
let iobj = TypedObjectId::Instrument(op.instrument_id());
match self.objects.get(&iobj) {
Some(ObjectState::Live) => {
let identical = self
.instrument_values
.get(&op.instrument_id())
.is_some_and(|known| known == &op.instrument);
return if identical {
OperationEffect::NoOp {
reason: NoOpReason::AlreadyApplied,
}
} else {
OperationEffect::NoOp {
reason: NoOpReason::PreconditionFailedUnderReduction {
reason: PreconditionFailureReason::RecreateContentMismatch,
},
}
};
}
Some(ObjectState::Tombstoned { .. }) => {
return OperationEffect::NoOp {
reason: NoOpReason::TargetTombstoned,
}
}
None => {}
}
if let Some(score) = self.graph.as_mut() {
score.instruments.push(op.instrument.clone());
}
self.mint_container(env, iobj);
self.instrument_values
.insert(op.instrument_id(), op.instrument.clone());
OperationEffect::Applied
}
/// Set-union mint of a `TimeSignature` carried by a `SetTimeSignature` /// Set-union mint of a `TimeSignature` carried by a `SetTimeSignature`
/// (operation_catalog §"Meter and Tempo Overwrites"): fresh id mints; /// (operation_catalog §"Meter and Tempo Overwrites"): fresh id mints;
/// byte-identical re-carry is idempotent; a differing value under a live /// byte-identical re-carry is idempotent; a differing value under a live
@ -7233,6 +7391,7 @@ impl<'a> Reducer<'a> {
staff_layout_chain: self.staff_layout_chain.clone(), staff_layout_chain: self.staff_layout_chain.clone(),
staff_values: self.staff_values.clone(), staff_values: self.staff_values.clone(),
time_signature_values: self.time_signature_values.clone(), time_signature_values: self.time_signature_values.clone(),
instrument_values: self.instrument_values.clone(),
structures: self.structures.clone(), structures: self.structures.clone(),
region_instances: self.region_instances.clone(), region_instances: self.region_instances.clone(),
instance_voices: self.instance_voices.clone(), instance_voices: self.instance_voices.clone(),
@ -7269,6 +7428,7 @@ impl<'a> Reducer<'a> {
self.staff_layout_chain = s.staff_layout_chain; self.staff_layout_chain = s.staff_layout_chain;
self.staff_values = s.staff_values; self.staff_values = s.staff_values;
self.time_signature_values = s.time_signature_values; self.time_signature_values = s.time_signature_values;
self.instrument_values = s.instrument_values;
self.structures = s.structures; self.structures = s.structures;
self.region_instances = s.region_instances; self.region_instances = s.region_instances;
self.instance_voices = s.instance_voices; self.instance_voices = s.instance_voices;
@ -7452,7 +7612,7 @@ mod tests {
use crate::causal::CausalContext; use crate::causal::CausalContext;
use crate::stamp::{HybridLogicalClock, OperationStamp}; use crate::stamp::{HybridLogicalClock, OperationStamp};
use crate::support::AuthorId; use crate::support::AuthorId;
use epiphany_core::{RationalTime, ReplicaId, StaffInstanceId, WallClockTime}; use epiphany_core::{IdentityContext, RationalTime, ReplicaId, StaffInstanceId, WallClockTime};
fn pos(n: i64) -> MusicalPosition { fn pos(n: i64) -> MusicalPosition {
MusicalPosition(RationalTime::from_int(n as i32)) MusicalPosition(RationalTime::from_int(n as i32))
@ -10486,6 +10646,27 @@ mod tests {
0, 0,
"DeleteRepeatStructure carries a bare id — a major-0 layout" "DeleteRepeatStructure carries a bare id — a major-0 layout"
); );
// Genesis tranche G1 (i7): CreateInstrument is *unconditionally* v2 —
// contract pin 4/trap 3. Unlike CreateRegion/SetStaffLayout above,
// whose v2 embedding rides an `Option` and is value-dependent,
// Instrument's schema-major-2 appends (sound_config, default_clef,
// default_staff_lines, ...) are mandatory fields, so even the most
// minimal instrument — every optional field at `None`, built via
// `Instrument::new` exactly like `valuegen::instrument` does — stamps
// 2. Asserted on the minimal value specifically, so an arm that
// becomes value-dependent (checking e.g. `range.is_some()`) is
// caught immediately rather than only on a richer fixture.
let minimal_instrument =
epiphany_core::Instrument::new(epiphany_core::InstrumentId::new(ReplicaId(9), 7), "x");
assert_eq!(
OperationKind::CreateInstrument(crate::payload::CreateInstrumentOp {
instrument: minimal_instrument,
})
.schema_major(),
2,
"CreateInstrument is unconditionally v2, even for the minimal instrument"
);
} }
#[test] #[test]
@ -10553,6 +10734,15 @@ mod tests {
// moves. Nothing leaked: `canonical_bytes` embeds effects, conflicts, // moves. Nothing leaked: `canonical_bytes` embeds effects, conflicts,
// and anomalies, never payload values, and the new payload's // and anomalies, never payload values, and the new payload's
// constituents are major-0 layouts regardless. // constituents are major-0 layouts regardless.
//
// Re-pinned again at genesis tranche G1
// (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`): `gen_payload` gained
// `CreateInstrument`, discriminant 31, and `rng.below(28)` became
// `below(29)` — the same reshuffle, for the same reason. Nothing
// leaked here either: `CreateInstrument.schema_major()` is 2
// (unconditionally, contract pin 4), but `MaterializedState` stamps
// no schema major at all — that is an `OperationEnvelopeBlock`
// concern in `epiphany-bundle`, which this packet does not touch.
let mut rng = epiphany_determinism::fuzz::SplitMix64::new(0xBA5E); let mut rng = epiphany_determinism::fuzz::SplitMix64::new(0xBA5E);
let envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200); let envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200);
let mut set = OperationSet::new(); let mut set = OperationSet::new();
@ -10562,7 +10752,7 @@ mod tests {
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect(); let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!( assert_eq!(
hex, hex,
"594f29e20250a9ff36f0033a59e380e4514aa32a6e78d062579b950667439e20" "61af8ebbba1c4d98360ec44812e5d97a89a720738c1e3573d865f26b48addd1d"
); );
} }
@ -12365,4 +12555,397 @@ mod tests {
); );
} }
} }
// === Genesis tranche G1 (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`). =======
/// The full from-empty spine, one replica-1 operation per even counter
/// (0, 2, 4, 6, 8, 10) and one minted entity id per odd counter
/// (1, 3, 5, 7, 9, 11) — `CreateInstrument` (i1's load-bearing chain,
/// the ruling's acceptance criterion 1): `CreateInstrument` ->
/// `CreateStaff` -> `CreateRegion` -> `CreateStaffInstance` ->
/// `CreateVoice` -> `InsertEvent`. Returns the envelopes in canonical
/// authoring order, plus the ids a caller needs to inspect the result.
struct GenesisSpine {
envelopes: Vec<OperationEnvelope>,
event_id: EventId,
}
fn genesis_spine_envelopes() -> GenesisSpine {
let instrument_id = InstrumentId::new(ReplicaId(1), 1);
let staff_id = StaffId::new(ReplicaId(1), 3);
let region_id = RegionId::new(ReplicaId(1), 5);
let instance_id = StaffInstanceId::new(ReplicaId(1), 7);
let voice_id = VoiceId::new(ReplicaId(1), 9);
let event_id = EventId::new(ReplicaId(1), 11);
let create_instrument = prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: crate::valuegen::instrument(instrument_id),
}),
);
let create_staff = prim_env(
1,
2,
20,
CausalContext::new(),
OperationKind::CreateStaff(CreateStaffOp {
staff: crate::valuegen::staff(staff_id, instrument_id),
}),
);
let create_region = prim_env(
1,
4,
30,
CausalContext::new(),
OperationKind::CreateRegion(CreateRegionOp {
region: crate::valuegen::region(region_id),
}),
);
let create_instance = prim_env(
1,
6,
40,
CausalContext::new(),
OperationKind::CreateStaffInstance(CreateStaffInstanceOp {
region: region_id,
instance: crate::valuegen::staff_instance(instance_id, staff_id),
}),
);
let create_voice = prim_env(
1,
8,
50,
CausalContext::new(),
OperationKind::CreateVoice(CreateVoiceOp {
staff_instance: instance_id,
voice: crate::valuegen::voice(voice_id),
}),
);
let insert_event = prim_env(
1,
10,
60,
CausalContext::new(),
OperationKind::InsertEvent(InsertEventOp {
staff_instance: instance_id,
event: crate::valuegen::insert_event_value(
event_id,
voice_id,
pos(0),
epiphany_core::MusicalDuration::whole(),
&[],
),
}),
);
GenesisSpine {
envelopes: vec![
create_instrument,
create_staff,
create_region,
create_instance,
create_voice,
insert_event,
],
event_id,
}
}
/// (i1) The packet's load-bearing test and the ruling's acceptance
/// criterion 1: a document created empty and given only operations
/// materializes a note-bearing `Score` — no fixture, no base.
/// `CreateInstrument` is the single missing link, so this also asserts
/// every intermediate link `Applied` rather than only the final note, so
/// a future regression is pinned at its actual source.
///
/// **Mutation** (verified by hand, not left permanently in this test):
/// drop the `CreateInstrument` envelope from the set. `CreateStaff` then
/// finds no live instrument and reduces to
/// `NoOp{PreconditionFailedUnderReduction{TargetMissing}}`, which cascades
/// (no staff instance, no voice, no event) so the final `contains` check
/// fails too — see the report for the observed failure text.
#[test]
fn from_empty_operations_alone_materialize_a_note() {
let spine = genesis_spine_envelopes();
let mut set = OperationSet::new();
set.accept_all(spine.envelopes);
let identity = IdentityContext::new(ReplicaId(1));
let out = reduce_operation_set_onto(&set, &Score::empty(identity));
for counter in [0, 2, 4, 6, 8, 10] {
assert_eq!(
effect_at(&out.state, counter),
Some(&OperationEffect::Applied),
"spine operation at counter {counter} must apply"
);
}
assert!(
out.score.events.contains(spine.event_id),
"the spine reaches a note"
);
}
/// (i2) Re-carry idempotence: the same `CreateInstrument` twice ->
/// `AlreadyApplied`, and a differing second carry under the same live id
/// -> `RecreateContentMismatch` — exactly `create_staff`'s discipline
/// (contract pin 6).
///
/// **Mutation:** make the second carry differ in one field ->
/// `RecreateContentMismatch` where the baseline case (identical carries)
/// reduces `AlreadyApplied`. Both branches are standing assertions here
/// (not a temporary edit), so the contrast is permanent.
#[test]
fn create_instrument_recarry_is_idempotent_or_a_precondition_no_op() {
let instrument_id = InstrumentId::new(ReplicaId(1), 1);
let value = crate::valuegen::instrument(instrument_id);
let create = prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: value.clone(),
}),
);
let identical = prim_env(
2,
0,
20,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: value.clone(),
}),
);
let mut differing_value = value.clone();
differing_value.name = String::from("something else");
let differing = prim_env(
3,
0,
30,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: differing_value,
}),
);
let mut set = OperationSet::new();
set.accept_all(vec![differing.clone(), identical.clone(), create.clone()]);
let state = set.reduce();
let effect_of = |id: OperationId| {
state
.effects
.iter()
.find(|(e, _)| *e == id)
.map(|(_, eff)| eff)
};
assert_eq!(effect_of(create.id), Some(&OperationEffect::Applied));
assert_eq!(
effect_of(identical.id),
Some(&OperationEffect::NoOp {
reason: NoOpReason::AlreadyApplied,
}),
"a byte-identical re-create reduces idempotently"
);
assert_eq!(
effect_of(differing.id),
Some(&OperationEffect::NoOp {
reason: NoOpReason::PreconditionFailedUnderReduction {
reason: PreconditionFailureReason::RecreateContentMismatch,
},
}),
"a differing value under a live id is a precondition no-op"
);
}
/// (i3) Re-carry against a *base* instrument — the pin-8 case.
/// `TypedObjectId::Instrument` liveness is seeded from the base
/// (`seed_from_graph`), but without `instrument_values` seeded too, a
/// byte-identical re-carry against a base instrument has nothing to
/// compare.
///
/// **Mutation:** skip seeding `instrument_values` in `seed_from_graph` ->
/// the byte-identical re-carry is misreported
/// `RecreateContentMismatch` instead of `AlreadyApplied`.
#[test]
fn create_instrument_recarry_against_a_base_instrument_is_idempotent() {
let instrument_id = InstrumentId::new(ReplicaId(1), 1);
let value = crate::valuegen::instrument(instrument_id);
let mut base = Score::empty(IdentityContext::new(ReplicaId(1)));
base.instruments.push(value.clone());
let recreate = prim_env(
2,
0,
10,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp { instrument: value }),
);
let mut set = OperationSet::new();
set.accept_all(vec![recreate.clone()]);
let out = reduce_operation_set_onto(&set, &base);
let effect = out
.state
.effects
.iter()
.find(|(id, _)| *id == recreate.id)
.map(|(_, eff)| eff);
assert_eq!(
effect,
Some(&OperationEffect::NoOp {
reason: NoOpReason::AlreadyApplied,
}),
"byte-identical re-carry against a base instrument is idempotent"
);
assert_eq!(
out.score.instruments.len(),
1,
"no duplicate instrument is minted"
);
}
/// (i4) Order independence (ruling acceptance criterion 2): two replicas
/// racing to create the *same* instrument id with differing values (a
/// concurrent genesis-era scenario) converge to byte-identical
/// `MaterializedState` regardless of delivery order — canonical order,
/// never insertion order, decides the winner.
///
/// **Mutation:** perturb one op's reduction order (its stamp) — asserted
/// permanently below as `assert_ne!`, so the equality above is proven
/// non-vacuous: a *genuine* reordering really does change the converged
/// bytes, which is what the primary assertion would have caught had
/// insertion order actually leaked into the result.
#[test]
fn concurrent_differing_creates_of_the_same_instrument_converge_regardless_of_delivery_order() {
let instrument_id = InstrumentId::new(ReplicaId(9), 1);
let value_a = crate::valuegen::instrument(instrument_id);
let mut value_b = value_a.clone();
value_b.name = String::from("a concurrently-authored name");
let create_a = prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: value_a,
}),
);
let create_b = prim_env(
2,
0,
10,
CausalContext::new(),
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: value_b,
}),
);
let forward = {
let mut set = OperationSet::new();
set.accept_all(vec![create_a.clone(), create_b.clone()]);
set.reduce().canonical_bytes()
};
let reversed = {
let mut set = OperationSet::new();
set.accept_all(vec![create_b.clone(), create_a.clone()]);
set.reduce().canonical_bytes()
};
assert_eq!(
forward, reversed,
"delivery order must not affect the converged state"
);
// Proves the equality above is not vacuous: perturbing one op's own
// reduction-order-determining field (its HLC stamp) so it now sorts
// *before* its concurrent sibling really does flip the winner and
// change the converged bytes.
let perturbed = {
let mut earlier_b = create_b.clone();
earlier_b.stamp =
OperationStamp::new(HybridLogicalClock::new(WallClockTime(5), 0), earlier_b.id);
let mut set = OperationSet::new();
set.accept_all(vec![create_a.clone(), earlier_b]);
set.reduce().canonical_bytes()
};
assert_ne!(
forward, perturbed,
"a genuine reduction-order change must change the winner"
);
}
/// (i5) The identity cursor (ruling §3 point 3). Minting from a
/// reduced-from-empty score must not collide with any id the log already
/// used — `genesis_spine_envelopes` places replica 1's ids at counters
/// `0..=11` (an operation id at every even counter, a minted entity id at
/// every odd one), so the derived cursor must be exactly `12`.
///
/// **Mutation:** leave `next_counter` at the seed (`0`) -> the next mint
/// collides with `create_instrument`'s own `OperationId` counter.
#[test]
fn identity_cursor_advances_past_every_id_the_replica_used() {
let spine = genesis_spine_envelopes();
let mut set = OperationSet::new();
set.accept_all(spine.envelopes);
let identity = IdentityContext::new(ReplicaId(1));
let mut out = reduce_operation_set_onto(&set, &Score::empty(identity));
assert_eq!(
out.score.identity.next_counter, 12,
"the cursor advances past every id (op or entity) the log used"
);
let minted: EventId = out.score.identity.mint();
assert_eq!(
minted,
EventId::new(ReplicaId(1), 12),
"a fresh mint from the reduced identity collides with nothing in the log"
);
}
/// (i6) Base-free reduction skips a referential precondition that
/// graph-aware reduction enforces — a **designed asymmetry**, not a bug:
/// base-free reduction has no instrument universe to check against
/// (`reduce.rs` — `self.graph.is_some()` guards `create_staff`'s
/// reference-resolution block). A later reader must not "fix" this by
/// adding the check to base-free reduction; the fix, if this is ever
/// wrong, is to stop calling `reduce_operation_set` on a from-empty
/// document (contract pin 10 / ruling §4).
#[test]
fn base_free_reduction_skips_the_referential_precondition_graph_aware_reduction_enforces() {
let staff_id = StaffId::new(ReplicaId(1), 1);
let missing_instrument = InstrumentId::new(ReplicaId(1), 99); // never minted
let create_staff = prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::CreateStaff(CreateStaffOp {
staff: crate::valuegen::staff(staff_id, missing_instrument),
}),
);
let mut set = OperationSet::new();
set.accept_all(vec![create_staff.clone()]);
let base_free = reduce_operation_set(&set);
assert_eq!(
effect_at(&base_free, 0),
Some(&OperationEffect::Applied),
"base-free reduction has no instrument universe to check against"
);
let identity = IdentityContext::new(ReplicaId(1));
let onto = reduce_operation_set_onto(&set, &Score::empty(identity));
assert_eq!(
effect_at(&onto.state, 0),
Some(&OperationEffect::NoOp {
reason: NoOpReason::PreconditionFailedUnderReduction {
reason: PreconditionFailureReason::TargetMissing,
},
}),
"graph-aware reduction enforces the precondition base-free skipped"
);
}
} }

View File

@ -8,12 +8,13 @@ use epiphany_determinism::{sorted_canonical, CanonicalEncode};
use unicode_normalization::UnicodeNormalization; use unicode_normalization::UnicodeNormalization;
use crate::payload::{ use crate::payload::{
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, DeleteCrossCuttingOp, DeleteEventOp, CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp,
DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, RespellPitchOp, SetMetadataOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, OperationKind, OperationKindTag, RespellPitchOp, SetMetadataOp, SetMetricGridOp,
SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
SetUserSystemBreakOp, TransactionDescriptor, TransposeIntervalOp, TransposeOp, SetUserSystemBreakOp, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
}; };
use crate::support::OperationKindRegistryId; use crate::support::OperationKindRegistryId;
@ -218,6 +219,9 @@ impl TextValue for OperationKind {
self.tag(), self.tag(),
vec![op.targets.project(), op.interval.project()], vec![op.targets.project(), op.interval.project()],
), ),
OperationKind::CreateInstrument(op) => {
production(self.tag(), vec![op.instrument.project()])
}
} }
} }
@ -531,6 +535,14 @@ impl TextValue for OperationKind {
interval: TextValue::parse(interval)?, interval: TextValue::parse(interval)?,
}) })
} }
OperationKindTag::CreateInstrument => {
let [instrument] = fields(s, tag, 1)? else {
unreachable!("the arity-1 check returned one field")
};
OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: TextValue::parse(instrument)?,
})
}
}) })
} }
} }
@ -584,7 +596,7 @@ mod tests {
#[test] #[test]
fn every_operation_kind_round_trips_with_canonical_text() { fn every_operation_kind_round_trips_with_canonical_text() {
let tags: Vec<_> = all_tags().collect(); let tags: Vec<_> = all_tags().collect();
assert_eq!(tags.len(), 31, "the grammar has 31 kind productions"); assert_eq!(tags.len(), 32, "the grammar has 32 kind productions");
for tag in tags { for tag in tags {
round_trip(&sample_kind(tag)); round_trip(&sample_kind(tag));
} }

View File

@ -103,6 +103,10 @@ pub enum V0OperationKind {
/// Born at wire-disc 30 under major-0 layouts (Push 4a); no lossy v0 /// Born at wire-disc 30 under major-0 layouts (Push 4a); no lossy v0
/// form, so it projects verbatim like the repeat-authoring pair. /// form, so it projects verbatim like the repeat-authoring pair.
TransposeInterval(crate::payload::TransposeIntervalOp), TransposeInterval(crate::payload::TransposeIntervalOp),
// Genesis tranche G1 — born at wire-disc 31; no lossy v0 form (v0 predates
// the catalog entirely), so it round-trips by identity like every other
// v1-native kind above.
CreateInstrument(crate::payload::CreateInstrumentOp),
} }
/// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant /// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant

View File

@ -361,6 +361,13 @@ pub fn staff(id: StaffId, instrument: epiphany_core::InstrumentId) -> epiphany_c
} }
} }
/// A minimal [`Instrument`](epiphany_core::Instrument) (genesis tranche G1) —
/// the value a `CreateInstrument` mints: named for its counter, every
/// schema-major-2 field at `Instrument::new`'s canonical default.
pub fn instrument(id: epiphany_core::InstrumentId) -> epiphany_core::Instrument {
epiphany_core::Instrument::new(id, format!("instrument-{}", id.counter()))
}
/// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature) /// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature)
/// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to /// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to
/// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group /// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group

View File

@ -16,11 +16,11 @@
//! The `class` string is informative, not normative: implementations need not //! The `class` string is informative, not normative: implementations need not
//! agree on error taxonomy, only on the accept/reject verdict. //! agree on error taxonomy, only on the accept/reject verdict.
use epiphany_core::{EventId, OperationId, ReplicaId, TypedObjectId}; use epiphany_core::{EventId, InstrumentId, OperationId, ReplicaId, TypedObjectId};
use epiphany_determinism::CanonicalEncode; use epiphany_determinism::CanonicalEncode;
use crate::{ use crate::{
IntegrityAnomaly, IntegrityAnomalyKind, MaterializedState, ObjectState, IntegrityAnomaly, IntegrityAnomalyKind, MaterializedState, ObjectState, OperationEnvelope,
OperationKindRegistryId, OperationKindTag, PendingReason, OperationKindRegistryId, OperationKindTag, PendingReason,
}; };
@ -255,6 +255,49 @@ pub fn decode_vectors() -> Vec<DecodeVector> {
short_registered, short_registered,
)); ));
// --- OperationEnvelope carrying CreateInstrument (genesis tranche G1) --
//
// `ops.operation_kind_tag` above pins only the bare, payload-free tag
// byte; nothing in this corpus previously exercised a *value-carrying*
// `OperationKind` payload's decode path at all. Committed here so a
// future encoder/decoder change to this payload moves this vector's
// bytes deliberately, in the diff (the 3b-i lesson the module doc names:
// round-trip locking alone cannot see a self-consistent reorder of both
// halves).
const OE: &str = "ops.operation_envelope";
let envelope = OperationEnvelope {
id: OperationId::new(ReplicaId(1), 1),
author: crate::support::AuthorId(0),
stamp: crate::stamp::OperationStamp::new(
crate::stamp::HybridLogicalClock::new(epiphany_core::WallClockTime(1), 1),
OperationId::new(ReplicaId(1), 1),
),
causal_context: crate::causal::CausalContext::new(),
transaction: None,
payload: crate::payload::OperationPayload::Primitive(
crate::payload::OperationKind::CreateInstrument(crate::payload::CreateInstrumentOp {
instrument: crate::valuegen::instrument(InstrumentId::new(ReplicaId(1), 1)),
}),
),
};
let envelope_bytes = envelope.to_canonical_bytes();
v.push(row(
OE,
"accept",
"-",
"create_instrument",
envelope_bytes.clone(),
));
let mut trailing = envelope_bytes;
trailing.push(0);
v.push(row(
OE,
"reject",
"trailing-bytes",
"create_instrument_trailing",
trailing,
));
v v
} }
@ -277,6 +320,10 @@ pub fn check(surface: &str, bytes: &[u8]) -> Option<Result<bool, String>> {
Err(e) => Err(format!("{e}")), Err(e) => Err(format!("{e}")),
}), }),
"ops.operation_kind_tag" => Some(decode_tag(bytes)), "ops.operation_kind_tag" => Some(decode_tag(bytes)),
"ops.operation_envelope" => Some(match crate::envdecode::decode_envelope(bytes) {
Ok(env) => Ok(env.to_canonical_bytes() == bytes),
Err(e) => Err(format!("{e:?}")),
}),
_ => None, _ => None,
} }
} }
@ -315,7 +362,11 @@ mod tests {
/// pinning half a contract. /// pinning half a contract.
#[test] #[test]
fn every_surface_carries_both_verdicts() { fn every_surface_carries_both_verdicts() {
for surface in ["ops.materialized_state", "ops.operation_kind_tag"] { for surface in [
"ops.materialized_state",
"ops.operation_kind_tag",
"ops.operation_envelope",
] {
let rows: Vec<_> = decode_vectors() let rows: Vec<_> = decode_vectors()
.into_iter() .into_iter()
.filter(|(s, ..)| *s == surface) .filter(|(s, ..)| *s == surface)
@ -340,4 +391,43 @@ mod tests {
assert!(classes.contains(&"non-canonical-map-order")); assert!(classes.contains(&"non-canonical-map-order"));
assert!(classes.contains(&"non-canonical-vec-order")); assert!(classes.contains(&"non-canonical-vec-order"));
} }
/// (i8) Genesis tranche G1
/// (`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`): the `CreateInstrument`
/// envelope decode vector, pinned to a **literal byte array copied from
/// the committed corpus** (`spec/vectors/decode_vectors.txt`,
/// `ops.operation_envelope`/`create_instrument`) — not derived by calling
/// `.to_canonical_bytes()` here. `every_vector_gets_its_declared_verdict`
/// above checks `decode_vectors()`'s *own* output against `check`, which
/// cannot see a self-consistent encoder/decoder reorder: the 3b-i lesson
/// (`epiphany-core`'s `schema_major_3_tuning_context_wire_bytes_are_frozen`)
/// is that a swap applied identically to both halves passed 1283 tests
/// and 8/8 conformance, because every check in that failure mode compared
/// the live encoder against itself. Bytes written here by hand — as this
/// module's own `decode_vectors()` writes them, into the *committed* file
/// a future encoder change must move deliberately, in the diff — close
/// that hole for `CreateInstrument` specifically.
#[test]
fn create_instrument_envelope_decode_vector_is_pinned_to_literal_bytes() {
#[rustfmt::skip]
let bytes: Vec<u8> = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1f, 0x41, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00,
0x00, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x31, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let result = check("ops.operation_envelope", &bytes)
.expect("ops.operation_envelope is owned by this crate");
assert_eq!(
result,
Ok(true),
"the committed literal bytes must decode and re-encode injectively"
);
}
} }

View File

@ -301,10 +301,16 @@ fn the_kind_productions_are_the_operation_vocabulary() {
// four bugs. // four bugs.
.map(|t| t.catalog_name().to_string()) .map(|t| t.catalog_name().to_string())
.collect(); .collect();
// The count is still hand-maintained, which is the same shape the comment
// above warns about — derived list, literal total. Every tranche that
// appends a tag must bump it (genesis G1 took it from 31 to 32 by adding
// `CreateInstrument`). It stays a literal on purpose: deriving it from
// `PAYLOAD_FREE.len()` would make the assertion vacuous, since that is the
// very list it exists to pin.
assert_eq!( assert_eq!(
expected.len(), expected.len(),
31, 32,
"30 payload-free kinds plus `Registered`" "31 payload-free kinds plus `Registered`"
); );
let actual = alternatives("kind"); let actual = alternatives("kind");
@ -547,9 +553,16 @@ fn worked_example_header_is_the_implemented_companion_version() {
.map(|(version, _)| version) .map(|(version, _)| version)
}) })
.expect("the title page declares the companion version"); .expect("the title page declares the companion version");
// Pinned to the crate constant rather than a literal: the point of this
// assertion is that the spec's title page and the implemented version never
// drift apart, and a literal here made a companion bump edit two places by
// hand — exactly the "update only one of the two" failure the doc comment
// above warns about. Genesis G1 bumped 0.7.0 → 0.8.0 and tripped it.
let (major, minor, patch) = epiphany_textproj::COMPANION_VERSION;
assert_eq!( assert_eq!(
title_version, "0.7.0", title_version,
"this implementation targets exactly companion 0.7.0" format!("{major}.{minor}.{patch}"),
"the spec title page must declare the version this implementation targets"
); );
let expected = format!("(text-projection ({}))", title_version.replace('.', " ")); let expected = format!("(text-projection ({}))", title_version.replace('.', " "));

View File

@ -19,7 +19,14 @@ use epiphany_ops::OperationEnvelope;
/// ///
/// A parser must reject every other version rather than migrating or /// A parser must reject every other version rather than migrating or
/// normalizing it on read. /// normalizing it on read.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 7, 0); ///
/// Bumped 0.7.0 → 0.8.0 by the genesis tranche G1, which appended
/// `create-instrument` to the `kind` production — the first operation kind
/// added since the header was gated to a single version. Extending the grammar
/// without moving this constant would leave two incompatible grammars both
/// claiming `(0 7 0)`. Cached projections do not migrate: a `TextProjection`
/// chunk is a non-canonical accelerator, so a stale one is regenerated.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 8, 0);
/// A parsed canonical Text Projection document. /// A parsed canonical Text Projection document.
/// ///

View File

@ -642,7 +642,11 @@ mod tests {
out out
} }
const HEADER: &str = "(text-projection (0 7 0))"; // Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1). Kept a
// literal because `projection` takes `&[&str]` and a formatted String
// would ripple through every call site; `the_test_header_tracks_the_
// implemented_version` below fails loudly if the two ever drift.
const HEADER: &str = "(text-projection (0 8 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001)"; const DOCUMENT: &str = "(document #x00000000000000000000000000000001)";
/// A minimal but complete valid projection: just the two mandatory lines. /// A minimal but complete valid projection: just the two mandatory lines.
@ -650,6 +654,19 @@ mod tests {
projection(&[HEADER, DOCUMENT]) projection(&[HEADER, DOCUMENT])
} }
/// `HEADER` is a literal, so nothing but this test stops it drifting from
/// the version the crate actually implements. Without it a companion bump
/// leaves every fixture below silently rejected by the version gate, and
/// the failures point at section ordering rather than at the header.
#[test]
fn the_test_header_tracks_the_implemented_version() {
let (major, minor, patch) = COMPANION_VERSION;
assert_eq!(
HEADER,
format!("(text-projection ({major} {minor} {patch}))")
);
}
/// A simple, independent (empty causal context) envelope, so several of /// A simple, independent (empty causal context) envelope, so several of
/// these can be combined without any causal-order machinery beyond their /// these can be combined without any causal-order machinery beyond their
/// HLC physical time. /// HLC physical time.

View File

@ -576,7 +576,11 @@ mod tests {
#[test] #[test]
fn header_matches_the_worked_example() { fn header_matches_the_worked_example() {
assert_eq!(project_header().render(), "(text-projection (0 7 0))"); let (major, minor, patch) = crate::COMPANION_VERSION;
assert_eq!(
project_header().render(),
format!("(text-projection ({major} {minor} {patch}))")
);
} }
#[test] #[test]

View File

@ -322,16 +322,23 @@ pub fn document_vectors() -> Vec<TextVector> {
.map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec())) .map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec()))
.collect(); .collect();
// The rejected version must be one this crate does NOT implement. Genesis
// G1 moved `COMPANION_VERSION` to 0.8.0, which had been this vector's
// "future" version — leaving it would have made the negative vector assert
// that the *correct* header is rejected. It now names 0.7.0, the
// immediately superseded companion, which is the better test anyway:
// rejecting the version right behind you is exactly the deferred
// migrate-on-read posture (`req:textproj:header-version`).
let wrong_version = replace_once( let wrong_version = replace_once(
minimal, minimal,
"(text-projection (0 7 0))",
"(text-projection (0 8 0))", "(text-projection (0 8 0))",
"(text-projection (0 7 0))",
); );
vectors.push(( vectors.push((
SURFACE, SURFACE,
"reject", "reject",
"wrong-header-version", "wrong-header-version",
"future_companion_version", "superseded_companion_version",
wrong_version.into_bytes(), wrong_version.into_bytes(),
)); ));

Binary file not shown.

View File

@ -1075,6 +1075,69 @@ anchor; there is no \texttt{DeleteStaff} in this catalogue revision --- an
empty-only staff delete mirroring the container discipline is a later empty-only staff delete mirroring the container discipline is a later
schema-fill). schema-fill).
\section{CreateInstrument}
\label{sec:k0:create-instrument}
Ratified with the genesis operation tranche's first rung (G1;
\texttt{spec/CONTRACT\_GENESIS\_G1\_INSTRUMENT.md}, executing
\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}). \texttt{CreateStaff}
(Section~\ref{sec:k0:create-staff}) already requires a live
\texttt{Instrument}, and until this primitive existed nothing could mint one
except a canonical base --- so a document created empty
(\texttt{Score::empty}) and given only operations could reach a
\texttt{CreateStaff} but never satisfy it. \texttt{CreateInstrument} is the
single missing link between an empty score and a note: with it, the chain
\texttt{CreateInstrument} $\rightarrow$ \texttt{CreateStaff} $\rightarrow$
\texttt{CreateRegion} $\rightarrow$ \texttt{CreateStaffInstance}
$\rightarrow$ \texttt{CreateVoice} $\rightarrow$ \texttt{InsertEvent}
materializes a note-bearing score from operations alone.
\textbf{Payload schema.} \texttt{CreateInstrumentOp \{ instrument: Instrument
\}} --- the full abstract-instrument value (schema major 2): identity, name,
declared pitch range, abbreviation, sound configuration, transposition
interval, default clef, default staff-line configuration, and unpitched
members. Unlike every carried type in
Section~\ref{sec:k0:structural-containers} and
Section~\ref{sec:k0:create-staff}, \texttt{Instrument} holds no outbound
entity reference, so this primitive needs no referential precondition at
all --- only mint and idempotence.
\textbf{Canonical encoding.} The length-framed canonical bytes of
\texttt{instrument}. \texttt{Instrument}'s schema-major-2 appends
(\texttt{sound\_config}, \texttt{default\_clef},
\texttt{default\_staff\_lines}, and the rest) are mandatory fields, not
\texttt{Option}-hidden, so no lower-major layout for this payload exists:
under minimal stamping (Binary Format companion \sectionsc{Schema Major 2})
a block carrying a \texttt{CreateInstrument} is \emph{born at v2}, exactly
as \texttt{CreateStaff} is --- not the value-dependent shape
\texttt{CreateRegion}/\texttt{SetStaffLayout} use.
\textbf{Reduction rule.} Set-union creation of an \texttt{Instrument} on the
score root, mirroring \texttt{CreateStaff}'s discipline exactly since
neither carried type references another entity: a create mints the
instrument live if its id is fresh; a repeat create carrying a
byte-identical value reduces idempotently
(\texttt{NoOpReason::AlreadyApplied}); a create whose id is already live
with a \emph{differing} value is a precondition no-op with
\texttt{RecreateContentMismatch}; and a create naming a tombstoned id is a
precondition no-op with \texttt{TargetTombstoned}. There is no
graph-aware reference-resolution block, because there is nothing for one to
check.
\textbf{Conflict cases.} None at reduction time (set-union; the
differing-value re-create is a precondition gate, not a conflict).
\textbf{Undo semantics.} Undo of a create tombstones the minted instrument;
\texttt{StrictInverse} conflicts if a live staff references it (tombstoning
it would strand the staff).
\textbf{Re-anchoring.} Not applicable, for the same reason as
\texttt{CreateStaff}: an instrument mint references no tombstonable anchor.
There is no \texttt{DeleteInstrument} in this catalogue revision ---
\texttt{CreateStaff} ships today with no \texttt{DeleteStaff} either, and
full delete/modify coverage of the genesis-authored fields is later
tranche work (\texttt{spec/PLAN\_GENESIS\_OPS.md} G3).
\section{Repeat Structures} \section{Repeat Structures}
\label{sec:k0:repeat-structures} \label{sec:k0:repeat-structures}

Binary file not shown.

View File

@ -234,7 +234,7 @@
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt] {\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.7.0 --- Canonical blob rejection and single-version header gating}\\[4pt] {\normalsize\color{epiphanyink}Version 0.8.0 --- The genesis operation vocabulary reaches the grammar}\\[4pt]
{\small\color{epiphanyslate}Normative for the text form it defines} {\small\color{epiphanyslate}Normative for the text form it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -467,7 +467,7 @@ A projection is, in order:
\begin{requirement} \begin{requirement}
\label{req:textproj:header-version} \label{req:textproj:header-version}
A parser implementing this companion \MUST{} accept exactly one header A parser implementing this companion \MUST{} accept exactly one header
version: \texttt{(0 7 0)}, the version of the companion it implements. It version: \texttt{(0 8 0)}, the version of the companion it implements. It
\MUST{} reject any other version at line one. \MUST{} reject any other version at line one.
Multi-version acceptance and text migrate-on-read are deferred in the same Multi-version acceptance and text migrate-on-read are deferred in the same
@ -518,7 +518,7 @@ projection introduces no ordering of its own.
A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is
unreferenced by canonical state unreferenced by canonical state
(Requirement~\ref{req:textproj:canonical-blobs}). At companion (Requirement~\ref{req:textproj:canonical-blobs}). At companion
version~0.7.0, neither a canonical operation nor canonical reduced state can version~0.8.0, neither a canonical operation nor canonical reduced state can
carry a \texttt{BlobId}; canonical state therefore cannot reference a blob, carry a \texttt{BlobId}; canonical state therefore cannot reference a blob,
and a parser \MUST{} reject every \texttt{(blob ...)} line. and a parser \MUST{} reject every \texttt{(blob ...)} line.
\end{requirement} \end{requirement}
@ -1041,6 +1041,7 @@ kind ::= "(insert-event " bytes " " value ")"
| "(create-repeat-structure " value ")" | "(create-repeat-structure " value ")"
| "(delete-repeat-structure " bytes ")" | "(delete-repeat-structure " bytes ")"
| "(transpose-interval (" bytes* ") " value ")" | "(transpose-interval (" bytes* ") " value ")"
| "(create-instrument " value ")"
tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")" tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")"
| "(rewrite-tuplets (" bytes* "))" | "(rewrite-tuplets (" bytes* "))"
@ -1117,7 +1118,7 @@ A document of one operation --- a transposition of two pitches up a perfect
fifth, over a compacted base --- projects to five lines: fifth, over a compacted base --- projects to five lines:
\begin{lstlisting} \begin{lstlisting}
(text-projection (0 7 0)) (text-projection (0 8 0))
(document #x05050505050505050505050505050505) (document #x05050505050505050505050505050505)
(profile full (0 1 0) (constraints 67108864 (retention 1 () true))) (profile full (0 1 0) (constraints 67108864 (retention 1 () true)))
(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000) (canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)
@ -1305,6 +1306,25 @@ absorb it, exactly as the binary decoder does.
version-keyed migration path rather than teaching the current parser to version-keyed migration path rather than teaching the current parser to
speculate. The worked example now uses the implemented header and contains speculate. The worked example now uses the implemented header and contains
only grammar-valid, canonical lines. \\ only grammar-valid, canonical lines. \\
\today & Chapter 5 & 0.8.0 --- The genesis operation vocabulary reaches the
grammar. The \texttt{kind} production gains
\texttt{"(create-instrument " value ")"}, the first operation kind appended
since the header was gated to a single version at 0.7.0
(\texttt{req:textproj:operation-vocabulary}).
The bump is forced rather than cosmetic. A parser \MUST{} accept exactly the
version of the companion it implements
(\texttt{req:textproj:header-version}), so extending the grammar while
holding the version would leave two mutually incompatible grammars both
claiming \texttt{(0 7 0)} --- an older parser meeting the new production
would fail with no version signal to explain why, which is the precise
failure the single-version gate exists to prevent.
Cached projections at \texttt{(0 7 0)} do not migrate and are not expected
to. A \texttt{TextProjection} chunk is a non-canonical accelerator a writer
may discard, so a stale projection is regenerated from the canonical
document rather than converted. Multi-version acceptance and text
migrate-on-read remain deferred, unchanged. \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}

View File

@ -68,13 +68,18 @@ ops.operation_kind_tag accept - tag_27 1b
ops.operation_kind_tag accept - tag_28 1c ops.operation_kind_tag accept - tag_28 1c
ops.operation_kind_tag accept - tag_29 1d ops.operation_kind_tag accept - tag_29 1d
ops.operation_kind_tag accept - tag_30 1e ops.operation_kind_tag accept - tag_30 1e
ops.operation_kind_tag accept - tag_31 1f
ops.operation_kind_tag accept - registered 1000000000000000000123456789abcdef ops.operation_kind_tag accept - registered 1000000000000000000123456789abcdef
ops.operation_kind_tag reject unknown-discriminant tag_31_one_past_the_vocabulary 1f ops.operation_kind_tag reject unknown-discriminant tag_32_one_past_the_vocabulary 20
ops.operation_kind_tag reject unknown-discriminant tag_200 c8 ops.operation_kind_tag reject unknown-discriminant tag_200 c8
ops.operation_kind_tag reject truncated tag_empty - ops.operation_kind_tag reject truncated tag_empty -
ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000 ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000
ops.operation_kind_tag reject truncated registered_one_byte_short 10000000000000000000000000000000 ops.operation_kind_tag reject truncated registered_one_byte_short 10000000000000000000000000000000
# ops.operation_envelope
ops.operation_envelope accept - create_instrument 000000000000000100000000000000010000000000000000000000000000000001000000000000000100000000000000000000010000000000000001000000000000000000001f4100000010000000000000000000000100000000000000010c000000696e737472756d656e742d31000000000000000002000508000000000000000000f03f000000000000
ops.operation_envelope reject trailing-bytes create_instrument_trailing 000000000000000100000000000000010000000000000000000000000000000001000000000000000100000000000000000000010000000000000001000000000000000000001f4100000010000000000000000000000100000000000000010c000000696e737472756d656e742d31000000000000000002000508000000000000000000f03f00000000000000
# bundle.manifest # bundle.manifest
bundle.manifest accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000 bundle.manifest accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000
bundle.manifest accept - one_operation_root ae38d9cd408df9b59917c43a82c9d1880505050505050505050505050505050500000000000000000001000000111111111111111111111111111111111111111111111111111111111111111100000001004002000000000000400000000000000040000000000000000000111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000 bundle.manifest accept - one_operation_root ae38d9cd408df9b59917c43a82c9d1880505050505050505050505050505050500000000000000000001000000111111111111111111111111111111111111111111111111111111111111111100000001004002000000000000400000000000000040000000000000000000111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000

View File

@ -22,16 +22,16 @@
# document bytes are normative. `<utf8-hex>` is lowercase with no separators. # document bytes are normative. `<utf8-hex>` is lowercase with no separators.
# textproj.document # textproj.document
textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject wrong-header-version future_companion_version 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929 textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929