Skip to main content

canwu_api/
lib.rs

1//! Public programmatic, query, semantic-agent, explanation, and debug interfaces.
2
3#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]
4
5pub use canwu_core::{
6    ArmyId, BoundaryId, CommandAttemptId, CommandId, CommandRequestId, CoreEntityKind,
7    DecisionRequestId, DecisionTicketId, DecisionTraceId, DomainEntityKindClass, DomainEntityType,
8    DomainKindClass, DomainRecordKind, DomainRecordRef, DomainRecordType, DomainRecordVersionRef,
9    DomainRecordVersionSource, DomainValueKindClass, DomainValueType, EntityRef, 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
118/// Main in-process API. All returned world values are detached snapshots.
119pub struct Canwu {
120    simulation: Simulation,
121}
122
123/// Public API for a live runtime whose sealed evidence segments are stored by the caller.
124pub 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    /// Enters the explicit compact-journal interface without discarding evidence.
151    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    /// Deprecated compatibility scenario. New hosts should use an integration-owned scenario.
228    pub fn demo(seed: u64) -> Result<Self, CanwuError> {
229        let (simulation, _) = Simulation::demo(seed)?;
230        Ok(Self { simulation })
231    }
232
233    /// IDs for the deprecated compatibility scenario.
234    #[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    /// Returns the persisted authoritative transaction revision.
257    ///
258    /// Accepted commands, persisted expected rejections, and completed
259    /// settlement boundaries each advance it exactly once. Failed work, exact
260    /// retries, bare clock movement, queued but unadmitted ingress, and plugin
261    /// setup do not advance it; combine it with command expected-time guards.
262    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    /// Deprecated detached format-5 compatibility projection.
290    #[must_use]
291    pub fn world(&self) -> WorldSnapshot {
292        self.simulation.world()
293    }
294
295    /// Trusted host/admin access to the complete knowledge snapshot.
296    ///
297    /// Do not expose this public API to player, agent, observer, or remote clients;
298    /// use [`Canwu::viewer`] or [`Canwu::viewer_for_actor`] instead.
299    #[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    /// Returns whether an exact domain-record version exists in current or retained evidence.
335    #[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    /// Returns whether a generic evidence identity is retained or archived.
345    #[must_use]
346    pub fn evidence_exists(&self, reference: &EvidenceRef) -> bool {
347        self.simulation.evidence_exists(reference)
348    }
349
350    /// Returns when retained evidence first became authoritative.
351    ///
352    /// Compacted identity-only receipts return `None`; load the archived
353    /// evidence body before making decisions that require temporal ordering.
354    #[must_use]
355    pub fn evidence_time(&self, reference: &EvidenceRef) -> Option<SimTime> {
356        self.simulation.evidence_time(reference)
357    }
358
359    /// Resolves the retained record body for one exact domain-record version.
360    ///
361    /// A compacted archive receipt proves existence but does not expose the
362    /// version body through this trusted-host query.
363    #[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    /// Returns the retained or archive-resolvable exact identity for the
372    /// authoritative current record version. Missing provenance fails closed.
373    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    /// Returns one trusted-host page bound to an authoritative revision.
393    ///
394    /// Use the returned revision as `expected_revision` on subsequent pages.
395    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    /// Attaches caller-owned package archive storage for authenticated cold
519    /// history resolution during normal admissions, settlement, and queries.
520    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    /// Loads one opaque package archive object from the attached provider.
528    /// Package integrations authenticate the bytes against their committed
529    /// archive roots before use.
530    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    /// Replays with package archive storage attached before the first
739    /// recorded boundary, so package-owned cold history participates in
740    /// ordinary admission and settlement throughout replay.
741    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    /// Trusted host/admin holder query. Player-facing callers must use a
771    /// restricted [`CanwuViewer`].
772    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    /// Creates the restricted viewer dictated entirely by the persisted run
788    /// policy and seat binding.
789    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    /// Character-seat and compatibility convenience. It never upgrades an
798    /// institution, public, research, or developer policy to a person.
799    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    /// Deprecated detached format-5 compatibility projection.
1067    #[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/// An observation identity authorized by the run's persisted observation
1253/// policy. This type is intentionally constructed through
1254/// [`Canwu::viewer_context`] so an observation request cannot self-escalate.
1255#[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
1302/// Restricted player/agent/observer API. It deliberately exposes no raw
1303/// snapshot, event, boundary, domain-record, or audit-origin access.
1304pub 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    /// Queries only the holder selected by a bound person or institution
1316    /// principal. Public and diagnostic principals must use their separately
1317    /// named capabilities.
1318    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    /// Selects an existing holder under an explicit research/developer policy.
1337    /// Returned records remain the origin-free holder projection.
1338    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    /// Returns one audit-bearing stored record only for research/developer
1359    /// principals. Normal holder queries never expose origin evidence.
1360    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}