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