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. The current version
399    /// resolves to its live body.
400    #[must_use]
401    pub fn domain_record_version(
402        &self,
403        reference: &DomainRecordVersionRef,
404    ) -> Option<DomainRecord> {
405        self.simulation.domain_record_version(reference)
406    }
407
408    /// Returns the retained or archive-resolvable exact identity for the
409    /// authoritative current record version. Missing provenance fails closed.
410    pub fn current_domain_record_version(
411        &self,
412        reference: &DomainRecordRef,
413    ) -> Result<Option<DomainRecordVersionRef>, CanwuError> {
414        self.simulation.current_domain_record_version(reference)
415    }
416
417    #[must_use]
418    pub fn typed_domain_record<T: DomainRecordType>(
419        &self,
420        reference: &TypedDomainRecordRef<T>,
421    ) -> Option<&DomainRecord> {
422        self.simulation.typed_domain_record(reference)
423    }
424
425    pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord> {
426        self.simulation.domain_records()
427    }
428
429    /// Returns one trusted-host page bound to an authoritative revision.
430    ///
431    /// Use the returned revision as `expected_revision` on subsequent pages.
432    pub fn domain_record_page(
433        &self,
434        kind: &DomainRecordKind,
435        after: Option<&DomainRecordRef>,
436        limit: usize,
437        expected_revision: Option<u64>,
438    ) -> Result<DomainRecordPage, CanwuError> {
439        self.simulation
440            .domain_record_page(kind, after, limit, expected_revision)
441    }
442
443    #[must_use]
444    pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
445        self.simulation.decision_ticket(id)
446    }
447
448    #[must_use]
449    pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
450        self.simulation.decision_controller(id)
451    }
452
453    #[must_use]
454    pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
455        self.simulation.decision_trace(id)
456    }
457
458    #[must_use]
459    pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
460        self.simulation.decision_attempt(id)
461    }
462
463    #[must_use]
464    pub fn decision_hot_state(&self) -> DecisionHotState {
465        self.simulation.decision_hot_state()
466    }
467
468    #[must_use]
469    pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
470        self.simulation.decision_history_location(key)
471    }
472
473    pub fn decision_history_location_with_provider(
474        &self,
475        key: &DecisionHistoryKey,
476        provider: &dyn DecisionArchiveProvider,
477    ) -> Result<DecisionHistoryLocation, CanwuError> {
478        self.simulation
479            .decision_history_location_with_provider(key, provider)
480    }
481
482    #[must_use]
483    pub fn random_draws(&self) -> &[RandomDrawRecord] {
484        self.simulation.random_draws()
485    }
486
487    #[must_use]
488    pub fn boundary_head_hash(&self) -> Option<&str> {
489        self.simulation.boundary_head_hash()
490    }
491
492    #[must_use]
493    pub const fn schema(&self) -> &SchemaRegistry {
494        self.simulation.schema()
495    }
496
497    pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor> {
498        self.simulation.plugin_descriptors()
499    }
500
501    #[must_use]
502    pub fn replay_journal(&self) -> ReplayJournal {
503        self.simulation.replay_journal()
504    }
505
506    pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
507        self.simulation.outbox_entries()
508    }
509
510    pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
511        self.simulation.evidence_cursor()
512    }
513
514    pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
515        self.simulation.checkpoint()
516    }
517
518    pub fn archive_reachability_manifest(
519        &self,
520        retained_checkpoints: &[SimulationCheckpoint],
521        page_retention: &StatePageRetentionLedger,
522        decision_provider: &dyn DecisionArchiveProvider,
523        plugin_provider: &dyn PluginArchiveObjectProvider,
524    ) -> Result<ArchiveReachabilityManifest, CanwuError> {
525        self.simulation.archive_reachability_manifest(
526            retained_checkpoints,
527            page_retention,
528            decision_provider,
529            plugin_provider,
530        )
531    }
532
533    pub fn journal_segment_since(
534        &self,
535        start: EvidenceCursor,
536    ) -> Result<EvidenceJournalSegment, CanwuError> {
537        self.simulation.journal_segment_since(start)
538    }
539
540    pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError> {
541        self.simulation.checkpoint_journal()
542    }
543
544    pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError> {
545        self.simulation.checkpoint_journal_json()
546    }
547
548    pub fn register_plugin<P: SimulationPlugin + ?Sized>(
549        &mut self,
550        plugin: &P,
551    ) -> Result<(), CanwuError> {
552        self.simulation.register_plugin(plugin)
553    }
554
555    /// Attaches caller-owned package archive storage for authenticated cold
556    /// history resolution during normal admissions, settlement, and queries.
557    pub fn set_plugin_archive_object_provider(
558        &mut self,
559        provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
560    ) {
561        self.simulation.set_plugin_archive_object_provider(provider);
562    }
563
564    /// Loads one opaque package archive object from the attached provider.
565    /// Package integrations authenticate the bytes against their committed
566    /// archive roots before use.
567    pub fn plugin_archive_object(
568        &self,
569        namespace: &str,
570        object_id: &str,
571    ) -> Result<Option<Vec<u8>>, CanwuError> {
572        self.simulation.plugin_archive_object(namespace, object_id)
573    }
574
575    pub fn submit(&mut self, command: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
576        self.simulation.submit(command)
577    }
578
579    pub fn process_command(
580        &mut self,
581        request: CommandRequest,
582    ) -> Result<CommandOutcome, CanwuError> {
583        self.simulation.process_command(request)
584    }
585
586    pub fn enqueue_command(
587        &mut self,
588        due_at: SimTime,
589        priority: i32,
590        request: CommandRequest,
591    ) -> Result<IngressReceipt, CanwuError> {
592        self.simulation.enqueue_command(due_at, priority, request)
593    }
594
595    pub fn enqueue_plugin_ingress(
596        &mut self,
597        request: PluginIngressRequest,
598    ) -> Result<IngressReceipt, CanwuError> {
599        self.simulation.enqueue_plugin_ingress(request)
600    }
601
602    pub fn enqueue_permitted_plugin_ingress(
603        &mut self,
604        request: PluginIngressRequest,
605        permit: &PluginIngressPermit,
606    ) -> Result<IngressReceipt, CanwuError> {
607        self.simulation
608            .enqueue_permitted_plugin_ingress(request, permit)
609    }
610
611    /// Withdraws a still-pending plugin ingress item that the host enqueued
612    /// with [`Self::enqueue_plugin_ingress`], strictly before its due time.
613    ///
614    /// Due, admitted, archived, or already cancelled items fail with
615    /// [`ErrorCode::LateIngress`]; items of internal packet types or items a
616    /// plugin scheduled inside the engine fail with
617    /// [`ErrorCode::InvalidAuthority`]; unknown IDs fail with
618    /// [`ErrorCode::EvidenceUnavailable`]; non-plugin targets and reasons that
619    /// are empty, untrimmed, or longer than
620    /// [`MAX_INGRESS_CANCELLATION_REASON_BYTES`] fail with
621    /// [`ErrorCode::InvalidPayload`]; declared read-only runs fail with
622    /// [`ErrorCode::InteractionReadOnly`]. The returned receipt names the
623    /// terminal [`IngressPayload::PluginCancellation`] journal record. The
624    /// withdrawn item is never admitted, never settles, and nothing is rolled
625    /// back; snapshots, checkpoint journals, and exact replay preserve the
626    /// cancellation.
627    pub fn cancel_plugin_ingress(
628        &mut self,
629        ingress_id: IngressId,
630        reason: impl Into<String>,
631    ) -> Result<IngressReceipt, CanwuError> {
632        self.simulation.cancel_plugin_ingress(ingress_id, reason)
633    }
634
635    /// Withdraws a still-pending item of an internal packet type through the
636    /// owning plugin's opaque registration permit. The permit covers
637    /// host-enqueued items of that exact type and items the same plugin
638    /// scheduled inside the engine; timing rules match
639    /// [`Self::cancel_plugin_ingress`].
640    pub fn cancel_permitted_plugin_ingress(
641        &mut self,
642        ingress_id: IngressId,
643        permit: &PluginIngressPermit,
644        reason: impl Into<String>,
645    ) -> Result<IngressReceipt, CanwuError> {
646        self.simulation
647            .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
648    }
649
650    pub fn prepare_decision(
651        &self,
652        decision_request_id: DecisionRequestId,
653        command_request_id: Option<CommandRequestId>,
654        ticket_id: DecisionTicketId,
655        policy: &dyn DecisionPolicy,
656    ) -> Result<DecisionEvaluation, CanwuError> {
657        self.simulation
658            .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
659    }
660
661    pub fn prepare_decision_at(
662        &self,
663        due_at: SimTime,
664        decision_request_id: DecisionRequestId,
665        command_request_id: Option<CommandRequestId>,
666        ticket_id: DecisionTicketId,
667        policy: &dyn DecisionPolicy,
668    ) -> Result<DecisionEvaluation, CanwuError> {
669        self.simulation.prepare_decision_at(
670            due_at,
671            decision_request_id,
672            command_request_id,
673            ticket_id,
674            policy,
675        )
676    }
677
678    pub fn enqueue_decision(
679        &mut self,
680        due_at: SimTime,
681        priority: i32,
682        request: DecisionIngressRequest,
683    ) -> Result<IngressReceipt, CanwuError> {
684        self.simulation.enqueue_decision(due_at, priority, request)
685    }
686
687    pub fn drive_decision(
688        &mut self,
689        due_at: SimTime,
690        priority: i32,
691        decision_request_id: DecisionRequestId,
692        command_request_id: Option<CommandRequestId>,
693        ticket_id: DecisionTicketId,
694        policy: &dyn DecisionPolicy,
695    ) -> Result<DecisionEvaluation, CanwuError> {
696        self.simulation.drive_decision(
697            due_at,
698            priority,
699            decision_request_id,
700            command_request_id,
701            ticket_id,
702            policy,
703        )
704    }
705
706    pub fn schedule_calendar_boundary(
707        &mut self,
708        due_at: SimTime,
709        cadences: Vec<SystemCadence>,
710    ) -> Result<IngressReceipt, CanwuError> {
711        self.simulation.schedule_calendar_boundary(due_at, cadences)
712    }
713
714    pub fn advance_canonical(
715        &mut self,
716        duration: SimDuration,
717    ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
718        self.simulation.advance_canonical(duration)
719    }
720
721    pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
722        self.simulation.step_canonical()
723    }
724
725    pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
726        self.simulation.advance(duration)
727    }
728
729    pub fn settle_boundary(
730        &mut self,
731        request: BoundaryRequest,
732    ) -> Result<BoundaryReceipt, CanwuError> {
733        self.simulation.settle_boundary(request)
734    }
735
736    pub fn wait(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
737        self.advance(duration)
738    }
739
740    pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError> {
741        self.simulation.step()
742    }
743
744    #[must_use]
745    pub fn snapshot(&self) -> SimulationSnapshot {
746        self.simulation.snapshot()
747    }
748
749    pub fn snapshot_json(&self) -> Result<String, CanwuError> {
750        self.simulation.snapshot_json()
751    }
752
753    pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError> {
754        let simulation = Simulation::from_snapshot_json(json)?;
755        Ok(Self { simulation })
756    }
757
758    pub fn from_snapshot_json_with_plugins(
759        json: &str,
760        plugins: &[&dyn SimulationPlugin],
761    ) -> Result<Self, CanwuError> {
762        Ok(Self {
763            simulation: Simulation::from_snapshot_json_with_plugins(json, plugins)?,
764        })
765    }
766
767    pub fn from_checkpoint_and_journal(
768        checkpoint: SimulationCheckpoint,
769        segments: Vec<EvidenceJournalSegment>,
770    ) -> Result<Self, CanwuError> {
771        Ok(Self {
772            simulation: Simulation::from_checkpoint_and_journal(checkpoint, segments)?,
773        })
774    }
775
776    pub fn from_checkpoint_journal(bundle: CheckpointJournal) -> Result<Self, CanwuError> {
777        Ok(Self {
778            simulation: Simulation::from_checkpoint_journal(bundle)?,
779        })
780    }
781
782    pub fn from_checkpoint_journal_with_plugins(
783        bundle: CheckpointJournal,
784        plugins: &[&dyn SimulationPlugin],
785    ) -> Result<Self, CanwuError> {
786        Ok(Self {
787            simulation: Simulation::from_checkpoint_journal_with_plugins(bundle, plugins)?,
788        })
789    }
790
791    pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError> {
792        Ok(Self {
793            simulation: Simulation::from_checkpoint_journal_json(json)?,
794        })
795    }
796
797    pub fn from_checkpoint_journal_json_with_plugins(
798        json: &str,
799        plugins: &[&dyn SimulationPlugin],
800    ) -> Result<Self, CanwuError> {
801        Ok(Self {
802            simulation: Simulation::from_checkpoint_journal_json_with_plugins(json, plugins)?,
803        })
804    }
805
806    pub fn replay_from_journal(
807        plugins: &[&dyn SimulationPlugin],
808        journal: &ReplayJournal,
809    ) -> Result<Self, CanwuError> {
810        let simulation = Simulation::replay_from_journal(plugins, journal)?;
811        Ok(Self { simulation })
812    }
813
814    /// Replays with package archive storage attached before the first
815    /// recorded boundary, so package-owned cold history participates in
816    /// ordinary admission and settlement throughout replay.
817    pub fn replay_from_journal_with_archive_provider(
818        plugins: &[&dyn SimulationPlugin],
819        journal: &ReplayJournal,
820        archive_provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
821    ) -> Result<Self, CanwuError> {
822        let simulation = Simulation::replay_from_journal_with_archive_provider(
823            plugins,
824            journal,
825            archive_provider,
826        )?;
827        Ok(Self { simulation })
828    }
829
830    pub fn replay_from_journal_json(
831        plugins: &[&dyn SimulationPlugin],
832        json: &str,
833    ) -> Result<Self, CanwuError> {
834        Ok(Self {
835            simulation: Simulation::replay_from_journal_json(plugins, json)?,
836        })
837    }
838
839    #[must_use]
840    pub fn fork(&self) -> Self {
841        Self {
842            simulation: self.simulation.fork(),
843        }
844    }
845
846    /// Trusted host/admin holder query. Player-facing callers must use a
847    /// restricted [`CanwuViewer`].
848    pub fn admin_query_knowledge(
849        &self,
850        holder: KnowledgeHolderRef,
851        query: &KnowledgeQuery,
852    ) -> Result<KnowledgeQueryResult, CanwuError> {
853        self.simulation
854            .knowledge()
855            .query_current(
856                holder,
857                query,
858                self.simulation.boundaries().last().map(|value| value.id),
859            )
860            .map_err(map_knowledge_query_error)
861    }
862
863    /// Creates the restricted viewer dictated entirely by the persisted run
864    /// policy and seat binding.
865    pub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError> {
866        let principal = self.declared_observation_principal()?;
867        Ok(CanwuViewer {
868            canwu: self,
869            context: KnowledgeViewContext { principal },
870        })
871    }
872
873    /// Character-seat and compatibility convenience. It never upgrades an
874    /// institution, public, research, or developer policy to a person.
875    pub fn viewer_for_actor(&self, actor: PersonId) -> Result<CanwuViewer<'_>, CanwuError> {
876        if !self.entity_exists(&EntityRef::Person(actor)) {
877            return Err(CanwuError::new(
878                ErrorCode::ActorNotFound,
879                format!("actor {actor} was not found"),
880            ));
881        }
882        let principal = match self.run_configuration().declared() {
883            Some(configuration)
884                if configuration.observation == ObservationPolicy::ActorBound
885                    && configuration.seat == SeatPolicy::CharacterBound
886                    && configuration
887                        .seat_binding
888                        .as_ref()
889                        .and_then(|binding| binding.actor)
890                        == Some(actor) =>
891            {
892                ObservationPrincipal::Person(actor)
893            }
894            Some(_) => {
895                return Err(CanwuError::new(
896                    ErrorCode::InvalidAuthority,
897                    "the persisted run policy does not authorize a character viewer",
898                ));
899            }
900            None => ObservationPrincipal::Person(actor),
901        };
902        Ok(CanwuViewer {
903            canwu: self,
904            context: KnowledgeViewContext { principal },
905        })
906    }
907
908    pub fn viewer_context(&self, actor: PersonId) -> Result<ViewerContext, CanwuError> {
909        let viewer = self.viewer_for_actor(actor)?;
910        Ok(ViewerContext {
911            principal: viewer.context.principal.clone(),
912            observation: ObservationPolicy::ActorBound,
913            checkpoint_hash: self.checkpoint_hash().to_owned(),
914        })
915    }
916
917    fn declared_observation_principal(&self) -> Result<ObservationPrincipal, CanwuError> {
918        let Some(configuration) = self.run_configuration().declared() else {
919            return Err(CanwuError::new(
920                ErrorCode::InvalidAuthority,
921                "legacy runs require viewer_for_actor with an existing character",
922            ));
923        };
924        match configuration.observation {
925            ObservationPolicy::ActorBound => match configuration.seat {
926                SeatPolicy::CharacterBound => configuration
927                    .seat_binding
928                    .as_ref()
929                    .and_then(|binding| binding.actor)
930                    .map(ObservationPrincipal::Person)
931                    .ok_or_else(|| {
932                        CanwuError::new(
933                            ErrorCode::InvalidAuthority,
934                            "character-bound observation lacks an actor binding",
935                        )
936                    }),
937                SeatPolicy::InstitutionBound => configuration
938                    .seat_binding
939                    .as_ref()
940                    .and_then(|binding| binding.institution.clone())
941                    .map(ObservationPrincipal::Institution)
942                    .ok_or_else(|| {
943                        CanwuError::new(
944                            ErrorCode::InvalidAuthority,
945                            "institution-bound observation lacks an institution binding",
946                        )
947                    }),
948                SeatPolicy::ObserverSeat | SeatPolicy::AdvisorSeat | SeatPolicy::None => {
949                    Err(CanwuError::new(
950                        ErrorCode::InvalidAuthority,
951                        "actor-bound observation requires a character or institution seat",
952                    ))
953                }
954            },
955            ObservationPolicy::PublicObserver => Ok(ObservationPrincipal::Public),
956            ObservationPolicy::ResearchFull => Ok(ObservationPrincipal::Research),
957            ObservationPolicy::DeveloperDiagnostic => Ok(ObservationPrincipal::Developer),
958        }
959    }
960
961    #[must_use]
962    pub fn explain(&self, request: &ExplanationRequest) -> Explanation {
963        match request {
964            ExplanationRequest::Event(event_id) => self.explain_event(*event_id),
965            ExplanationRequest::Failure(error) => Explanation {
966                summary: error.message.clone(),
967                causal_chain: vec![ExplanationStep {
968                    label: format!("Validation failed: {:?}", error.code),
969                    event: None,
970                }],
971            },
972        }
973    }
974
975    fn explain_event(&self, event_id: EventId) -> Explanation {
976        let mut chain = Vec::new();
977        let events = self.events();
978        let mut current = event_by_id(events, event_id);
979        while let Some(event) = current {
980            chain.push(ExplanationStep {
981                label: event.summary.clone(),
982                event: Some(event.id),
983            });
984            current = match &event.cause {
985                Some(CauseRef::Boundary(boundary)) => {
986                    chain.push(ExplanationStep {
987                        label: format!("Committed by boundary {boundary}"),
988                        event: None,
989                    });
990                    None
991                }
992                Some(CauseRef::Event(parent)) => event_by_id(events, *parent),
993                Some(CauseRef::Command(command)) => {
994                    chain.push(ExplanationStep {
995                        label: format!("Accepted command {command}"),
996                        event: None,
997                    });
998                    None
999                }
1000                Some(CauseRef::System(system)) => {
1001                    chain.push(ExplanationStep {
1002                        label: format!("Produced by system {system}"),
1003                        event: None,
1004                    });
1005                    None
1006                }
1007                None => None,
1008            };
1009        }
1010        Explanation {
1011            summary: chain.first().map_or_else(
1012                || "Event was not found".to_owned(),
1013                |step| step.label.clone(),
1014            ),
1015            causal_chain: chain,
1016        }
1017    }
1018}
1019
1020fn event_by_id(events: &[SimEvent], event_id: EventId) -> Option<&SimEvent> {
1021    let index = usize::try_from(event_id.get().checked_sub(1)?).ok()?;
1022    events.get(index).filter(|event| event.id == event_id)
1023}
1024
1025impl CompactedCanwu {
1026    pub fn from_checkpoint_and_journal(
1027        checkpoint: SimulationCheckpoint,
1028        segments: Vec<EvidenceJournalSegment>,
1029    ) -> Result<Self, CanwuError> {
1030        Ok(Self {
1031            simulation: CompactedSimulation::from_checkpoint_and_journal(checkpoint, segments)?,
1032        })
1033    }
1034
1035    pub fn from_checkpoint_and_journal_with_plugins(
1036        checkpoint: SimulationCheckpoint,
1037        segments: Vec<EvidenceJournalSegment>,
1038        plugins: &[&dyn SimulationPlugin],
1039    ) -> Result<Self, CanwuError> {
1040        Ok(Self {
1041            simulation: CompactedSimulation::from_checkpoint_and_journal_with_plugins(
1042                checkpoint, segments, plugins,
1043            )?,
1044        })
1045    }
1046
1047    pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
1048        self.simulation.evidence_cursor()
1049    }
1050
1051    pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
1052        self.simulation.checkpoint()
1053    }
1054
1055    pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
1056        self.simulation.outbox_entries()
1057    }
1058
1059    pub fn outbox_entries_for_segment(
1060        &self,
1061        segment: &EvidenceJournalSegment,
1062    ) -> Result<Vec<OutboxEntry>, CanwuError> {
1063        self.simulation.outbox_entries_for_segment(segment)
1064    }
1065
1066    #[must_use]
1067    pub fn archived_evidence_receipt(
1068        &self,
1069        reference: &EvidenceRef,
1070    ) -> Option<&ArchivedEvidenceReceipt> {
1071        self.simulation.archived_evidence_receipt(reference)
1072    }
1073
1074    pub fn load_archived_evidence_segment(
1075        &self,
1076        reference: &EvidenceRef,
1077        provider: &dyn ArchiveProvider,
1078    ) -> Result<EvidenceJournalSegment, CanwuError> {
1079        self.simulation
1080            .load_archived_evidence_segment(reference, provider)
1081    }
1082
1083    pub fn seal_evidence(&mut self) -> Result<Option<EvidenceJournalSegment>, CanwuError> {
1084        self.simulation.seal_evidence()
1085    }
1086
1087    pub fn prepare_evidence_seal(&self) -> Result<Option<PreparedEvidenceSeal>, CanwuError> {
1088        self.simulation.prepare_evidence_seal()
1089    }
1090
1091    pub fn commit_evidence_seal(
1092        &mut self,
1093        token: &EvidenceSealToken,
1094        provider: &dyn ArchiveProvider,
1095    ) -> Result<(), CanwuError> {
1096        self.simulation.commit_evidence_seal(token, provider)
1097    }
1098
1099    pub fn snapshot_with_segments(
1100        &self,
1101        segments: Vec<EvidenceJournalSegment>,
1102    ) -> Result<SimulationSnapshot, CanwuError> {
1103        self.simulation.snapshot_with_segments(segments)
1104    }
1105
1106    pub fn replay_journal_with_segments(
1107        &self,
1108        segments: Vec<EvidenceJournalSegment>,
1109    ) -> Result<ReplayJournal, CanwuError> {
1110        self.simulation.replay_journal_with_segments(segments)
1111    }
1112
1113    #[must_use]
1114    pub const fn time(&self) -> SimTime {
1115        self.simulation.time()
1116    }
1117
1118    #[must_use]
1119    pub const fn revision(&self) -> u64 {
1120        self.simulation.revision()
1121    }
1122
1123    #[must_use]
1124    pub fn checkpoint_hash(&self) -> &str {
1125        self.simulation.checkpoint_hash()
1126    }
1127
1128    #[must_use]
1129    pub fn boundary_head_hash(&self) -> Option<&str> {
1130        self.simulation.boundary_head_hash()
1131    }
1132
1133    pub fn entities(&self) -> impl Iterator<Item = &EntityRef> {
1134        self.simulation.entities()
1135    }
1136
1137    #[must_use]
1138    pub fn entity_exists(&self, entity: &EntityRef) -> bool {
1139        self.simulation.entity_exists(entity)
1140    }
1141
1142    /// Deprecated detached format-5 compatibility projection.
1143    #[must_use]
1144    pub fn world(&self) -> WorldSnapshot {
1145        self.simulation.world()
1146    }
1147
1148    /// Trusted host access to a person's committed life and custody state.
1149    /// `None` means no change has been committed: the person is alive and free.
1150    #[must_use]
1151    pub fn person_availability(&self, person: PersonId) -> Option<&PersonAvailability> {
1152        self.simulation.person_availability(person)
1153    }
1154
1155    /// Trusted host access to every committed person availability, in
1156    /// person-ID order. Persons without an entry are alive and free.
1157    pub fn person_availabilities(&self) -> impl Iterator<Item = (&PersonId, &PersonAvailability)> {
1158        self.simulation.person_availabilities()
1159    }
1160
1161    /// Trusted host access to the registered transition manifests whose
1162    /// ready boundary has not settled, in manifest-ID order. Settled
1163    /// manifests leave their audit on the boundary record and receipt.
1164    pub fn pending_transition_manifests(&self) -> impl Iterator<Item = &PendingTransitionManifest> {
1165        self.simulation.pending_transition_manifests()
1166    }
1167
1168    #[must_use]
1169    pub fn knowledge(&self) -> &KnowledgeSnapshot {
1170        self.simulation.knowledge()
1171    }
1172
1173    #[must_use]
1174    pub fn domain_record(&self, reference: &DomainRecordRef) -> Option<&DomainRecord> {
1175        self.simulation.domain_record(reference)
1176    }
1177
1178    #[must_use]
1179    pub fn typed_domain_record<T: DomainRecordType>(
1180        &self,
1181        reference: &TypedDomainRecordRef<T>,
1182    ) -> Option<&DomainRecord> {
1183        self.simulation.typed_domain_record(reference)
1184    }
1185
1186    #[must_use]
1187    pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
1188        self.simulation.decision_ticket(id)
1189    }
1190
1191    #[must_use]
1192    pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
1193        self.simulation.decision_controller(id)
1194    }
1195
1196    #[must_use]
1197    pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
1198        self.simulation.decision_trace(id)
1199    }
1200
1201    #[must_use]
1202    pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
1203        self.simulation.decision_attempt(id)
1204    }
1205
1206    #[must_use]
1207    pub fn decision_hot_state(&self) -> DecisionHotState {
1208        self.simulation.decision_hot_state()
1209    }
1210
1211    #[must_use]
1212    pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
1213        self.simulation.decision_history_location(key)
1214    }
1215
1216    pub fn decision_history_location_with_provider(
1217        &self,
1218        key: &DecisionHistoryKey,
1219        provider: &dyn DecisionArchiveProvider,
1220    ) -> Result<DecisionHistoryLocation, CanwuError> {
1221        self.simulation
1222            .decision_history_location_with_provider(key, provider)
1223    }
1224
1225    pub fn submit(&mut self, envelope: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
1226        self.simulation.submit(envelope)
1227    }
1228
1229    pub fn process_command(
1230        &mut self,
1231        request: CommandRequest,
1232    ) -> Result<CommandOutcome, CanwuError> {
1233        self.simulation.process_command(request)
1234    }
1235
1236    pub fn enqueue_command(
1237        &mut self,
1238        due_at: SimTime,
1239        priority: i32,
1240        request: CommandRequest,
1241    ) -> Result<IngressReceipt, CanwuError> {
1242        self.simulation.enqueue_command(due_at, priority, request)
1243    }
1244
1245    pub fn enqueue_plugin_ingress(
1246        &mut self,
1247        request: PluginIngressRequest,
1248    ) -> Result<IngressReceipt, CanwuError> {
1249        self.simulation.enqueue_plugin_ingress(request)
1250    }
1251
1252    pub fn enqueue_permitted_plugin_ingress(
1253        &mut self,
1254        request: PluginIngressRequest,
1255        permit: &PluginIngressPermit,
1256    ) -> Result<IngressReceipt, CanwuError> {
1257        self.simulation
1258            .enqueue_permitted_plugin_ingress(request, permit)
1259    }
1260
1261    /// Withdraws a still-pending plugin ingress item that the host enqueued
1262    /// with [`Self::enqueue_plugin_ingress`], strictly before its due time.
1263    ///
1264    /// Due, admitted, archived, or already cancelled items fail with
1265    /// [`ErrorCode::LateIngress`]; items of internal packet types or items a
1266    /// plugin scheduled inside the engine fail with
1267    /// [`ErrorCode::InvalidAuthority`]; unknown IDs fail with
1268    /// [`ErrorCode::EvidenceUnavailable`]; non-plugin targets and reasons that
1269    /// are empty, untrimmed, or longer than
1270    /// [`MAX_INGRESS_CANCELLATION_REASON_BYTES`] fail with
1271    /// [`ErrorCode::InvalidPayload`]; declared read-only runs fail with
1272    /// [`ErrorCode::InteractionReadOnly`]. The returned receipt names the
1273    /// terminal [`IngressPayload::PluginCancellation`] journal record. The
1274    /// withdrawn item is never admitted, never settles, and nothing is rolled
1275    /// back; snapshots, checkpoint journals, and exact replay preserve the
1276    /// cancellation.
1277    pub fn cancel_plugin_ingress(
1278        &mut self,
1279        ingress_id: IngressId,
1280        reason: impl Into<String>,
1281    ) -> Result<IngressReceipt, CanwuError> {
1282        self.simulation.cancel_plugin_ingress(ingress_id, reason)
1283    }
1284
1285    /// Withdraws a still-pending item of an internal packet type through the
1286    /// owning plugin's opaque registration permit. The permit covers
1287    /// host-enqueued items of that exact type and items the same plugin
1288    /// scheduled inside the engine; timing rules match
1289    /// [`Self::cancel_plugin_ingress`].
1290    pub fn cancel_permitted_plugin_ingress(
1291        &mut self,
1292        ingress_id: IngressId,
1293        permit: &PluginIngressPermit,
1294        reason: impl Into<String>,
1295    ) -> Result<IngressReceipt, CanwuError> {
1296        self.simulation
1297            .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
1298    }
1299
1300    pub fn prepare_decision(
1301        &self,
1302        decision_request_id: DecisionRequestId,
1303        command_request_id: Option<CommandRequestId>,
1304        ticket_id: DecisionTicketId,
1305        policy: &dyn DecisionPolicy,
1306    ) -> Result<DecisionEvaluation, CanwuError> {
1307        self.simulation
1308            .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
1309    }
1310
1311    pub fn prepare_decision_at(
1312        &self,
1313        due_at: SimTime,
1314        decision_request_id: DecisionRequestId,
1315        command_request_id: Option<CommandRequestId>,
1316        ticket_id: DecisionTicketId,
1317        policy: &dyn DecisionPolicy,
1318    ) -> Result<DecisionEvaluation, CanwuError> {
1319        self.simulation.prepare_decision_at(
1320            due_at,
1321            decision_request_id,
1322            command_request_id,
1323            ticket_id,
1324            policy,
1325        )
1326    }
1327
1328    pub fn enqueue_decision(
1329        &mut self,
1330        due_at: SimTime,
1331        priority: i32,
1332        request: DecisionIngressRequest,
1333    ) -> Result<IngressReceipt, CanwuError> {
1334        self.simulation.enqueue_decision(due_at, priority, request)
1335    }
1336
1337    pub fn drive_decision(
1338        &mut self,
1339        due_at: SimTime,
1340        priority: i32,
1341        decision_request_id: DecisionRequestId,
1342        command_request_id: Option<CommandRequestId>,
1343        ticket_id: DecisionTicketId,
1344        policy: &dyn DecisionPolicy,
1345    ) -> Result<DecisionEvaluation, CanwuError> {
1346        self.simulation.drive_decision(
1347            due_at,
1348            priority,
1349            decision_request_id,
1350            command_request_id,
1351            ticket_id,
1352            policy,
1353        )
1354    }
1355
1356    pub fn schedule_calendar_boundary(
1357        &mut self,
1358        due_at: SimTime,
1359        cadences: Vec<SystemCadence>,
1360    ) -> Result<IngressReceipt, CanwuError> {
1361        self.simulation.schedule_calendar_boundary(due_at, cadences)
1362    }
1363
1364    pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
1365        self.simulation.advance(duration)
1366    }
1367
1368    pub fn advance_canonical(
1369        &mut self,
1370        duration: SimDuration,
1371    ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
1372        self.simulation.advance_canonical(duration)
1373    }
1374
1375    pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
1376        self.simulation.step_canonical()
1377    }
1378
1379    pub fn settle_boundary(
1380        &mut self,
1381        request: BoundaryRequest,
1382    ) -> Result<BoundaryReceipt, CanwuError> {
1383        self.simulation.settle_boundary(request)
1384    }
1385}
1386
1387/// An observation identity authorized by the run's persisted observation
1388/// policy. This type is intentionally constructed through
1389/// [`Canwu::viewer_context`] so an observation request cannot self-escalate.
1390#[derive(Clone, Debug, Eq, PartialEq)]
1391pub enum ObservationPrincipal {
1392    Person(PersonId),
1393    Institution(EntityRef),
1394    Public,
1395    Research,
1396    Developer,
1397}
1398
1399impl ObservationPrincipal {
1400    const fn person(&self) -> Option<PersonId> {
1401        match self {
1402            Self::Person(actor) => Some(*actor),
1403            Self::Institution(_) | Self::Public | Self::Research | Self::Developer => None,
1404        }
1405    }
1406}
1407
1408#[derive(Clone, Debug, Eq, PartialEq)]
1409pub struct ViewerContext {
1410    principal: ObservationPrincipal,
1411    observation: ObservationPolicy,
1412    checkpoint_hash: String,
1413}
1414
1415impl ViewerContext {
1416    #[must_use]
1417    pub const fn principal(&self) -> &ObservationPrincipal {
1418        &self.principal
1419    }
1420
1421    #[must_use]
1422    pub const fn actor(&self) -> Option<PersonId> {
1423        self.principal.person()
1424    }
1425
1426    #[must_use]
1427    pub const fn observation(&self) -> ObservationPolicy {
1428        self.observation
1429    }
1430}
1431
1432#[derive(Clone, Debug)]
1433struct KnowledgeViewContext {
1434    principal: ObservationPrincipal,
1435}
1436
1437/// Restricted player/agent/observer API. It deliberately exposes no raw
1438/// snapshot, event, boundary, domain-record, or audit-origin access.
1439pub struct CanwuViewer<'a> {
1440    canwu: &'a Canwu,
1441    context: KnowledgeViewContext,
1442}
1443
1444impl CanwuViewer<'_> {
1445    #[must_use]
1446    pub const fn principal(&self) -> &ObservationPrincipal {
1447        &self.context.principal
1448    }
1449
1450    /// Queries only the holder selected by a bound person or institution
1451    /// principal. Public and diagnostic principals must use their separately
1452    /// named capabilities.
1453    pub fn query_knowledge(
1454        &self,
1455        query: &KnowledgeQuery,
1456    ) -> Result<KnowledgeQueryResult, CanwuError> {
1457        let holder = match &self.context.principal {
1458            ObservationPrincipal::Person(actor) => KnowledgeHolderRef::Person(*actor),
1459            ObservationPrincipal::Institution(entity) => KnowledgeHolderRef::Entity(entity.clone()),
1460            ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1461            ObservationPrincipal::Research | ObservationPrincipal::Developer => {
1462                return Err(CanwuError::new(
1463                    ErrorCode::InvalidKnowledgeAuthority,
1464                    "diagnostic viewers must select a holder explicitly",
1465                ));
1466            }
1467        };
1468        self.canwu.admin_query_knowledge(holder, query)
1469    }
1470
1471    /// Selects an existing holder under an explicit research/developer policy.
1472    /// Returned records remain the origin-free holder projection.
1473    pub fn query_holder_knowledge(
1474        &self,
1475        holder: KnowledgeHolderRef,
1476        query: &KnowledgeQuery,
1477    ) -> Result<KnowledgeQueryResult, CanwuError> {
1478        match self.context.principal {
1479            ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1480            ObservationPrincipal::Person(_)
1481            | ObservationPrincipal::Institution(_)
1482            | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1483        }
1484        if !knowledge_holder_exists(self.canwu, &holder) {
1485            return Err(CanwuError::new(
1486                ErrorCode::InvalidKnowledgeHolder,
1487                "the requested knowledge holder does not exist",
1488            ));
1489        }
1490        self.canwu.admin_query_knowledge(holder, query)
1491    }
1492
1493    /// Returns one audit-bearing stored record only for research/developer
1494    /// principals. Normal holder queries never expose origin evidence.
1495    pub fn audit_knowledge_record(
1496        &self,
1497        holder: &KnowledgeHolderRef,
1498        record: HolderKnowledgeRecordId,
1499    ) -> Result<KnowledgeRecord, CanwuError> {
1500        match self.context.principal {
1501            ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1502            ObservationPrincipal::Person(_)
1503            | ObservationPrincipal::Institution(_)
1504            | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1505        }
1506        if !knowledge_holder_exists(self.canwu, holder) {
1507            return Err(CanwuError::new(
1508                ErrorCode::InvalidKnowledgeHolder,
1509                "the requested knowledge holder does not exist",
1510            ));
1511        }
1512        let index = usize::try_from(record.get().saturating_sub(1)).map_err(|_| {
1513            CanwuError::new(
1514                ErrorCode::KnowledgeRecordNotFound,
1515                "holder-relative knowledge record ID is outside the supported range",
1516            )
1517        })?;
1518        self.canwu
1519            .knowledge()
1520            .for_holder(holder)
1521            .and_then(|records| records.values().nth(index))
1522            .cloned()
1523            .ok_or_else(|| {
1524                CanwuError::new(
1525                    ErrorCode::KnowledgeRecordNotFound,
1526                    "holder-relative knowledge record was not found",
1527                )
1528            })
1529    }
1530
1531    #[must_use]
1532    pub fn visible_changes_since(&self, since: SimTime) -> Vec<VisibleChange> {
1533        let context = ViewerContext {
1534            principal: self.context.principal.clone(),
1535            observation: observation_for_principal(&self.context.principal),
1536            checkpoint_hash: self.canwu.checkpoint_hash().to_owned(),
1537        };
1538        self.canwu
1539            .events()
1540            .iter()
1541            .filter(|event| event.timestamp > since)
1542            .filter_map(|event| {
1543                let audience = self.canwu.simulation.event_audience(event);
1544                visible_change(&context, event, &audience)
1545            })
1546            .collect()
1547    }
1548
1549    /// Returns the holder-facing projection of the retained rule-evaluation
1550    /// traces of `subject` that this principal may see, from boundaries after
1551    /// `after` (every retained boundary when `None`), in boundary and
1552    /// recording order.
1553    ///
1554    /// Visibility follows the holder ledger that [`Self::query_knowledge`]
1555    /// reads. A person or institution principal sees a trace when the subject
1556    /// is its own entity, or when a knowledge publication to its ledger that
1557    /// names the subject (as an entity target or, for a domain entity, as a
1558    /// domain-record target) was visible before the trace was evaluated: in
1559    /// an earlier boundary, or as a same-boundary publication in phase 4 of
1560    /// the same boundary. A holder
1561    /// therefore never gains the breakdowns of a subject evaluated before it
1562    /// learned of it. Research and developer principals see every trace; a
1563    /// public principal cannot read traces, as it cannot read a private
1564    /// ledger.
1565    ///
1566    /// Like holder knowledge views, the projection omits evidence. Term
1567    /// evidence and the producing plugin and system stay on the trusted
1568    /// [`BoundaryRecord::evaluation_traces`] read through [`Canwu::boundaries`].
1569    pub fn evaluation_traces(
1570        &self,
1571        subject: &EntityRef,
1572        after: Option<BoundaryId>,
1573    ) -> Result<Vec<EvaluationTraceView>, CanwuError> {
1574        let (own, holder) = match &self.context.principal {
1575            ObservationPrincipal::Person(actor) => (
1576                EntityRef::Person(*actor),
1577                KnowledgeHolderRef::Person(*actor),
1578            ),
1579            ObservationPrincipal::Institution(entity) => {
1580                (entity.clone(), KnowledgeHolderRef::Entity(entity.clone()))
1581            }
1582            ObservationPrincipal::Research | ObservationPrincipal::Developer => {
1583                return Ok(self.canwu.evaluation_trace_views(subject, after, None));
1584            }
1585            ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1586        };
1587        if &own == subject {
1588            return Ok(self.canwu.evaluation_trace_views(subject, after, None));
1589        }
1590        // Every generic ledger record is committed by exactly one boundary
1591        // knowledge change, so the first change naming the subject is the
1592        // exact cut at which the holder learned of it. A next-boundary
1593        // publication becomes known only once its boundary has settled.
1594        let learned = self.canwu.boundaries().iter().find_map(|boundary| {
1595            boundary
1596                .knowledge_changes
1597                .iter()
1598                .filter(|change| {
1599                    change.holder == holder
1600                        && change
1601                            .records
1602                            .iter()
1603                            .any(|record| knowledge_names_subject(record, subject))
1604                })
1605                .map(|change| match change.visibility {
1606                    StateVisibility::SameBoundary => (boundary.id, change.phase),
1607                    StateVisibility::NextBoundary => {
1608                        (boundary.id, BoundaryPhase::SaveReplayAndDiagnosticHashing)
1609                    }
1610                })
1611                .min()
1612        });
1613        Ok(learned.map_or_else(Vec::new, |learned| {
1614            self.canwu
1615                .evaluation_trace_views(subject, after, Some(learned))
1616        }))
1617    }
1618}
1619
1620impl Canwu {
1621    fn evaluation_trace_views(
1622        &self,
1623        subject: &EntityRef,
1624        after: Option<BoundaryId>,
1625        learned: Option<(BoundaryId, BoundaryPhase)>,
1626    ) -> Vec<EvaluationTraceView> {
1627        let boundaries = self.boundaries();
1628        let start = after.map_or(0, |after| {
1629            boundaries.partition_point(|boundary| boundary.id <= after)
1630        });
1631        boundaries[start..]
1632            .iter()
1633            .flat_map(|boundary| {
1634                boundary
1635                    .evaluation_traces
1636                    .iter()
1637                    .map(move |entry| (boundary.id, entry))
1638            })
1639            .filter(|(boundary, entry)| {
1640                &entry.trace.subject == subject
1641                    && learned.is_none_or(|learned| learned < (*boundary, entry.phase))
1642            })
1643            .map(|(_, entry)| EvaluationTraceView::from(&entry.trace))
1644            .collect()
1645    }
1646}
1647
1648fn knowledge_names_subject(record: &KnowledgeRecord, subject: &EntityRef) -> bool {
1649    record.subjects.iter().any(|named| match &named.target {
1650        KnowledgeSubjectTarget::Entity(entity) => entity == subject,
1651        KnowledgeSubjectTarget::DomainRecord(reference) => {
1652            matches!(subject, EntityRef::Domain(domain) if domain == reference)
1653        }
1654        KnowledgeSubjectTarget::Event(_) => false,
1655    })
1656}
1657
1658/// Holder-facing projection of one [`EvaluationTraceRecord`], returned by
1659/// [`CanwuViewer::evaluation_traces`]. It keeps the rule, subject, boundary,
1660/// result, and each term's contribution, and omits evidence identities.
1661#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1662pub struct EvaluationTraceView {
1663    pub rule_id: String,
1664    pub rule_version: String,
1665    pub subject: EntityRef,
1666    pub terms: Vec<EvaluationTermView>,
1667    pub result: i64,
1668    pub boundary: BoundaryId,
1669}
1670
1671/// One term of an [`EvaluationTraceView`].
1672#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1673pub struct EvaluationTermView {
1674    pub term_id: String,
1675    pub contribution: i64,
1676}
1677
1678impl From<&EvaluationTraceRecord> for EvaluationTraceView {
1679    fn from(record: &EvaluationTraceRecord) -> Self {
1680        Self {
1681            rule_id: record.rule_id.clone(),
1682            rule_version: record.rule_version.clone(),
1683            subject: record.subject.clone(),
1684            terms: record
1685                .terms
1686                .iter()
1687                .map(|term| EvaluationTermView {
1688                    term_id: term.term_id.clone(),
1689                    contribution: term.contribution,
1690                })
1691                .collect(),
1692            result: record.result,
1693            boundary: record.boundary,
1694        }
1695    }
1696}
1697
1698const fn observation_for_principal(principal: &ObservationPrincipal) -> ObservationPolicy {
1699    match principal {
1700        ObservationPrincipal::Person(_) | ObservationPrincipal::Institution(_) => {
1701            ObservationPolicy::ActorBound
1702        }
1703        ObservationPrincipal::Public => ObservationPolicy::PublicObserver,
1704        ObservationPrincipal::Research => ObservationPolicy::ResearchFull,
1705        ObservationPrincipal::Developer => ObservationPolicy::DeveloperDiagnostic,
1706    }
1707}
1708
1709fn invalid_knowledge_authority() -> CanwuError {
1710    CanwuError::new(
1711        ErrorCode::InvalidKnowledgeAuthority,
1712        "this observation principal cannot read a private knowledge ledger",
1713    )
1714}
1715
1716fn knowledge_holder_exists(canwu: &Canwu, holder: &KnowledgeHolderRef) -> bool {
1717    match holder {
1718        KnowledgeHolderRef::Person(actor) => canwu.entity_exists(&EntityRef::Person(*actor)),
1719        KnowledgeHolderRef::Entity(entity) => canwu.entity_exists(entity),
1720    }
1721}
1722
1723fn map_knowledge_query_error(error: KnowledgeQueryError) -> CanwuError {
1724    match error {
1725        KnowledgeQueryError::ReadCutUnavailable => CanwuError::new(
1726            ErrorCode::KnowledgeReadCutUnavailable,
1727            "knowledge cursor read cut is no longer available",
1728        ),
1729        KnowledgeQueryError::InvalidLimit => CanwuError::new(
1730            ErrorCode::KnowledgeLimitExceeded,
1731            "knowledge query page size is outside the supported range",
1732        ),
1733        KnowledgeQueryError::InvalidCursor
1734        | KnowledgeQueryError::InvalidLedger
1735        | KnowledgeQueryError::Encoding => CanwuError::new(
1736            ErrorCode::InvalidKnowledgeRecord,
1737            "knowledge query, cursor, or ledger is invalid",
1738        ),
1739    }
1740}
1741
1742#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1743pub struct VisibleChange {
1744    pub timestamp: SimTime,
1745    pub summary: String,
1746    pub source_event: EventId,
1747}
1748
1749fn visible_change(
1750    viewer: &ViewerContext,
1751    event: &SimEvent,
1752    plugin_audience: &EventAudience,
1753) -> Option<VisibleChange> {
1754    let visible = event_visible_to(viewer, event, plugin_audience);
1755    visible.then(|| VisibleChange {
1756        timestamp: event.timestamp,
1757        summary: event.summary.clone(),
1758        source_event: event.id,
1759    })
1760}
1761
1762fn event_visible_to(viewer: &ViewerContext, event: &SimEvent, audience: &EventAudience) -> bool {
1763    if matches!(
1764        viewer.observation,
1765        ObservationPolicy::ResearchFull | ObservationPolicy::DeveloperDiagnostic
1766    ) {
1767        return true;
1768    }
1769    match audience {
1770        EventAudience::Public => true,
1771        EventAudience::Actor(actor) => viewer.principal.person() == Some(*actor),
1772        EventAudience::Actors(actors) => viewer
1773            .principal
1774            .person()
1775            .is_some_and(|actor| actors.binary_search(&actor).is_ok()),
1776        EventAudience::KnowledgeHolder(holder) => {
1777            principal_matches_holder(&viewer.principal, holder)
1778        }
1779        EventAudience::AffectedActors => viewer
1780            .principal
1781            .person()
1782            .is_some_and(|actor| event.affected_entities.contains(&EntityRef::Person(actor))),
1783        EventAudience::Private => false,
1784    }
1785}
1786
1787fn principal_matches_holder(principal: &ObservationPrincipal, holder: &KnowledgeHolderRef) -> bool {
1788    match (principal, holder) {
1789        (ObservationPrincipal::Person(actor), KnowledgeHolderRef::Person(holder)) => {
1790            actor == holder
1791        }
1792        (ObservationPrincipal::Institution(institution), KnowledgeHolderRef::Entity(holder)) => {
1793            institution == holder
1794        }
1795        (ObservationPrincipal::Research | ObservationPrincipal::Developer, _) => true,
1796        (
1797            ObservationPrincipal::Person(_)
1798            | ObservationPrincipal::Institution(_)
1799            | ObservationPrincipal::Public,
1800            _,
1801        ) => false,
1802    }
1803}
1804
1805#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1806#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1807pub enum ExplanationRequest {
1808    Event(EventId),
1809    Failure(CanwuError),
1810}
1811
1812#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1813pub struct ExplanationStep {
1814    pub label: String,
1815    pub event: Option<EventId>,
1816}
1817
1818#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1819pub struct Explanation {
1820    pub summary: String,
1821    pub causal_chain: Vec<ExplanationStep>,
1822}