Skip to main content

canwu_api/
lib.rs

1//! Public programmatic, query, semantic-agent, explanation, and debug interfaces.
2
3#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]
4
5pub use canwu_core::{
6    ArmyId, BoundaryId, CommandAttemptId, CommandId, CommandRequestId, CoreEntityKind,
7    DecisionRequestId, DecisionTicketId, DecisionTraceId, DomainEntityKindClass, DomainEntityType,
8    DomainKindClass, DomainRecordKind, DomainRecordRef, DomainRecordType, DomainRecordVersionRef,
9    DomainRecordVersionSource, DomainValueKindClass, DomainValueType, EntityRef, EvaluationTerm,
10    EvaluationTraceRecord, EventId, EvidenceRef, GovernmentId, HolderKnowledgeRecordId, IngressId,
11    KnowledgeHolderPolicy, KnowledgeHolderRef, KnowledgeRecordId, KnowledgeRecordKind,
12    KnowledgeSchemaId, LetterId, OrganizationId, PersonId, RandomDrawId, ResourceId, RouteId,
13    SchemaRegistry, SchemaRegistryError, SimulationGranularity, TerritoryId, TypeSchema,
14    TypedDomainRecordRef,
15};
16pub use canwu_event::{CauseRef, EventAudience, EventKind, EventKindError, SimEvent};
17pub use canwu_knowledge::{
18    ActorKnowledge, ArmyKnowledge, EstimateRange, KnowledgeCursor, KnowledgeHistoryView,
19    KnowledgeOrigin, KnowledgeQuery, KnowledgeQueryError, KnowledgeQueryResult, KnowledgeReadCut,
20    KnowledgeRecord, KnowledgeRecordDraft, KnowledgeRecordView, KnowledgeSnapshot, KnowledgeSource,
21    KnowledgeSubject, KnowledgeSubjectTarget, MAX_KNOWLEDGE_PAGE_SIZE,
22};
23pub use canwu_routing::{
24    DepartureSlot, DurationSample, PlanningSnapshot, ROUTING_ALGORITHM_VERSION, RouteCost,
25    RouteLeg, RoutePlan, RoutingAlgorithm, RoutingCache, RoutingConnection, RoutingConnectionRef,
26    RoutingEndpoint, RoutingEndpointKind, RoutingError, RoutingNetwork, RoutingNodeRef,
27    RoutingPolicy, RoutingRequest, TransferMode, TraversalModel, plan_route,
28};
29use canwu_sim::Simulation;
30pub use canwu_sim::{
31    ADMISSION_CURSOR_FORMAT_VERSION, ArchiveProvider, ArchiveReachabilityManifest, ArchiveStore,
32    ArchiveStoreOutcome, ArchivedEvidenceLocator, ArchivedEvidenceReceipt,
33    ArchivedPluginIngressProvenance, ArchivedSegmentHeader, Army, ArtifactManifest, BoundaryChange,
34    BoundaryContext, BoundaryDirective, BoundaryEmission, BoundaryEmissionKind,
35    BoundaryEvaluationTrace, BoundaryIngressGeneration, BoundaryKnowledgeChange,
36    BoundaryPersonAvailabilityChange, BoundaryPersonCreation, BoundaryPhase, BoundaryProposal,
37    BoundaryReceipt, BoundaryRecord, BoundaryRequest, BoundarySystemContract,
38    BoundarySystemHandler, CHECKPOINT_JOURNAL_FORMAT_VERSION, COMMITMENT_FORMAT_VERSION,
39    CONTROLLER_AUTHORITY_UNAVAILABLE_REASON, CanwuError, CheckpointJournal, Command,
40    CommandAttemptOutcome, CommandAttemptRecord, CommandAuthority, CommandContext, CommandEnvelope,
41    CommandIngress, CommandOutcome, CommandPolicyContext, CommandReceipt, CommandRecord,
42    CommandRejection, CommandRequest, CommitmentRoots, CompactedSimulation, ControllerDecision,
43    ControllerPolicy, CreatedPerson, CustodyState, DECISION_ARCHIVE_BUCKET_PAGE_FORMAT_VERSION,
44    DECISION_ARCHIVE_FORMAT_VERSION, DECISION_MAKER_UNAVAILABLE_REASON,
45    DECISION_REQUEST_COMMITMENT_DOMAIN, DecisionAction, DecisionArchiveBlob,
46    DecisionArchiveBucketPage, DecisionArchiveProvider, DecisionArchiveReceipt,
47    DecisionArchiveRecord, DecisionArchiveStore, DecisionArchiveStoreOutcome,
48    DecisionAttemptErrorCode, DecisionAttemptOutcome, DecisionAttemptRecord, DecisionAuthority,
49    DecisionContext, DecisionController, DecisionControllerBinding, DecisionError,
50    DecisionErrorCode, DecisionEvaluation, DecisionExternalEvidence, DecisionFactorContribution,
51    DecisionHistoryCursor, DecisionHistoryKey, DecisionHistoryLocation, DecisionHistoryPage,
52    DecisionHistoryQueryBudget, DecisionHotState, DecisionIngressRequest,
53    DecisionLocatorScaleMetrics, DecisionMutation, DecisionOption, DecisionOptionEvaluation,
54    DecisionOptionWeight, DecisionOrigin, DecisionOutcome, DecisionPolicy, DecisionPolicyIdentity,
55    DecisionPolicyKind, DecisionRandomEvidence, DecisionRule, DecisionStage, DecisionState,
56    DecisionTicket, DecisionTicketDraft, DecisionTicketState, DecisionTrace, DemoIds, DomainRecord,
57    DomainRecordChange, DomainRecordClass, DomainRecordCommitmentRoots, DomainRecordDraft,
58    DomainRecordLifecycle, DomainRecordMutation, DomainRecordMutationPolicy, DomainRecordOperation,
59    DomainRecordPage, DomainRecordPageRoots, DomainRecordSchema, DomainReference,
60    DomainReferenceSchema, DomainReferenceTarget, DomainReferenceTargetKind, ENGINE_VERSION,
61    ErrorCode, EvaluationLimitsV1, EvidenceArchiveIndex, EvidenceCursor, EvidenceIndexEntry,
62    EvidenceItemLocator, EvidenceJournalKind, EvidenceJournalRoots, EvidenceJournalSegment,
63    EvidenceNestedLocator, EvidenceSealToken, ExternalDecisionOption, ExternalDecisionRequest,
64    ExternalDecisionResponse, ExternalPolicy, Government, GuardedUtilityPolicy,
65    HumanDecisionResponse, HumanPolicy, IDENTITY_EVIDENCE_DEPENDENCIES_FIELD,
66    IDENTITY_EVIDENCE_DEPENDENCIES_FORMAT_VERSION, IdentityEvidenceDependenciesV1,
67    IngressCancellationAuthority, IngressClass, IngressPayload, IngressReceipt, IngressRecord,
68    InteractionPolicy, Issuer, KnowledgeLimitsV1, KnowledgeSubjectSchema,
69    KnowledgeSubjectTargetKind, KnowledgeWriteGrant, LetterCargo, LetterStatus, LifeState,
70    LlmModelIdentity, LlmPolicy, MAX_DECISION_ARCHIVE_BATCH_ENTRIES,
71    MAX_DECISION_HISTORY_PAGE_BYTES, MAX_DECISION_HISTORY_PAGE_SIZE,
72    MAX_INGRESS_CANCELLATION_REASON_BYTES, MAX_OWNER_AUTHORIZED_MUTATIONS,
73    MAX_OWNER_AUTHORIZED_PARTICIPANTS, MAX_STATE_DELTA_PAGES, MAX_STATE_PAGE_BYTES,
74    MaintenanceChangeRecord, MaintenanceDependencyResolverDescriptor, MaintenanceDisposition,
75    MaintenanceIngressRequest, MaintenanceRejectionReceipt, MapPoint,
76    OWNER_AUTHORIZED_MAINTENANCE_FORMAT_VERSION, ObservationPolicy, OrderedRulePolicy, OutboxEntry,
77    OwnerAuthorizedMaintenanceDraft, OwnerAuthorizedMaintenanceParticipant,
78    OwnerAuthorizedMaintenanceRequest, OwnerAuthorizedMutation, OwnerAuthorizedParticipantDraft,
79    OwnerAuthorizedParticipantProposal, OwnerAuthorizedParticipantRole,
80    OwnerAuthorizedRecordExpectation, PAGED_CHECKPOINT_FORMAT_VERSION,
81    PAYLOAD_REQUIRED_EVIDENCE_CONTINUATION_FIELD,
82    PAYLOAD_REQUIRED_EVIDENCE_CONTINUATION_FORMAT_VERSION, PLUGIN_DESCRIPTOR_FORMAT_VERSION,
83    PagedSimulationCheckpoint, PatriciaStoreMetrics, PayloadProperty,
84    PayloadRequiredEvidenceContinuationV1, PayloadSchema, PayloadValueType,
85    PersistentDomainRecordStore, Person, PersonAvailability, PersonDraft, PersonTransitState,
86    PluginActionDescriptor, PluginArchiveObjectProvider, PluginArchiveReachabilityParticipant,
87    PluginArchiveRetention, PluginCommandHandler, PluginComponentRecord, PluginDescriptor,
88    PluginIngressDescriptor, PluginIngressPermit, PluginIngressRequest, PluginIngressTarget,
89    PluginKnowledgeSchema, PluginRegistrar, PluginRegistry, PolicyDecision,
90    PortablePagedSimulationCheckpoint, PreparedDecisionArchive, PreparedDecisionIngress,
91    PreparedEvidenceSeal, PreparedPagedSimulationCheckpoint, PreparedStateDelta,
92    QueuedExternalPolicy, QueuedHumanPolicy, QueuedLlmPolicy, RUN_CONFIGURATION_FORMAT_VERSION,
93    RUN_MANIFEST_FORMAT_VERSION, RandomAlgorithm, RandomDecisionResolution, RandomDrawAddress,
94    RandomDrawOutcome, RandomDrawProducer, RandomDrawRecord, RandomOperationAddressV1,
95    RandomOperationTarget, RandomSample, RandomStreamKey, RandomStreamState, ReplayJournal,
96    ReservationAllocation, ReservationDisposition, ReservationOffer, ReservationOfferRecord,
97    ReservationPoolKey, ReservationRef, ReservationRequest, ReservationRequestRecord, Route,
98    RuleChoice, RulePolicy, RunConfiguration, RunConfigurationSnapshot, RunManifest, RunPurpose,
99    SNAPSHOT_FORMAT_VERSION, STATE_PAGE_CODEC, STATE_PAGE_FORMAT_VERSION,
100    STATE_REVISION_FORMAT_VERSION, Scenario, SeatBinding, SeatPolicy, SimulationCheckpoint,
101    SimulationPlugin, SimulationSnapshot, SimulationSystemHandler, SimulationView, StateKey,
102    StatePageBlob, StatePageProvider, StatePageRetentionHandle, StatePageRetentionLedger,
103    StatePageRetentionPhase, StatePageStore, StateVisibility, SystemCadence, SystemContract,
104    SystemDirective, Territory, TracePolicy, TransitState, UtilityEvaluator, UtilityPolicy,
105    UtilityProfile, VerifiedDecisionArchiveCommit, VerifiedOwnerAuthorizedMaintenanceCommit,
106    WeightedUtilityEvaluator, WeightedUtilityPolicy, WorldSnapshot, canonical_byte_hash,
107    canonical_hash, format8_decision_locator_scale_probe, format8_patricia_scale_probe,
108    identity_evidence_dependencies_property_v1, payload_required_evidence_continuation_property_v1,
109    prepare_state_delta, state_page_id, verify_state_delta,
110};
111// Transition manifests and their audit (gap G-04).
112pub use canwu_sim::{
113    MAX_PENDING_TRANSITION_MANIFESTS, MAX_PENDING_TRANSITION_MANIFESTS_PER_COORDINATOR,
114    MAX_TRANSITION_EXPECTED_VERSIONS, MAX_TRANSITION_LINEAGE_ID_BYTES, MAX_TRANSITION_PARTICIPANTS,
115    MAX_TRANSITION_READY_HORIZON, PendingTransitionManifest, TransitionAuditOutcome,
116    TransitionAuditRecord, TransitionManifest, TransitionManifestId, TransitionParticipant,
117    TransitionParticipantAudit, TransitionRecordVersion,
118};
119pub use canwu_time::{SimDuration, SimTime};
120pub use canwu_transport::{
121    BookingAllocationV1, CAPACITY_BOOKING_ALLOCATION_DIGEST_DOMAIN, CapacityAllocationFailureV1,
122    CapacityBooking, CapacityBookingAllocationEvidenceV1, CapacityBookingId,
123    CapacityBookingRequestV1, CapacityBookingStatus, DeliveryCompletionRequest, DeliverySaga,
124    Handoff, HandoffId, HandoffKind, ItineraryRevision, ItineraryRevisionId,
125    ItineraryRevisionReason, LegExecution, LegExecutionId, LegExecutionStatus, MovementInitiative,
126    MovementOrder, MovementOrderError, MovementOrderId, MovementSubject, MovementSubjectRole,
127    ReconciliationOutcome, SagaState, TRANSPORT_SEMANTIC_VERSION, TransportCapacityPoolV1,
128    TransportError, TransportExecution, TransportExecutionId, TransportExecutionState,
129    allocate_capacity_bookings, capacity_booking_allocation_operation_key,
130    delivery_completion_operation_key,
131};
132use serde::{Deserialize, Serialize};
133
134/// Main in-process API. All returned world values are detached snapshots.
135pub struct Canwu {
136    simulation: Simulation,
137}
138
139/// Public API for a live runtime whose sealed evidence segments are stored by the caller.
140pub struct CompactedCanwu {
141    simulation: CompactedSimulation,
142}
143
144impl PluginArchiveObjectProvider for Canwu {
145    fn load_plugin_archive_object(
146        &self,
147        namespace: &str,
148        object_id: &str,
149    ) -> Result<Option<Vec<u8>>, CanwuError> {
150        self.plugin_archive_object(namespace, object_id)
151    }
152}
153
154impl Canwu {
155    #[must_use]
156    pub const fn version() -> &'static str {
157        ENGINE_VERSION
158    }
159
160    pub fn new(seed: u64, scenario: Scenario) -> Result<Self, CanwuError> {
161        Ok(Self {
162            simulation: Simulation::new(seed, scenario)?,
163        })
164    }
165
166    /// Enters the explicit compact-journal interface without discarding evidence.
167    pub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError> {
168        Ok(CompactedCanwu {
169            simulation: self.simulation.into_compacted()?,
170        })
171    }
172
173    pub fn new_with_plugins(
174        seed: u64,
175        scenario: Scenario,
176        plugins: &[&dyn SimulationPlugin],
177    ) -> Result<Self, CanwuError> {
178        Ok(Self {
179            simulation: Simulation::new_with_plugins(seed, scenario, plugins)?,
180        })
181    }
182
183    pub fn new_with_manifest(
184        seed: u64,
185        scenario: Scenario,
186        run_manifest: RunManifest,
187    ) -> Result<Self, CanwuError> {
188        Ok(Self {
189            simulation: Simulation::new_with_manifest(seed, scenario, run_manifest)?,
190        })
191    }
192
193    pub fn new_with_manifest_and_plugins(
194        seed: u64,
195        scenario: Scenario,
196        run_manifest: RunManifest,
197        plugins: &[&dyn SimulationPlugin],
198    ) -> Result<Self, CanwuError> {
199        Ok(Self {
200            simulation: Simulation::new_with_manifest_and_plugins(
201                seed,
202                scenario,
203                run_manifest,
204                plugins,
205            )?,
206        })
207    }
208
209    pub fn new_with_run_configuration(
210        seed: u64,
211        scenario: Scenario,
212        run_manifest: RunManifest,
213        run_configuration: RunConfiguration,
214    ) -> Result<Self, CanwuError> {
215        Ok(Self {
216            simulation: Simulation::new_with_run_configuration(
217                seed,
218                scenario,
219                run_manifest,
220                run_configuration,
221            )?,
222        })
223    }
224
225    pub fn new_with_run_configuration_and_plugins(
226        seed: u64,
227        scenario: Scenario,
228        run_manifest: RunManifest,
229        run_configuration: RunConfiguration,
230        plugins: &[&dyn SimulationPlugin],
231    ) -> Result<Self, CanwuError> {
232        Ok(Self {
233            simulation: Simulation::new_with_run_configuration_and_plugins(
234                seed,
235                scenario,
236                run_manifest,
237                run_configuration,
238                plugins,
239            )?,
240        })
241    }
242
243    /// Deprecated compatibility scenario. New hosts should use an integration-owned scenario.
244    pub fn demo(seed: u64) -> Result<Self, CanwuError> {
245        let (simulation, _) = Simulation::demo(seed)?;
246        Ok(Self { simulation })
247    }
248
249    /// IDs for the deprecated compatibility scenario.
250    #[must_use]
251    pub fn demo_ids() -> DemoIds {
252        let (_, ids) = canwu_sim::demo_scenario();
253        ids
254    }
255
256    #[must_use]
257    pub const fn time(&self) -> SimTime {
258        self.simulation.time()
259    }
260
261    #[must_use]
262    pub const fn run_manifest(&self) -> &RunManifest {
263        self.simulation.run_manifest()
264    }
265
266    #[must_use]
267    pub const fn run_configuration(&self) -> &RunConfigurationSnapshot {
268        self.simulation.run_configuration()
269    }
270
271    #[must_use]
272    /// Returns the persisted authoritative transaction revision.
273    ///
274    /// Accepted commands, persisted expected rejections, and completed
275    /// settlement boundaries each advance it exactly once. Failed work, exact
276    /// retries, bare clock movement, queued but unadmitted ingress, and plugin
277    /// setup do not advance it; combine it with command expected-time guards.
278    pub fn revision(&self) -> u64 {
279        self.simulation.revision()
280    }
281
282    #[must_use]
283    pub fn run_manifest_hash(&self) -> &str {
284        self.simulation.run_manifest_hash()
285    }
286
287    #[must_use]
288    pub fn checkpoint_hash(&self) -> &str {
289        self.simulation.checkpoint_hash()
290    }
291
292    pub fn authoritative_state_hash(&self) -> Result<String, CanwuError> {
293        self.simulation.authoritative_state_hash()
294    }
295
296    pub fn entities(&self) -> impl Iterator<Item = &EntityRef> {
297        self.simulation.entities()
298    }
299
300    #[must_use]
301    pub fn entity_exists(&self, entity: &EntityRef) -> bool {
302        self.simulation.entity_exists(entity)
303    }
304
305    /// Deprecated detached format-5 compatibility projection.
306    #[must_use]
307    pub fn world(&self) -> WorldSnapshot {
308        self.simulation.world()
309    }
310
311    /// Trusted host access to a person's committed life and custody state.
312    /// `None` means no change has been committed: the person is alive and free.
313    #[must_use]
314    pub fn person_availability(&self, person: PersonId) -> Option<&PersonAvailability> {
315        self.simulation.person_availability(person)
316    }
317
318    /// Trusted host access to every committed person availability, in
319    /// person-ID order. Persons without an entry are alive and free.
320    pub fn person_availabilities(&self) -> impl Iterator<Item = (&PersonId, &PersonAvailability)> {
321        self.simulation.person_availabilities()
322    }
323
324    /// Trusted host access to the registered transition manifests whose
325    /// ready boundary has not settled, in manifest-ID order. Settled
326    /// manifests leave their audit on the boundary record and receipt.
327    pub fn pending_transition_manifests(&self) -> impl Iterator<Item = &PendingTransitionManifest> {
328        self.simulation.pending_transition_manifests()
329    }
330
331    /// Trusted host/admin access to the complete knowledge snapshot.
332    ///
333    /// Do not expose this public API to player, agent, observer, or remote clients;
334    /// use [`Canwu::viewer`] or [`Canwu::viewer_for_actor`] instead.
335    #[must_use]
336    pub fn knowledge(&self) -> &KnowledgeSnapshot {
337        self.simulation.knowledge()
338    }
339
340    #[must_use]
341    pub fn events(&self) -> &[SimEvent] {
342        self.simulation.events()
343    }
344
345    #[must_use]
346    pub fn commands(&self) -> &[CommandRecord] {
347        self.simulation.command_log()
348    }
349
350    #[must_use]
351    pub fn boundaries(&self) -> &[BoundaryRecord] {
352        self.simulation.boundaries()
353    }
354
355    #[must_use]
356    pub fn command_attempts(&self) -> &[CommandAttemptRecord] {
357        self.simulation.command_attempts()
358    }
359
360    #[must_use]
361    pub fn ingress_log(&self) -> &[IngressRecord] {
362        self.simulation.ingress_log()
363    }
364
365    #[must_use]
366    pub fn domain_record(&self, reference: &DomainRecordRef) -> Option<&DomainRecord> {
367        self.simulation.domain_record(reference)
368    }
369
370    /// Returns whether an exact domain-record version exists in current or retained evidence.
371    #[must_use]
372    pub fn domain_record_version_evidence_exists(
373        &self,
374        reference: &DomainRecordVersionRef,
375    ) -> bool {
376        self.simulation
377            .domain_record_version_evidence_exists(reference)
378    }
379
380    /// Returns whether a generic evidence identity is retained or archived.
381    #[must_use]
382    pub fn evidence_exists(&self, reference: &EvidenceRef) -> bool {
383        self.simulation.evidence_exists(reference)
384    }
385
386    /// Returns when retained evidence first became authoritative.
387    ///
388    /// Compacted identity-only receipts return `None`; load the archived
389    /// evidence body before making decisions that require temporal ordering.
390    #[must_use]
391    pub fn evidence_time(&self, reference: &EvidenceRef) -> Option<SimTime> {
392        self.simulation.evidence_time(reference)
393    }
394
395    /// Resolves the retained record body for one exact domain-record version.
396    ///
397    /// A compacted archive receipt proves existence but does not expose the
398    /// version body through this trusted-host query.
399    #[must_use]
400    pub fn domain_record_version(
401        &self,
402        reference: &DomainRecordVersionRef,
403    ) -> Option<DomainRecord> {
404        self.simulation.domain_record_version(reference)
405    }
406
407    /// Returns the retained or archive-resolvable exact identity for the
408    /// authoritative current record version. Missing provenance fails closed.
409    pub fn current_domain_record_version(
410        &self,
411        reference: &DomainRecordRef,
412    ) -> Result<Option<DomainRecordVersionRef>, CanwuError> {
413        self.simulation.current_domain_record_version(reference)
414    }
415
416    #[must_use]
417    pub fn typed_domain_record<T: DomainRecordType>(
418        &self,
419        reference: &TypedDomainRecordRef<T>,
420    ) -> Option<&DomainRecord> {
421        self.simulation.typed_domain_record(reference)
422    }
423
424    pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord> {
425        self.simulation.domain_records()
426    }
427
428    /// Returns one trusted-host page bound to an authoritative revision.
429    ///
430    /// Use the returned revision as `expected_revision` on subsequent pages.
431    pub fn domain_record_page(
432        &self,
433        kind: &DomainRecordKind,
434        after: Option<&DomainRecordRef>,
435        limit: usize,
436        expected_revision: Option<u64>,
437    ) -> Result<DomainRecordPage, CanwuError> {
438        self.simulation
439            .domain_record_page(kind, after, limit, expected_revision)
440    }
441
442    #[must_use]
443    pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
444        self.simulation.decision_ticket(id)
445    }
446
447    #[must_use]
448    pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
449        self.simulation.decision_controller(id)
450    }
451
452    #[must_use]
453    pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
454        self.simulation.decision_trace(id)
455    }
456
457    #[must_use]
458    pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
459        self.simulation.decision_attempt(id)
460    }
461
462    #[must_use]
463    pub fn decision_hot_state(&self) -> DecisionHotState {
464        self.simulation.decision_hot_state()
465    }
466
467    #[must_use]
468    pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
469        self.simulation.decision_history_location(key)
470    }
471
472    pub fn decision_history_location_with_provider(
473        &self,
474        key: &DecisionHistoryKey,
475        provider: &dyn DecisionArchiveProvider,
476    ) -> Result<DecisionHistoryLocation, CanwuError> {
477        self.simulation
478            .decision_history_location_with_provider(key, provider)
479    }
480
481    #[must_use]
482    pub fn random_draws(&self) -> &[RandomDrawRecord] {
483        self.simulation.random_draws()
484    }
485
486    #[must_use]
487    pub fn boundary_head_hash(&self) -> Option<&str> {
488        self.simulation.boundary_head_hash()
489    }
490
491    #[must_use]
492    pub const fn schema(&self) -> &SchemaRegistry {
493        self.simulation.schema()
494    }
495
496    pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor> {
497        self.simulation.plugin_descriptors()
498    }
499
500    #[must_use]
501    pub fn replay_journal(&self) -> ReplayJournal {
502        self.simulation.replay_journal()
503    }
504
505    pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
506        self.simulation.outbox_entries()
507    }
508
509    pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
510        self.simulation.evidence_cursor()
511    }
512
513    pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
514        self.simulation.checkpoint()
515    }
516
517    pub fn archive_reachability_manifest(
518        &self,
519        retained_checkpoints: &[SimulationCheckpoint],
520        page_retention: &StatePageRetentionLedger,
521        decision_provider: &dyn DecisionArchiveProvider,
522        plugin_provider: &dyn PluginArchiveObjectProvider,
523    ) -> Result<ArchiveReachabilityManifest, CanwuError> {
524        self.simulation.archive_reachability_manifest(
525            retained_checkpoints,
526            page_retention,
527            decision_provider,
528            plugin_provider,
529        )
530    }
531
532    pub fn journal_segment_since(
533        &self,
534        start: EvidenceCursor,
535    ) -> Result<EvidenceJournalSegment, CanwuError> {
536        self.simulation.journal_segment_since(start)
537    }
538
539    pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError> {
540        self.simulation.checkpoint_journal()
541    }
542
543    pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError> {
544        self.simulation.checkpoint_journal_json()
545    }
546
547    pub fn register_plugin<P: SimulationPlugin + ?Sized>(
548        &mut self,
549        plugin: &P,
550    ) -> Result<(), CanwuError> {
551        self.simulation.register_plugin(plugin)
552    }
553
554    /// Attaches caller-owned package archive storage for authenticated cold
555    /// history resolution during normal admissions, settlement, and queries.
556    pub fn set_plugin_archive_object_provider(
557        &mut self,
558        provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
559    ) {
560        self.simulation.set_plugin_archive_object_provider(provider);
561    }
562
563    /// Loads one opaque package archive object from the attached provider.
564    /// Package integrations authenticate the bytes against their committed
565    /// archive roots before use.
566    pub fn plugin_archive_object(
567        &self,
568        namespace: &str,
569        object_id: &str,
570    ) -> Result<Option<Vec<u8>>, CanwuError> {
571        self.simulation.plugin_archive_object(namespace, object_id)
572    }
573
574    pub fn submit(&mut self, command: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
575        self.simulation.submit(command)
576    }
577
578    pub fn process_command(
579        &mut self,
580        request: CommandRequest,
581    ) -> Result<CommandOutcome, CanwuError> {
582        self.simulation.process_command(request)
583    }
584
585    pub fn enqueue_command(
586        &mut self,
587        due_at: SimTime,
588        priority: i32,
589        request: CommandRequest,
590    ) -> Result<IngressReceipt, CanwuError> {
591        self.simulation.enqueue_command(due_at, priority, request)
592    }
593
594    pub fn enqueue_plugin_ingress(
595        &mut self,
596        request: PluginIngressRequest,
597    ) -> Result<IngressReceipt, CanwuError> {
598        self.simulation.enqueue_plugin_ingress(request)
599    }
600
601    pub fn enqueue_permitted_plugin_ingress(
602        &mut self,
603        request: PluginIngressRequest,
604        permit: &PluginIngressPermit,
605    ) -> Result<IngressReceipt, CanwuError> {
606        self.simulation
607            .enqueue_permitted_plugin_ingress(request, permit)
608    }
609
610    /// Withdraws a still-pending plugin ingress item that the host enqueued
611    /// with [`Self::enqueue_plugin_ingress`], strictly before its due time.
612    ///
613    /// Due, admitted, archived, or already cancelled items fail with
614    /// [`ErrorCode::LateIngress`]; items of internal packet types or items a
615    /// plugin scheduled inside the engine fail with
616    /// [`ErrorCode::InvalidAuthority`]; unknown IDs fail with
617    /// [`ErrorCode::EvidenceUnavailable`]; non-plugin targets and reasons that
618    /// are empty, untrimmed, or longer than
619    /// [`MAX_INGRESS_CANCELLATION_REASON_BYTES`] fail with
620    /// [`ErrorCode::InvalidPayload`]; declared read-only runs fail with
621    /// [`ErrorCode::InteractionReadOnly`]. The returned receipt names the
622    /// terminal [`IngressPayload::PluginCancellation`] journal record. The
623    /// withdrawn item is never admitted, never settles, and nothing is rolled
624    /// back; snapshots, checkpoint journals, and exact replay preserve the
625    /// cancellation.
626    pub fn cancel_plugin_ingress(
627        &mut self,
628        ingress_id: IngressId,
629        reason: impl Into<String>,
630    ) -> Result<IngressReceipt, CanwuError> {
631        self.simulation.cancel_plugin_ingress(ingress_id, reason)
632    }
633
634    /// Withdraws a still-pending item of an internal packet type through the
635    /// owning plugin's opaque registration permit. The permit covers
636    /// host-enqueued items of that exact type and items the same plugin
637    /// scheduled inside the engine; timing rules match
638    /// [`Self::cancel_plugin_ingress`].
639    pub fn cancel_permitted_plugin_ingress(
640        &mut self,
641        ingress_id: IngressId,
642        permit: &PluginIngressPermit,
643        reason: impl Into<String>,
644    ) -> Result<IngressReceipt, CanwuError> {
645        self.simulation
646            .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
647    }
648
649    pub fn prepare_decision(
650        &self,
651        decision_request_id: DecisionRequestId,
652        command_request_id: Option<CommandRequestId>,
653        ticket_id: DecisionTicketId,
654        policy: &dyn DecisionPolicy,
655    ) -> Result<DecisionEvaluation, CanwuError> {
656        self.simulation
657            .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
658    }
659
660    pub fn prepare_decision_at(
661        &self,
662        due_at: SimTime,
663        decision_request_id: DecisionRequestId,
664        command_request_id: Option<CommandRequestId>,
665        ticket_id: DecisionTicketId,
666        policy: &dyn DecisionPolicy,
667    ) -> Result<DecisionEvaluation, CanwuError> {
668        self.simulation.prepare_decision_at(
669            due_at,
670            decision_request_id,
671            command_request_id,
672            ticket_id,
673            policy,
674        )
675    }
676
677    pub fn enqueue_decision(
678        &mut self,
679        due_at: SimTime,
680        priority: i32,
681        request: DecisionIngressRequest,
682    ) -> Result<IngressReceipt, CanwuError> {
683        self.simulation.enqueue_decision(due_at, priority, request)
684    }
685
686    pub fn drive_decision(
687        &mut self,
688        due_at: SimTime,
689        priority: i32,
690        decision_request_id: DecisionRequestId,
691        command_request_id: Option<CommandRequestId>,
692        ticket_id: DecisionTicketId,
693        policy: &dyn DecisionPolicy,
694    ) -> Result<DecisionEvaluation, CanwuError> {
695        self.simulation.drive_decision(
696            due_at,
697            priority,
698            decision_request_id,
699            command_request_id,
700            ticket_id,
701            policy,
702        )
703    }
704
705    pub fn schedule_calendar_boundary(
706        &mut self,
707        due_at: SimTime,
708        cadences: Vec<SystemCadence>,
709    ) -> Result<IngressReceipt, CanwuError> {
710        self.simulation.schedule_calendar_boundary(due_at, cadences)
711    }
712
713    pub fn advance_canonical(
714        &mut self,
715        duration: SimDuration,
716    ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
717        self.simulation.advance_canonical(duration)
718    }
719
720    pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
721        self.simulation.step_canonical()
722    }
723
724    pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
725        self.simulation.advance(duration)
726    }
727
728    pub fn settle_boundary(
729        &mut self,
730        request: BoundaryRequest,
731    ) -> Result<BoundaryReceipt, CanwuError> {
732        self.simulation.settle_boundary(request)
733    }
734
735    pub fn wait(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
736        self.advance(duration)
737    }
738
739    pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError> {
740        self.simulation.step()
741    }
742
743    #[must_use]
744    pub fn snapshot(&self) -> SimulationSnapshot {
745        self.simulation.snapshot()
746    }
747
748    pub fn snapshot_json(&self) -> Result<String, CanwuError> {
749        self.simulation.snapshot_json()
750    }
751
752    pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError> {
753        let simulation = Simulation::from_snapshot_json(json)?;
754        Ok(Self { simulation })
755    }
756
757    pub fn from_snapshot_json_with_plugins(
758        json: &str,
759        plugins: &[&dyn SimulationPlugin],
760    ) -> Result<Self, CanwuError> {
761        Ok(Self {
762            simulation: Simulation::from_snapshot_json_with_plugins(json, plugins)?,
763        })
764    }
765
766    pub fn from_checkpoint_and_journal(
767        checkpoint: SimulationCheckpoint,
768        segments: Vec<EvidenceJournalSegment>,
769    ) -> Result<Self, CanwuError> {
770        Ok(Self {
771            simulation: Simulation::from_checkpoint_and_journal(checkpoint, segments)?,
772        })
773    }
774
775    pub fn from_checkpoint_journal(bundle: CheckpointJournal) -> Result<Self, CanwuError> {
776        Ok(Self {
777            simulation: Simulation::from_checkpoint_journal(bundle)?,
778        })
779    }
780
781    pub fn from_checkpoint_journal_with_plugins(
782        bundle: CheckpointJournal,
783        plugins: &[&dyn SimulationPlugin],
784    ) -> Result<Self, CanwuError> {
785        Ok(Self {
786            simulation: Simulation::from_checkpoint_journal_with_plugins(bundle, plugins)?,
787        })
788    }
789
790    pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError> {
791        Ok(Self {
792            simulation: Simulation::from_checkpoint_journal_json(json)?,
793        })
794    }
795
796    pub fn from_checkpoint_journal_json_with_plugins(
797        json: &str,
798        plugins: &[&dyn SimulationPlugin],
799    ) -> Result<Self, CanwuError> {
800        Ok(Self {
801            simulation: Simulation::from_checkpoint_journal_json_with_plugins(json, plugins)?,
802        })
803    }
804
805    pub fn replay_from_journal(
806        plugins: &[&dyn SimulationPlugin],
807        journal: &ReplayJournal,
808    ) -> Result<Self, CanwuError> {
809        let simulation = Simulation::replay_from_journal(plugins, journal)?;
810        Ok(Self { simulation })
811    }
812
813    /// Replays with package archive storage attached before the first
814    /// recorded boundary, so package-owned cold history participates in
815    /// ordinary admission and settlement throughout replay.
816    pub fn replay_from_journal_with_archive_provider(
817        plugins: &[&dyn SimulationPlugin],
818        journal: &ReplayJournal,
819        archive_provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
820    ) -> Result<Self, CanwuError> {
821        let simulation = Simulation::replay_from_journal_with_archive_provider(
822            plugins,
823            journal,
824            archive_provider,
825        )?;
826        Ok(Self { simulation })
827    }
828
829    pub fn replay_from_journal_json(
830        plugins: &[&dyn SimulationPlugin],
831        json: &str,
832    ) -> Result<Self, CanwuError> {
833        Ok(Self {
834            simulation: Simulation::replay_from_journal_json(plugins, json)?,
835        })
836    }
837
838    #[must_use]
839    pub fn fork(&self) -> Self {
840        Self {
841            simulation: self.simulation.fork(),
842        }
843    }
844
845    /// Trusted host/admin holder query. Player-facing callers must use a
846    /// restricted [`CanwuViewer`].
847    pub fn admin_query_knowledge(
848        &self,
849        holder: KnowledgeHolderRef,
850        query: &KnowledgeQuery,
851    ) -> Result<KnowledgeQueryResult, CanwuError> {
852        self.simulation
853            .knowledge()
854            .query_current(
855                holder,
856                query,
857                self.simulation.boundaries().last().map(|value| value.id),
858            )
859            .map_err(map_knowledge_query_error)
860    }
861
862    /// Creates the restricted viewer dictated entirely by the persisted run
863    /// policy and seat binding.
864    pub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError> {
865        let principal = self.declared_observation_principal()?;
866        Ok(CanwuViewer {
867            canwu: self,
868            context: KnowledgeViewContext { principal },
869        })
870    }
871
872    /// Character-seat and compatibility convenience. It never upgrades an
873    /// institution, public, research, or developer policy to a person.
874    pub fn viewer_for_actor(&self, actor: PersonId) -> Result<CanwuViewer<'_>, CanwuError> {
875        if !self.entity_exists(&EntityRef::Person(actor)) {
876            return Err(CanwuError::new(
877                ErrorCode::ActorNotFound,
878                format!("actor {actor} was not found"),
879            ));
880        }
881        let principal = match self.run_configuration().declared() {
882            Some(configuration)
883                if configuration.observation == ObservationPolicy::ActorBound
884                    && configuration.seat == SeatPolicy::CharacterBound
885                    && configuration
886                        .seat_binding
887                        .as_ref()
888                        .and_then(|binding| binding.actor)
889                        == Some(actor) =>
890            {
891                ObservationPrincipal::Person(actor)
892            }
893            Some(_) => {
894                return Err(CanwuError::new(
895                    ErrorCode::InvalidAuthority,
896                    "the persisted run policy does not authorize a character viewer",
897                ));
898            }
899            None => ObservationPrincipal::Person(actor),
900        };
901        Ok(CanwuViewer {
902            canwu: self,
903            context: KnowledgeViewContext { principal },
904        })
905    }
906
907    pub fn viewer_context(&self, actor: PersonId) -> Result<ViewerContext, CanwuError> {
908        let viewer = self.viewer_for_actor(actor)?;
909        Ok(ViewerContext {
910            principal: viewer.context.principal.clone(),
911            observation: ObservationPolicy::ActorBound,
912            checkpoint_hash: self.checkpoint_hash().to_owned(),
913        })
914    }
915
916    fn declared_observation_principal(&self) -> Result<ObservationPrincipal, CanwuError> {
917        let Some(configuration) = self.run_configuration().declared() else {
918            return Err(CanwuError::new(
919                ErrorCode::InvalidAuthority,
920                "legacy runs require viewer_for_actor with an existing character",
921            ));
922        };
923        match configuration.observation {
924            ObservationPolicy::ActorBound => match configuration.seat {
925                SeatPolicy::CharacterBound => configuration
926                    .seat_binding
927                    .as_ref()
928                    .and_then(|binding| binding.actor)
929                    .map(ObservationPrincipal::Person)
930                    .ok_or_else(|| {
931                        CanwuError::new(
932                            ErrorCode::InvalidAuthority,
933                            "character-bound observation lacks an actor binding",
934                        )
935                    }),
936                SeatPolicy::InstitutionBound => configuration
937                    .seat_binding
938                    .as_ref()
939                    .and_then(|binding| binding.institution.clone())
940                    .map(ObservationPrincipal::Institution)
941                    .ok_or_else(|| {
942                        CanwuError::new(
943                            ErrorCode::InvalidAuthority,
944                            "institution-bound observation lacks an institution binding",
945                        )
946                    }),
947                SeatPolicy::ObserverSeat | SeatPolicy::AdvisorSeat | SeatPolicy::None => {
948                    Err(CanwuError::new(
949                        ErrorCode::InvalidAuthority,
950                        "actor-bound observation requires a character or institution seat",
951                    ))
952                }
953            },
954            ObservationPolicy::PublicObserver => Ok(ObservationPrincipal::Public),
955            ObservationPolicy::ResearchFull => Ok(ObservationPrincipal::Research),
956            ObservationPolicy::DeveloperDiagnostic => Ok(ObservationPrincipal::Developer),
957        }
958    }
959
960    #[must_use]
961    pub fn explain(&self, request: &ExplanationRequest) -> Explanation {
962        match request {
963            ExplanationRequest::Event(event_id) => self.explain_event(*event_id),
964            ExplanationRequest::Failure(error) => Explanation {
965                summary: error.message.clone(),
966                causal_chain: vec![ExplanationStep {
967                    label: format!("Validation failed: {:?}", error.code),
968                    event: None,
969                }],
970            },
971        }
972    }
973
974    fn explain_event(&self, event_id: EventId) -> Explanation {
975        let mut chain = Vec::new();
976        let events = self.events();
977        let mut current = event_by_id(events, event_id);
978        while let Some(event) = current {
979            chain.push(ExplanationStep {
980                label: event.summary.clone(),
981                event: Some(event.id),
982            });
983            current = match &event.cause {
984                Some(CauseRef::Boundary(boundary)) => {
985                    chain.push(ExplanationStep {
986                        label: format!("Committed by boundary {boundary}"),
987                        event: None,
988                    });
989                    None
990                }
991                Some(CauseRef::Event(parent)) => event_by_id(events, *parent),
992                Some(CauseRef::Command(command)) => {
993                    chain.push(ExplanationStep {
994                        label: format!("Accepted command {command}"),
995                        event: None,
996                    });
997                    None
998                }
999                Some(CauseRef::System(system)) => {
1000                    chain.push(ExplanationStep {
1001                        label: format!("Produced by system {system}"),
1002                        event: None,
1003                    });
1004                    None
1005                }
1006                None => None,
1007            };
1008        }
1009        Explanation {
1010            summary: chain.first().map_or_else(
1011                || "Event was not found".to_owned(),
1012                |step| step.label.clone(),
1013            ),
1014            causal_chain: chain,
1015        }
1016    }
1017}
1018
1019fn event_by_id(events: &[SimEvent], event_id: EventId) -> Option<&SimEvent> {
1020    let index = usize::try_from(event_id.get().checked_sub(1)?).ok()?;
1021    events.get(index).filter(|event| event.id == event_id)
1022}
1023
1024impl CompactedCanwu {
1025    pub fn from_checkpoint_and_journal(
1026        checkpoint: SimulationCheckpoint,
1027        segments: Vec<EvidenceJournalSegment>,
1028    ) -> Result<Self, CanwuError> {
1029        Ok(Self {
1030            simulation: CompactedSimulation::from_checkpoint_and_journal(checkpoint, segments)?,
1031        })
1032    }
1033
1034    pub fn from_checkpoint_and_journal_with_plugins(
1035        checkpoint: SimulationCheckpoint,
1036        segments: Vec<EvidenceJournalSegment>,
1037        plugins: &[&dyn SimulationPlugin],
1038    ) -> Result<Self, CanwuError> {
1039        Ok(Self {
1040            simulation: CompactedSimulation::from_checkpoint_and_journal_with_plugins(
1041                checkpoint, segments, plugins,
1042            )?,
1043        })
1044    }
1045
1046    pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
1047        self.simulation.evidence_cursor()
1048    }
1049
1050    pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
1051        self.simulation.checkpoint()
1052    }
1053
1054    pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
1055        self.simulation.outbox_entries()
1056    }
1057
1058    pub fn outbox_entries_for_segment(
1059        &self,
1060        segment: &EvidenceJournalSegment,
1061    ) -> Result<Vec<OutboxEntry>, CanwuError> {
1062        self.simulation.outbox_entries_for_segment(segment)
1063    }
1064
1065    #[must_use]
1066    pub fn archived_evidence_receipt(
1067        &self,
1068        reference: &EvidenceRef,
1069    ) -> Option<&ArchivedEvidenceReceipt> {
1070        self.simulation.archived_evidence_receipt(reference)
1071    }
1072
1073    pub fn load_archived_evidence_segment(
1074        &self,
1075        reference: &EvidenceRef,
1076        provider: &dyn ArchiveProvider,
1077    ) -> Result<EvidenceJournalSegment, CanwuError> {
1078        self.simulation
1079            .load_archived_evidence_segment(reference, provider)
1080    }
1081
1082    pub fn seal_evidence(&mut self) -> Result<Option<EvidenceJournalSegment>, CanwuError> {
1083        self.simulation.seal_evidence()
1084    }
1085
1086    pub fn prepare_evidence_seal(&self) -> Result<Option<PreparedEvidenceSeal>, CanwuError> {
1087        self.simulation.prepare_evidence_seal()
1088    }
1089
1090    pub fn commit_evidence_seal(
1091        &mut self,
1092        token: &EvidenceSealToken,
1093        provider: &dyn ArchiveProvider,
1094    ) -> Result<(), CanwuError> {
1095        self.simulation.commit_evidence_seal(token, provider)
1096    }
1097
1098    pub fn snapshot_with_segments(
1099        &self,
1100        segments: Vec<EvidenceJournalSegment>,
1101    ) -> Result<SimulationSnapshot, CanwuError> {
1102        self.simulation.snapshot_with_segments(segments)
1103    }
1104
1105    pub fn replay_journal_with_segments(
1106        &self,
1107        segments: Vec<EvidenceJournalSegment>,
1108    ) -> Result<ReplayJournal, CanwuError> {
1109        self.simulation.replay_journal_with_segments(segments)
1110    }
1111
1112    #[must_use]
1113    pub const fn time(&self) -> SimTime {
1114        self.simulation.time()
1115    }
1116
1117    #[must_use]
1118    pub const fn revision(&self) -> u64 {
1119        self.simulation.revision()
1120    }
1121
1122    #[must_use]
1123    pub fn checkpoint_hash(&self) -> &str {
1124        self.simulation.checkpoint_hash()
1125    }
1126
1127    #[must_use]
1128    pub fn boundary_head_hash(&self) -> Option<&str> {
1129        self.simulation.boundary_head_hash()
1130    }
1131
1132    pub fn entities(&self) -> impl Iterator<Item = &EntityRef> {
1133        self.simulation.entities()
1134    }
1135
1136    #[must_use]
1137    pub fn entity_exists(&self, entity: &EntityRef) -> bool {
1138        self.simulation.entity_exists(entity)
1139    }
1140
1141    /// Deprecated detached format-5 compatibility projection.
1142    #[must_use]
1143    pub fn world(&self) -> WorldSnapshot {
1144        self.simulation.world()
1145    }
1146
1147    /// Trusted host access to a person's committed life and custody state.
1148    /// `None` means no change has been committed: the person is alive and free.
1149    #[must_use]
1150    pub fn person_availability(&self, person: PersonId) -> Option<&PersonAvailability> {
1151        self.simulation.person_availability(person)
1152    }
1153
1154    /// Trusted host access to every committed person availability, in
1155    /// person-ID order. Persons without an entry are alive and free.
1156    pub fn person_availabilities(&self) -> impl Iterator<Item = (&PersonId, &PersonAvailability)> {
1157        self.simulation.person_availabilities()
1158    }
1159
1160    /// Trusted host access to the registered transition manifests whose
1161    /// ready boundary has not settled, in manifest-ID order. Settled
1162    /// manifests leave their audit on the boundary record and receipt.
1163    pub fn pending_transition_manifests(&self) -> impl Iterator<Item = &PendingTransitionManifest> {
1164        self.simulation.pending_transition_manifests()
1165    }
1166
1167    #[must_use]
1168    pub fn knowledge(&self) -> &KnowledgeSnapshot {
1169        self.simulation.knowledge()
1170    }
1171
1172    #[must_use]
1173    pub fn domain_record(&self, reference: &DomainRecordRef) -> Option<&DomainRecord> {
1174        self.simulation.domain_record(reference)
1175    }
1176
1177    #[must_use]
1178    pub fn typed_domain_record<T: DomainRecordType>(
1179        &self,
1180        reference: &TypedDomainRecordRef<T>,
1181    ) -> Option<&DomainRecord> {
1182        self.simulation.typed_domain_record(reference)
1183    }
1184
1185    #[must_use]
1186    pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
1187        self.simulation.decision_ticket(id)
1188    }
1189
1190    #[must_use]
1191    pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
1192        self.simulation.decision_controller(id)
1193    }
1194
1195    #[must_use]
1196    pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
1197        self.simulation.decision_trace(id)
1198    }
1199
1200    #[must_use]
1201    pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
1202        self.simulation.decision_attempt(id)
1203    }
1204
1205    #[must_use]
1206    pub fn decision_hot_state(&self) -> DecisionHotState {
1207        self.simulation.decision_hot_state()
1208    }
1209
1210    #[must_use]
1211    pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
1212        self.simulation.decision_history_location(key)
1213    }
1214
1215    pub fn decision_history_location_with_provider(
1216        &self,
1217        key: &DecisionHistoryKey,
1218        provider: &dyn DecisionArchiveProvider,
1219    ) -> Result<DecisionHistoryLocation, CanwuError> {
1220        self.simulation
1221            .decision_history_location_with_provider(key, provider)
1222    }
1223
1224    pub fn submit(&mut self, envelope: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
1225        self.simulation.submit(envelope)
1226    }
1227
1228    pub fn process_command(
1229        &mut self,
1230        request: CommandRequest,
1231    ) -> Result<CommandOutcome, CanwuError> {
1232        self.simulation.process_command(request)
1233    }
1234
1235    pub fn enqueue_command(
1236        &mut self,
1237        due_at: SimTime,
1238        priority: i32,
1239        request: CommandRequest,
1240    ) -> Result<IngressReceipt, CanwuError> {
1241        self.simulation.enqueue_command(due_at, priority, request)
1242    }
1243
1244    pub fn enqueue_plugin_ingress(
1245        &mut self,
1246        request: PluginIngressRequest,
1247    ) -> Result<IngressReceipt, CanwuError> {
1248        self.simulation.enqueue_plugin_ingress(request)
1249    }
1250
1251    pub fn enqueue_permitted_plugin_ingress(
1252        &mut self,
1253        request: PluginIngressRequest,
1254        permit: &PluginIngressPermit,
1255    ) -> Result<IngressReceipt, CanwuError> {
1256        self.simulation
1257            .enqueue_permitted_plugin_ingress(request, permit)
1258    }
1259
1260    /// Withdraws a still-pending plugin ingress item that the host enqueued
1261    /// with [`Self::enqueue_plugin_ingress`], strictly before its due time.
1262    ///
1263    /// Due, admitted, archived, or already cancelled items fail with
1264    /// [`ErrorCode::LateIngress`]; items of internal packet types or items a
1265    /// plugin scheduled inside the engine fail with
1266    /// [`ErrorCode::InvalidAuthority`]; unknown IDs fail with
1267    /// [`ErrorCode::EvidenceUnavailable`]; non-plugin targets and reasons that
1268    /// are empty, untrimmed, or longer than
1269    /// [`MAX_INGRESS_CANCELLATION_REASON_BYTES`] fail with
1270    /// [`ErrorCode::InvalidPayload`]; declared read-only runs fail with
1271    /// [`ErrorCode::InteractionReadOnly`]. The returned receipt names the
1272    /// terminal [`IngressPayload::PluginCancellation`] journal record. The
1273    /// withdrawn item is never admitted, never settles, and nothing is rolled
1274    /// back; snapshots, checkpoint journals, and exact replay preserve the
1275    /// cancellation.
1276    pub fn cancel_plugin_ingress(
1277        &mut self,
1278        ingress_id: IngressId,
1279        reason: impl Into<String>,
1280    ) -> Result<IngressReceipt, CanwuError> {
1281        self.simulation.cancel_plugin_ingress(ingress_id, reason)
1282    }
1283
1284    /// Withdraws a still-pending item of an internal packet type through the
1285    /// owning plugin's opaque registration permit. The permit covers
1286    /// host-enqueued items of that exact type and items the same plugin
1287    /// scheduled inside the engine; timing rules match
1288    /// [`Self::cancel_plugin_ingress`].
1289    pub fn cancel_permitted_plugin_ingress(
1290        &mut self,
1291        ingress_id: IngressId,
1292        permit: &PluginIngressPermit,
1293        reason: impl Into<String>,
1294    ) -> Result<IngressReceipt, CanwuError> {
1295        self.simulation
1296            .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
1297    }
1298
1299    pub fn prepare_decision(
1300        &self,
1301        decision_request_id: DecisionRequestId,
1302        command_request_id: Option<CommandRequestId>,
1303        ticket_id: DecisionTicketId,
1304        policy: &dyn DecisionPolicy,
1305    ) -> Result<DecisionEvaluation, CanwuError> {
1306        self.simulation
1307            .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
1308    }
1309
1310    pub fn prepare_decision_at(
1311        &self,
1312        due_at: SimTime,
1313        decision_request_id: DecisionRequestId,
1314        command_request_id: Option<CommandRequestId>,
1315        ticket_id: DecisionTicketId,
1316        policy: &dyn DecisionPolicy,
1317    ) -> Result<DecisionEvaluation, CanwuError> {
1318        self.simulation.prepare_decision_at(
1319            due_at,
1320            decision_request_id,
1321            command_request_id,
1322            ticket_id,
1323            policy,
1324        )
1325    }
1326
1327    pub fn enqueue_decision(
1328        &mut self,
1329        due_at: SimTime,
1330        priority: i32,
1331        request: DecisionIngressRequest,
1332    ) -> Result<IngressReceipt, CanwuError> {
1333        self.simulation.enqueue_decision(due_at, priority, request)
1334    }
1335
1336    pub fn drive_decision(
1337        &mut self,
1338        due_at: SimTime,
1339        priority: i32,
1340        decision_request_id: DecisionRequestId,
1341        command_request_id: Option<CommandRequestId>,
1342        ticket_id: DecisionTicketId,
1343        policy: &dyn DecisionPolicy,
1344    ) -> Result<DecisionEvaluation, CanwuError> {
1345        self.simulation.drive_decision(
1346            due_at,
1347            priority,
1348            decision_request_id,
1349            command_request_id,
1350            ticket_id,
1351            policy,
1352        )
1353    }
1354
1355    pub fn schedule_calendar_boundary(
1356        &mut self,
1357        due_at: SimTime,
1358        cadences: Vec<SystemCadence>,
1359    ) -> Result<IngressReceipt, CanwuError> {
1360        self.simulation.schedule_calendar_boundary(due_at, cadences)
1361    }
1362
1363    pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
1364        self.simulation.advance(duration)
1365    }
1366
1367    pub fn advance_canonical(
1368        &mut self,
1369        duration: SimDuration,
1370    ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
1371        self.simulation.advance_canonical(duration)
1372    }
1373
1374    pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
1375        self.simulation.step_canonical()
1376    }
1377
1378    pub fn settle_boundary(
1379        &mut self,
1380        request: BoundaryRequest,
1381    ) -> Result<BoundaryReceipt, CanwuError> {
1382        self.simulation.settle_boundary(request)
1383    }
1384}
1385
1386/// An observation identity authorized by the run's persisted observation
1387/// policy. This type is intentionally constructed through
1388/// [`Canwu::viewer_context`] so an observation request cannot self-escalate.
1389#[derive(Clone, Debug, Eq, PartialEq)]
1390pub enum ObservationPrincipal {
1391    Person(PersonId),
1392    Institution(EntityRef),
1393    Public,
1394    Research,
1395    Developer,
1396}
1397
1398impl ObservationPrincipal {
1399    const fn person(&self) -> Option<PersonId> {
1400        match self {
1401            Self::Person(actor) => Some(*actor),
1402            Self::Institution(_) | Self::Public | Self::Research | Self::Developer => None,
1403        }
1404    }
1405}
1406
1407#[derive(Clone, Debug, Eq, PartialEq)]
1408pub struct ViewerContext {
1409    principal: ObservationPrincipal,
1410    observation: ObservationPolicy,
1411    checkpoint_hash: String,
1412}
1413
1414impl ViewerContext {
1415    #[must_use]
1416    pub const fn principal(&self) -> &ObservationPrincipal {
1417        &self.principal
1418    }
1419
1420    #[must_use]
1421    pub const fn actor(&self) -> Option<PersonId> {
1422        self.principal.person()
1423    }
1424
1425    #[must_use]
1426    pub const fn observation(&self) -> ObservationPolicy {
1427        self.observation
1428    }
1429}
1430
1431#[derive(Clone, Debug)]
1432struct KnowledgeViewContext {
1433    principal: ObservationPrincipal,
1434}
1435
1436/// Restricted player/agent/observer API. It deliberately exposes no raw
1437/// snapshot, event, boundary, domain-record, or audit-origin access.
1438pub struct CanwuViewer<'a> {
1439    canwu: &'a Canwu,
1440    context: KnowledgeViewContext,
1441}
1442
1443impl CanwuViewer<'_> {
1444    #[must_use]
1445    pub const fn principal(&self) -> &ObservationPrincipal {
1446        &self.context.principal
1447    }
1448
1449    /// Queries only the holder selected by a bound person or institution
1450    /// principal. Public and diagnostic principals must use their separately
1451    /// named capabilities.
1452    pub fn query_knowledge(
1453        &self,
1454        query: &KnowledgeQuery,
1455    ) -> Result<KnowledgeQueryResult, CanwuError> {
1456        let holder = match &self.context.principal {
1457            ObservationPrincipal::Person(actor) => KnowledgeHolderRef::Person(*actor),
1458            ObservationPrincipal::Institution(entity) => KnowledgeHolderRef::Entity(entity.clone()),
1459            ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1460            ObservationPrincipal::Research | ObservationPrincipal::Developer => {
1461                return Err(CanwuError::new(
1462                    ErrorCode::InvalidKnowledgeAuthority,
1463                    "diagnostic viewers must select a holder explicitly",
1464                ));
1465            }
1466        };
1467        self.canwu.admin_query_knowledge(holder, query)
1468    }
1469
1470    /// Selects an existing holder under an explicit research/developer policy.
1471    /// Returned records remain the origin-free holder projection.
1472    pub fn query_holder_knowledge(
1473        &self,
1474        holder: KnowledgeHolderRef,
1475        query: &KnowledgeQuery,
1476    ) -> Result<KnowledgeQueryResult, CanwuError> {
1477        match self.context.principal {
1478            ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1479            ObservationPrincipal::Person(_)
1480            | ObservationPrincipal::Institution(_)
1481            | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1482        }
1483        if !knowledge_holder_exists(self.canwu, &holder) {
1484            return Err(CanwuError::new(
1485                ErrorCode::InvalidKnowledgeHolder,
1486                "the requested knowledge holder does not exist",
1487            ));
1488        }
1489        self.canwu.admin_query_knowledge(holder, query)
1490    }
1491
1492    /// Returns one audit-bearing stored record only for research/developer
1493    /// principals. Normal holder queries never expose origin evidence.
1494    pub fn audit_knowledge_record(
1495        &self,
1496        holder: &KnowledgeHolderRef,
1497        record: HolderKnowledgeRecordId,
1498    ) -> Result<KnowledgeRecord, CanwuError> {
1499        match self.context.principal {
1500            ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1501            ObservationPrincipal::Person(_)
1502            | ObservationPrincipal::Institution(_)
1503            | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1504        }
1505        if !knowledge_holder_exists(self.canwu, holder) {
1506            return Err(CanwuError::new(
1507                ErrorCode::InvalidKnowledgeHolder,
1508                "the requested knowledge holder does not exist",
1509            ));
1510        }
1511        let index = usize::try_from(record.get().saturating_sub(1)).map_err(|_| {
1512            CanwuError::new(
1513                ErrorCode::KnowledgeRecordNotFound,
1514                "holder-relative knowledge record ID is outside the supported range",
1515            )
1516        })?;
1517        self.canwu
1518            .knowledge()
1519            .for_holder(holder)
1520            .and_then(|records| records.values().nth(index))
1521            .cloned()
1522            .ok_or_else(|| {
1523                CanwuError::new(
1524                    ErrorCode::KnowledgeRecordNotFound,
1525                    "holder-relative knowledge record was not found",
1526                )
1527            })
1528    }
1529
1530    #[must_use]
1531    pub fn visible_changes_since(&self, since: SimTime) -> Vec<VisibleChange> {
1532        let context = ViewerContext {
1533            principal: self.context.principal.clone(),
1534            observation: observation_for_principal(&self.context.principal),
1535            checkpoint_hash: self.canwu.checkpoint_hash().to_owned(),
1536        };
1537        self.canwu
1538            .events()
1539            .iter()
1540            .filter(|event| event.timestamp > since)
1541            .filter_map(|event| {
1542                let audience = self.canwu.simulation.event_audience(event);
1543                visible_change(&context, event, &audience)
1544            })
1545            .collect()
1546    }
1547
1548    /// Returns the holder-facing projection of the retained rule-evaluation
1549    /// traces of `subject` that this principal may see, from boundaries after
1550    /// `after` (every retained boundary when `None`), in boundary and
1551    /// recording order.
1552    ///
1553    /// Visibility follows the holder ledger that [`Self::query_knowledge`]
1554    /// reads. A person or institution principal sees a trace when the subject
1555    /// is its own entity, or when a knowledge publication to its ledger that
1556    /// names the subject (as an entity target or, for a domain entity, as a
1557    /// domain-record target) was visible before the trace was evaluated: in
1558    /// an earlier boundary, or as a same-boundary publication in phase 4 of
1559    /// the same boundary. A holder
1560    /// therefore never gains the breakdowns of a subject evaluated before it
1561    /// learned of it. Research and developer principals see every trace; a
1562    /// public principal cannot read traces, as it cannot read a private
1563    /// ledger.
1564    ///
1565    /// Like holder knowledge views, the projection omits evidence. Term
1566    /// evidence and the producing plugin and system stay on the trusted
1567    /// [`BoundaryRecord::evaluation_traces`] read through [`Canwu::boundaries`].
1568    pub fn evaluation_traces(
1569        &self,
1570        subject: &EntityRef,
1571        after: Option<BoundaryId>,
1572    ) -> Result<Vec<EvaluationTraceView>, CanwuError> {
1573        let (own, holder) = match &self.context.principal {
1574            ObservationPrincipal::Person(actor) => (
1575                EntityRef::Person(*actor),
1576                KnowledgeHolderRef::Person(*actor),
1577            ),
1578            ObservationPrincipal::Institution(entity) => {
1579                (entity.clone(), KnowledgeHolderRef::Entity(entity.clone()))
1580            }
1581            ObservationPrincipal::Research | ObservationPrincipal::Developer => {
1582                return Ok(self.canwu.evaluation_trace_views(subject, after, None));
1583            }
1584            ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1585        };
1586        if &own == subject {
1587            return Ok(self.canwu.evaluation_trace_views(subject, after, None));
1588        }
1589        // Every generic ledger record is committed by exactly one boundary
1590        // knowledge change, so the first change naming the subject is the
1591        // exact cut at which the holder learned of it. A next-boundary
1592        // publication becomes known only once its boundary has settled.
1593        let learned = self.canwu.boundaries().iter().find_map(|boundary| {
1594            boundary
1595                .knowledge_changes
1596                .iter()
1597                .filter(|change| {
1598                    change.holder == holder
1599                        && change
1600                            .records
1601                            .iter()
1602                            .any(|record| knowledge_names_subject(record, subject))
1603                })
1604                .map(|change| match change.visibility {
1605                    StateVisibility::SameBoundary => (boundary.id, change.phase),
1606                    StateVisibility::NextBoundary => {
1607                        (boundary.id, BoundaryPhase::SaveReplayAndDiagnosticHashing)
1608                    }
1609                })
1610                .min()
1611        });
1612        Ok(learned.map_or_else(Vec::new, |learned| {
1613            self.canwu
1614                .evaluation_trace_views(subject, after, Some(learned))
1615        }))
1616    }
1617}
1618
1619impl Canwu {
1620    fn evaluation_trace_views(
1621        &self,
1622        subject: &EntityRef,
1623        after: Option<BoundaryId>,
1624        learned: Option<(BoundaryId, BoundaryPhase)>,
1625    ) -> Vec<EvaluationTraceView> {
1626        let boundaries = self.boundaries();
1627        let start = after.map_or(0, |after| {
1628            boundaries.partition_point(|boundary| boundary.id <= after)
1629        });
1630        boundaries[start..]
1631            .iter()
1632            .flat_map(|boundary| {
1633                boundary
1634                    .evaluation_traces
1635                    .iter()
1636                    .map(move |entry| (boundary.id, entry))
1637            })
1638            .filter(|(boundary, entry)| {
1639                &entry.trace.subject == subject
1640                    && learned.is_none_or(|learned| learned < (*boundary, entry.phase))
1641            })
1642            .map(|(_, entry)| EvaluationTraceView::from(&entry.trace))
1643            .collect()
1644    }
1645}
1646
1647fn knowledge_names_subject(record: &KnowledgeRecord, subject: &EntityRef) -> bool {
1648    record.subjects.iter().any(|named| match &named.target {
1649        KnowledgeSubjectTarget::Entity(entity) => entity == subject,
1650        KnowledgeSubjectTarget::DomainRecord(reference) => {
1651            matches!(subject, EntityRef::Domain(domain) if domain == reference)
1652        }
1653        KnowledgeSubjectTarget::Event(_) => false,
1654    })
1655}
1656
1657/// Holder-facing projection of one [`EvaluationTraceRecord`], returned by
1658/// [`CanwuViewer::evaluation_traces`]. It keeps the rule, subject, boundary,
1659/// result, and each term's contribution, and omits evidence identities.
1660#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1661pub struct EvaluationTraceView {
1662    pub rule_id: String,
1663    pub rule_version: String,
1664    pub subject: EntityRef,
1665    pub terms: Vec<EvaluationTermView>,
1666    pub result: i64,
1667    pub boundary: BoundaryId,
1668}
1669
1670/// One term of an [`EvaluationTraceView`].
1671#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1672pub struct EvaluationTermView {
1673    pub term_id: String,
1674    pub contribution: i64,
1675}
1676
1677impl From<&EvaluationTraceRecord> for EvaluationTraceView {
1678    fn from(record: &EvaluationTraceRecord) -> Self {
1679        Self {
1680            rule_id: record.rule_id.clone(),
1681            rule_version: record.rule_version.clone(),
1682            subject: record.subject.clone(),
1683            terms: record
1684                .terms
1685                .iter()
1686                .map(|term| EvaluationTermView {
1687                    term_id: term.term_id.clone(),
1688                    contribution: term.contribution,
1689                })
1690                .collect(),
1691            result: record.result,
1692            boundary: record.boundary,
1693        }
1694    }
1695}
1696
1697const fn observation_for_principal(principal: &ObservationPrincipal) -> ObservationPolicy {
1698    match principal {
1699        ObservationPrincipal::Person(_) | ObservationPrincipal::Institution(_) => {
1700            ObservationPolicy::ActorBound
1701        }
1702        ObservationPrincipal::Public => ObservationPolicy::PublicObserver,
1703        ObservationPrincipal::Research => ObservationPolicy::ResearchFull,
1704        ObservationPrincipal::Developer => ObservationPolicy::DeveloperDiagnostic,
1705    }
1706}
1707
1708fn invalid_knowledge_authority() -> CanwuError {
1709    CanwuError::new(
1710        ErrorCode::InvalidKnowledgeAuthority,
1711        "this observation principal cannot read a private knowledge ledger",
1712    )
1713}
1714
1715fn knowledge_holder_exists(canwu: &Canwu, holder: &KnowledgeHolderRef) -> bool {
1716    match holder {
1717        KnowledgeHolderRef::Person(actor) => canwu.entity_exists(&EntityRef::Person(*actor)),
1718        KnowledgeHolderRef::Entity(entity) => canwu.entity_exists(entity),
1719    }
1720}
1721
1722fn map_knowledge_query_error(error: KnowledgeQueryError) -> CanwuError {
1723    match error {
1724        KnowledgeQueryError::ReadCutUnavailable => CanwuError::new(
1725            ErrorCode::KnowledgeReadCutUnavailable,
1726            "knowledge cursor read cut is no longer available",
1727        ),
1728        KnowledgeQueryError::InvalidLimit => CanwuError::new(
1729            ErrorCode::KnowledgeLimitExceeded,
1730            "knowledge query page size is outside the supported range",
1731        ),
1732        KnowledgeQueryError::InvalidCursor
1733        | KnowledgeQueryError::InvalidLedger
1734        | KnowledgeQueryError::Encoding => CanwuError::new(
1735            ErrorCode::InvalidKnowledgeRecord,
1736            "knowledge query, cursor, or ledger is invalid",
1737        ),
1738    }
1739}
1740
1741#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1742pub struct VisibleChange {
1743    pub timestamp: SimTime,
1744    pub summary: String,
1745    pub source_event: EventId,
1746}
1747
1748fn visible_change(
1749    viewer: &ViewerContext,
1750    event: &SimEvent,
1751    plugin_audience: &EventAudience,
1752) -> Option<VisibleChange> {
1753    let visible = event_visible_to(viewer, event, plugin_audience);
1754    visible.then(|| VisibleChange {
1755        timestamp: event.timestamp,
1756        summary: event.summary.clone(),
1757        source_event: event.id,
1758    })
1759}
1760
1761fn event_visible_to(viewer: &ViewerContext, event: &SimEvent, audience: &EventAudience) -> bool {
1762    if matches!(
1763        viewer.observation,
1764        ObservationPolicy::ResearchFull | ObservationPolicy::DeveloperDiagnostic
1765    ) {
1766        return true;
1767    }
1768    match audience {
1769        EventAudience::Public => true,
1770        EventAudience::Actor(actor) => viewer.principal.person() == Some(*actor),
1771        EventAudience::Actors(actors) => viewer
1772            .principal
1773            .person()
1774            .is_some_and(|actor| actors.binary_search(&actor).is_ok()),
1775        EventAudience::KnowledgeHolder(holder) => {
1776            principal_matches_holder(&viewer.principal, holder)
1777        }
1778        EventAudience::AffectedActors => viewer
1779            .principal
1780            .person()
1781            .is_some_and(|actor| event.affected_entities.contains(&EntityRef::Person(actor))),
1782        EventAudience::Private => false,
1783    }
1784}
1785
1786fn principal_matches_holder(principal: &ObservationPrincipal, holder: &KnowledgeHolderRef) -> bool {
1787    match (principal, holder) {
1788        (ObservationPrincipal::Person(actor), KnowledgeHolderRef::Person(holder)) => {
1789            actor == holder
1790        }
1791        (ObservationPrincipal::Institution(institution), KnowledgeHolderRef::Entity(holder)) => {
1792            institution == holder
1793        }
1794        (ObservationPrincipal::Research | ObservationPrincipal::Developer, _) => true,
1795        (
1796            ObservationPrincipal::Person(_)
1797            | ObservationPrincipal::Institution(_)
1798            | ObservationPrincipal::Public,
1799            _,
1800        ) => false,
1801    }
1802}
1803
1804#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1805#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1806pub enum ExplanationRequest {
1807    Event(EventId),
1808    Failure(CanwuError),
1809}
1810
1811#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1812pub struct ExplanationStep {
1813    pub label: String,
1814    pub event: Option<EventId>,
1815}
1816
1817#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1818pub struct Explanation {
1819    pub summary: String,
1820    pub causal_chain: Vec<ExplanationStep>,
1821}