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:
parent
24f8c8099a
commit
3b09595196
|
|
@ -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
|
||||
`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.
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -3513,6 +3513,12 @@ canonical_value! {
|
|||
TimeSignature,
|
||||
TempoSegment,
|
||||
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
|
||||
// embeds the full value.
|
||||
RepeatStructure,
|
||||
|
|
|
|||
|
|
@ -437,6 +437,13 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject
|
|||
OperationKind::CreateStaff(op) => {
|
||||
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) => {
|
||||
let mut objects = vec![(TypedObjectId::Region(op.region), ctx(Some(op.region), None))];
|
||||
if let Some(signature) = &op.time_signature {
|
||||
|
|
|
|||
|
|
@ -1100,22 +1100,25 @@ mod tests {
|
|||
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`
|
||||
// 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
|
||||
// `TransposeInterval`, so the test was pinning a bug: a barrier that
|
||||
// prohibited the new operation encoded fine and would not read back.
|
||||
// This assertion named 30 until Push 5 / P4, and 31 until genesis G1 —
|
||||
// each time, by then, the number had become a real kind, so the test
|
||||
// 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];
|
||||
bytes.extend(set_blob(&[]));
|
||||
bytes.extend(set_blob(&[vec![31u8]]));
|
||||
bytes.extend(set_blob(&[vec![32u8]]));
|
||||
bytes.push(0);
|
||||
assert_eq!(
|
||||
EditBarrier::decode_canonical_bytes(&bytes),
|
||||
Err(BarrierDecodeError::InvalidTag {
|
||||
kind: "OperationKindTag",
|
||||
tag: 31
|
||||
tag: 32
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ use epiphany_core::{
|
|||
};
|
||||
use epiphany_core::{CanonicalValue, TempoSegment};
|
||||
use epiphany_core::{
|
||||
EventId, InstrumentId, MetricGrid, MusicalPosition, OperationId, PitchId, PitchSpelling,
|
||||
RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, ScoreMetadata, Staff, StaffInstance,
|
||||
StaffInstanceId, StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval,
|
||||
TupletId, TypedObjectId, Voice, VoiceId, WallClockTime,
|
||||
EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition, OperationId, PitchId,
|
||||
PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, ScoreMetadata, Staff,
|
||||
StaffInstance, StaffInstanceId, StaffLineConfiguration, TimeAnchor, TimeSignature,
|
||||
TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, WallClockTime,
|
||||
};
|
||||
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 => {
|
||||
return Err(EnvelopeDecodeError::InvalidTag {
|
||||
kind: "OperationKind",
|
||||
|
|
@ -870,6 +873,11 @@ pub(crate) mod tests {
|
|||
repeat: RepeatStructureId::new(ReplicaId(7), 1),
|
||||
})
|
||||
}
|
||||
OperationKindTag::CreateInstrument => {
|
||||
OperationKind::CreateInstrument(CreateInstrumentOp {
|
||||
instrument: valuegen::instrument(InstrumentId::new(ReplicaId(7), 1)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,13 +32,14 @@ use crate::envelope::OperationEnvelope;
|
|||
use crate::opset::OperationSet;
|
||||
use crate::payload::OperationKindTag;
|
||||
use crate::payload::{
|
||||
CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, CreateStaffInstanceOp,
|
||||
CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp,
|
||||
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp,
|
||||
DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp,
|
||||
ModifyIdentifiedPitchOp, OperationKind, OperationPayload, RespellPitchOp, SetMetadataOp,
|
||||
SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
|
||||
SetUserSystemBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation,
|
||||
CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp, CreateRepeatStructureOp,
|
||||
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp,
|
||||
DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp,
|
||||
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
|
||||
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationPayload,
|
||||
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp,
|
||||
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransposeIntervalOp, TransposeOp,
|
||||
TupletCompensation,
|
||||
};
|
||||
use crate::stamp::{HybridLogicalClock, OperationStamp};
|
||||
use crate::support::AuthorId;
|
||||
|
|
@ -89,7 +90,7 @@ fn pitch(n: u64) -> PitchId {
|
|||
|
||||
/// Generates a random payload over the shared id space.
|
||||
fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
|
||||
let kind = match rng.below(28) {
|
||||
let kind = match rng.below(29) {
|
||||
0 => {
|
||||
let voice = VoiceId::new(ReplicaId(7), rng.below(3));
|
||||
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,
|
||||
},
|
||||
}),
|
||||
// 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 {
|
||||
staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)),
|
||||
instrument_override: None,
|
||||
|
|
|
|||
|
|
@ -124,15 +124,16 @@ pub use envelope::{
|
|||
pub use migrate::{migrate_v0_envelope, project_v1_to_v0, MigrationError};
|
||||
pub use opset::{AcceptOutcome, OperationSet};
|
||||
pub use payload::{
|
||||
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp,
|
||||
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp,
|
||||
DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp,
|
||||
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
|
||||
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationKindTag,
|
||||
OperationPayload, PositionRemapping, ResolveConflictPayload, ResolveEquivocationPayload,
|
||||
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp,
|
||||
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory,
|
||||
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation,
|
||||
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
|
||||
CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
|
||||
CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
|
||||
DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
|
||||
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
|
||||
OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ResolveConflictPayload,
|
||||
ResolveEquivocationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp,
|
||||
SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp,
|
||||
TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
|
||||
TupletCompensation,
|
||||
};
|
||||
pub use reduce::{
|
||||
canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason,
|
||||
|
|
|
|||
|
|
@ -179,6 +179,8 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind {
|
|||
OperationKind::DeleteRepeatStructure(op) => V0OperationKind::DeleteRepeatStructure(*op),
|
||||
// Push 4a: born past v0; projected verbatim.
|
||||
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::DeleteRepeatStructure(op) => OperationKind::DeleteRepeatStructure(*op),
|
||||
// Genesis tranche G1: identity round-trip (no lossy v0 form).
|
||||
V0OperationKind::CreateInstrument(op) => OperationKind::CreateInstrument(op.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@
|
|||
|
||||
use epiphany_core::{
|
||||
Beam, CanonicalValue, Event, EventDuration, EventId, EventPosition, IdentifiedPitch,
|
||||
InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch, PitchId,
|
||||
PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, RepeatStructureId, Rest,
|
||||
ScoreMetadata, Slur, Spanner, Staff, StaffId, StaffInstance, StaffInstanceId,
|
||||
Instrument, InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch,
|
||||
PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, RepeatStructureId,
|
||||
Rest, ScoreMetadata, Slur, Spanner, Staff, StaffId, StaffInstance, StaffInstanceId,
|
||||
StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, TimeSignature, TransactionId,
|
||||
TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId,
|
||||
};
|
||||
|
|
@ -195,6 +195,14 @@ pub enum OperationKind {
|
|||
/// Push 4a: the faithful transpose. Appended past 29 — a schema-minor
|
||||
/// vocabulary append (`req:binfmt:kind-discriminants`).
|
||||
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 {
|
||||
|
|
@ -212,12 +220,17 @@ impl OperationKind {
|
|||
pub fn schema_major(&self) -> u16 {
|
||||
match self {
|
||||
// Mandatory v2 appends: CrossCuttingValue (Slur/Tie/Beam/Spanner
|
||||
// bodies), Staff (default_clef + filled line config), and
|
||||
// ScoreMetadata (six appended fields).
|
||||
// bodies), Staff (default_clef + filled line config),
|
||||
// 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::ModifyCrossCutting(_)
|
||||
| OperationKind::CreateStaff(_)
|
||||
| OperationKind::SetMetadata(_) => 2,
|
||||
| OperationKind::SetMetadata(_)
|
||||
| OperationKind::CreateInstrument(_) => 2,
|
||||
// Value-dependent: the embedded StaffLineConfiguration rides an
|
||||
// Option; None encodes byte-identically to the prior major.
|
||||
OperationKind::CreateRegion(op) => {
|
||||
|
|
@ -287,6 +300,10 @@ impl OperationKind {
|
|||
// Push 4a; appended past 29. Every constituent is a major-0
|
||||
// layout, so `schema_major` leaves it in the catch-all 0 arm.
|
||||
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::DeleteRepeatStructure(_) => OperationKindTag::DeleteRepeatStructure,
|
||||
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::CreateRepeatStructure(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,
|
||||
/// Push 4a.
|
||||
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
|
||||
|
|
@ -513,6 +539,7 @@ operation_kind_tag_vocabulary! {
|
|||
CreateRepeatStructure = 28 => "create-repeat-structure",
|
||||
DeleteRepeatStructure = 29 => "delete-repeat-structure",
|
||||
TransposeInterval = 30 => "transpose-interval",
|
||||
CreateInstrument = 31 => "create-instrument",
|
||||
}
|
||||
|
||||
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
|
||||
/// resolved musical position in a region's default metric grid
|
||||
/// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
|
|||
use epiphany_core::{
|
||||
canonical_pitch_bytes, derive_promoted_voice_id, simplest_spelling, AnchorOffset,
|
||||
AnnotationAnchor, CanonicalValue, Event, EventDuration, EventId, EventPosition,
|
||||
GestureAnchoring, InstrumentId, MeterChange, MetricGrid, MusicalDuration, MusicalPosition,
|
||||
OperationId, Pitch, PitchId, PitchSpelling, RationalTime, RegionEdge, RegionId,
|
||||
RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment, SpellingDirective,
|
||||
SpellingScope, SpellingSource, Staff, StaffId, StaffInstance, StaffInstanceId,
|
||||
StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, TimeAnchor, TimeSignature,
|
||||
TimeSignatureId, TransactionId, TransposeRefusal, TranspositionInterval, TypedObjectId, Voice,
|
||||
VoiceId, VoiceOrigin,
|
||||
GestureAnchoring, Instrument, InstrumentId, MeterChange, MetricGrid, MusicalDuration,
|
||||
MusicalPosition, OperationId, Pitch, PitchId, PitchSpelling, RationalTime, RegionEdge,
|
||||
RegionId, RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment,
|
||||
SpellingDirective, SpellingScope, SpellingSource, Staff, StaffId, StaffInstance,
|
||||
StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, TimeAnchor,
|
||||
TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal, TranspositionInterval,
|
||||
TypedObjectId, Voice, VoiceId, VoiceOrigin,
|
||||
};
|
||||
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::opset::OperationSet;
|
||||
use crate::payload::{
|
||||
resolved_anchor_position, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp,
|
||||
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp,
|
||||
DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp,
|
||||
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
|
||||
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationPayload,
|
||||
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp,
|
||||
SetTimeSignatureOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation,
|
||||
resolved_anchor_position, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
|
||||
CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
|
||||
CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
|
||||
DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
|
||||
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
|
||||
OperationKind, OperationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp,
|
||||
SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
|
||||
TransposeIntervalOp, TransposeOp, TupletCompensation,
|
||||
};
|
||||
use crate::stamp::StampTuple;
|
||||
use crate::support::{ObjectKind, SerializedCanonicalInputs};
|
||||
|
|
@ -913,6 +914,13 @@ struct Reducer<'a> {
|
|||
// precondition no-op). Seeded from the base graph.
|
||||
staff_values: BTreeMap<StaffId, Staff>,
|
||||
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>>,
|
||||
// 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
|
||||
|
|
@ -958,6 +966,13 @@ struct Reducer<'a> {
|
|||
equivocation_resolutions: BTreeMap<OperationId, (OperationId, crate::EnvelopeHash)>,
|
||||
promoted_singles: BTreeMap<OperationId, &'a OperationEnvelope>,
|
||||
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.
|
||||
|
|
@ -1003,6 +1018,7 @@ struct WorkingSnapshot {
|
|||
staff_layout_chain: BTreeMap<StaffInstanceId, WriteChain<StaffLayoutValue>>,
|
||||
staff_values: BTreeMap<StaffId, Staff>,
|
||||
time_signature_values: BTreeMap<TimeSignatureId, TimeSignature>,
|
||||
instrument_values: BTreeMap<InstrumentId, Instrument>,
|
||||
structures: BTreeMap<TypedObjectId, Vec<TypedObjectId>>,
|
||||
region_instances: BTreeMap<RegionId, BTreeSet<StaffInstanceId>>,
|
||||
instance_voices: BTreeMap<StaffInstanceId, BTreeSet<VoiceId>>,
|
||||
|
|
@ -1281,6 +1297,7 @@ impl<'a> Reducer<'a> {
|
|||
staff_layout_chain: BTreeMap::new(),
|
||||
staff_values: BTreeMap::new(),
|
||||
time_signature_values: BTreeMap::new(),
|
||||
instrument_values: BTreeMap::new(),
|
||||
structures: BTreeMap::new(),
|
||||
region_instances: BTreeMap::new(),
|
||||
instance_voices: BTreeMap::new(),
|
||||
|
|
@ -1296,11 +1313,16 @@ impl<'a> Reducer<'a> {
|
|||
equivocation_resolutions: BTreeMap::new(),
|
||||
promoted_singles: BTreeMap::new(),
|
||||
graph: None,
|
||||
from_empty_base: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_onto(op_set: &'a OperationSet, base: &Score) -> Self {
|
||||
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.seed_from_graph();
|
||||
reducer
|
||||
|
|
@ -1318,6 +1340,12 @@ impl<'a> Reducer<'a> {
|
|||
for instrument in &score.instruments {
|
||||
self.objects
|
||||
.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 {
|
||||
self.objects
|
||||
|
|
@ -1913,6 +1941,31 @@ impl<'a> Reducer<'a> {
|
|||
pending.into_iter().chain(held).collect();
|
||||
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 state = MaterializedState {
|
||||
effects: self.effects,
|
||||
|
|
@ -1927,6 +1980,60 @@ impl<'a> Reducer<'a> {
|
|||
(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) {
|
||||
let a = IntegrityAnomaly::new(kind);
|
||||
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::CreateRepeatStructure(op) => self.create_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::UndoTransaction(op) => self.undo_transaction(env, op),
|
||||
|
|
@ -3854,6 +3962,56 @@ impl<'a> Reducer<'a> {
|
|||
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`
|
||||
/// (operation_catalog §"Meter and Tempo Overwrites"): fresh id mints;
|
||||
/// 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_values: self.staff_values.clone(),
|
||||
time_signature_values: self.time_signature_values.clone(),
|
||||
instrument_values: self.instrument_values.clone(),
|
||||
structures: self.structures.clone(),
|
||||
region_instances: self.region_instances.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_values = s.staff_values;
|
||||
self.time_signature_values = s.time_signature_values;
|
||||
self.instrument_values = s.instrument_values;
|
||||
self.structures = s.structures;
|
||||
self.region_instances = s.region_instances;
|
||||
self.instance_voices = s.instance_voices;
|
||||
|
|
@ -7452,7 +7612,7 @@ mod tests {
|
|||
use crate::causal::CausalContext;
|
||||
use crate::stamp::{HybridLogicalClock, OperationStamp};
|
||||
use crate::support::AuthorId;
|
||||
use epiphany_core::{RationalTime, ReplicaId, StaffInstanceId, WallClockTime};
|
||||
use epiphany_core::{IdentityContext, RationalTime, ReplicaId, StaffInstanceId, WallClockTime};
|
||||
|
||||
fn pos(n: i64) -> MusicalPosition {
|
||||
MusicalPosition(RationalTime::from_int(n as i32))
|
||||
|
|
@ -10486,6 +10646,27 @@ mod tests {
|
|||
0,
|
||||
"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]
|
||||
|
|
@ -10553,6 +10734,15 @@ mod tests {
|
|||
// moves. Nothing leaked: `canonical_bytes` embeds effects, conflicts,
|
||||
// and anomalies, never payload values, and the new payload's
|
||||
// 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 envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200);
|
||||
let mut set = OperationSet::new();
|
||||
|
|
@ -10562,7 +10752,7 @@ mod tests {
|
|||
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
|
||||
assert_eq!(
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ use epiphany_determinism::{sorted_canonical, CanonicalEncode};
|
|||
use unicode_normalization::UnicodeNormalization;
|
||||
|
||||
use crate::payload::{
|
||||
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp,
|
||||
CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, DeleteCrossCuttingOp, DeleteEventOp,
|
||||
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp,
|
||||
DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp,
|
||||
ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, RespellPitchOp, SetMetadataOp,
|
||||
SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
|
||||
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
|
||||
CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
|
||||
DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp,
|
||||
DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
|
||||
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
|
||||
OperationKind, OperationKindTag, RespellPitchOp, SetMetadataOp, SetMetricGridOp,
|
||||
SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
|
||||
SetUserSystemBreakOp, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
|
||||
};
|
||||
use crate::support::OperationKindRegistryId;
|
||||
|
|
@ -218,6 +219,9 @@ impl TextValue for OperationKind {
|
|||
self.tag(),
|
||||
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)?,
|
||||
})
|
||||
}
|
||||
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]
|
||||
fn every_operation_kind_round_trips_with_canonical_text() {
|
||||
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 {
|
||||
round_trip(&sample_kind(tag));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ pub enum V0OperationKind {
|
|||
/// Born at wire-disc 30 under major-0 layouts (Push 4a); no lossy v0
|
||||
/// form, so it projects verbatim like the repeat-authoring pair.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
/// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to
|
||||
/// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
//! The `class` string is informative, not normative: implementations need not
|
||||
//! 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 crate::{
|
||||
IntegrityAnomaly, IntegrityAnomalyKind, MaterializedState, ObjectState,
|
||||
IntegrityAnomaly, IntegrityAnomalyKind, MaterializedState, ObjectState, OperationEnvelope,
|
||||
OperationKindRegistryId, OperationKindTag, PendingReason,
|
||||
};
|
||||
|
||||
|
|
@ -255,6 +255,49 @@ pub fn decode_vectors() -> Vec<DecodeVector> {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -277,6 +320,10 @@ pub fn check(surface: &str, bytes: &[u8]) -> Option<Result<bool, String>> {
|
|||
Err(e) => Err(format!("{e}")),
|
||||
}),
|
||||
"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,
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +362,11 @@ mod tests {
|
|||
/// pinning half a contract.
|
||||
#[test]
|
||||
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()
|
||||
.into_iter()
|
||||
.filter(|(s, ..)| *s == surface)
|
||||
|
|
@ -340,4 +391,43 @@ mod tests {
|
|||
assert!(classes.contains(&"non-canonical-map-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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,10 +301,16 @@ fn the_kind_productions_are_the_operation_vocabulary() {
|
|||
// four bugs.
|
||||
.map(|t| t.catalog_name().to_string())
|
||||
.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!(
|
||||
expected.len(),
|
||||
31,
|
||||
"30 payload-free kinds plus `Registered`"
|
||||
32,
|
||||
"31 payload-free kinds plus `Registered`"
|
||||
);
|
||||
|
||||
let actual = alternatives("kind");
|
||||
|
|
@ -547,9 +553,16 @@ fn worked_example_header_is_the_implemented_companion_version() {
|
|||
.map(|(version, _)| 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!(
|
||||
title_version, "0.7.0",
|
||||
"this implementation targets exactly companion 0.7.0"
|
||||
title_version,
|
||||
format!("{major}.{minor}.{patch}"),
|
||||
"the spec title page must declare the version this implementation targets"
|
||||
);
|
||||
|
||||
let expected = format!("(text-projection ({}))", title_version.replace('.', " "));
|
||||
|
|
|
|||
|
|
@ -19,7 +19,14 @@ use epiphany_ops::OperationEnvelope;
|
|||
///
|
||||
/// A parser must reject every other version rather than migrating or
|
||||
/// 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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -642,7 +642,11 @@ mod tests {
|
|||
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)";
|
||||
|
||||
/// A minimal but complete valid projection: just the two mandatory lines.
|
||||
|
|
@ -650,6 +654,19 @@ mod tests {
|
|||
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
|
||||
/// these can be combined without any causal-order machinery beyond their
|
||||
/// HLC physical time.
|
||||
|
|
|
|||
|
|
@ -576,7 +576,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -322,16 +322,23 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
.map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec()))
|
||||
.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(
|
||||
minimal,
|
||||
"(text-projection (0 7 0))",
|
||||
"(text-projection (0 8 0))",
|
||||
"(text-projection (0 7 0))",
|
||||
);
|
||||
vectors.push((
|
||||
SURFACE,
|
||||
"reject",
|
||||
"wrong-header-version",
|
||||
"future_companion_version",
|
||||
"superseded_companion_version",
|
||||
wrong_version.into_bytes(),
|
||||
));
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
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}
|
||||
\label{sec:k0:repeat-structures}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -234,7 +234,7 @@
|
|||
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
|
||||
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
|
||||
{\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}
|
||||
\vfill
|
||||
\end{titlepage}
|
||||
|
|
@ -467,7 +467,7 @@ A projection is, in order:
|
|||
\begin{requirement}
|
||||
\label{req:textproj:header-version}
|
||||
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.
|
||||
|
||||
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
|
||||
unreferenced by canonical state
|
||||
(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,
|
||||
and a parser \MUST{} reject every \texttt{(blob ...)} line.
|
||||
\end{requirement}
|
||||
|
|
@ -1041,6 +1041,7 @@ kind ::= "(insert-event " bytes " " value ")"
|
|||
| "(create-repeat-structure " value ")"
|
||||
| "(delete-repeat-structure " bytes ")"
|
||||
| "(transpose-interval (" bytes* ") " value ")"
|
||||
| "(create-instrument " value ")"
|
||||
|
||||
tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")"
|
||||
| "(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:
|
||||
|
||||
\begin{lstlisting}
|
||||
(text-projection (0 7 0))
|
||||
(text-projection (0 8 0))
|
||||
(document #x05050505050505050505050505050505)
|
||||
(profile full (0 1 0) (constraints 67108864 (retention 1 () true)))
|
||||
(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
|
||||
speculate. The worked example now uses the implemented header and contains
|
||||
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
|
||||
\end{longtable}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_29 1d
|
||||
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 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 truncated tag_empty -
|
||||
ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000
|
||||
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 accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000
|
||||
bundle.manifest accept - one_operation_root ae38d9cd408df9b59917c43a82c9d1880505050505050505050505050505050500000000000000000001000000111111111111111111111111111111111111111111111111111111111111111100000001004002000000000000400000000000000040000000000000000000111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@
|
|||
# document bytes are normative. `<utf8-hex>` is lowercase with no separators.
|
||||
|
||||
# textproj.document
|
||||
textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject wrong-header-version future_companion_version 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
|
||||
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
|
||||
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
|
||||
textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
|
||||
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
|
||||
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
|
||||
|
|
|
|||
Loading…
Reference in New Issue