cloud-sdk 0.55.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
//! Plan-confirm execution authority for state-changing operations.

mod clock;
mod direct;
mod execution_error;
mod fingerprint;
mod shared;
mod state;

pub use clock::PermitClock;
pub use direct::{CostPermit, DestructivePermit, MutationPermit};
pub use execution_error::PermitExecutionError;
pub use fingerprint::{
    CanonicalPlanFingerprint, PlanConfirmation, PlanFingerprintBuildError, PlanFingerprintDigest,
    PlanFingerprintRef, PlanSubject, build_canonical_plan, build_plan_digest,
};
pub use shared::{
    SharedCostPermit, SharedDestructivePermit, SharedMutationPermit, SharedPermitState,
};
pub use state::PermitAttempt;

use core::fmt;

use subtle::{Choice, ConstantTimeEq};

/// Maximum bytes admitted for each account, tenant, or permit-scope value.
pub const MAX_PLAN_SCOPE_BYTES: usize = 1024;
/// Minimum caller-provided idempotency identity length.
pub const MIN_PERMIT_IDEMPOTENCY_BYTES: usize = 16;
/// Maximum caller-provided idempotency identity length.
pub const MAX_PERMIT_IDEMPOTENCY_BYTES: usize = 64;

/// Runtime execution authority required by an operation.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum PermitScope {
    /// Non-destructive state mutation without a known direct charge.
    Mutation,
    /// Destructive or disabling state mutation.
    Destructive,
    /// Mutation that may directly incur provider charges.
    Cost,
}

/// Caller assessment of whether the planned request changes effective state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PlanChange {
    /// The request would make no effective change and must not be authorized.
    NoOp,
    /// The caller confirmed an effective state change.
    ChangesState,
}

/// Repetition policy selected during plan confirmation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReplayPolicy {
    /// Exactly one attempt is authorized.
    SingleAttempt,
    /// A proven `NotSent` attempt may be recovered and repeated.
    RecoverNotSent,
    /// Uncertain delivery may repeat only after operation-specific reconciliation.
    ReconcileThenRetry,
}

/// Invalid attempt budget.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AttemptBudgetError {
    /// At least one attempt is required.
    Zero,
}

impl_static_error!(AttemptBudgetError, Self::Zero => "permit attempt budget must be nonzero");

/// Nonzero maximum number of attempts sharing one authority.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct AttemptBudget(u16);

impl AttemptBudget {
    /// Creates a nonzero attempt budget.
    pub const fn new(value: u16) -> Result<Self, AttemptBudgetError> {
        if value == 0 {
            return Err(AttemptBudgetError::Zero);
        }
        Ok(Self(value))
    }

    /// Returns the total attempt bound.
    #[must_use]
    pub const fn get(self) -> u16 {
        self.0
    }
}

/// Caller-observed wall-clock timestamp in Unix seconds.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct PermitTimestamp(u64);

impl PermitTimestamp {
    /// Wraps caller-provided Unix seconds without acquiring a clock.
    #[must_use]
    pub const fn from_seconds(seconds: u64) -> Self {
        Self(seconds)
    }

    /// Returns Unix seconds.
    #[must_use]
    pub const fn as_seconds(self) -> u64 {
        self.0
    }
}

/// Invalid bounded permit-validity interval.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PermitValidityError {
    /// Expiry must be later than issuance.
    Empty,
    /// The interval exceeds the shared-state representation.
    TooLong,
}

impl_static_error!(PermitValidityError,
    Self::Empty => "permit expiry must follow issuance",
    Self::TooLong => "permit validity interval is too long",
);

/// Caller-owned issuance and expiry observations.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PermitValidity {
    issued_at: PermitTimestamp,
    expires_at: PermitTimestamp,
    duration: u32,
}

impl PermitValidity {
    /// Creates a bounded interval suitable for direct and atomic shared state.
    pub fn new(
        issued_at: PermitTimestamp,
        expires_at: PermitTimestamp,
    ) -> Result<Self, PermitValidityError> {
        let duration = expires_at
            .0
            .checked_sub(issued_at.0)
            .ok_or(PermitValidityError::Empty)?;
        if duration == 0 {
            return Err(PermitValidityError::Empty);
        }
        let duration = u32::try_from(duration).map_err(|_| PermitValidityError::TooLong)?;
        Ok(Self {
            issued_at,
            expires_at,
            duration,
        })
    }

    /// Returns the issuance timestamp.
    #[must_use]
    pub const fn issued_at(self) -> PermitTimestamp {
        self.issued_at
    }

    /// Returns the expiry timestamp.
    #[must_use]
    pub const fn expires_at(self) -> PermitTimestamp {
        self.expires_at
    }

