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
//! Machine-readable durability vocabulary for storage slots.
//!
//! Every piece of state a storage provider supplies is declared `Durable`,
//! `RebuildableCache`, or `Scratch`, together with how the slot actually
//! resolved. The classes are serializable so deployment tooling can act on
//! them (for example clone `Durable` domains only in state-generation
//! deploys). Fail-closed rule (enforced at composition): a `Durable` slot
//! resolving to a non-persistent store is a startup error unless the realm
//! explicitly declares that domain ephemeral — never a silent in-memory
//! fallback.
use serde::{Deserialize, Serialize};
/// What a storage domain's contents mean for durability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DurabilityClass {
/// Loss is data loss (sessions, runtime checkpoints, schedules,
/// workgraph, blobs, artifacts, memory text).
Durable,
/// Derivable from durable state; loss costs a rebuild (indexes,
/// projections, caches).
RebuildableCache,
/// Ephemeral by design.
Scratch,
}
/// How a slot actually resolved at composition time.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DurabilityResolution {
/// Backed by persistent storage.
Persistent,
/// Non-persistent by an explicit declaration (memory backend, declared
/// ephemeral domain, tests) — a legitimate configured choice.
DeclaredEphemeral,
/// Non-persistent without a declaration. Composition refuses this for
/// `Durable` slots (the fail-closed rule).
NonPersistent,
}
/// One storage slot's declaration: domain name, class, and resolution.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DurabilityDeclaration {
/// Stable domain name ("sessions", "runtime", "schedule", "workgraph",
/// "blobs", "artifacts", ...).
pub domain: String,
pub class: DurabilityClass,
pub resolution: DurabilityResolution,
}
impl DurabilityDeclaration {
pub fn durable(domain: &str, resolution: DurabilityResolution) -> Self {
Self {
domain: domain.to_string(),
class: DurabilityClass::Durable,
resolution,
}
}
/// True when this declaration violates the fail-closed rule on its own
/// (callers additionally consult the realm manifest's declared
/// ephemeral domains before refusing).
pub fn is_undeclared_nonpersistent_durable(&self) -> bool {
self.class == DurabilityClass::Durable
&& self.resolution == DurabilityResolution::NonPersistent
}
}