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