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