a3s_memory/repository/change_token.rs
1use super::MemoryRepositoryError;
2use serde::{Deserialize, Serialize};
3
4/// Stable identity of the namespace change-token contract.
5pub const MEMORY_NAMESPACE_CHANGE_TOKEN_PROFILE_V1: &str =
6 "a3s.memory.namespace-change-token.sequence.v1";
7
8/// Bounded, content-free evidence that one namespace did not change.
9///
10/// A token is meaningful only for repeated reads of the same exact namespace
11/// from the same repository history. Equal tokens prove that no novel,
12/// successful repository apply changed that namespace between the reads.
13/// Sequences may jump, but must never repeat after a change. Admission and use
14/// events do not change the token.
15///
16/// The token is not a namespace or backend identity, a snapshot, a lock, or a
17/// distributed lease. Consumers must retain their ordinary snapshot and
18/// revision-CAS proofs whenever the token is unavailable or changes.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct MemoryNamespaceChangeToken {
22 profile: String,
23 sequence: u64,
24}
25
26impl MemoryNamespaceChangeToken {
27 /// Construct a token for a backend-owned monotonic sequence.
28 pub fn new(sequence: u64) -> Self {
29 Self {
30 profile: MEMORY_NAMESPACE_CHANGE_TOKEN_PROFILE_V1.to_string(),
31 sequence,
32 }
33 }
34
35 pub fn profile(&self) -> &str {
36 &self.profile
37 }
38
39 pub fn sequence(&self) -> u64 {
40 self.sequence
41 }
42
43 /// Verify that a deserialized or backend-provided token uses this contract.
44 pub fn verify(&self) -> Result<(), MemoryRepositoryError> {
45 if self.profile != MEMORY_NAMESPACE_CHANGE_TOKEN_PROFILE_V1 {
46 return Err(MemoryRepositoryError::invariant(
47 "namespace change token uses an unsupported profile",
48 ));
49 }
50 Ok(())
51 }
52}