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 tie_break: None,
63 },
64 }],
65 ..BoundaryProposal::default()
66 })
67}
68
69struct UncertaintyPlugin;
70
71impl SimulationPlugin for UncertaintyPlugin {
72 fn name(&self) -> &'static str {
73 "example-uncertainty"
74 }
75
76 fn version(&self) -> &'static str {
77 "1.0.0"
78 }
79
80 fn semantic_hash(&self) -> &'static str {
81 "95f702ca4cf602640094c022286fe741b47c9d7aa8573a719fce21e8df97b561"
82 }
83
84 fn register(&self, registrar: &mut canwu_api::PluginRegistrar<'_>) -> Result<(), CanwuError> {
85 let mut contract = BoundarySystemContract::new(
86 "resolve-random-decision",
87 BoundaryPhase::StrategicAggregation,
88 SystemCadence::Daily,
89 );
90 contract.reads = vec![StateKey::core_decisions()];
91 contract.random_streams = vec![decision_stream()];
92 registrar.register_boundary_system(contract, random_resolution_system)
93 }
94}
95
96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}
147
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}
175
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}
229
230fn main() -> Result<(), Box<dyn std::error::Error>> {
231 random_branch()?;
232 llm_interface_branch()?;
233 Ok(())
234}