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:";
14
15#[must_use]
16pub fn is_canic_owned_intent_resource_key(resource_key: &IntentResourceKey) -> bool {
17    resource_key.starts_with(CANIC_INTENT_RESOURCE_PREFIX)
18}
19
20/// Opaque, versioned binding for every field that changes an external effect.
21#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
22pub struct PayloadBinding {
23    pub schema_version: u32,
24    pub digest: [u8; 32],
25}
26
27impl PayloadBinding {
28    #[must_use]
29    pub const fn new(digest: [u8; 32]) -> Self {
30        Self {
31            schema_version: PAYLOAD_BINDING_SCHEMA_VERSION,
32            digest,
33        }
34    }
35}
36
37/// Consumer-validated terminal decision for one receipt-backed reservation.
38#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
39pub enum TerminalEvidenceDecision {
40    Committed,
41    RolledBack,
42}
43
44/// Bounded proof reference validated by the domain owner before settlement.
45#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
46pub struct TerminalEvidence {
47    pub source_canister: Principal,
48    pub schema_version: u32,
49    pub decision: TerminalEvidenceDecision,
50    pub fingerprint: [u8; 32],
51}
52
53impl TerminalEvidence {
54    #[must_use]
55    pub const fn new(
56        source_canister: Principal,
57        decision: TerminalEvidenceDecision,
58        fingerprint: [u8; 32],
59    ) -> Self {
60        Self {
61            source_canister,
62            schema_version: TERMINAL_EVIDENCE_SCHEMA_VERSION,
63            decision,
64            fingerprint,
65        }
66    }
67}
68
69/// Durable lifecycle of one receipt-backed intent.
70#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
71pub enum ReceiptBackedIntentState {
72    Pending,
73    Committed { evidence: TerminalEvidence },
74    RolledBack { evidence: TerminalEvidence },
75}
76
77/// Read-only projection of one receipt-backed intent.
78#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct ReceiptBackedIntent {
80    pub schema_version: u32,
81    pub operation_id: OperationId,
82    pub payload_binding: PayloadBinding,
83    pub resource_key: IntentResourceKey,
84    pub quantity: u64,
85    pub state: ReceiptBackedIntentState,
86    pub revision: u64,
87    pub created_at_ns: u64,
88    pub updated_at_ns: u64,
89}
90
91/// Complete input for beginning one locally decidable, expirable intent.
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct BeginLocalIntentInput {
94    pub resource_key: IntentResourceKey,
95    pub quantity: u64,
96    pub ttl_secs: Option<u64>,
97    pub reservation_limit: Option<u64>,
98}
99
100/// Complete input for idempotently beginning or loading one reservation.
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct BeginReceiptBackedIntentInput {
103    pub operation_id: OperationId,
104    pub payload_binding: PayloadBinding,
105    pub resource_key: IntentResourceKey,
106    pub quantity: u64,
107    pub reservation_limit: u64,
108}
109
110/// Idempotent outcome of receipt-backed begin-or-load.
111#[derive(Clone, Debug, Eq, PartialEq)]
112pub enum BeginReceiptBackedIntentResult {
113    Created {
114        revision: u64,
115    },
116    ExistingPending {
117        revision: u64,
118    },
119    ExistingCommitted {
120        revision: u64,
121        evidence: TerminalEvidence,
122    },
123    ExistingRolledBack {
124        revision: u64,
125        evidence: TerminalEvidence,
126    },
127    BindingConflict,
128    CapacityExceeded {
129        current_quantity: u64,
130        requested_quantity: u64,
131        limit: u64,
132    },
133    StoreCapacityReached {
134        current_records: u64,
135        limit: u64,
136    },
137}
138
139/// Complete input for compare-and-set terminal settlement.
140#[derive(Clone, Debug, Eq, PartialEq)]
141pub struct SettleReceiptBackedIntentInput {
142    pub operation_id: OperationId,
143    pub expected_revision: u64,
144    pub expected_payload_binding: PayloadBinding,
145    pub evidence: TerminalEvidence,
146}
147
148/// Outcome of receipt-backed compare-and-set settlement.
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub enum SettleReceiptBackedIntentResult {
151    Settled {
152        revision: u64,
153        state: ReceiptBackedIntentState,
154    },
155    AlreadySettled {
156        revision: u64,
157        state: ReceiptBackedIntentState,
158    },
159    NotFound,
160    RevisionConflict {
161        actual_revision: u64,
162    },
163    BindingConflict,
164}
165
166/// Complete input for deleting terminal evidence after its external receipt is released.
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct RemoveTerminalReceiptBackedIntentInput {
169    pub operation_id: OperationId,
170    pub expected_revision: u64,
171    pub expected_payload_binding: PayloadBinding,
172}
173
174/// Outcome of deleting one exact terminal receipt-backed intent.
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum RemoveTerminalReceiptBackedIntentResult {
177    Removed,
178    NotFound,
179    NotTerminal,
180    RevisionConflict { actual_revision: u64 },
181    BindingConflict,
182}