//! Deterministic property-test generators for the public types of A //! ([`epiphany_determinism`]), B ([`epiphany_core`]), C ([`epiphany_ops`]), and //! D ([`epiphany_bundle`]). Agent E's layout-IR types //! ([`epiphany_layout_ir`]) are generated in [`crate::layout_stub`], which //! drives the real crate. //! //! Every generator draws from the seeded [`Rng`], so a failing case reproduces //! from its seed (Appendix D §"Randomness": no platform entropy in the harness). //! Whole **graphs** are generated by Agent B's own positive/negative generators, //! re-exported as [`graph`]. use epiphany_bundle::{ chunk_id, content_hash_for, BlobId, BlobRef, ChunkKind, ChunkRef, CommitState, CompressionAlgorithm, DocumentId, ExtensionDeclaration, ExtensionId, FrontierBytes, LineageId, Manifest, ProfileConstraints, ProfileDeclaration, ProfileId, ProfileRegistryId, ReductionAlgorithmVersion, RetentionPolicy, SchemaVersion, SemVer, SnapshotId, SnapshotRef, Superblock, WallClockDuration as BundleWallClockDuration, WallClockTime as BundleWallClockTime, }; use epiphany_core::{ AnalysisLayerId, AnalyticalAnnotationId, BarlineAlignmentGroupId, BeamId, ChordSymbolId, CommentId, EventId, GraphicGestureId, GraphicObjectId, InstrumentId, IntegrityAnomalyId, LyricLineId, MarkerId, MeasureId, MusicalDuration, MusicalPosition, ObjectKindRegistryId, OperationId, PartDefinitionId, PitchId, RationalTime, RegionId, RepeatStructureId, ReplicaId, SlurId, SpannerId, StaffGroupId, StaffId, StaffInstanceId, TieId, TimeSignatureId, TransactionId, TupletId, TypedObjectId, ViewId, VoiceId, WallClockDuration, WallClockTime, }; use epiphany_determinism::{ CanonicalEncode, CanonicalF64, ChunkId, ContentHash, DomainTag, QuantizedCoord, Tolerance, ToleranceClass, ToleranceGovernance, }; use epiphany_ops::valuegen; use epiphany_ops::{ AnomalousReplicaSegment, AuthorId, CausalContext, ChangeRegionTimeModelOp, ConflictId, ConflictKind, ConflictKindRegistryId, ConflictRecord, ConflictRegistry, ConflictResolutionState, CreateCrossCuttingOp, CrossCuttingValue, DeleteEventOp, DeleteIdentifiedPitchOp, ExtensionPreconditionId, FieldPath, HybridLogicalClock, InsertEventOp, InsertIdentifiedPitchOp, IntegrityAnomaly, IntegrityAnomalyKind, IntegrityAnomalyRegistryId, MaterializedState, ModifyEventOp, ModifyIdentifiedPitchOp, NoOpReason, ObjectKind, ObjectState, OperationEffect, OperationEnvelope, OperationKind, OperationKindRegistryId, OperationPayload, OperationSet, OperationStamp, PendingReason, PositionRemapping, PreconditionFailureReason, PreconditionFailureRegistryId, ReanchorReason, ReanchorReasonRegistryId, ReanchorResult, RepairKind, RepairKindRegistryId, RepairRecord, ReplicaAnomalyReason, ReplicaAnomalyRegistryId, ResolutionAction, ResolutionRegistryId, ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetUserSystemBreakOp, TransactionCategory, TransactionDescriptor, TransposeOp, TupletCompensation, TupletCompensationKind, UndoPolicy, UndoTransactionPayload, }; use crate::rng::Rng; /// Agent B's score-graph generators and shrinkers, re-exported so the testkit's /// consumers have one import for *all* generators: `testkit::generators::graph`. pub use epiphany_core::generators as graph; // =========================================================================== // Agent A — epiphany-determinism // =========================================================================== /// A canonical staff-space coordinate. Every `i64` unit count is valid. pub fn quantized_coord(rng: &mut Rng) -> QuantizedCoord { QuantizedCoord::from_units(rng.next_u64() as i64) } /// A finite canonical `f64`. Non-finite bit patterns (NaN/inf), a tiny fraction /// of the space, are re-drawn; `-0.0` is canonicalized to `+0.0` by the type. pub fn canonical_f64(rng: &mut Rng) -> CanonicalF64 { for _ in 0..8 { if let Some(c) = CanonicalF64::new(f64::from_bits(rng.next_u64())) { return c; } } CanonicalF64::new(0.0).unwrap() } /// A content hash (any 32 bytes are valid). pub fn content_hash(rng: &mut Rng) -> ContentHash { ContentHash(rng.array32()) } /// A chunk id (newtype over [`ContentHash`]). pub fn chunk_id_gen(rng: &mut Rng) -> ChunkId { ChunkId(content_hash(rng)) } /// A valid domain tag, drawn from the built-in `MUSC*` vocabulary. pub fn domain_tag(rng: &mut Rng) -> DomainTag { *rng.choose(&DomainTag::BUILTINS) } /// A typed tolerance (not a canonical-serialized type — generated for /// completeness of the "every public type" charter, exercised by validation /// tests rather than the byte round-trip). pub fn tolerance(rng: &mut Rng) -> Tolerance { let class = *rng.choose(&[ ToleranceClass::AcousticCents, ToleranceClass::LayoutCoordinate, ToleranceClass::QualityMetric, ToleranceClass::TempoIntegration, ToleranceClass::SolverResidual, ]); let governance = *rng.choose(&[ ToleranceGovernance::Equality, ToleranceGovernance::Validation, ToleranceGovernance::Diagnostic, ]); let absolute = (rng.range(1, 1_000_000) as f64) / 1_000_000.0; Tolerance::absolute(class, absolute, governance).expect("finite positive tolerance") } // =========================================================================== // Agent B — epiphany-core: identifiers, time, typed-object id // =========================================================================== /// A non-reserved replica identifier (never [`ReplicaId::SYSTEM_DERIVED`]). pub fn replica_id(rng: &mut Rng) -> ReplicaId { ReplicaId::from_entropy(rng.next_u64().to_le_bytes()).unwrap_or(ReplicaId(0x5151_5151)) } /// An operation identifier with a non-reserved replica. pub fn operation_id(rng: &mut Rng) -> OperationId { OperationId::new(replica_id(rng), rng.next_u64()) } /// An opaque author identifier. pub fn author_id(rng: &mut Rng) -> AuthorId { AuthorId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128) } /// A content-addressed conflict identifier value. pub fn conflict_id(rng: &mut Rng) -> ConflictId { ConflictId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128) } macro_rules! id_generator { ($(#[$m:meta])* $fn_name:ident -> $ty:ident) => { $(#[$m])* pub fn $fn_name(rng: &mut Rng) -> $ty { $ty::new(replica_id(rng), rng.next_u64()) } }; } id_generator!(/// An event identifier. event_id -> EventId); id_generator!(/// A pitch identifier. pitch_id -> PitchId); id_generator!(/// A voice identifier. voice_id -> VoiceId); id_generator!(/// A staff identifier. staff_id -> StaffId); id_generator!(/// A staff-instance identifier. staff_instance_id -> StaffInstanceId); id_generator!(/// A staff-group identifier. staff_group_id -> StaffGroupId); id_generator!(/// A region identifier. region_id -> RegionId); id_generator!(/// An instrument identifier. instrument_id -> InstrumentId); id_generator!(/// A part-definition identifier. part_definition_id -> PartDefinitionId); id_generator!(/// A measure identifier. measure_id -> MeasureId); id_generator!(/// A barline-alignment-group identifier. barline_alignment_group_id -> BarlineAlignmentGroupId); id_generator!(/// A tuplet identifier. tuplet_id -> TupletId); id_generator!(/// A slur identifier. slur_id -> SlurId); id_generator!(/// A tie identifier. tie_id -> TieId); id_generator!(/// A beam identifier. beam_id -> BeamId); id_generator!(/// A spanner identifier. spanner_id -> SpannerId); id_generator!(/// A marker identifier. marker_id -> MarkerId); id_generator!(/// An analytical-annotation identifier. analytical_annotation_id -> AnalyticalAnnotationId); id_generator!(/// A comment identifier. comment_id -> CommentId); id_generator!(/// A graphic-object identifier. graphic_object_id -> GraphicObjectId); id_generator!(/// A graphic-gesture identifier. graphic_gesture_id -> GraphicGestureId); id_generator!(/// A time-signature identifier. time_signature_id -> TimeSignatureId); id_generator!(/// An analysis-layer identifier. analysis_layer_id -> AnalysisLayerId); id_generator!(/// A repeat-structure identifier. repeat_structure_id -> RepeatStructureId); id_generator!(/// A lyric-line identifier. lyric_line_id -> LyricLineId); id_generator!(/// A chord-symbol identifier. chord_symbol_id -> ChordSymbolId); id_generator!(/// A view identifier. view_id -> ViewId); id_generator!(/// An object-kind registry identifier. object_kind_registry_id -> ObjectKindRegistryId); id_generator!(/// A transaction identifier. transaction_id -> TransactionId); id_generator!(/// An integrity-anomaly identifier. integrity_anomaly_id -> IntegrityAnomalyId); /// A small (inline) rational time value with a non-zero denominator. pub fn rational_time_small(rng: &mut Rng) -> RationalTime { loop { let num = (rng.next_u64() % (1 << 20)) as i64 - (1 << 19); let den = rng.range(1, 4096) as i64; if let Some(r) = RationalTime::new(num, den) { return r; } } } /// A promoted (arbitrary-precision `Large`) rational time value. The numerator /// is forced far enough beyond the inline `i32` range that even after dividing /// by the (small) denominator's gcd the reduced numerator still exceeds /// `i32::MAX`, so the value cannot demote to the inline arm. pub fn rational_time_large(rng: &mut Rng) -> RationalTime { // `i32::MAX * 16` guarantees `num / den > i32::MAX` for every den <= 13. let big = (i32::MAX as i64) * 16 + (rng.next_u64() % 1_000_000_000) as i64; let den = *rng.choose(&[1i64, 3, 7, 11, 13]); RationalTime::new(big, den).expect("non-zero denominator") } /// A rational time value, half inline and half promoted — so the round-trip /// corpus exercises both [`RationalTime`] arms. pub fn rational_time(rng: &mut Rng) -> RationalTime { if rng.boolean() { rational_time_small(rng) } else { rational_time_large(rng) } } /// A musical position (rational, possibly promoted). pub fn musical_position(rng: &mut Rng) -> MusicalPosition { MusicalPosition(rational_time(rng)) } /// A musical duration (rational, possibly promoted). pub fn musical_duration(rng: &mut Rng) -> MusicalDuration { MusicalDuration(rational_time(rng)) } /// A wall-clock time (signed nanoseconds). pub fn wallclock_time(rng: &mut Rng) -> WallClockTime { WallClockTime(rng.next_u64() as i64) } /// A wall-clock duration (signed nanoseconds). pub fn wallclock_duration(rng: &mut Rng) -> WallClockDuration { WallClockDuration(rng.next_u64() as i64) } /// A tagged object identifier across the **whole** graph identity family — every /// [`TypedObjectId`] variant including the variable-width `Registered` form, so /// the round-trip harness exercises every discriminant and both encodings. pub fn typed_object_id(rng: &mut Rng) -> TypedObjectId { match rng.below(28) { 0 => TypedObjectId::Event(event_id(rng)), 1 => TypedObjectId::Pitch(pitch_id(rng)), 2 => TypedObjectId::Voice(voice_id(rng)), 3 => TypedObjectId::Staff(staff_id(rng)), 4 => TypedObjectId::StaffInstance(staff_instance_id(rng)), 5 => TypedObjectId::StaffGroup(staff_group_id(rng)), 6 => TypedObjectId::Region(region_id(rng)), 7 => TypedObjectId::Instrument(instrument_id(rng)), 8 => TypedObjectId::PartDefinition(part_definition_id(rng)), 9 => TypedObjectId::Measure(measure_id(rng)), 10 => TypedObjectId::BarlineAlignmentGroup(barline_alignment_group_id(rng)), 11 => TypedObjectId::Slur(slur_id(rng)), 12 => TypedObjectId::Tie(tie_id(rng)), 13 => TypedObjectId::Beam(beam_id(rng)), 14 => TypedObjectId::Spanner(spanner_id(rng)), 15 => TypedObjectId::Marker(marker_id(rng)), 16 => TypedObjectId::AnalyticalAnnotation(analytical_annotation_id(rng)), 17 => TypedObjectId::Comment(comment_id(rng)), 18 => TypedObjectId::GraphicObject(graphic_object_id(rng)), 19 => TypedObjectId::GraphicGesture(graphic_gesture_id(rng)), 20 => TypedObjectId::TimeSignature(time_signature_id(rng)), 21 => TypedObjectId::AnalysisLayer(analysis_layer_id(rng)), 22 => TypedObjectId::Tuplet(tuplet_id(rng)), 23 => TypedObjectId::RepeatStructure(repeat_structure_id(rng)), 24 => TypedObjectId::LyricLine(lyric_line_id(rng)), 25 => TypedObjectId::ChordSymbol(chord_symbol_id(rng)), 26 => TypedObjectId::View(view_id(rng)), _ => TypedObjectId::Registered( object_kind_registry_id(rng), ((rng.next_u64() as u128) << 64) | rng.next_u64() as u128, ), } } // =========================================================================== // Agent C — epiphany-ops: real operation envelopes // =========================================================================== /// The shared object-identifier namespace authoring replicas mint *references* /// into, so operations from different replicas address the *same* events, /// pitches, and voices — which is what makes their reductions interact /// (tombstones, already-applied, field conflicts) and what gives the convergence /// harness something non-trivial to converge. const OBJ_REPLICA: ReplicaId = ReplicaId(0x0B7E_C700); fn obj_event(n: u64) -> EventId { EventId::new(OBJ_REPLICA, n) } fn obj_pitch(n: u64) -> PitchId { PitchId::new(OBJ_REPLICA, n) } /// A conflict-resolution action (every core and registered variant, /// including `Dismiss`). pub fn resolution_action(rng: &mut Rng) -> ResolutionAction { match rng.below(6) { 0 => ResolutionAction::AcceptLoser, 1 => ResolutionAction::KeepWinner, 2 => ResolutionAction::Override { override_operation: operation_id(rng), }, 3 => ResolutionAction::Reanchor { new_target: typed_object_id(rng), }, 4 => ResolutionAction::Dismiss, _ => ResolutionAction::Registered(ResolutionRegistryId( ((rng.next_u64() as u128) << 64) | rng.next_u64() as u128, )), } } /// An undo policy (`StrictInverse`/`BestEffort`/`Cascade`). pub fn undo_policy(rng: &mut Rng) -> UndoPolicy { *rng.choose(&[ UndoPolicy::StrictInverse, UndoPolicy::BestEffort, UndoPolicy::Cascade, ]) } /// A conflict kind covering every core and extension variant. pub fn conflict_kind(rng: &mut Rng) -> ConflictKind { match rng.below(6) { 0 => ConflictKind::StructuralFieldCollision { winner: operation_id(rng), loser: operation_id(rng), field: FieldPath(String::from("event.pitch")), }, 1 => ConflictKind::TransactionConflict { transaction: transaction_id(rng), failed_members: vec![operation_id(rng), operation_id(rng)], }, 2 => ConflictKind::TombstonedTarget { target: typed_object_id(rng), operation: operation_id(rng), }, 3 => ConflictKind::ReanchorFailure { original_referent: typed_object_id(rng), referencing_object: typed_object_id(rng), }, 4 => ConflictKind::TimeModelMigrationFailure { region: region_id(rng), incompatible_events: vec![typed_object_id(rng), typed_object_id(rng)], }, _ => ConflictKind::ExtensionConflict { kind_id: ConflictKindRegistryId( ((rng.next_u64() as u128) << 64) | rng.next_u64() as u128, ), details: rng.byte_vec(0, 24), }, } } /// A conflict resolution state covering every variant. pub fn conflict_resolution_state(rng: &mut Rng) -> ConflictResolutionState { match rng.below(3) { 0 => ConflictResolutionState::Unresolved, 1 => ConflictResolutionState::Resolved { by: operation_id(rng), action: resolution_action(rng), }, _ => ConflictResolutionState::Dismissed { by: operation_id(rng), }, } } /// A self-consistent content-addressed conflict record. pub fn conflict_record(rng: &mut Rng) -> ConflictRecord { let mut record = ConflictRecord::new( conflict_kind(rng), vec![operation_id(rng), operation_id(rng)], vec![typed_object_id(rng), typed_object_id(rng)], ); record.resolution_state = conflict_resolution_state(rng); record } /// A canonical conflict registry containing a few generated records. pub fn conflict_registry(rng: &mut Rng) -> ConflictRegistry { let mut registry = ConflictRegistry::new(); for _ in 0..rng.range_usize(0, 4) { registry.insert(conflict_record(rng)); } registry } /// A typed precondition failure (every core and registered variant). pub fn precondition_failure_reason(rng: &mut Rng) -> PreconditionFailureReason { match rng.below(10) { 0 => PreconditionFailureReason::TargetMissing, 1 => PreconditionFailureReason::TargetTombstoned, 2 => PreconditionFailureReason::WrongRegionTimeModel, 3 => PreconditionFailureReason::TupletCompensationInvalid, 4 => PreconditionFailureReason::EventDurationInvalid, 5 => PreconditionFailureReason::PositionOutsideRegion, 6 => PreconditionFailureReason::PitchSpaceMismatch, 7 => PreconditionFailureReason::VoiceMissing, 8 => PreconditionFailureReason::ExtensionPrecondition(ExtensionPreconditionId( rng.next_u64() as u128, )), _ => PreconditionFailureReason::Registered(PreconditionFailureRegistryId( rng.next_u64() as u128 )), } } /// A no-op reason covering every variant. pub fn no_op_reason(rng: &mut Rng) -> NoOpReason { match rng.below(5) { 0 => NoOpReason::TargetTombstoned, 1 => NoOpReason::AlreadyApplied, 2 => NoOpReason::SupersededByLaterOperation { superseder: operation_id(rng), }, 3 => NoOpReason::PreconditionFailedUnderReduction { reason: precondition_failure_reason(rng), }, _ => NoOpReason::TransactionConflict, } } /// A re-anchor reason covering every variant. pub fn reanchor_reason(rng: &mut Rng) -> ReanchorReason { match rng.below(6) { 0 => ReanchorReason::SameVoiceNearer, 1 => ReanchorReason::SameStaffInstanceNearer, 2 => ReanchorReason::SameStaffNearer, 3 => ReanchorReason::SameRegionNearer, 4 => ReanchorReason::ExplicitFallback, _ => ReanchorReason::DeclaredByExtension(ReanchorReasonRegistryId(rng.next_u64() as u128)), } } /// A tuplet compensation result covering every variant. pub fn tuplet_compensation_kind(rng: &mut Rng) -> TupletCompensationKind { *rng.choose(&[ TupletCompensationKind::ReplaceWithRest, TupletCompensationKind::RewriteTuplets, TupletCompensationKind::CascadeDeleteTuplets, ]) } /// A repair kind covering every core and registered variant. pub fn repair_kind(rng: &mut Rng) -> RepairKind { match rng.below(8) { 0 => RepairKind::Reanchored { from: typed_object_id(rng), to: typed_object_id(rng), reason: reanchor_reason(rng), }, 1 => RepairKind::SpannerTruncated { removed_members: vec![typed_object_id(rng), typed_object_id(rng)], }, 2 => RepairKind::Orphaned, 3 => RepairKind::CascadeDeleted, 4 => RepairKind::AttachmentTombstoned, 5 => RepairKind::VoicePromoted { from: voice_id(rng), to: voice_id(rng), }, 6 => RepairKind::TupletCompensated { compensation_kind: tuplet_compensation_kind(rng), }, _ => RepairKind::Registered(RepairKindRegistryId(rng.next_u64() as u128)), } } /// A complete repair record. pub fn repair_record(rng: &mut Rng) -> RepairRecord { RepairRecord { kind: repair_kind(rng), target: typed_object_id(rng), } } /// An operation effect covering every variant. pub fn operation_effect(rng: &mut Rng) -> OperationEffect { match rng.below(5) { 0 => OperationEffect::Applied, 1 => OperationEffect::AppliedWithRepair { repairs: vec![repair_record(rng)], }, 2 => OperationEffect::Conflicted { conflict: ConflictId(rng.next_u64() as u128), }, 3 => OperationEffect::TombstonedTarget { target: typed_object_id(rng), }, _ => OperationEffect::NoOp { reason: no_op_reason(rng), }, } } /// The result of a re-anchor attempt (every variant). pub fn reanchor_result(rng: &mut Rng) -> ReanchorResult { match rng.below(5) { 0 => ReanchorResult::Reanchored { new_target: typed_object_id(rng), reason: reanchor_reason(rng), }, 1 => ReanchorResult::TombstonedTarget, 2 => ReanchorResult::Orphaned, 3 => ReanchorResult::Conflicted { conflict: ConflictId(rng.next_u64() as u128), }, _ => ReanchorResult::CascadeDeleted, } } /// An integrity anomaly kind covering every core and registered variant. pub fn integrity_anomaly_kind(rng: &mut Rng) -> IntegrityAnomalyKind { match rng.below(4) { 0 => IntegrityAnomalyKind::SystemIdentifierCollision { kind: match rng.below(3) { 0 => ObjectKind::Voice, 1 => ObjectKind::Pitch, _ => ObjectKind::Registered(OperationKindRegistryId(rng.next_u64() as u128)), }, colliding_counter: rng.next_u64(), input_set_a: SerializedCanonicalInputs(rng.byte_vec(0, 16)), input_set_b: SerializedCanonicalInputs(rng.byte_vec(0, 16)), }, 1 => IntegrityAnomalyKind::OperationSlotEquivocated { operation_id: operation_id(rng), }, 2 => IntegrityAnomalyKind::ReplicaStreamQuarantined { replica: replica_id(rng), first_bad_counter: rng.next_u64(), }, _ => IntegrityAnomalyKind::Registered(IntegrityAnomalyRegistryId(rng.next_u64() as u128)), } } /// A content-derived integrity anomaly. pub fn integrity_anomaly(rng: &mut Rng) -> IntegrityAnomaly { IntegrityAnomaly::new(integrity_anomaly_kind(rng)) } /// A per-replica anomaly reason covering core and registered forms. pub fn replica_anomaly_reason(rng: &mut Rng) -> ReplicaAnomalyReason { if rng.boolean() { ReplicaAnomalyReason::HlcMonotonicityViolation { violating_pair: (operation_id(rng), operation_id(rng)), } } else { ReplicaAnomalyReason::Registered(ReplicaAnomalyRegistryId(rng.next_u64() as u128)) } } /// An anomalous replica segment with a sorted excluded-id list. pub fn anomalous_replica_segment(rng: &mut Rng) -> AnomalousReplicaSegment { let replica = replica_id(rng); let first_bad_counter = rng.range(0, 32); AnomalousReplicaSegment { replica, first_bad_counter, reason: replica_anomaly_reason(rng), excluded: (first_bad_counter..first_bad_counter + rng.range(1, 4)) .map(|counter| OperationId::new(replica, counter)) .collect(), } } /// An object-existence state covering live and tombstoned forms. pub fn object_state(rng: &mut Rng) -> ObjectState { if rng.boolean() { ObjectState::Live } else { ObjectState::Tombstoned { deleted_by: operation_id(rng), minted_by: operation_id(rng), } } } /// A pending reason covering every blocker category. pub fn pending_reason(rng: &mut Rng) -> PendingReason { let blocker = operation_id(rng); match rng.below(4) { 0 => PendingReason::MissingCausalPredecessor { missing: blocker }, 1 => PendingReason::DependsOnEquivocated { on: blocker }, 2 => PendingReason::DependsOnExcluded { on: blocker }, _ => PendingReason::DependsOnPending { on: blocker }, } } /// A random representative operation payload over a shared id space of `events` /// events and `pitches` pitches. Covers the primitive reduction disciplines /// (Chapter 6 §6.10: insert, delete, respell, cross-cutting create, /// layout-semantic break, time-model change, transaction declaration, /// registered extension op) and the two meta-operations (resolve-conflict and /// undo-transaction). /// /// Zero-sized id spaces are treated as size 1 (the shared object namespace /// always has at least one addressable id), so this never calls /// [`Rng::below`](crate::rng::Rng::below) with a zero bound. pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationPayload { let events = events.max(1); let pitches = pitches.max(1); // ~1 in 6 operations is a meta-operation (resolve-conflict / undo). match rng.below(12) { 10 => { return OperationPayload::ResolveConflict(ResolveConflictPayload { target: ConflictId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128), action: resolution_action(rng), }) } 11 => { return OperationPayload::UndoTransaction(UndoTransactionPayload { target: TransactionId::new(OBJ_REPLICA, rng.below(events)), policy: undo_policy(rng), }) } _ => {} } let kind = match rng.below(13) { 0 => { let pitches = if rng.boolean() { vec![obj_pitch(rng.below(pitches))] } else { vec![] }; OperationKind::InsertEvent(InsertEventOp { staff_instance: StaffInstanceId::new(OBJ_REPLICA, rng.below(2)), event: valuegen::insert_event_value( obj_event(rng.below(events)), VoiceId::new(OBJ_REPLICA, rng.below(4)), MusicalPosition(RationalTime::from_int(rng.below(events) as i32)), MusicalDuration::whole(), &pitches, ), }) } 1 => OperationKind::DeleteEvent(DeleteEventOp { event: obj_event(rng.below(events)), tuplet_compensation: TupletCompensation::NotInTuplet, }), 2 => OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(rng.below(pitches)), spelling: valuegen::spelling(rng.below(4) as u8 + 1), }), 3 => OperationKind::CreateCrossCutting(CreateCrossCuttingOp { structure: CrossCuttingValue::Slur(valuegen::slur( SlurId::new(OBJ_REPLICA, rng.below(events)), obj_event(rng.below(events)), obj_event(rng.below(events)), )), }), 4 => OperationKind::SetUserSystemBreak(SetUserSystemBreakOp { region: RegionId::new(OBJ_REPLICA, 0), anchor: valuegen::region_start_anchor( RegionId::new(OBJ_REPLICA, 0), MusicalPosition(RationalTime::from_int(rng.below(4) as i32)), ), present: rng.boolean(), }), 5 => OperationKind::ChangeRegionTimeModel(ChangeRegionTimeModelOp { region: RegionId::new(OBJ_REPLICA, rng.below(2)), new_time_model: match rng.below(3) { 0 => valuegen::metric_model(), 1 => valuegen::proportional_model(), _ => valuegen::aleatoric_model(), }, declared_incompatible: Vec::new(), remapping: PositionRemapping::PreserveTime, }), 6 => OperationKind::DeclareTransaction(TransactionDescriptor { id: TransactionId::new(OBJ_REPLICA, rng.below(events)), label: String::from("edit"), category: Some(*rng.choose(&[ TransactionCategory::NoteEntry, TransactionCategory::Structural, TransactionCategory::Layout, ])), }), // Group 1 (M2): event & pitch leaf-field ops over the shared id space. 7 => OperationKind::ModifyEvent(ModifyEventOp { event: valuegen::insert_event_value( obj_event(rng.below(events)), VoiceId::new(OBJ_REPLICA, rng.below(4)), MusicalPosition(RationalTime::from_int(rng.below(events) as i32)), MusicalDuration::whole(), &[obj_pitch(rng.below(pitches))], ), }), 8 => OperationKind::Transpose(TransposeOp { targets: vec![obj_pitch(rng.below(pitches))], chromatic_steps: rng.below(5) as i32 - 2, }), 9 => OperationKind::InsertIdentifiedPitch(InsertIdentifiedPitchOp { event: obj_event(rng.below(events)), pitch: valuegen::identified_pitch(obj_pitch(rng.below(pitches))), }), 10 => OperationKind::DeleteIdentifiedPitch(DeleteIdentifiedPitchOp { pitch: obj_pitch(rng.below(pitches)), }), 11 => OperationKind::ModifyIdentifiedPitch(ModifyIdentifiedPitchOp { pitch: obj_pitch(rng.below(pitches)), value: valuegen::pitch_value_nth(rng.below(4) as u8 + 1), }), _ => OperationKind::Registered( OperationKindRegistryId(rng.next_u64() as u128), rng.byte_vec(0, 16), ), }; OperationPayload::Primitive(kind) } /// A well-formed set of real [`OperationEnvelope`]s authored by `n_replicas` /// replicas over a shared id space, **honoring the HLC authoring contract** /// (spec §"Identifiers", the `OperationStamp` requirement): every operation's /// stamp is strictly greater than the stamp of every operation in its causal /// context. This is achieved with the standard hybrid-logical-clock send rule — /// a new stamp is `max(local clock reading, this replica's previous stamp, the /// maximum stamp over the declared causal predecessors)`, with the logical /// counter bumped on ties — so the canonical reduction order (which sorts by the /// HLC tuple) genuinely places causal predecessors before successors. /// /// The DVV causal context references only **prior** operations: the author /// always sees its own contiguous history, plus a random prefix of each other /// replica's history. Concurrency across replicas is what makes delivery order /// differ from the canonical reduction order. /// /// [`assert_causal_order_respected`](crate::convergence::assert_causal_order_respected) /// verifies the resulting histories actually satisfy the authoring contract. /// A reusable authoring context: each [`Session::author`] call mints one /// well-formed, causally-conformant envelope on a chosen replica via the HLC /// send rule + prior-history DVV described on [`operation_envelopes`]. struct Session { counters: Vec, clocks: Vec, /// Per-replica stamp history `(physical, logical)`, indexed by counter. /// Stamps are monotonic within a replica, so `stamps[rr][k]` is the max over /// `0..=k`. stamps: Vec>, out: Vec, } impl Session { fn new(n_replicas: usize) -> Self { let n = n_replicas.max(1); Session { counters: vec![0; n], clocks: vec![0; n], stamps: vec![Vec::new(); n], out: Vec::new(), } } fn n_replicas(&self) -> usize { self.counters.len() } /// Authors one operation on replica `r` carrying `payload`, honoring the HLC /// authoring contract (the new stamp strictly outranks every causal /// predecessor) with a prior-history DVV (own history always seen; others /// sampled). fn author(&mut self, rng: &mut Rng, r: usize, payload: OperationPayload) { let n_replicas = self.counters.len(); let replica = ReplicaId(r as u64 + 1); let c = self.counters[r]; let id = OperationId::new(replica, c); let mut ctx = CausalContext::new(); let mut pred_max = (0i64, 0u32); if c > 0 { ctx = ctx.with_seen(replica, c - 1); pred_max = pred_max.max(self.stamps[r][(c - 1) as usize]); } for rr in 0..n_replicas { if rr == r { continue; } let known = self.counters[rr]; if known > 0 && rng.boolean() { let k = rng.below(known); ctx = ctx.with_seen(ReplicaId(rr as u64 + 1), k); pred_max = pred_max.max(self.stamps[rr][k as usize]); } } self.clocks[r] += rng.below(3) as i64; let pt = self.clocks[r]; let prev = self.stamps[r].last().copied().unwrap_or((0, 0)); let l = pt.max(prev.0).max(pred_max.0); let logical = if l == prev.0 && l == pred_max.0 { prev.1.max(pred_max.1) + 1 } else if l == prev.0 { prev.1 + 1 } else if l == pred_max.0 { pred_max.1 + 1 } else { 0 }; self.stamps[r].push((l, logical)); self.counters[r] += 1; let env = OperationEnvelope { id, author: AuthorId(replica.0 as u128), stamp: OperationStamp::new(HybridLogicalClock::new(WallClockTime(l), logical), id), causal_context: ctx, transaction: None, payload, }; debug_assert!(epiphany_ops::well_formed(&env).is_ok()); self.out.push(env); } } /// A well-formed set of real [`OperationEnvelope`]s authored by `n_replicas` /// replicas over a shared id space, **honoring the HLC authoring contract** /// (spec §"Identifiers", the `OperationStamp` requirement): every operation's /// stamp is strictly greater than the stamp of every operation in its causal /// context. This is achieved with the standard hybrid-logical-clock send rule — /// a new stamp is `max(local clock reading, this replica's previous stamp, the /// maximum stamp over the declared causal predecessors)`, with the logical /// counter bumped on ties — so the canonical reduction order (which sorts by the /// HLC tuple) genuinely places causal predecessors before successors. /// /// The DVV causal context references only **prior** operations: the author /// always sees its own contiguous history, plus a random prefix of each other /// replica's history. Concurrency across replicas is what makes delivery order /// differ from the canonical reduction order. /// /// [`assert_causal_order_respected`](crate::convergence::assert_causal_order_respected) /// verifies the resulting histories actually satisfy the authoring contract. pub fn operation_envelopes( rng: &mut Rng, n_ops: usize, n_replicas: usize, events: u64, pitches: u64, ) -> Vec { let mut session = Session::new(n_replicas); for _ in 0..n_ops { let r = rng.below(session.n_replicas() as u64) as usize; let payload = operation_payload(rng, events, pitches); session.author(rng, r, payload); } session.out } /// A real materialized state produced by reducing a generated conformant /// operation set. This is preferable to assembling reducer-owned invariants by /// hand for ordinary property tests. pub fn materialized_state(rng: &mut Rng, n_ops: usize) -> MaterializedState { let envelopes = operation_envelopes(rng, n_ops, 3, 8, 8); let mut set = OperationSet::new(); set.accept_all(envelopes); set.reduce() } /// Bars per staff in [`two_staff_edit_session`]. pub const TWO_STAFF_BARS: u64 = 50; /// Half-note events per 4/4 bar (so the session spans exactly [`TWO_STAFF_BARS`]). const EVENTS_PER_BAR: u64 = 2; /// Per-staff event count in [`two_staff_edit_session`]: [`TWO_STAFF_BARS`] bars × /// two half-notes per 4/4 bar. pub const TWO_STAFF_EVENTS_PER_STAFF: u64 = TWO_STAFF_BARS * EVENTS_PER_BAR; /// An `InsertEvent` payload minting `event` (with `pitch`) into the given /// staff-instance / voice as the `index`-th half-note (position `index/2` /// whole-notes, i.e. two per 4/4 bar). fn insert_at(instance: u64, voice: u64, event: u64, pitch: u64, index: u64) -> OperationPayload { OperationPayload::Primitive(OperationKind::InsertEvent(InsertEventOp { staff_instance: StaffInstanceId::new(OBJ_REPLICA, instance), event: valuegen::insert_event_value( obj_event(event), VoiceId::new(OBJ_REPLICA, voice), MusicalPosition(RationalTime::new(index as i64, EVENTS_PER_BAR as i64).unwrap()), MusicalDuration(RationalTime::new(1, EVENTS_PER_BAR as i64).unwrap()), &[obj_pitch(pitch)], ), })) } /// The v0 criterion-1 scenario, **instantiated** (not merely modeled): a /// two-staff score built by two replicas. Each staff (instance 0 / voice 0 and /// instance 1 / voice 1) is filled with [`TWO_STAFF_EVENTS_PER_STAFF`] events /// spanning ~50 bars, authored by *both* replicas (alternating), so both staves /// are genuinely populated. A batch of overlapping deletes and respellings over /// the shared id space then makes reduction order — not delivery order — decide /// the materialized result. [`assert_two_staff_populated`] checks both staves /// actually survive reduction. pub fn two_staff_edit_session(rng: &mut Rng) -> Vec { let n = TWO_STAFF_EVENTS_PER_STAFF; let mut session = Session::new(2); for i in 0..n { session.author(rng, (i % 2) as usize, insert_at(0, 0, i, i, i)); session.author( rng, ((i + 1) % 2) as usize, insert_at(1, 1, n + i, n + i, i), ); } let total = 2 * n; for _ in 0..80 { let r = rng.below(2) as usize; let payload = if rng.boolean() { OperationPayload::Primitive(OperationKind::DeleteEvent(DeleteEventOp { event: obj_event(rng.below(total)), tuplet_compensation: TupletCompensation::NotInTuplet, })) } else { OperationPayload::Primitive(OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(rng.below(total)), spelling: valuegen::spelling(rng.below(4) as u8 + 1), })) }; session.author(rng, r, payload); } session.out } /// Asserts both staves of a [`two_staff_edit_session`] are actually populated: /// each staff's voice is live and at least one of its events survives reduction. /// (The reducer materializes voices/events but not staff-instance objects, so /// liveness is checked at the voice and event level.) pub fn assert_two_staff_populated(envelopes: &[OperationEnvelope]) { let mut set = OperationSet::new(); set.accept_all(envelopes.iter().cloned()); let state = set.reduce(); let n = TWO_STAFF_EVENTS_PER_STAFF; let voice_live = |v: u64| { matches!( state .objects .get(&TypedObjectId::Voice(VoiceId::new(OBJ_REPLICA, v))), Some(ObjectState::Live) ) }; assert!(voice_live(0), "staff 0 voice is not live after reduction"); assert!(voice_live(1), "staff 1 voice is not live after reduction"); let live_events = |lo: u64, hi: u64| { (lo..hi) .filter(|e| { matches!( state.objects.get(&TypedObjectId::Event(obj_event(*e))), Some(ObjectState::Live) ) }) .count() }; assert!(live_events(0, n) > 0, "staff 0 has no live events"); assert!(live_events(n, 2 * n) > 0, "staff 1 has no live events"); } /// Whole-note position past which [`graph_edit_session`] inserts, chosen to /// clear `valid_score`'s base content (quarter-note events in `[0, 1)`). const GRAPH_SESSION_OFFSET: i64 = 4; /// An `InsertEvent` payload targeting a **real** base voice (`staff_instance` /// must be the voice's actual container), minting `event`/`pitch` under /// [`OBJ_REPLICA`] as the `index`-th half-note past the base content. Unlike /// [`insert_at`], this addresses ids that already exist in a base [`graph`] /// score, so the payload survives [`OperationSet::reduce_onto`] (which rejects /// inserts into unknown voices) rather than only the base-free reducer. fn insert_into( staff_instance: StaffInstanceId, voice: VoiceId, event: u64, pitch: u64, index: u64, ) -> OperationPayload { OperationPayload::Primitive(OperationKind::InsertEvent(InsertEventOp { staff_instance, // position = GRAPH_SESSION_OFFSET + index/2 (two half-notes per 4/4 bar). event: valuegen::insert_event_value( obj_event(event), voice, MusicalPosition( RationalTime::new( GRAPH_SESSION_OFFSET * EVENTS_PER_BAR as i64 + index as i64, 2, ) .unwrap(), ), MusicalDuration(RationalTime::new(1, 2).unwrap()), &[obj_pitch(pitch)], ), })) } /// The graph-level twin of [`two_staff_edit_session`]: a real ~50-bar edit /// session targeting the **actual** voices of `base`, for the /// [`OperationSet::reduce_onto`] convergence gate (acceptance criterion 1). /// /// Two replicas alternately insert [`TWO_STAFF_EVENTS_PER_STAFF`] half-note /// events past the base content into the base's first two voices (so both /// staves are genuinely edited), then concurrently respell and delete over the /// shared minted id space, so the canonical reduction — not delivery order — /// decides the materialized `Score`. Returns the targeted voices alongside the /// envelopes (the convergence harness checks each one actually grew). /// /// Requires a base with at least one voice; callers pass a base scanned for two /// (see `epiphany_core::generators::valid_score`). pub fn graph_edit_session( base: &epiphany_core::Score, rng: &mut Rng, ) -> (Vec<(StaffInstanceId, VoiceId)>, Vec) { let targets: Vec<(StaffInstanceId, VoiceId)> = base .voices() .map(|(_, instance, voice)| (instance, voice.id)) .take(2) .collect(); assert!( !targets.is_empty(), "graph_edit_session requires a base score with at least one voice" ); let n = TWO_STAFF_EVENTS_PER_STAFF; let mut session = Session::new(2); for i in 0..n { for (ti, &(instance, voice)) in targets.iter().enumerate() { // Each target voice's consecutive half-notes alternate authoring // replicas; the two voices are edited by opposite replicas at any // given index. Positions are distinct within a voice (no // same-position collision → clean inserts), while cross-replica // causal sampling makes delivery order differ from reduction order. let r = (i as usize + ti) % 2; let object = ti as u64 * n + i; session.author(rng, r, insert_into(instance, voice, object, object, i)); } } let total = targets.len() as u64 * n; for _ in 0..80 { let r = rng.below(2) as usize; let payload = if rng.boolean() { OperationPayload::Primitive(OperationKind::DeleteEvent(DeleteEventOp { event: obj_event(rng.below(total)), tuplet_compensation: TupletCompensation::NotInTuplet, })) } else { OperationPayload::Primitive(OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(rng.below(total)), spelling: valuegen::spelling(rng.below(4) as u8 + 1), })) }; session.author(rng, r, payload); } (targets, session.out) } /// A `(base, mutated)` pair of operation sets whose operations have **identical /// identities, stamps, and causal contexts** but differ in payload *content* /// (one respelling's spelling). Both sets insert a pitch and respell it, so the /// respelling takes effect; reducing them therefore yields *different* canonical /// bytes — the exact rebuttal to an id-only "serializer" that would collapse /// distinct scores. `mutated` is `base.clone()` with only one payload changed, /// so the identity/ordering metadata is provably preserved. pub fn content_mutation_pair() -> (Vec, Vec) { let mut rng = Rng::new(0x00C0_1117_E27E_5EED); let mut session = Session::new(1); // Insert event 0 carrying pitch 0 (so the pitch is live), then respell it. session.author(&mut rng, 0, insert_at(0, 0, 0, 0, 0)); session.author( &mut rng, 0, OperationPayload::Primitive(OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(0), spelling: valuegen::spelling(0xAA), })), ); let base = session.out; let mut mutated = base.clone(); if let OperationPayload::Primitive(OperationKind::RespellPitch(op)) = &mut mutated[1].payload { op.spelling = valuegen::spelling(0xBB); } else { unreachable!("the second op is the respelling"); } (base, mutated) } /// The canonical [`CausalContext`] bytes for exactly the structurally accepted /// operation identifiers in `envelopes`. /// /// Each replica's contiguous prefix beginning at counter zero is represented in /// the DVV vector; identifiers after a gap are represented as dots. Malformed /// envelopes are excluded because [`OperationSet`] excludes them from the /// materialized snapshot as well. The returned bytes are the operation layer's /// real canonical encoding, not a bundle-local approximation. pub fn frontier_bytes(envelopes: &[OperationEnvelope]) -> Vec { use std::collections::{BTreeMap, BTreeSet}; let mut by_replica: BTreeMap> = BTreeMap::new(); for envelope in envelopes { if epiphany_ops::well_formed(envelope).is_ok() { by_replica .entry(envelope.id.replica) .or_default() .insert(envelope.id.counter); } } let mut frontier = CausalContext::new(); for (replica, counters) in by_replica { let mut next = 0u64; while counters.contains(&next) { if next == u64::MAX { break; } next += 1; } if next > 0 { frontier = frontier.with_seen(replica, next - 1); } for counter in counters.range(next..) { frontier = frontier.with_dot(OperationId::new(replica, *counter)); } } frontier.to_canonical_bytes() } /// A second envelope addressing the same [`OperationId`] as `env` but with /// different canonical bytes — the input that drives a `Single` slot to /// `Equivocated`. The stamp's id stays pinned to `env.id`, so it is still /// well-formed. pub fn equivocating_twin(env: &OperationEnvelope) -> OperationEnvelope { let mut twin = env.clone(); twin.payload = OperationPayload::Primitive(OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(0), spelling: valuegen::spelling(0xEE), })); if twin.envelope_hash() == env.envelope_hash() { twin.payload = OperationPayload::Primitive(OperationKind::RespellPitch(RespellPitchOp { pitch: obj_pitch(1), spelling: valuegen::spelling(0x11), })); } twin } // =========================================================================== // Agent D — epiphany-bundle: refs, snapshots, blobs, a rich manifest // =========================================================================== /// One of the chunk kinds. pub fn chunk_kind(rng: &mut Rng) -> ChunkKind { *rng.choose(&[ ChunkKind::OperationEnvelopeBlock, ChunkKind::OperationIndex, ChunkKind::Snapshot, ChunkKind::Blob, ChunkKind::ExtensionData, ChunkKind::TextProjection, ChunkKind::LayoutCache, ChunkKind::IntegrityIndex, ChunkKind::Manifest, ]) } /// A compression algorithm (every variant). Compression is metadata, not /// identity, so any value is valid in a [`ChunkRef`]. pub fn compression_algorithm(rng: &mut Rng) -> CompressionAlgorithm { match rng.below(3) { 0 => CompressionAlgorithm::None, 1 => CompressionAlgorithm::Zstd { level: rng.range(1, 22) as u8, }, _ => CompressionAlgorithm::Reserved(rng.range(2, 255) as u8), } } /// A content-addressed chunk reference of the given kind, derived from a random /// payload so its id and hash are internally consistent (content identity is the /// uncompressed payload, independent of the `compression` metadata). pub fn chunk_ref(rng: &mut Rng, kind: ChunkKind) -> ChunkRef { let payload = rng.byte_vec(1, 64); let schema = SchemaVersion::V0; ChunkRef { id: chunk_id(kind, schema, &payload), kind, schema_version: schema, offset: rng.range(0, 1 << 32), compressed_length: payload.len() as u64, uncompressed_length: payload.len() as u64, compression: compression_algorithm(rng), hash: content_hash_for(kind, schema, &payload), } } /// A committed superblock with varied fields. Committed so it parses as /// [`epiphany_bundle::SlotParse::Valid`] for ordinary selection. pub fn superblock(rng: &mut Rng) -> Superblock { Superblock { generation: rng.range(0, 1_000), manifest_offset: rng.range(0, 1 << 32), manifest_length: rng.range(1, 1 << 20), manifest_hash: content_hash(rng), manifest_schema_version: SchemaVersion::V0, reduction_algorithm_version: ReductionAlgorithmVersion(rng.range(0, 8) as u32), profile_id: profile_id(rng), commit_state: CommitState::Committed, commit_timestamp: BundleWallClockTime(rng.range(0, 1 << 40) as i64), } } /// A snapshot reference (a canonical base or acceleration snapshot) under a /// varied profile. The `hash` is the snapshot root chunk's hash (the field's /// documented meaning: "hash of the snapshot's root chunk, for fast verification"). pub fn snapshot_ref(rng: &mut Rng) -> SnapshotRef { let profile = profile_id(rng); snapshot_ref_with_profile(rng, profile) } /// A snapshot reference whose `profile_id` is the caller's choice (so a manifest /// can keep its snapshots' profiles among its declared profiles). pub fn snapshot_ref_with_profile(rng: &mut Rng, profile: ProfileId) -> SnapshotRef { let root = chunk_ref(rng, ChunkKind::Snapshot); let hash = root.hash; SnapshotRef { snapshot_id: SnapshotId(rng.array16()), covers_causal_frontier: FrontierBytes::from_bytes(rng.byte_vec(0, 24)), reduction_algorithm_version: ReductionAlgorithmVersion(rng.range(0, 8) as u32), profile_id: profile, root, hash, } } /// A profile identifier (every variant). pub fn profile_id(rng: &mut Rng) -> ProfileId { match rng.below(4) { 0 => ProfileId::Full, 1 => ProfileId::ReadOnly, 2 => ProfileId::Lite, _ => ProfileId::Custom(ProfileRegistryId(rng.array16())), } } /// A profile identifier that is never [`ProfileId::Full`] — for a *second* /// declaration alongside a Full one, so the two ids do not collide. pub fn non_full_profile_id(rng: &mut Rng) -> ProfileId { match rng.below(3) { 0 => ProfileId::ReadOnly, 1 => ProfileId::Lite, _ => ProfileId::Custom(ProfileRegistryId(rng.array16())), } } /// A blob reference with a valid RFC 6838 media type. The `hash` is the payload's /// content hash, consistent with the content-derived `blob_id`. pub fn blob_ref(rng: &mut Rng) -> BlobRef { let payload = rng.byte_vec(1, 64); let len = payload.len() as u64; let blob_id = BlobId::of_payload(&payload); BlobRef { // The verification hash is the blob's content hash, consistent with its // content-derived id. hash: blob_id.0, blob_id, media_type: String::from("application/octet-stream"), offset: rng.range(0, 1 << 32), compressed_length: len, uncompressed_length: len, compression: CompressionAlgorithm::None, declared_max_uncompressed_length: if rng.boolean() { Some(len.max(1)) } else { None }, } } /// An extension declaration. `preserved_chunk_roots` is kept to at most one /// entry so the canonical sort/dedup `encode` applies is a no-op and the decoded /// value compares equal to the original. pub fn extension_declaration(rng: &mut Rng) -> ExtensionDeclaration { ExtensionDeclaration { extension_id: ExtensionId(rng.array16()), version: SemVer::new( rng.range(0, 3) as u32, rng.range(0, 9) as u32, rng.range(0, 9) as u32, ), required: rng.boolean(), preserved_chunk_roots: if rng.boolean() { vec![chunk_ref(rng, ChunkKind::ExtensionData)] } else { vec![] }, affected_object_kinds: rng.byte_vec(0, 8), edit_barriers: rng.byte_vec(0, 8), } } /// A retention policy with varied (non-default) fields. pub fn retention_policy(rng: &mut Rng) -> RetentionPolicy { RetentionPolicy { retain_previous_manifests: rng.range(0, 8) as u32, retain_duration: if rng.boolean() { Some(BundleWallClockDuration(rng.range(0, 1 << 40) as i64)) } else { None }, retain_named_checkpoints: rng.boolean(), } } /// A profile declaration with a varied profile id and constraints (a varied /// [`retention_policy`] and block-size limit). pub fn profile_declaration(rng: &mut Rng) -> ProfileDeclaration { let profile = profile_id(rng); profile_declaration_with(rng, profile) } /// A profile declaration with the caller's `profile_id` and varied constraints. pub fn profile_declaration_with(rng: &mut Rng, profile: ProfileId) -> ProfileDeclaration { ProfileDeclaration { profile_id: profile, version: SemVer::new(rng.range(0, 2) as u32, rng.range(0, 9) as u32, 0), constraints: ProfileConstraints { max_uncompressed_block_size: rng.range(1 << 16, 64 << 20), retention_policy: retention_policy(rng), }, } } /// A **rich** manifest exercising every optional field and reference vector: /// lineage, multiple operation roots, an operation index, a canonical base, /// acceleration snapshots, blobs, extension and (two, distinct-id) profile /// declarations, and the text-projection / integrity optional roots. Snapshots /// reference only declared profiles, and the two profile declarations have /// distinct ids (no `Full`/`Full` collision). /// /// **Scope.** This is a *codec / structural* manifest generator — it exercises /// `Manifest::encode`/`decode` byte-stability and the decoder's validation. It is /// not a fully bundle-valid manifest: its `ChunkRef`/`BlobRef` references are not /// backed by chunks in any store (so `Bundle::open` + `verify_canonical_chunks` /// would not accept it). Bundle *lifecycle* validity is covered by /// [`crate::roundtrip::committed_manifest`] (a manifest produced by real commits) /// and the bundle's own crash/manifest-selection harnesses. pub fn rich_manifest(rng: &mut Rng) -> Manifest { let mut m = Manifest::empty(DocumentId(rng.array16())); m.generation = rng.range(0, 1_000); m.lineage_id = if rng.boolean() { Some(LineageId(rng.array16())) } else { None }; for _ in 0..rng.range_usize(0, 3) { m.operation_roots .push(chunk_ref(rng, ChunkKind::OperationEnvelopeBlock)); } m.operation_index_root = if rng.boolean() { Some(chunk_ref(rng, ChunkKind::OperationIndex)) } else { None }; // Two profile declarations with distinct ids (Full + a non-Full one). let extra_id = non_full_profile_id(rng); let extra = profile_declaration_with(rng, extra_id); m.profile_declarations = vec![ProfileDeclaration::full(), extra]; // Snapshots reference only declared profiles (Full or the extra). let declared = [ProfileId::Full, extra_id]; m.canonical_base = if rng.boolean() { let p = *rng.choose(&declared); Some(snapshot_ref_with_profile(rng, p)) } else { None }; for _ in 0..rng.range_usize(0, 2) { let p = *rng.choose(&declared); m.acceleration_snapshots .push(snapshot_ref_with_profile(rng, p)); } for _ in 0..rng.range_usize(0, 2) { m.blob_roots.push(blob_ref(rng)); } for _ in 0..rng.range_usize(0, 2) { m.extension_declarations.push(extension_declaration(rng)); } m.text_projection_root = if rng.boolean() { Some(chunk_ref(rng, ChunkKind::TextProjection)) } else { None }; m.integrity_root = if rng.boolean() { Some(chunk_ref(rng, ChunkKind::IntegrityIndex)) } else { None }; m } #[cfg(test)] mod tests { use super::*; #[test] fn envelope_set_is_well_formed_and_deterministic() { let mut a = Rng::new(42); let mut b = Rng::new(42); let sa = operation_envelopes(&mut a, 64, 3, 6, 6); let sb = operation_envelopes(&mut b, 64, 3, 6, 6); assert_eq!(sa.len(), 64); assert_eq!(sa, sb, "generation must be reproducible from the seed"); assert!(sa.iter().all(|e| epiphany_ops::well_formed(e).is_ok())); } #[test] fn equivocating_twin_differs_in_bytes_but_not_id() { let mut rng = Rng::new(7); let set = operation_envelopes(&mut rng, 4, 2, 6, 6); let twin = equivocating_twin(&set[0]); assert_eq!(twin.id, set[0].id); assert_eq!(twin.stamp.id, set[0].id); assert_ne!(twin.envelope_hash(), set[0].envelope_hash()); } #[test] fn rational_large_is_actually_promoted() { let mut rng = Rng::new(3); for _ in 0..100 { let r = rational_time_large(&mut rng); assert!( matches!(r, RationalTime::Large(_)), "rational_time_large must produce the promoted arm, got {r:?}" ); } } #[test] fn rich_manifest_is_deterministic() { let mut a = Rng::new(99); let mut b = Rng::new(99); assert_eq!(rich_manifest(&mut a), rich_manifest(&mut b)); } #[test] fn zero_sized_operation_spaces_are_supported() { let mut rng = Rng::new(5); for _ in 0..128 { let _ = operation_payload(&mut rng, 0, 0); } } #[test] fn frontier_uses_the_real_dvv_encoding_and_preserves_gaps() { let mut rng = Rng::new(11); let mut envelope = operation_envelopes(&mut rng, 1, 1, 1, 1).remove(0); envelope.id = OperationId::new(ReplicaId(1), 5); envelope.stamp.id = envelope.id; let expected = CausalContext::new() .with_dot(envelope.id) .to_canonical_bytes(); assert_eq!(frontier_bytes(std::slice::from_ref(&envelope)), expected); envelope.stamp.hlc.physical_time = WallClockTime(-1); assert_eq!( frontier_bytes(&[envelope]), CausalContext::new().to_canonical_bytes(), "rejected envelopes must not enter the snapshot frontier" ); } #[test] fn generated_reduction_outputs_decode_canonically() { for seed in 0..256 { let mut rng = Rng::new(seed); let mut state = MaterializedState::default(); state .effects .push((operation_id(&mut rng), operation_effect(&mut rng))); state.conflicts = conflict_registry(&mut rng); state.anomalies.push(integrity_anomaly(&mut rng)); state .objects .insert(typed_object_id(&mut rng), object_state(&mut rng)); state .spellings .insert(pitch_id(&mut rng), valuegen::spelling(rng.below(7) as u8)); state.breaks.insert( (region_id(&mut rng), musical_position(&mut rng)), rng.boolean(), ); state .pending .push((operation_id(&mut rng), pending_reason(&mut rng))); let bytes = state.canonical_bytes(); let decoded = MaterializedState::decode_canonical(&bytes).unwrap(); assert_eq!(decoded.canonical_bytes(), bytes); let _ = reanchor_result(&mut rng); let _ = anomalous_replica_segment(&mut rng); } } }