Routed correspondence from Wuxi
The scenario
Section titled “The scenario”A commander in Wuxi carries a sealed letter for one recipient. The program runs the delivery twice: first the commander knows the recipient’s address as a Wuxi delivery district, then as a Beijing one. Each route is planned from the commander’s own knowledge of stations, timetables, and addresses.
The question the example answers: how does a message cross a transport network using only what its carrier knows, with the route and each delivery step recorded?
The network is synthetic. Station names, timetables, and travel times are demonstration data.
What this example shows
Section titled “What this example shows”- The
canwu-correspondencedomain extension. ItsCorrespondencePluginruns next toInformationPluginfromcanwu-information. A correspondence (CorrespondenceOperation) is one addressed message plus its delivery and transport. - Holder-relative knowledge: stations,
connections, and addresses live in one holder’s ledger (
KnowledgeHolderRef), and planning reads that ledger at a recorded read cut (KnowledgeReadCut). See reading holder knowledge. plan_routefromcanwu-routingandTransportExecutionfromcanwu-transport, which runs the legs and records aHandoffbetween them. See routing and transport.- The information lifecycle from
canwu-information: aDispatchand itsDeliveryAttempt. See encoded interception. - An automatic communication opportunity decided by operation-keyed random draws.
Run it
Section titled “Run it”cargo run -p canwu-correspondence --example routed_correspondenceOutput:
Wuxi local delivery: 1 leg(s), arrival minute 30 wuxi/hub -> wuxi/delivery/recipient via HorseWuxi to Beijing: 2 leg(s), arrival minute 3060 wuxi/hub -> beijing/station via Rail beijing/station -> beijing/delivery/recipient via HorseThe arrival minute is the plan’s estimated_arrival_at, counted from the start
of the run. The Beijing plan takes the direct train at hour 1, reaches Beijing
station two days later at minute 2940, and adds two hours on horseback:
2940 + 120 = 3060. The route through Nanjing would reach Beijing station a day
later.
How a letter moves
Section titled “How a letter moves”View diagram source
flowchart TD
K["Carrier's ledger (the sender here): stations, timetables, address"]
O["Communication opportunity or decision ticket"]
C["initiate_correspondence_v1"]
P["RoutePlan from the ledger at a read cut"]
A["Dispatch active, DeliveryAttempt created"]
L["Transport legs, Handoff at each transfer stop"]
D["Attempt Delivered, dispatch Completed"]
X["Disaster: new ItineraryRevision for the same attempt"]
I["Interception: Access record, delivery continues"]
F["Carrier seized or late arrival: attempt Failed"]
R["resolve_correspondence_v1 from the sender"]
O --> C --> P
K --> P
P --> A --> L --> D
L -. disaster .-> X
X -. reroute .-> L
L -. interception .-> I
L -. seizure or deadline .-> F
F -.-> R
R -. retry with a new attempt .-> P
Solid arrows are the path the example runs. Dotted arrows are incidents and recovery, covered by the tests described at the end of this page.
Walkthrough
Section titled “Walkthrough”The code is in
routed_correspondence.rs
and its fixture
examples/support/mod.rs.
run(long_distance) builds a fresh simulation per request. The 1940 passed
to Canwu::new_with_plugins is the random seed.
1. Prepare the letter
Section titled “1. Prepare the letter”scenario_with_prepared_dispatch takes the commander (sender) and the observer
(recipient) from the deprecated Canwu::demo(1) compatibility scenario, then
adds a sealed-letter channel, the letter content, its representation, and a
dispatch:
&DispatchPayload { status: DispatchStatus::Prepared, target: DispatchTarget::Addressed(vec![recipient.clone()]), prepared_at: snapshot.initial_time, dispatched_at: None, completed_at: None,},The plugin accepts only a Prepared dispatch with this single recipient and
the same sender and channel profile as the request.
2. Give the carrier a map
Section titled “2. Give the carrier a map”network_seed returns a NetworkKnowledgeSeed. The Beijing version knows a
direct train, a route through Nanjing, and two final-mile roads. The direct
train uses TraversalModel::Departures, with departures at hour 1 on days 0,
5, and 10:
connection_departure( "wuxi-beijing-direct", "wuxi/hub", "beijing/station", TransferMode::Rail, departure, SimDuration::days(2),),The seed also holds a KnownAddress that maps the recipient to
beijing/delivery/recipient (or wuxi/delivery/recipient). run submits the
seed as KNOWLEDGE_INGRESS for KnowledgeHolderRef::Person(sender), and the
plugin publishes each endpoint, connection, and address as a knowledge record
in the sender’s ledger.
3. Offer a communication opportunity
Section titled “3. Offer a communication opportunity”In the same boundary, run submits OPPORTUNITY_INGRESS:
CommunicationOpportunityRequest { operation_key: operation_key.to_owned(), sender: EntityRef::Person(sender), candidates: vec![recipient.clone()], reason: "routine correspondence".to_owned(), probability_per_mille: 1_000, automatic: true,}The plugin makes one operation-keyed draw to decide whether the opportunity
occurs and another to pick a recipient from candidates, so replay gets the
same result. With 1_000 per mille and one candidate, the observer is always
selected, and the opportunity record gets status SelectedAutomatic.
4. Send the correspondence command
Section titled “4. Send the correspondence command”let request = InitiateCorrespondenceRequest { operation_key: operation_key.to_owned(), sender: EntityRef::Person(sender), recipient, carrier: KnowledgeHolderRef::Person(sender), channel_profile: "sealed-letter".to_owned(), origin: RoutingNodeRef::new("wuxi/hub"), due_at: canwu.time() + SimDuration::days(10), prepared_dispatch, delivery_attempt_operation: InformationOperationId::new( "example.correspondence", format!("{operation_key}-attempt"), ), routing_policy: canwu_api::RoutingPolicy::default(), capacity_admission: CorrespondenceCapacityAdmission::Unconstrained, execution_id: canwu_api::TransportExecutionId(if long_distance { 2 } else { 1 }), automatic_opportunity: Some(opportunity_ref(operation_key)), carrier_delegation: None,};correspondence_command wraps it as the initiate_correspondence_v1 plugin
command, issued by Issuer::System with
CommandAuthority::no_responsible_actor(...). The plugin accepts that
authority only with a SelectedAutomatic opportunity for the same key,
sender, and recipient. The carrier is the sender, so carrier_delegation is
None.
5. Plan and activate
Section titled “5. Plan and activate”settle_start in
plugin.rs
reads the carrier’s ledger, resolves the address, and plans:
let (snapshot, address) = carrier_planning_snapshot( view, &admitted.request.carrier, &admitted.request.recipient, context.at,)?;let route_plan = plan_route( &snapshot, &RoutingRequest { origin: admitted.request.origin.clone(), destination: address.destination.clone(), departure_at: context.at, policy: admitted.request.routing_policy.clone(), },).map_err(|error| invalid_record(error.to_string()))?;The default RoutingPolicy picks the earliest arrival. The plugin stores a
CorrespondenceOperation with the plan, the address, the read cut, and
planning evidence, marks the opportunity Consumed, and asks the information
plugin to activate the dispatch. Activation creates delivery attempt 1 with the
request’s due_at.
6. Move leg by leg
Section titled “6. Move leg by leg”The plugin schedules its own ProgressAction::StartLeg and CompleteLeg
ingress at each leg’s planned times and rejects progress submitted by the host.
When a leg before the last completes, complete_leg records a planned
Handoff at that stop; the Beijing run records one at beijing/station. When
the last leg arrives:
let status = if context.at <= operation.current_due_at { DeliveryAttemptStatus::Delivered} else { DeliveryAttemptStatus::Failed};A delivered attempt completes the dispatch, and the operation becomes
CorrespondenceStatus::Settled. run steps up to 80 boundaries until
typed_domain_record(&correspondence_operation_ref(operation_key)) shows
Settled, and print_plan prints the route_plan.
What to notice
Section titled “What to notice”- Both requests run through the same plugin code. Only the seed data and the address differ.
- The plan uses only connections in the carrier’s ledger.
RoutePlan.estimated_arrival_atis an estimate;DeliveryAttempt.due_atis the deadline. A late arrival fails the attempt, marks the operationDeadlineMissed, and keeps the originaldue_at.- Each step leaves a record: the planning history with read cuts, the transport legs and handoffs, and versioned dispatch and attempt records.
Try changing
Section titled “Try changing”- Remove the direct train. Delete
wuxi-beijing-directinnetwork_seed. The Beijing plan becomes three legs throughnanjing/stationand arrives at minute 4500: Nanjing at hour 25, Beijing station at hour 73, then two hours on horseback. - Miss the deadline. Set
due_attocanwu.time() + SimDuration::days(1), return from the loop onceoperation.status.is_terminal(), and printoperation.status. The local letter settles. The Beijing letter arrives at minute 3060, after the deadline at minute 1440, and ends asDeadlineMissed. - Suppress the opportunity. Set
probability_per_milleto0. The opportunity is stored asSuppressed, the plugin rejects the command, andrunpanics because no operation appears.
Beyond the example
Section titled “Beyond the example”The example stops once both letters settle. The rest of the lifecycle is in the
crate and is exercised by its tests:
routed_delivery.rs
covers decision-backed sending, incidents, and recovery;
gap_g25_correspondence_delegated_carrier.rs
covers delegated carriers; and
gap_g26_correspondence_carrier_seized.rs
covers carrier seizure. Most of these tests end by restoring a snapshot and
rebuilding the run with Canwu::replay_from_journal
(exact replay).
- Sending by decision.
correspondence_decision_ticketbuilds a decision ticket whosesendoption carries the command. The plugin accepts it when the decision origin is the sender;routed_delivery.rsuses this path. - Incidents. The application submits a
CorrespondenceIncidentRequestwith aprobability_per_millethroughINCIDENT_INGRESS. The plugin rolls it with an operation-keyed draw and records it on the operation; an incident that arrives in an inapplicable state is kept as suppressed evidence.Disasterfails the current leg and replans from the current stop without the blocked connections. The result is a reroute: a successorItineraryRevisionfor the same attempt. With no known route, the operation waits asWaitingForRoute.Interceptionwrites anAccessrecord for the interceptor. Delivery continues.CarrierSeized { seized_by, custody_handoff }is a carrier seizure. The leg fails, a terminalHandoffKind::Seizurehandoff is recorded, and the attempt and its transport execution close as failed. Only the carrier receives anattempt_reportknowledge record, and it omits the seizer.
- Recovery. The sender’s decision-backed
resolve_correspondence_v1command takes oneCorrespondenceRecoveryAction.ReplanCurrentAttemptcontinues aWaitingForRouteattempt after the carrier learns new connections.RetryDeliverystarts a delivery retry: a new attempt that references the old one under theDomainReferencerole"previous_attempt", with a new deadline andTransportExecution.FinalizeDispatchcompletes the dispatch, and the operation ends asFailedorDeadlineMissed. Until then, a failed attempt leaves the dispatchActive. - Delegated carriers. A delegated carrier
first issues
delegate_carrier_v1under its own command authority. ItsDelegationClaimV1names the carrier asperformed_by, the sender asperformed_for, and thecarry_correspondencecapability. From the next boundary the sender cites that command inInitiateCorrespondenceRequest::carrier_delegation. Planning then reads the carrier’s ledger, and the engine publishes none of it to the sender. The claim must cover each dispatch, including retries, and a newer delegation from the same carrier to the same sender replaces the older one. - Capacity.
CorrespondenceCapacityAdmissionhas one variant,Unconstrained: planning and delivery ignore transport capacity. Capacity pools belong tocanwu-movement; see routing and transport.
Source
Section titled “Source”Open the runnable example
Read the delivery, reroute, and retry tests
Read the delegated carrier test
Read the carrier seizure test