1#![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, EventId,
10 EvidenceRef, GovernmentId, HolderKnowledgeRecordId, IngressId, KnowledgeHolderPolicy,
11 KnowledgeHolderRef, KnowledgeRecordId, KnowledgeRecordKind, KnowledgeSchemaId, LetterId,
12 OrganizationId, PersonId, RandomDrawId, ResourceId, RouteId, SchemaRegistry,
13 SchemaRegistryError, SimulationGranularity, TerritoryId, TypeSchema, TypedDomainRecordRef,
14};
15pub use canwu_event::{CauseRef, EventAudience, EventKind, EventKindError, SimEvent};
16pub use canwu_knowledge::{
17 ActorKnowledge, ArmyKnowledge, EstimateRange, KnowledgeCursor, KnowledgeHistoryView,
18 KnowledgeOrigin, KnowledgeQuery, KnowledgeQueryError, KnowledgeQueryResult, KnowledgeReadCut,
19 KnowledgeRecord, KnowledgeRecordDraft, KnowledgeRecordView, KnowledgeSnapshot, KnowledgeSource,
20 KnowledgeSubject, KnowledgeSubjectTarget, MAX_KNOWLEDGE_PAGE_SIZE,
21};
22pub use canwu_routing::{
23 DepartureSlot, DurationSample, PlanningSnapshot, ROUTING_ALGORITHM_VERSION, RouteCost,
24 RouteLeg, RoutePlan, RoutingAlgorithm, RoutingCache, RoutingConnection, RoutingConnectionRef,
25 RoutingEndpoint, RoutingEndpointKind, RoutingError, RoutingNetwork, RoutingNodeRef,
26 RoutingPolicy, RoutingRequest, TransferMode, TraversalModel, plan_route,
27};
28use canwu_sim::Simulation;
29pub use canwu_sim::{
30 ADMISSION_CURSOR_FORMAT_VERSION, ArchiveProvider, ArchiveReachabilityManifest, ArchiveStore,
31 ArchiveStoreOutcome, ArchivedEvidenceLocator, ArchivedEvidenceReceipt,
32 ArchivedPluginIngressProvenance, ArchivedSegmentHeader, Army, ArtifactManifest, BoundaryChange,
33 BoundaryContext, BoundaryDirective, BoundaryEmission, BoundaryEmissionKind,
34 BoundaryIngressGeneration, BoundaryKnowledgeChange, BoundaryPersonAvailabilityChange,
35 BoundaryPersonCreation, BoundaryPhase, BoundaryProposal, BoundaryReceipt, BoundaryRecord,
36 BoundaryRequest, BoundarySystemContract, BoundarySystemHandler,
37 CHECKPOINT_JOURNAL_FORMAT_VERSION, COMMITMENT_FORMAT_VERSION,
38 CONTROLLER_AUTHORITY_UNAVAILABLE_REASON, CanwuError, CheckpointJournal, Command,
39 CommandAttemptOutcome, CommandAttemptRecord, CommandAuthority, CommandContext, CommandEnvelope,
40 CommandIngress, CommandOutcome, CommandPolicyContext, CommandReceipt, CommandRecord,
41 CommandRejection, CommandRequest, CommitmentRoots, CompactedSimulation, ControllerDecision,
42 ControllerPolicy, CreatedPerson, CustodyState, DECISION_ARCHIVE_BUCKET_PAGE_FORMAT_VERSION,
43 DECISION_ARCHIVE_FORMAT_VERSION, DECISION_MAKER_UNAVAILABLE_REASON,
44 DECISION_REQUEST_COMMITMENT_DOMAIN, DecisionAction, DecisionArchiveBlob,
45 DecisionArchiveBucketPage, DecisionArchiveProvider, DecisionArchiveReceipt,
46 DecisionArchiveRecord, DecisionArchiveStore, DecisionArchiveStoreOutcome,
47 DecisionAttemptErrorCode, DecisionAttemptOutcome, DecisionAttemptRecord, DecisionAuthority,
48 DecisionContext, DecisionController, DecisionControllerBinding, DecisionError,
49 DecisionErrorCode, DecisionEvaluation, DecisionExternalEvidence, DecisionFactorContribution,
50 DecisionHistoryCursor, DecisionHistoryKey, DecisionHistoryLocation, DecisionHistoryPage,
51 DecisionHistoryQueryBudget, DecisionHotState, DecisionIngressRequest,
52 DecisionLocatorScaleMetrics, DecisionMutation, DecisionOption, DecisionOptionEvaluation,
53 DecisionOptionWeight, DecisionOrigin, DecisionOutcome, DecisionPolicy, DecisionPolicyIdentity,
54 DecisionPolicyKind, DecisionRandomEvidence, DecisionRule, DecisionStage, DecisionState,
55 DecisionTicket, DecisionTicketDraft, DecisionTicketState, DecisionTrace, DemoIds, DomainRecord,
56 DomainRecordChange, DomainRecordClass, DomainRecordCommitmentRoots, DomainRecordDraft,
57 DomainRecordLifecycle, DomainRecordMutation, DomainRecordMutationPolicy, DomainRecordOperation,
58 DomainRecordPage, DomainRecordPageRoots, DomainRecordSchema, DomainReference,
59 DomainReferenceSchema, DomainReferenceTarget, DomainReferenceTargetKind, ENGINE_VERSION,
60 ErrorCode, EvidenceArchiveIndex, EvidenceCursor, EvidenceIndexEntry, EvidenceItemLocator,
61 EvidenceJournalKind, EvidenceJournalRoots, EvidenceJournalSegment, EvidenceNestedLocator,
62 EvidenceSealToken, ExternalDecisionOption, ExternalDecisionRequest, ExternalDecisionResponse,
63 ExternalPolicy, Government, GuardedUtilityPolicy, HumanDecisionResponse, HumanPolicy,
64 IDENTITY_EVIDENCE_DEPENDENCIES_FIELD, IDENTITY_EVIDENCE_DEPENDENCIES_FORMAT_VERSION,
65 IdentityEvidenceDependenciesV1, IngressCancellationAuthority, IngressClass, IngressPayload,
66 IngressReceipt, IngressRecord, InteractionPolicy, Issuer, KnowledgeLimitsV1,
67 KnowledgeSubjectSchema, KnowledgeSubjectTargetKind, KnowledgeWriteGrant, LetterCargo,
68 LetterStatus, LifeState, LlmModelIdentity, LlmPolicy, MAX_DECISION_ARCHIVE_BATCH_ENTRIES,
69 MAX_DECISION_HISTORY_PAGE_BYTES, MAX_DECISION_HISTORY_PAGE_SIZE,
70 MAX_INGRESS_CANCELLATION_REASON_BYTES, MAX_OWNER_AUTHORIZED_MUTATIONS,
71 MAX_OWNER_AUTHORIZED_PARTICIPANTS, MAX_STATE_DELTA_PAGES, MAX_STATE_PAGE_BYTES,
72 MaintenanceChangeRecord, MaintenanceDependencyResolverDescriptor, MaintenanceDisposition,
73 MaintenanceIngressRequest, MaintenanceRejectionReceipt, MapPoint,
74 OWNER_AUTHORIZED_MAINTENANCE_FORMAT_VERSION, ObservationPolicy, OrderedRulePolicy, OutboxEntry,
75 OwnerAuthorizedMaintenanceDraft, OwnerAuthorizedMaintenanceParticipant,
76 OwnerAuthorizedMaintenanceRequest, OwnerAuthorizedMutation, OwnerAuthorizedParticipantDraft,
77 OwnerAuthorizedParticipantProposal, OwnerAuthorizedParticipantRole,
78 OwnerAuthorizedRecordExpectation, PAGED_CHECKPOINT_FORMAT_VERSION,
79 PAYLOAD_REQUIRED_EVIDENCE_CONTINUATION_FIELD,
80 PAYLOAD_REQUIRED_EVIDENCE_CONTINUATION_FORMAT_VERSION, PLUGIN_DESCRIPTOR_FORMAT_VERSION,
81 PagedSimulationCheckpoint, PatriciaStoreMetrics, PayloadProperty,
82 PayloadRequiredEvidenceContinuationV1, PayloadSchema, PayloadValueType,
83 PersistentDomainRecordStore, Person, PersonAvailability, PersonDraft, PersonTransitState,
84 PluginActionDescriptor, PluginArchiveObjectProvider, PluginArchiveReachabilityParticipant,
85 PluginArchiveRetention, PluginCommandHandler, PluginComponentRecord, PluginDescriptor,
86 PluginIngressDescriptor, PluginIngressPermit, PluginIngressRequest, PluginIngressTarget,
87 PluginKnowledgeSchema, PluginRegistrar, PluginRegistry, PolicyDecision,
88 PortablePagedSimulationCheckpoint, PreparedDecisionArchive, PreparedDecisionIngress,
89 PreparedEvidenceSeal, PreparedPagedSimulationCheckpoint, PreparedStateDelta,
90 QueuedExternalPolicy, QueuedHumanPolicy, QueuedLlmPolicy, RUN_CONFIGURATION_FORMAT_VERSION,
91 RUN_MANIFEST_FORMAT_VERSION, RandomAlgorithm, RandomDecisionResolution, RandomDrawAddress,
92 RandomDrawOutcome, RandomDrawProducer, RandomDrawRecord, RandomOperationAddressV1,
93 RandomOperationTarget, RandomSample, RandomStreamKey, RandomStreamState, ReplayJournal,
94 ReservationAllocation, ReservationDisposition, ReservationOffer, ReservationOfferRecord,
95 ReservationPoolKey, ReservationRef, ReservationRequest, ReservationRequestRecord, Route,
96 RuleChoice, RulePolicy, RunConfiguration, RunConfigurationSnapshot, RunManifest, RunPurpose,
97 SNAPSHOT_FORMAT_VERSION, STATE_PAGE_CODEC, STATE_PAGE_FORMAT_VERSION,
98 STATE_REVISION_FORMAT_VERSION, Scenario, SeatBinding, SeatPolicy, SimulationCheckpoint,
99 SimulationPlugin, SimulationSnapshot, SimulationSystemHandler, SimulationView, StateKey,
100 StatePageBlob, StatePageProvider, StatePageRetentionHandle, StatePageRetentionLedger,
101 StatePageRetentionPhase, StatePageStore, StateVisibility, SystemCadence, SystemContract,
102 SystemDirective, Territory, TracePolicy, TransitState, UtilityEvaluator, UtilityPolicy,
103 UtilityProfile, VerifiedDecisionArchiveCommit, VerifiedOwnerAuthorizedMaintenanceCommit,
104 WeightedUtilityEvaluator, WeightedUtilityPolicy, WorldSnapshot, canonical_byte_hash,
105 canonical_hash, format8_decision_locator_scale_probe, format8_patricia_scale_probe,
106 identity_evidence_dependencies_property_v1, payload_required_evidence_continuation_property_v1,
107 prepare_state_delta, state_page_id, verify_state_delta,
108};
109pub use canwu_time::{SimDuration, SimTime};
110pub use canwu_transport::{
111 CapacityBooking, CapacityBookingId, CapacityBookingStatus, DeliveryCompletionRequest,
112 DeliverySaga, Handoff, HandoffId, HandoffKind, ItineraryRevision, ItineraryRevisionId,
113 ItineraryRevisionReason, LegExecution, LegExecutionId, LegExecutionStatus, MovementInitiative,
114 MovementOrder, MovementOrderError, MovementOrderId, MovementSubject, MovementSubjectRole,
115 ReconciliationOutcome, SagaState, TRANSPORT_SEMANTIC_VERSION, TransportError,
116 TransportExecution, TransportExecutionId, TransportExecutionState,
117 delivery_completion_operation_key,
118};
119use serde::{Deserialize, Serialize};
120
121pub struct Canwu {
123 simulation: Simulation,
124}
125
126pub struct CompactedCanwu {
128 simulation: CompactedSimulation,
129}
130
131impl PluginArchiveObjectProvider for Canwu {
132 fn load_plugin_archive_object(
133 &self,
134 namespace: &str,
135 object_id: &str,
136 ) -> Result<Option<Vec<u8>>, CanwuError> {
137 self.plugin_archive_object(namespace, object_id)
138 }
139}
140
141impl Canwu {
142 #[must_use]
143 pub const fn version() -> &'static str {
144 ENGINE_VERSION
145 }
146
147 pub fn new(seed: u64, scenario: Scenario) -> Result<Self, CanwuError> {
148 Ok(Self {
149 simulation: Simulation::new(seed, scenario)?,
150 })
151 }
152
153 pub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError> {
155 Ok(CompactedCanwu {
156 simulation: self.simulation.into_compacted()?,
157 })
158 }
159
160 pub fn new_with_plugins(
161 seed: u64,
162 scenario: Scenario,
163 plugins: &[&dyn SimulationPlugin],
164 ) -> Result<Self, CanwuError> {
165 Ok(Self {
166 simulation: Simulation::new_with_plugins(seed, scenario, plugins)?,
167 })
168 }
169
170 pub fn new_with_manifest(
171 seed: u64,
172 scenario: Scenario,
173 run_manifest: RunManifest,
174 ) -> Result<Self, CanwuError> {
175 Ok(Self {
176 simulation: Simulation::new_with_manifest(seed, scenario, run_manifest)?,
177 })
178 }
179
180 pub fn new_with_manifest_and_plugins(
181 seed: u64,
182 scenario: Scenario,
183 run_manifest: RunManifest,
184 plugins: &[&dyn SimulationPlugin],
185 ) -> Result<Self, CanwuError> {
186 Ok(Self {
187 simulation: Simulation::new_with_manifest_and_plugins(
188 seed,
189 scenario,
190 run_manifest,
191 plugins,
192 )?,
193 })
194 }
195
196 pub fn new_with_run_configuration(
197 seed: u64,
198 scenario: Scenario,
199 run_manifest: RunManifest,
200 run_configuration: RunConfiguration,
201 ) -> Result<Self, CanwuError> {
202 Ok(Self {
203 simulation: Simulation::new_with_run_configuration(
204 seed,
205 scenario,
206 run_manifest,
207 run_configuration,
208 )?,
209 })
210 }
211
212 pub fn new_with_run_configuration_and_plugins(
213 seed: u64,
214 scenario: Scenario,
215 run_manifest: RunManifest,
216 run_configuration: RunConfiguration,
217 plugins: &[&dyn SimulationPlugin],
218 ) -> Result<Self, CanwuError> {
219 Ok(Self {
220 simulation: Simulation::new_with_run_configuration_and_plugins(
221 seed,
222 scenario,
223 run_manifest,
224 run_configuration,
225 plugins,
226 )?,
227 })
228 }
229
230 pub fn demo(seed: u64) -> Result<Self, CanwuError> {
232 let (simulation, _) = Simulation::demo(seed)?;
233 Ok(Self { simulation })
234 }
235
236 #[must_use]
238 pub fn demo_ids() -> DemoIds {
239 let (_, ids) = canwu_sim::demo_scenario();
240 ids
241 }
242
243 #[must_use]
244 pub const fn time(&self) -> SimTime {
245 self.simulation.time()
246 }
247
248 #[must_use]
249 pub const fn run_manifest(&self) -> &RunManifest {
250 self.simulation.run_manifest()
251 }
252
253 #[must_use]
254 pub const fn run_configuration(&self) -> &RunConfigurationSnapshot {
255 self.simulation.run_configuration()
256 }
257
258 #[must_use]
259 pub fn revision(&self) -> u64 {
266 self.simulation.revision()
267 }
268
269 #[must_use]
270 pub fn run_manifest_hash(&self) -> &str {
271 self.simulation.run_manifest_hash()
272 }
273
274 #[must_use]
275 pub fn checkpoint_hash(&self) -> &str {
276 self.simulation.checkpoint_hash()
277 }
278
279 pub fn authoritative_state_hash(&self) -> Result<String, CanwuError> {
280 self.simulation.authoritative_state_hash()
281 }
282
283 pub fn entities(&self) -> impl Iterator<Item = &EntityRef> {
284 self.simulation.entities()
285 }
286
287 #[must_use]
288 pub fn entity_exists(&self, entity: &EntityRef) -> bool {
289 self.simulation.entity_exists(entity)
290 }
291
292 #[must_use]
294 pub fn world(&self) -> WorldSnapshot {
295 self.simulation.world()
296 }
297
298 #[must_use]
301 pub fn person_availability(&self, person: PersonId) -> Option<&PersonAvailability> {
302 self.simulation.person_availability(person)
303 }
304
305 pub fn person_availabilities(&self) -> impl Iterator<Item = (&PersonId, &PersonAvailability)> {
308 self.simulation.person_availabilities()
309 }
310
311 #[must_use]
316 pub fn knowledge(&self) -> &KnowledgeSnapshot {
317 self.simulation.knowledge()
318 }
319
320 #[must_use]
321 pub fn events(&self) -> &[SimEvent] {
322 self.simulation.events()
323 }
324
325 #[must_use]
326 pub fn commands(&self) -> &[CommandRecord] {
327 self.simulation.command_log()
328 }
329
330 #[must_use]
331 pub fn boundaries(&self) -> &[BoundaryRecord] {
332 self.simulation.boundaries()
333 }
334
335 #[must_use]
336 pub fn command_attempts(&self) -> &[CommandAttemptRecord] {
337 self.simulation.command_attempts()
338 }
339
340 #[must_use]
341 pub fn ingress_log(&self) -> &[IngressRecord] {
342 self.simulation.ingress_log()
343 }
344
345 #[must_use]
346 pub fn domain_record(&self, reference: &DomainRecordRef) -> Option<&DomainRecord> {
347 self.simulation.domain_record(reference)
348 }
349
350 #[must_use]
352 pub fn domain_record_version_evidence_exists(
353 &self,
354 reference: &DomainRecordVersionRef,
355 ) -> bool {
356 self.simulation
357 .domain_record_version_evidence_exists(reference)
358 }
359
360 #[must_use]
362 pub fn evidence_exists(&self, reference: &EvidenceRef) -> bool {
363 self.simulation.evidence_exists(reference)
364 }
365
366 #[must_use]
371 pub fn evidence_time(&self, reference: &EvidenceRef) -> Option<SimTime> {
372 self.simulation.evidence_time(reference)
373 }
374
375 #[must_use]
380 pub fn domain_record_version(
381 &self,
382 reference: &DomainRecordVersionRef,
383 ) -> Option<DomainRecord> {
384 self.simulation.domain_record_version(reference)
385 }
386
387 pub fn current_domain_record_version(
390 &self,
391 reference: &DomainRecordRef,
392 ) -> Result<Option<DomainRecordVersionRef>, CanwuError> {
393 self.simulation.current_domain_record_version(reference)
394 }
395
396 #[must_use]
397 pub fn typed_domain_record<T: DomainRecordType>(
398 &self,
399 reference: &TypedDomainRecordRef<T>,
400 ) -> Option<&DomainRecord> {
401 self.simulation.typed_domain_record(reference)
402 }
403
404 pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord> {
405 self.simulation.domain_records()
406 }
407
408 pub fn domain_record_page(
412 &self,
413 kind: &DomainRecordKind,
414 after: Option<&DomainRecordRef>,
415 limit: usize,
416 expected_revision: Option<u64>,
417 ) -> Result<DomainRecordPage, CanwuError> {
418 self.simulation
419 .domain_record_page(kind, after, limit, expected_revision)
420 }
421
422 #[must_use]
423 pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
424 self.simulation.decision_ticket(id)
425 }
426
427 #[must_use]
428 pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
429 self.simulation.decision_controller(id)
430 }
431
432 #[must_use]
433 pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
434 self.simulation.decision_trace(id)
435 }
436
437 #[must_use]
438 pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
439 self.simulation.decision_attempt(id)
440 }
441
442 #[must_use]
443 pub fn decision_hot_state(&self) -> DecisionHotState {
444 self.simulation.decision_hot_state()
445 }
446
447 #[must_use]
448 pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
449 self.simulation.decision_history_location(key)
450 }
451
452 pub fn decision_history_location_with_provider(
453 &self,
454 key: &DecisionHistoryKey,
455 provider: &dyn DecisionArchiveProvider,
456 ) -> Result<DecisionHistoryLocation, CanwuError> {
457 self.simulation
458 .decision_history_location_with_provider(key, provider)
459 }
460
461 #[must_use]
462 pub fn random_draws(&self) -> &[RandomDrawRecord] {
463 self.simulation.random_draws()
464 }
465
466 #[must_use]
467 pub fn boundary_head_hash(&self) -> Option<&str> {
468 self.simulation.boundary_head_hash()
469 }
470
471 #[must_use]
472 pub const fn schema(&self) -> &SchemaRegistry {
473 self.simulation.schema()
474 }
475
476 pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor> {
477 self.simulation.plugin_descriptors()
478 }
479
480 #[must_use]
481 pub fn replay_journal(&self) -> ReplayJournal {
482 self.simulation.replay_journal()
483 }
484
485 pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
486 self.simulation.outbox_entries()
487 }
488
489 pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
490 self.simulation.evidence_cursor()
491 }
492
493 pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
494 self.simulation.checkpoint()
495 }
496
497 pub fn archive_reachability_manifest(
498 &self,
499 retained_checkpoints: &[SimulationCheckpoint],
500 page_retention: &StatePageRetentionLedger,
501 decision_provider: &dyn DecisionArchiveProvider,
502 plugin_provider: &dyn PluginArchiveObjectProvider,
503 ) -> Result<ArchiveReachabilityManifest, CanwuError> {
504 self.simulation.archive_reachability_manifest(
505 retained_checkpoints,
506 page_retention,
507 decision_provider,
508 plugin_provider,
509 )
510 }
511
512 pub fn journal_segment_since(
513 &self,
514 start: EvidenceCursor,
515 ) -> Result<EvidenceJournalSegment, CanwuError> {
516 self.simulation.journal_segment_since(start)
517 }
518
519 pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError> {
520 self.simulation.checkpoint_journal()
521 }
522
523 pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError> {
524 self.simulation.checkpoint_journal_json()
525 }
526
527 pub fn register_plugin<P: SimulationPlugin + ?Sized>(
528 &mut self,
529 plugin: &P,
530 ) -> Result<(), CanwuError> {
531 self.simulation.register_plugin(plugin)
532 }
533
534 pub fn set_plugin_archive_object_provider(
537 &mut self,
538 provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
539 ) {
540 self.simulation.set_plugin_archive_object_provider(provider);
541 }
542
543 pub fn plugin_archive_object(
547 &self,
548 namespace: &str,
549 object_id: &str,
550 ) -> Result<Option<Vec<u8>>, CanwuError> {
551 self.simulation.plugin_archive_object(namespace, object_id)
552 }
553
554 pub fn submit(&mut self, command: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
555 self.simulation.submit(command)
556 }
557
558 pub fn process_command(
559 &mut self,
560 request: CommandRequest,
561 ) -> Result<CommandOutcome, CanwuError> {
562 self.simulation.process_command(request)
563 }
564
565 pub fn enqueue_command(
566 &mut self,
567 due_at: SimTime,
568 priority: i32,
569 request: CommandRequest,
570 ) -> Result<IngressReceipt, CanwuError> {
571 self.simulation.enqueue_command(due_at, priority, request)
572 }
573
574 pub fn enqueue_plugin_ingress(
575 &mut self,
576 request: PluginIngressRequest,
577 ) -> Result<IngressReceipt, CanwuError> {
578 self.simulation.enqueue_plugin_ingress(request)
579 }
580
581 pub fn enqueue_permitted_plugin_ingress(
582 &mut self,
583 request: PluginIngressRequest,
584 permit: &PluginIngressPermit,
585 ) -> Result<IngressReceipt, CanwuError> {
586 self.simulation
587 .enqueue_permitted_plugin_ingress(request, permit)
588 }
589
590 pub fn cancel_plugin_ingress(
607 &mut self,
608 ingress_id: IngressId,
609 reason: impl Into<String>,
610 ) -> Result<IngressReceipt, CanwuError> {
611 self.simulation.cancel_plugin_ingress(ingress_id, reason)
612 }
613
614 pub fn cancel_permitted_plugin_ingress(
620 &mut self,
621 ingress_id: IngressId,
622 permit: &PluginIngressPermit,
623 reason: impl Into<String>,
624 ) -> Result<IngressReceipt, CanwuError> {
625 self.simulation
626 .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
627 }
628
629 pub fn prepare_decision(
630 &self,
631 decision_request_id: DecisionRequestId,
632 command_request_id: Option<CommandRequestId>,
633 ticket_id: DecisionTicketId,
634 policy: &dyn DecisionPolicy,
635 ) -> Result<DecisionEvaluation, CanwuError> {
636 self.simulation
637 .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
638 }
639
640 pub fn prepare_decision_at(
641 &self,
642 due_at: SimTime,
643 decision_request_id: DecisionRequestId,
644 command_request_id: Option<CommandRequestId>,
645 ticket_id: DecisionTicketId,
646 policy: &dyn DecisionPolicy,
647 ) -> Result<DecisionEvaluation, CanwuError> {
648 self.simulation.prepare_decision_at(
649 due_at,
650 decision_request_id,
651 command_request_id,
652 ticket_id,
653 policy,
654 )
655 }
656
657 pub fn enqueue_decision(
658 &mut self,
659 due_at: SimTime,
660 priority: i32,
661 request: DecisionIngressRequest,
662 ) -> Result<IngressReceipt, CanwuError> {
663 self.simulation.enqueue_decision(due_at, priority, request)
664 }
665
666 pub fn drive_decision(
667 &mut self,
668 due_at: SimTime,
669 priority: i32,
670 decision_request_id: DecisionRequestId,
671 command_request_id: Option<CommandRequestId>,
672 ticket_id: DecisionTicketId,
673 policy: &dyn DecisionPolicy,
674 ) -> Result<DecisionEvaluation, CanwuError> {
675 self.simulation.drive_decision(
676 due_at,
677 priority,
678 decision_request_id,
679 command_request_id,
680 ticket_id,
681 policy,
682 )
683 }
684
685 pub fn schedule_calendar_boundary(
686 &mut self,
687 due_at: SimTime,
688 cadences: Vec<SystemCadence>,
689 ) -> Result<IngressReceipt, CanwuError> {
690 self.simulation.schedule_calendar_boundary(due_at, cadences)
691 }
692
693 pub fn advance_canonical(
694 &mut self,
695 duration: SimDuration,
696 ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
697 self.simulation.advance_canonical(duration)
698 }
699
700 pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
701 self.simulation.step_canonical()
702 }
703
704 pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
705 self.simulation.advance(duration)
706 }
707
708 pub fn settle_boundary(
709 &mut self,
710 request: BoundaryRequest,
711 ) -> Result<BoundaryReceipt, CanwuError> {
712 self.simulation.settle_boundary(request)
713 }
714
715 pub fn wait(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
716 self.advance(duration)
717 }
718
719 pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError> {
720 self.simulation.step()
721 }
722
723 #[must_use]
724 pub fn snapshot(&self) -> SimulationSnapshot {
725 self.simulation.snapshot()
726 }
727
728 pub fn snapshot_json(&self) -> Result<String, CanwuError> {
729 self.simulation.snapshot_json()
730 }
731
732 pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError> {
733 let simulation = Simulation::from_snapshot_json(json)?;
734 Ok(Self { simulation })
735 }
736
737 pub fn from_snapshot_json_with_plugins(
738 json: &str,
739 plugins: &[&dyn SimulationPlugin],
740 ) -> Result<Self, CanwuError> {
741 Ok(Self {
742 simulation: Simulation::from_snapshot_json_with_plugins(json, plugins)?,
743 })
744 }
745
746 pub fn from_checkpoint_and_journal(
747 checkpoint: SimulationCheckpoint,
748 segments: Vec<EvidenceJournalSegment>,
749 ) -> Result<Self, CanwuError> {
750 Ok(Self {
751 simulation: Simulation::from_checkpoint_and_journal(checkpoint, segments)?,
752 })
753 }
754
755 pub fn from_checkpoint_journal(bundle: CheckpointJournal) -> Result<Self, CanwuError> {
756 Ok(Self {
757 simulation: Simulation::from_checkpoint_journal(bundle)?,
758 })
759 }
760
761 pub fn from_checkpoint_journal_with_plugins(
762 bundle: CheckpointJournal,
763 plugins: &[&dyn SimulationPlugin],
764 ) -> Result<Self, CanwuError> {
765 Ok(Self {
766 simulation: Simulation::from_checkpoint_journal_with_plugins(bundle, plugins)?,
767 })
768 }
769
770 pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError> {
771 Ok(Self {
772 simulation: Simulation::from_checkpoint_journal_json(json)?,
773 })
774 }
775
776 pub fn from_checkpoint_journal_json_with_plugins(
777 json: &str,
778 plugins: &[&dyn SimulationPlugin],
779 ) -> Result<Self, CanwuError> {
780 Ok(Self {
781 simulation: Simulation::from_checkpoint_journal_json_with_plugins(json, plugins)?,
782 })
783 }
784
785 pub fn replay_from_journal(
786 plugins: &[&dyn SimulationPlugin],
787 journal: &ReplayJournal,
788 ) -> Result<Self, CanwuError> {
789 let simulation = Simulation::replay_from_journal(plugins, journal)?;
790 Ok(Self { simulation })
791 }
792
793 pub fn replay_from_journal_with_archive_provider(
797 plugins: &[&dyn SimulationPlugin],
798 journal: &ReplayJournal,
799 archive_provider: std::rc::Rc<dyn PluginArchiveObjectProvider>,
800 ) -> Result<Self, CanwuError> {
801 let simulation = Simulation::replay_from_journal_with_archive_provider(
802 plugins,
803 journal,
804 archive_provider,
805 )?;
806 Ok(Self { simulation })
807 }
808
809 pub fn replay_from_journal_json(
810 plugins: &[&dyn SimulationPlugin],
811 json: &str,
812 ) -> Result<Self, CanwuError> {
813 Ok(Self {
814 simulation: Simulation::replay_from_journal_json(plugins, json)?,
815 })
816 }
817
818 #[must_use]
819 pub fn fork(&self) -> Self {
820 Self {
821 simulation: self.simulation.fork(),
822 }
823 }
824
825 pub fn admin_query_knowledge(
828 &self,
829 holder: KnowledgeHolderRef,
830 query: &KnowledgeQuery,
831 ) -> Result<KnowledgeQueryResult, CanwuError> {
832 self.simulation
833 .knowledge()
834 .query_current(
835 holder,
836 query,
837 self.simulation.boundaries().last().map(|value| value.id),
838 )
839 .map_err(map_knowledge_query_error)
840 }
841
842 pub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError> {
845 let principal = self.declared_observation_principal()?;
846 Ok(CanwuViewer {
847 canwu: self,
848 context: KnowledgeViewContext { principal },
849 })
850 }
851
852 pub fn viewer_for_actor(&self, actor: PersonId) -> Result<CanwuViewer<'_>, CanwuError> {
855 if !self.entity_exists(&EntityRef::Person(actor)) {
856 return Err(CanwuError::new(
857 ErrorCode::ActorNotFound,
858 format!("actor {actor} was not found"),
859 ));
860 }
861 let principal = match self.run_configuration().declared() {
862 Some(configuration)
863 if configuration.observation == ObservationPolicy::ActorBound
864 && configuration.seat == SeatPolicy::CharacterBound
865 && configuration
866 .seat_binding
867 .as_ref()
868 .and_then(|binding| binding.actor)
869 == Some(actor) =>
870 {
871 ObservationPrincipal::Person(actor)
872 }
873 Some(_) => {
874 return Err(CanwuError::new(
875 ErrorCode::InvalidAuthority,
876 "the persisted run policy does not authorize a character viewer",
877 ));
878 }
879 None => ObservationPrincipal::Person(actor),
880 };
881 Ok(CanwuViewer {
882 canwu: self,
883 context: KnowledgeViewContext { principal },
884 })
885 }
886
887 pub fn viewer_context(&self, actor: PersonId) -> Result<ViewerContext, CanwuError> {
888 let viewer = self.viewer_for_actor(actor)?;
889 Ok(ViewerContext {
890 principal: viewer.context.principal.clone(),
891 observation: ObservationPolicy::ActorBound,
892 checkpoint_hash: self.checkpoint_hash().to_owned(),
893 })
894 }
895
896 fn declared_observation_principal(&self) -> Result<ObservationPrincipal, CanwuError> {
897 let Some(configuration) = self.run_configuration().declared() else {
898 return Err(CanwuError::new(
899 ErrorCode::InvalidAuthority,
900 "legacy runs require viewer_for_actor with an existing character",
901 ));
902 };
903 match configuration.observation {
904 ObservationPolicy::ActorBound => match configuration.seat {
905 SeatPolicy::CharacterBound => configuration
906 .seat_binding
907 .as_ref()
908 .and_then(|binding| binding.actor)
909 .map(ObservationPrincipal::Person)
910 .ok_or_else(|| {
911 CanwuError::new(
912 ErrorCode::InvalidAuthority,
913 "character-bound observation lacks an actor binding",
914 )
915 }),
916 SeatPolicy::InstitutionBound => configuration
917 .seat_binding
918 .as_ref()
919 .and_then(|binding| binding.institution.clone())
920 .map(ObservationPrincipal::Institution)
921 .ok_or_else(|| {
922 CanwuError::new(
923 ErrorCode::InvalidAuthority,
924 "institution-bound observation lacks an institution binding",
925 )
926 }),
927 SeatPolicy::ObserverSeat | SeatPolicy::AdvisorSeat | SeatPolicy::None => {
928 Err(CanwuError::new(
929 ErrorCode::InvalidAuthority,
930 "actor-bound observation requires a character or institution seat",
931 ))
932 }
933 },
934 ObservationPolicy::PublicObserver => Ok(ObservationPrincipal::Public),
935 ObservationPolicy::ResearchFull => Ok(ObservationPrincipal::Research),
936 ObservationPolicy::DeveloperDiagnostic => Ok(ObservationPrincipal::Developer),
937 }
938 }
939
940 #[must_use]
941 pub fn explain(&self, request: &ExplanationRequest) -> Explanation {
942 match request {
943 ExplanationRequest::Event(event_id) => self.explain_event(*event_id),
944 ExplanationRequest::Failure(error) => Explanation {
945 summary: error.message.clone(),
946 causal_chain: vec![ExplanationStep {
947 label: format!("Validation failed: {:?}", error.code),
948 event: None,
949 }],
950 },
951 }
952 }
953
954 fn explain_event(&self, event_id: EventId) -> Explanation {
955 let mut chain = Vec::new();
956 let events = self.events();
957 let mut current = event_by_id(events, event_id);
958 while let Some(event) = current {
959 chain.push(ExplanationStep {
960 label: event.summary.clone(),
961 event: Some(event.id),
962 });
963 current = match &event.cause {
964 Some(CauseRef::Boundary(boundary)) => {
965 chain.push(ExplanationStep {
966 label: format!("Committed by boundary {boundary}"),
967 event: None,
968 });
969 None
970 }
971 Some(CauseRef::Event(parent)) => event_by_id(events, *parent),
972 Some(CauseRef::Command(command)) => {
973 chain.push(ExplanationStep {
974 label: format!("Accepted command {command}"),
975 event: None,
976 });
977 None
978 }
979 Some(CauseRef::System(system)) => {
980 chain.push(ExplanationStep {
981 label: format!("Produced by system {system}"),
982 event: None,
983 });
984 None
985 }
986 None => None,
987 };
988 }
989 Explanation {
990 summary: chain.first().map_or_else(
991 || "Event was not found".to_owned(),
992 |step| step.label.clone(),
993 ),
994 causal_chain: chain,
995 }
996 }
997}
998
999fn event_by_id(events: &[SimEvent], event_id: EventId) -> Option<&SimEvent> {
1000 let index = usize::try_from(event_id.get().checked_sub(1)?).ok()?;
1001 events.get(index).filter(|event| event.id == event_id)
1002}
1003
1004impl CompactedCanwu {
1005 pub fn from_checkpoint_and_journal(
1006 checkpoint: SimulationCheckpoint,
1007 segments: Vec<EvidenceJournalSegment>,
1008 ) -> Result<Self, CanwuError> {
1009 Ok(Self {
1010 simulation: CompactedSimulation::from_checkpoint_and_journal(checkpoint, segments)?,
1011 })
1012 }
1013
1014 pub fn from_checkpoint_and_journal_with_plugins(
1015 checkpoint: SimulationCheckpoint,
1016 segments: Vec<EvidenceJournalSegment>,
1017 plugins: &[&dyn SimulationPlugin],
1018 ) -> Result<Self, CanwuError> {
1019 Ok(Self {
1020 simulation: CompactedSimulation::from_checkpoint_and_journal_with_plugins(
1021 checkpoint, segments, plugins,
1022 )?,
1023 })
1024 }
1025
1026 pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError> {
1027 self.simulation.evidence_cursor()
1028 }
1029
1030 pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError> {
1031 self.simulation.checkpoint()
1032 }
1033
1034 pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError> {
1035 self.simulation.outbox_entries()
1036 }
1037
1038 pub fn outbox_entries_for_segment(
1039 &self,
1040 segment: &EvidenceJournalSegment,
1041 ) -> Result<Vec<OutboxEntry>, CanwuError> {
1042 self.simulation.outbox_entries_for_segment(segment)
1043 }
1044
1045 #[must_use]
1046 pub fn archived_evidence_receipt(
1047 &self,
1048 reference: &EvidenceRef,
1049 ) -> Option<&ArchivedEvidenceReceipt> {
1050 self.simulation.archived_evidence_receipt(reference)
1051 }
1052
1053 pub fn load_archived_evidence_segment(
1054 &self,
1055 reference: &EvidenceRef,
1056 provider: &dyn ArchiveProvider,
1057 ) -> Result<EvidenceJournalSegment, CanwuError> {
1058 self.simulation
1059 .load_archived_evidence_segment(reference, provider)
1060 }
1061
1062 pub fn seal_evidence(&mut self) -> Result<Option<EvidenceJournalSegment>, CanwuError> {
1063 self.simulation.seal_evidence()
1064 }
1065
1066 pub fn prepare_evidence_seal(&self) -> Result<Option<PreparedEvidenceSeal>, CanwuError> {
1067 self.simulation.prepare_evidence_seal()
1068 }
1069
1070 pub fn commit_evidence_seal(
1071 &mut self,
1072 token: &EvidenceSealToken,
1073 provider: &dyn ArchiveProvider,
1074 ) -> Result<(), CanwuError> {
1075 self.simulation.commit_evidence_seal(token, provider)
1076 }
1077
1078 pub fn snapshot_with_segments(
1079 &self,
1080 segments: Vec<EvidenceJournalSegment>,
1081 ) -> Result<SimulationSnapshot, CanwuError> {
1082 self.simulation.snapshot_with_segments(segments)
1083 }
1084
1085 pub fn replay_journal_with_segments(
1086 &self,
1087 segments: Vec<EvidenceJournalSegment>,
1088 ) -> Result<ReplayJournal, CanwuError> {
1089 self.simulation.replay_journal_with_segments(segments)
1090 }
1091
1092 #[must_use]
1093 pub const fn time(&self) -> SimTime {
1094 self.simulation.time()
1095 }
1096
1097 #[must_use]
1098 pub const fn revision(&self) -> u64 {
1099 self.simulation.revision()
1100 }
1101
1102 #[must_use]
1103 pub fn checkpoint_hash(&self) -> &str {
1104 self.simulation.checkpoint_hash()
1105 }
1106
1107 #[must_use]
1108 pub fn boundary_head_hash(&self) -> Option<&str> {
1109 self.simulation.boundary_head_hash()
1110 }
1111
1112 pub fn entities(&self) -> impl Iterator<Item = &EntityRef> {
1113 self.simulation.entities()
1114 }
1115
1116 #[must_use]
1117 pub fn entity_exists(&self, entity: &EntityRef) -> bool {
1118 self.simulation.entity_exists(entity)
1119 }
1120
1121 #[must_use]
1123 pub fn world(&self) -> WorldSnapshot {
1124 self.simulation.world()
1125 }
1126
1127 #[must_use]
1130 pub fn person_availability(&self, person: PersonId) -> Option<&PersonAvailability> {
1131 self.simulation.person_availability(person)
1132 }
1133
1134 pub fn person_availabilities(&self) -> impl Iterator<Item = (&PersonId, &PersonAvailability)> {
1137 self.simulation.person_availabilities()
1138 }
1139
1140 #[must_use]
1141 pub fn knowledge(&self) -> &KnowledgeSnapshot {
1142 self.simulation.knowledge()
1143 }
1144
1145 #[must_use]
1146 pub fn domain_record(&self, reference: &DomainRecordRef) -> Option<&DomainRecord> {
1147 self.simulation.domain_record(reference)
1148 }
1149
1150 #[must_use]
1151 pub fn typed_domain_record<T: DomainRecordType>(
1152 &self,
1153 reference: &TypedDomainRecordRef<T>,
1154 ) -> Option<&DomainRecord> {
1155 self.simulation.typed_domain_record(reference)
1156 }
1157
1158 #[must_use]
1159 pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket> {
1160 self.simulation.decision_ticket(id)
1161 }
1162
1163 #[must_use]
1164 pub fn decision_controller(&self, id: &str) -> Option<&DecisionControllerBinding> {
1165 self.simulation.decision_controller(id)
1166 }
1167
1168 #[must_use]
1169 pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace> {
1170 self.simulation.decision_trace(id)
1171 }
1172
1173 #[must_use]
1174 pub fn decision_attempt(&self, id: DecisionRequestId) -> Option<&DecisionAttemptRecord> {
1175 self.simulation.decision_attempt(id)
1176 }
1177
1178 #[must_use]
1179 pub fn decision_hot_state(&self) -> DecisionHotState {
1180 self.simulation.decision_hot_state()
1181 }
1182
1183 #[must_use]
1184 pub fn decision_history_location(&self, key: &DecisionHistoryKey) -> DecisionHistoryLocation {
1185 self.simulation.decision_history_location(key)
1186 }
1187
1188 pub fn decision_history_location_with_provider(
1189 &self,
1190 key: &DecisionHistoryKey,
1191 provider: &dyn DecisionArchiveProvider,
1192 ) -> Result<DecisionHistoryLocation, CanwuError> {
1193 self.simulation
1194 .decision_history_location_with_provider(key, provider)
1195 }
1196
1197 pub fn submit(&mut self, envelope: CommandEnvelope) -> Result<CommandReceipt, CanwuError> {
1198 self.simulation.submit(envelope)
1199 }
1200
1201 pub fn process_command(
1202 &mut self,
1203 request: CommandRequest,
1204 ) -> Result<CommandOutcome, CanwuError> {
1205 self.simulation.process_command(request)
1206 }
1207
1208 pub fn enqueue_command(
1209 &mut self,
1210 due_at: SimTime,
1211 priority: i32,
1212 request: CommandRequest,
1213 ) -> Result<IngressReceipt, CanwuError> {
1214 self.simulation.enqueue_command(due_at, priority, request)
1215 }
1216
1217 pub fn enqueue_plugin_ingress(
1218 &mut self,
1219 request: PluginIngressRequest,
1220 ) -> Result<IngressReceipt, CanwuError> {
1221 self.simulation.enqueue_plugin_ingress(request)
1222 }
1223
1224 pub fn enqueue_permitted_plugin_ingress(
1225 &mut self,
1226 request: PluginIngressRequest,
1227 permit: &PluginIngressPermit,
1228 ) -> Result<IngressReceipt, CanwuError> {
1229 self.simulation
1230 .enqueue_permitted_plugin_ingress(request, permit)
1231 }
1232
1233 pub fn cancel_plugin_ingress(
1250 &mut self,
1251 ingress_id: IngressId,
1252 reason: impl Into<String>,
1253 ) -> Result<IngressReceipt, CanwuError> {
1254 self.simulation.cancel_plugin_ingress(ingress_id, reason)
1255 }
1256
1257 pub fn cancel_permitted_plugin_ingress(
1263 &mut self,
1264 ingress_id: IngressId,
1265 permit: &PluginIngressPermit,
1266 reason: impl Into<String>,
1267 ) -> Result<IngressReceipt, CanwuError> {
1268 self.simulation
1269 .cancel_permitted_plugin_ingress(ingress_id, permit, reason)
1270 }
1271
1272 pub fn prepare_decision(
1273 &self,
1274 decision_request_id: DecisionRequestId,
1275 command_request_id: Option<CommandRequestId>,
1276 ticket_id: DecisionTicketId,
1277 policy: &dyn DecisionPolicy,
1278 ) -> Result<DecisionEvaluation, CanwuError> {
1279 self.simulation
1280 .prepare_decision(decision_request_id, command_request_id, ticket_id, policy)
1281 }
1282
1283 pub fn prepare_decision_at(
1284 &self,
1285 due_at: SimTime,
1286 decision_request_id: DecisionRequestId,
1287 command_request_id: Option<CommandRequestId>,
1288 ticket_id: DecisionTicketId,
1289 policy: &dyn DecisionPolicy,
1290 ) -> Result<DecisionEvaluation, CanwuError> {
1291 self.simulation.prepare_decision_at(
1292 due_at,
1293 decision_request_id,
1294 command_request_id,
1295 ticket_id,
1296 policy,
1297 )
1298 }
1299
1300 pub fn enqueue_decision(
1301 &mut self,
1302 due_at: SimTime,
1303 priority: i32,
1304 request: DecisionIngressRequest,
1305 ) -> Result<IngressReceipt, CanwuError> {
1306 self.simulation.enqueue_decision(due_at, priority, request)
1307 }
1308
1309 pub fn drive_decision(
1310 &mut self,
1311 due_at: SimTime,
1312 priority: i32,
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.drive_decision(
1319 due_at,
1320 priority,
1321 decision_request_id,
1322 command_request_id,
1323 ticket_id,
1324 policy,
1325 )
1326 }
1327
1328 pub fn schedule_calendar_boundary(
1329 &mut self,
1330 due_at: SimTime,
1331 cadences: Vec<SystemCadence>,
1332 ) -> Result<IngressReceipt, CanwuError> {
1333 self.simulation.schedule_calendar_boundary(due_at, cadences)
1334 }
1335
1336 pub fn advance(&mut self, duration: SimDuration) -> Result<Vec<SimEvent>, CanwuError> {
1337 self.simulation.advance(duration)
1338 }
1339
1340 pub fn advance_canonical(
1341 &mut self,
1342 duration: SimDuration,
1343 ) -> Result<Vec<BoundaryReceipt>, CanwuError> {
1344 self.simulation.advance_canonical(duration)
1345 }
1346
1347 pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError> {
1348 self.simulation.step_canonical()
1349 }
1350
1351 pub fn settle_boundary(
1352 &mut self,
1353 request: BoundaryRequest,
1354 ) -> Result<BoundaryReceipt, CanwuError> {
1355 self.simulation.settle_boundary(request)
1356 }
1357}
1358
1359#[derive(Clone, Debug, Eq, PartialEq)]
1363pub enum ObservationPrincipal {
1364 Person(PersonId),
1365 Institution(EntityRef),
1366 Public,
1367 Research,
1368 Developer,
1369}
1370
1371impl ObservationPrincipal {
1372 const fn person(&self) -> Option<PersonId> {
1373 match self {
1374 Self::Person(actor) => Some(*actor),
1375 Self::Institution(_) | Self::Public | Self::Research | Self::Developer => None,
1376 }
1377 }
1378}
1379
1380#[derive(Clone, Debug, Eq, PartialEq)]
1381pub struct ViewerContext {
1382 principal: ObservationPrincipal,
1383 observation: ObservationPolicy,
1384 checkpoint_hash: String,
1385}
1386
1387impl ViewerContext {
1388 #[must_use]
1389 pub const fn principal(&self) -> &ObservationPrincipal {
1390 &self.principal
1391 }
1392
1393 #[must_use]
1394 pub const fn actor(&self) -> Option<PersonId> {
1395 self.principal.person()
1396 }
1397
1398 #[must_use]
1399 pub const fn observation(&self) -> ObservationPolicy {
1400 self.observation
1401 }
1402}
1403
1404#[derive(Clone, Debug)]
1405struct KnowledgeViewContext {
1406 principal: ObservationPrincipal,
1407}
1408
1409pub struct CanwuViewer<'a> {
1412 canwu: &'a Canwu,
1413 context: KnowledgeViewContext,
1414}
1415
1416impl CanwuViewer<'_> {
1417 #[must_use]
1418 pub const fn principal(&self) -> &ObservationPrincipal {
1419 &self.context.principal
1420 }
1421
1422 pub fn query_knowledge(
1426 &self,
1427 query: &KnowledgeQuery,
1428 ) -> Result<KnowledgeQueryResult, CanwuError> {
1429 let holder = match &self.context.principal {
1430 ObservationPrincipal::Person(actor) => KnowledgeHolderRef::Person(*actor),
1431 ObservationPrincipal::Institution(entity) => KnowledgeHolderRef::Entity(entity.clone()),
1432 ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1433 ObservationPrincipal::Research | ObservationPrincipal::Developer => {
1434 return Err(CanwuError::new(
1435 ErrorCode::InvalidKnowledgeAuthority,
1436 "diagnostic viewers must select a holder explicitly",
1437 ));
1438 }
1439 };
1440 self.canwu.admin_query_knowledge(holder, query)
1441 }
1442
1443 pub fn query_holder_knowledge(
1446 &self,
1447 holder: KnowledgeHolderRef,
1448 query: &KnowledgeQuery,
1449 ) -> Result<KnowledgeQueryResult, CanwuError> {
1450 match self.context.principal {
1451 ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1452 ObservationPrincipal::Person(_)
1453 | ObservationPrincipal::Institution(_)
1454 | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1455 }
1456 if !knowledge_holder_exists(self.canwu, &holder) {
1457 return Err(CanwuError::new(
1458 ErrorCode::InvalidKnowledgeHolder,
1459 "the requested knowledge holder does not exist",
1460 ));
1461 }
1462 self.canwu.admin_query_knowledge(holder, query)
1463 }
1464
1465 pub fn audit_knowledge_record(
1468 &self,
1469 holder: &KnowledgeHolderRef,
1470 record: HolderKnowledgeRecordId,
1471 ) -> Result<KnowledgeRecord, CanwuError> {
1472 match self.context.principal {
1473 ObservationPrincipal::Research | ObservationPrincipal::Developer => {}
1474 ObservationPrincipal::Person(_)
1475 | ObservationPrincipal::Institution(_)
1476 | ObservationPrincipal::Public => return Err(invalid_knowledge_authority()),
1477 }
1478 if !knowledge_holder_exists(self.canwu, holder) {
1479 return Err(CanwuError::new(
1480 ErrorCode::InvalidKnowledgeHolder,
1481 "the requested knowledge holder does not exist",
1482 ));
1483 }
1484 let index = usize::try_from(record.get().saturating_sub(1)).map_err(|_| {
1485 CanwuError::new(
1486 ErrorCode::KnowledgeRecordNotFound,
1487 "holder-relative knowledge record ID is outside the supported range",
1488 )
1489 })?;
1490 self.canwu
1491 .knowledge()
1492 .for_holder(holder)
1493 .and_then(|records| records.values().nth(index))
1494 .cloned()
1495 .ok_or_else(|| {
1496 CanwuError::new(
1497 ErrorCode::KnowledgeRecordNotFound,
1498 "holder-relative knowledge record was not found",
1499 )
1500 })
1501 }
1502
1503 #[must_use]
1504 pub fn visible_changes_since(&self, since: SimTime) -> Vec<VisibleChange> {
1505 let context = ViewerContext {
1506 principal: self.context.principal.clone(),
1507 observation: observation_for_principal(&self.context.principal),
1508 checkpoint_hash: self.canwu.checkpoint_hash().to_owned(),
1509 };
1510 self.canwu
1511 .events()
1512 .iter()
1513 .filter(|event| event.timestamp > since)
1514 .filter_map(|event| {
1515 let audience = self.canwu.simulation.event_audience(event);
1516 visible_change(&context, event, &audience)
1517 })
1518 .collect()
1519 }
1520}
1521
1522const fn observation_for_principal(principal: &ObservationPrincipal) -> ObservationPolicy {
1523 match principal {
1524 ObservationPrincipal::Person(_) | ObservationPrincipal::Institution(_) => {
1525 ObservationPolicy::ActorBound
1526 }
1527 ObservationPrincipal::Public => ObservationPolicy::PublicObserver,
1528 ObservationPrincipal::Research => ObservationPolicy::ResearchFull,
1529 ObservationPrincipal::Developer => ObservationPolicy::DeveloperDiagnostic,
1530 }
1531}
1532
1533fn invalid_knowledge_authority() -> CanwuError {
1534 CanwuError::new(
1535 ErrorCode::InvalidKnowledgeAuthority,
1536 "this observation principal cannot read a private knowledge ledger",
1537 )
1538}
1539
1540fn knowledge_holder_exists(canwu: &Canwu, holder: &KnowledgeHolderRef) -> bool {
1541 match holder {
1542 KnowledgeHolderRef::Person(actor) => canwu.entity_exists(&EntityRef::Person(*actor)),
1543 KnowledgeHolderRef::Entity(entity) => canwu.entity_exists(entity),
1544 }
1545}
1546
1547fn map_knowledge_query_error(error: KnowledgeQueryError) -> CanwuError {
1548 match error {
1549 KnowledgeQueryError::ReadCutUnavailable => CanwuError::new(
1550 ErrorCode::KnowledgeReadCutUnavailable,
1551 "knowledge cursor read cut is no longer available",
1552 ),
1553 KnowledgeQueryError::InvalidLimit => CanwuError::new(
1554 ErrorCode::KnowledgeLimitExceeded,
1555 "knowledge query page size is outside the supported range",
1556 ),
1557 KnowledgeQueryError::InvalidCursor
1558 | KnowledgeQueryError::InvalidLedger
1559 | KnowledgeQueryError::Encoding => CanwuError::new(
1560 ErrorCode::InvalidKnowledgeRecord,
1561 "knowledge query, cursor, or ledger is invalid",
1562 ),
1563 }
1564}
1565
1566#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1567pub struct VisibleChange {
1568 pub timestamp: SimTime,
1569 pub summary: String,
1570 pub source_event: EventId,
1571}
1572
1573fn visible_change(
1574 viewer: &ViewerContext,
1575 event: &SimEvent,
1576 plugin_audience: &EventAudience,
1577) -> Option<VisibleChange> {
1578 let visible = event_visible_to(viewer, event, plugin_audience);
1579 visible.then(|| VisibleChange {
1580 timestamp: event.timestamp,
1581 summary: event.summary.clone(),
1582 source_event: event.id,
1583 })
1584}
1585
1586fn event_visible_to(viewer: &ViewerContext, event: &SimEvent, audience: &EventAudience) -> bool {
1587 if matches!(
1588 viewer.observation,
1589 ObservationPolicy::ResearchFull | ObservationPolicy::DeveloperDiagnostic
1590 ) {
1591 return true;
1592 }
1593 match audience {
1594 EventAudience::Public => true,
1595 EventAudience::Actor(actor) => viewer.principal.person() == Some(*actor),
1596 EventAudience::Actors(actors) => viewer
1597 .principal
1598 .person()
1599 .is_some_and(|actor| actors.binary_search(&actor).is_ok()),
1600 EventAudience::KnowledgeHolder(holder) => {
1601 principal_matches_holder(&viewer.principal, holder)
1602 }
1603 EventAudience::AffectedActors => viewer
1604 .principal
1605 .person()
1606 .is_some_and(|actor| event.affected_entities.contains(&EntityRef::Person(actor))),
1607 EventAudience::Private => false,
1608 }
1609}
1610
1611fn principal_matches_holder(principal: &ObservationPrincipal, holder: &KnowledgeHolderRef) -> bool {
1612 match (principal, holder) {
1613 (ObservationPrincipal::Person(actor), KnowledgeHolderRef::Person(holder)) => {
1614 actor == holder
1615 }
1616 (ObservationPrincipal::Institution(institution), KnowledgeHolderRef::Entity(holder)) => {
1617 institution == holder
1618 }
1619 (ObservationPrincipal::Research | ObservationPrincipal::Developer, _) => true,
1620 (
1621 ObservationPrincipal::Person(_)
1622 | ObservationPrincipal::Institution(_)
1623 | ObservationPrincipal::Public,
1624 _,
1625 ) => false,
1626 }
1627}
1628
1629#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1630#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1631pub enum ExplanationRequest {
1632 Event(EventId),
1633 Failure(CanwuError),
1634}
1635
1636#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1637pub struct ExplanationStep {
1638 pub label: String,
1639 pub event: Option<EventId>,
1640}
1641
1642#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1643pub struct Explanation {
1644 pub summary: String,
1645 pub causal_chain: Vec<ExplanationStep>,
1646}