1#![allow(clippy::unnecessary_wraps)]
2
3use canwu_api::{
4 BoundaryContext, BoundaryDirective, BoundaryPhase, BoundaryProposal, BoundarySystemContract,
5 Canwu, CanwuError, DecisionAuthority, DecisionContext, DecisionControllerBinding,
6 DecisionEvaluation, DecisionIngressRequest, DecisionMutation, DecisionOption,
7 DecisionOptionWeight, DecisionPolicyIdentity, DecisionPolicyKind, DecisionRequestId,
8 DecisionTicketDraft, DecisionTicketId, EntityRef, EvidenceRef, ExternalDecisionResponse,
9 ExternalPolicy, LlmModelIdentity, QueuedLlmPolicy, RandomDecisionResolution,
10 RandomOperationTarget, RandomStreamKey, SimDuration, SimTime, SimulationPlugin, SimulationView,
11 StateKey, SystemCadence,
12};
13use serde_json::json;
14use std::collections::BTreeMap;
15
16const RANDOM_TICKET: DecisionTicketId = DecisionTicketId::new(1);
17
18fn decision_stream() -> RandomStreamKey {
19 RandomStreamKey::new("example-uncertainty", "decision-selection", 1)
20}
21
22fn random_resolution_system(
23 view: &SimulationView<'_>,
24 context: &BoundaryContext,
25) -> Result<BoundaryProposal, CanwuError> {
26 let ticket = view
27 .decision_ticket(RANDOM_TICKET)?
28 .expect("the example opens the ticket before the daily boundary");
29 let calendar_ingress = context
30 .admitted_ingress
31 .first()
32 .copied()
33 .expect("the example runs this system from calendar ingress");
34 let option_weights = vec![
35 DecisionOptionWeight::new("fail", 25),
36 DecisionOptionWeight::new("pass", 75),
37 ];
38 let sample = view.random_sample_for_operation(
39 &decision_stream(),
40 EvidenceRef::Ingress(calendar_ingress),
41 "decision_selection",
42 "law-proposal-42",
43 RandomOperationTarget::DecisionTicket {
44 ticket_id: ticket.id,
45 ticket_version: ticket.version,
46 },
47 0,
48 100,
49 "select whether the law passes from configured weights",
50 )?;
51 Ok(BoundaryProposal {
52 directives: vec![BoundaryDirective::ResolveDecisionRandomly {
53 resolution: RandomDecisionResolution {
54 priority: 0,
55 decision_request_id: DecisionRequestId::new(3),
56 command_request_id: None,
57 ticket_id: ticket.id,
58 expected_version: ticket.version,
59 controller_id: ticket.assigned_controller.clone(),
60 sample,
61 option_weights,
62 },
63 }],
64 ..BoundaryProposal::default()
65 })
66}
67
68struct UncertaintyPlugin;
69
70impl SimulationPlugin for UncertaintyPlugin {
71 fn name(&self) -> &'static str {
72 "example-uncertainty"
73 }
74
75 fn version(&self) -> &'static str {
76 "1.0.0"
77 }
78
79 fn semantic_hash(&self) -> &'static str {
80 "95f702ca4cf602640094c022286fe741b47c9d7aa8573a719fce21e8df97b561"
81 }
82
83 fn register(&self, registrar: &mut canwu_api::PluginRegistrar<'_>) -> Result<(), CanwuError> {
84 let mut contract = BoundarySystemContract::new(
85 "resolve-random-decision",
86 BoundaryPhase::StrategicAggregation,
87 SystemCadence::Daily,
88 );
89 contract.reads = vec![StateKey::core_decisions()];
90 contract.random_streams = vec![decision_stream()];
91 registrar.register_boundary_system(contract, random_resolution_system)
92 }
93}
94
95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}
145
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}
173
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}
227
228fn main() -> Result<(), Box<dyn std::error::Error>> {
229 random_branch()?;
230 llm_interface_branch()?;
231 Ok(())
232}