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