    pub(crate) fn offset(self, now: PermitTimestamp) -> Result<u32, ExecutionPermitError> {
        let elapsed = now
            .0
            .checked_sub(self.issued_at.0)
            .ok_or(ExecutionPermitError::NotYetValid)?;
        if elapsed >= u64::from(self.duration) {
            return Err(ExecutionPermitError::Expired);
        }
        u32::try_from(elapsed).map_err(|_| ExecutionPermitError::Expired)
    }
}

/// Invalid three-letter currency code.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CurrencyCodeError {
    /// Currency codes must be exactly three ASCII letters.
    Invalid,
}

impl_static_error!(CurrencyCodeError, Self::Invalid => "currency code must be three uppercase ASCII letters");

/// Exact ISO-style uppercase currency code.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CurrencyCode([u8; 3]);

impl CurrencyCode {
    /// Validates an exact uppercase three-letter code.
    pub fn new(value: &str) -> Result<Self, CurrencyCodeError> {
        let bytes: [u8; 3] = value
            .as_bytes()
            .try_into()
            .map_err(|_| CurrencyCodeError::Invalid)?;
        if !bytes.iter().all(u8::is_ascii_uppercase) {
            return Err(CurrencyCodeError::Invalid);
        }
        Ok(Self(bytes))
    }

    /// Returns exact code bytes.
    #[must_use]
    pub const fn as_bytes(self) -> [u8; 3] {
        self.0
    }
}

/// Invalid cost confirmation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PlanCostError {
    /// An observed price must be nonzero for cost authority.
    ZeroObservedPrice,
    /// The observed price exceeds the caller's spending ceiling.
    SpendingCeilingExceeded,
}

impl_static_error!(PlanCostError,
    Self::ZeroObservedPrice => "observed price must be nonzero",
    Self::SpendingCeilingExceeded => "observed price exceeds spending ceiling",
);

/// Exact caller-observed price and spending ceiling in common scaled units.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PlanCost {
    currency: CurrencyCode,
    scale: u8,
    observed_units: u128,
    ceiling_units: u128,
}

impl PlanCost {
    /// Creates one exact bounded cost confirmation.
    pub fn new(
        currency: CurrencyCode,
        scale: u8,
        observed_units: u128,
        ceiling_units: u128,
    ) -> Result<Self, PlanCostError> {
        if observed_units == 0 {
            return Err(PlanCostError::ZeroObservedPrice);
        }
        if observed_units > ceiling_units {
            return Err(PlanCostError::SpendingCeilingExceeded);
        }
        Ok(Self {
            currency,
            scale,
            observed_units,
            ceiling_units,
        })
    }

    pub(crate) const fn fields(self) -> (CurrencyCode, u8, u128, u128) {
        (
            self.currency,
            self.scale,
            self.observed_units,
            self.ceiling_units,
        )
    }
}

/// Invalid account, tenant, or permit-context value.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PermitContextError {
    /// Permit context must not be empty.
    Empty,
    /// Permit context exceeds its fixed policy bound.
    TooLong,
}

impl_static_error!(PermitContextError,
    Self::Empty => "permit context is empty",
    Self::TooLong => "permit context exceeds the length limit",
);

/// Exact bounded context binding a permit to caller policy.
#[derive(Clone, Copy)]
pub struct PermitContext<'a>(&'a [u8]);

impl<'a> PermitContext<'a> {
    /// Validates a nonempty bounded context.
    pub fn new(value: &'a [u8]) -> Result<Self, PermitContextError> {
        if value.is_empty() {
            return Err(PermitContextError::Empty);
        }
        if value.len() > MAX_PLAN_SCOPE_BYTES {
            return Err(PermitContextError::TooLong);
        }
        Ok(Self(value))
    }

    pub(crate) const fn bytes(self) -> &'a [u8] {
        self.0
    }
}

impl fmt::Debug for PermitContext<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("PermitContext([redacted])")
    }
}

/// Optional exact account or tenant identity.
#[derive(Clone, Copy, Debug)]
pub enum PlanFingerprintScope<'a> {
    /// No identity applies in this dimension.
    Absent,
    /// Exact bounded identity bytes.
    Value(&'a [u8]),
}

impl<'a> PlanFingerprintScope<'a> {
    pub(crate) fn bytes(self) -> Result<Option<&'a [u8]>, PermitContextError> {
        match self {
            Self::Absent => Ok(None),
            Self::Value([]) => Err(PermitContextError::Empty),
            Self::Value(value) if value.len() > MAX_PLAN_SCOPE_BYTES => {
                Err(PermitContextError::TooLong)
            }
            Self::Value(value) => Ok(Some(value)),
        }
    }
}

