Skip to main content

canic_core/model/intent/
mod.rs

1//! Module: model::intent
2//!
3//! Responsibility: define pure receipt-backed intent identity, state, and operation results.
4//! Does not own: stable storage, domain receipt validation, or external-effect execution.
5//! Boundary: consumers validate domain evidence before constructing terminal evidence.
6
7use crate::{cdk::types::Principal, ids::IntentResourceKey, model::replay::OperationId};
8use serde::{Deserialize, Serialize};
9
10pub const PAYLOAD_BINDING_SCHEMA_VERSION: u32 = 1;
11pub const RECEIPT_BACKED_INTENT_SCHEMA_VERSION: u32 = 1;
12pub const TERMINAL_EVIDENCE_SCHEMA_VERSION: u32 = 1;
13pub const CANIC_INTENT_RESOURCE_PREFIX: &str = "canic:";
14pub const MAX_RECEIPT_BACKED_INTENT_REPLAY_WINDOW_NS: u64 = 24 * 60 * 60 * 1_000_000_000;
15
16/// Pure temporal admission decision supplied by the workflow policy boundary.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum ReceiptReplayWindowDecision {
19    Open,
20    Closed,
21    TooLong { remaining_ns: u64 },
22}
23
24#[must_use]
25pub fn is_canic_owned_intent_resource_key(resource_key: &IntentResourceKey) -> bool {
26    resource_key.starts_with(CANIC_INTENT_RESOURCE_PREFIX)
27}
28
29/// Opaque, versioned binding for every field that changes an external effect.
30#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct PayloadBinding {
32    pub schema_version: u32,
33    pub digest: [u8; 32],
34}
35
36impl PayloadBinding {
37    #[must_use]
38    pub const fn new(digest: [u8; 32]) -> Self {
39        Self {
40            schema_version: PAYLOAD_BINDING_SCHEMA_VERSION,
41            digest,
42        }
43    }
44}
45
46/// Consumer-validated terminal decision for one receipt-backed reservation.
47#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
48pub enum TerminalEvidenceDecision {
49    Committed,
50    RolledBack,
51}
52
53/// Bounded proof reference validated by the domain owner before settlement.
54#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
55pub struct TerminalEvidence {
56    pub source_canister: Principal,
57    pub schema_version: u32,
58    pub decision: TerminalEvidenceDecision,
59    pub fingerprint: [u8; 32],
60}
61
62impl TerminalEvidence {
63    #[must_use]
64    pub const fn new(
65        source_canister: Principal,
66        decision: TerminalEvidenceDecision,
67        fingerprint: [u8; 32],
68    ) -> Self {
69        Self {
70            source_canister,
71            schema_version: TERMINAL_EVIDENCE_SCHEMA_VERSION,
72            decision,
73            fingerprint,
74        }
75    }
76}
77
78/// Durable lifecycle of one receipt-backed intent.
79#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
80pub enum ReceiptBackedIntentState {
81    Pending,
82    Committed { evidence: TerminalEvidence },
83    RolledBack { evidence: TerminalEvidence },
84}
85
86/// Read-only projection of one receipt-backed intent.
87#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
88pub struct ReceiptBackedIntent {
89    pub schema_version: u32,
90    pub operation_id: OperationId,
91    pub payload_binding: PayloadBinding,
92    pub resource_key: IntentResourceKey,
93    pub quantity: u64,
94    pub state: ReceiptBackedIntentState,
95    pub revision: u64,
96    pub created_at_ns: u64,
97    pub updated_at_ns: u64,
98}
99
100/// Complete input for beginning one locally decidable, expirable intent.
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct BeginLocalIntentInput {
103    pub resource_key: IntentResourceKey,
104    pub quantity: u64,
105    pub ttl_secs: Option<u64>,
106    pub reservation_limit: Option<u64>,
107}
108
109/// Complete input for idempotently beginning or loading one reservation.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct BeginReceiptBackedIntentInput {
112    pub operation_id: OperationId,
113    pub payload_binding: PayloadBinding,
114    pub resource_key: IntentResourceKey,
115    pub quantity: u64,
116    pub reservation_limit: u64,
117    pub replay_deadline_ns: u64,
118}
119
120/// Internal placement admission that deliberately has no application replay deadline.
121#[derive(Clone, Debug, Eq, PartialEq)]
122pub struct BeginPlacementReceiptBackedIntentInput {
123    pub operation_id: OperationId,
124    pub payload_binding: PayloadBinding,
125    pub resource_key: IntentResourceKey,
126    pub quantity: u64,
127    pub reservation_limit: u64,
128}
129
130/// Idempotent outcome of receipt-backed begin-or-load.
131#[derive(Clone, Debug, Eq, PartialEq)]
132pub enum BeginReceiptBackedIntentResult {
133    Created {
134        revision: u64,
135    },
136    ExistingPending {
137        revision: u64,
138    },
139    ExistingCommitted {
140        revision: u64,
141        evidence: TerminalEvidence,
142    },
143    ExistingRolledBack {
144        revision: u64,
145        evidence: TerminalEvidence,
146    },
147    BindingConflict,
148    ReplayWindowClosed {
149        replay_deadline_ns: u64,
150    },
151    ReplayWindowTooLong {
152        remaining_ns: u64,
153        maximum_ns: u64,
154    },
155    CapacityExceeded {
156        current_quantity: u64,
157        requested_quantity: u64,
158        limit: u64,
159    },
160    StoreCapacityReached {
161        current_records: u64,
162        limit: u64,
163    },
164}
165
166/// Complete input for compare-and-set terminal settlement.
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct SettleReceiptBackedIntentInput {
169    pub operation_id: OperationId,
170    pub expected_revision: u64,
171    pub expected_payload_binding: PayloadBinding,
172    pub evidence: TerminalEvidence,
173}
174
175/// Outcome of receipt-backed compare-and-set settlement.
176#[derive(Clone, Copy, Debug, Eq, PartialEq)]
177pub enum SettleReceiptBackedIntentResult {
178    Settled {
179        revision: u64,
180        state: ReceiptBackedIntentState,
181    },
182    AlreadySettled {
183        revision: u64,
184        state: ReceiptBackedIntentState,
185    },
186    NotFound,
187    RevisionConflict {
188        actual_revision: u64,
189    },
190    BindingConflict,
191}
192
193/// Complete input for deleting terminal evidence after its external receipt is released.
194#[derive(Clone, Debug, Eq, PartialEq)]
195pub struct RemoveTerminalReceiptBackedIntentInput {
196    pub operation_id: OperationId,
197    pub expected_revision: u64,
198    pub expected_payload_binding: PayloadBinding,
199}
200
201/// Outcome of deleting one exact terminal receipt-backed intent.
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub enum RemoveTerminalReceiptBackedIntentResult {
204    Removed,
205    NotFound,
206    NotTerminal,
207    RevisionConflict { actual_revision: u64 },
208    BindingConflict,
209}