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