/// Invalid caller-provided repetition identity.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PermitIdempotencyKeyError {
    /// Identity has too few entropy-bearing bytes.
    TooShort,
    /// Identity exceeds the fixed policy bound.
    TooLong,
    /// An all-zero identity is rejected.
    AllZero,
}

impl_static_error!(PermitIdempotencyKeyError,
    Self::TooShort => "permit idempotency identity is too short",
    Self::TooLong => "permit idempotency identity is too long",
    Self::AllZero => "permit idempotency identity cannot be all zero",
);

/// Borrowed caller-owned identity used only for exact reconciliation matching.
#[derive(Clone, Copy)]
pub struct PermitIdempotencyKey<'a>(&'a [u8]);

impl<'a> PermitIdempotencyKey<'a> {
    /// Validates identity shape. Entropy quality remains a caller duty.
    pub fn new(value: &'a [u8]) -> Result<Self, PermitIdempotencyKeyError> {
        if value.len() < MIN_PERMIT_IDEMPOTENCY_BYTES {
            return Err(PermitIdempotencyKeyError::TooShort);
        }
        if value.len() > MAX_PERMIT_IDEMPOTENCY_BYTES {
            return Err(PermitIdempotencyKeyError::TooLong);
        }
        let mut nonzero = Choice::from(0);
        for byte in value {
            nonzero |= !byte.ct_eq(&0);
        }
        if !bool::from(nonzero) {
            return Err(PermitIdempotencyKeyError::AllZero);
        }
        Ok(Self(value))
    }

    pub(crate) const fn bytes(self) -> &'a [u8] {
        self.0
    }

    pub(crate) fn matches(self, other: Self) -> bool {
        self.0.len() == other.0.len() && bool::from(self.0.ct_eq(other.0))
    }
}

impl fmt::Debug for PermitIdempotencyKey<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("PermitIdempotencyKey([redacted])")
    }
}

/// Observable permit lifecycle state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PermitState {
    /// Ready to authorize one attempt.
    Ready,
    /// One caller currently owns the attempt.
    InFlight,
    /// A proven-not-sent attempt awaits generation-bound recovery.
    Recoverable,
    /// Delivery may have happened and operation-specific reconciliation is required.
    PendingReconciliation,
    /// Authority is permanently spent.
    Spent,
}

/// Generation-bound recovery authority returned only for `NotSent`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RecoveryToken(pub(crate) u16);

/// Generation-bound reconciliation authority returned after uncertain delivery.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReconciliationToken(pub(crate) u16);

/// State transition produced by one completed or failed attempt.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PermitDisposition {
    /// Authority is permanently spent.
    Spent,
    /// A proven-not-sent attempt may be explicitly recovered.
    Recoverable(RecoveryToken),
    /// Operation-specific reconciliation is required.
    PendingReconciliation(ReconciliationToken),
}

/// Permit authorization or lifecycle failure.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExecutionPermitError {
    /// The plan scope does not match the permit type.
    ScopeMismatch,
    /// The permit is not valid yet.
    NotYetValid,
    /// The permit has expired.
    Expired,
    /// Caller time moved backward.
    ClockRollback,
    /// Another caller owns the only in-flight attempt.
    AttemptInFlight,
    /// The permit needs explicit not-sent recovery.
    RecoveryRequired,
    /// The permit needs operation-specific reconciliation.
    ReconciliationRequired,
    /// The authority or attempt budget is spent.
    Spent,
    /// Recovery or reconciliation belongs to another generation.
    StaleGeneration,
    /// The supplied request fingerprint differs from the confirmed plan.
    FingerprintMismatch,
    /// Reconciliation used a different idempotency identity.
    IdempotencyMismatch,
    /// The selected replay policy forbids this transition.
    ReplayForbidden,
    /// Atomic generation capacity is exhausted.
    GenerationExhausted,
}

impl_static_error!(ExecutionPermitError,
    Self::ScopeMismatch => "execution permit scope does not match the plan",
    Self::NotYetValid => "execution permit is not valid yet",
    Self::Expired => "execution permit has expired",
    Self::ClockRollback => "execution permit clock moved backward",
    Self::AttemptInFlight => "execution permit already has an in-flight attempt",
    Self::RecoveryRequired => "execution permit requires not-sent recovery",
    Self::ReconciliationRequired => "execution permit requires reconciliation",
    Self::Spent => "execution permit is spent",
    Self::StaleGeneration => "execution permit generation is stale",
    Self::FingerprintMismatch => "execution permit fingerprint does not match",
    Self::IdempotencyMismatch => "execution permit idempotency identity does not match",
    Self::ReplayForbidden => "execution permit replay policy forbids repetition",
    Self::GenerationExhausted => "execution permit generation is exhausted",
);

#[cfg(test)]
mod tests;