Skip to main content

base64_ng/v2/assurance/
report.rs

1//! Stable redacted assurance and allocation-specific operation reports.
2
3use super::{
4    AccountingPosture, AssuranceGenerations, AssuranceLevel, AssuranceToken, CleanupError,
5    CleanupReport, JournalDisposition, LifecyclePosture, PendingStage, PhysicalProtection,
6    ProviderHealth, WipeEvidence,
7};
8use crate::runtime::{CtGatePosture, OperationBackendReport, WipePosture};
9
10/// Secret operation recorded for one protected allocation.
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12#[non_exhaustive]
13pub enum SecretOperation {
14    /// No encode or decode has started.
15    NotStarted,
16    /// Constant-time-oriented scalar encode.
17    Encode,
18    /// Constant-time-oriented scalar decode.
19    Decode,
20}
21
22impl SecretOperation {
23    /// Returns the stable operation identifier.
24    #[must_use]
25    pub const fn as_str(self) -> &'static str {
26        match self {
27            Self::NotStarted => "not-started",
28            Self::Encode => "secret-encode",
29            Self::Decode => "secret-decode",
30        }
31    }
32}
33
34/// Attestation attached to the exact token and operation.
35#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
36#[non_exhaustive]
37pub enum AttestationPosture {
38    /// No platform attestation is attached.
39    NotAttested,
40    /// Matching platform and provider attestation is attached.
41    Attested,
42}
43
44impl AttestationPosture {
45    /// Returns the stable posture identifier.
46    #[must_use]
47    pub const fn as_str(self) -> &'static str {
48        match self {
49            Self::NotAttested => "not-attested",
50            Self::Attested => "attested",
51        }
52    }
53}
54
55/// Secret-operation policy represented by one token.
56#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
57#[non_exhaustive]
58pub enum SecretPolicyPosture {
59    /// Best-effort secret policy without hardware attestation.
60    BestEffort,
61    /// High-assurance policy with matching runtime attestation.
62    HighAssuranceAttested,
63}
64
65impl SecretPolicyPosture {
66    /// Returns the stable policy identifier.
67    #[must_use]
68    pub const fn as_str(self) -> &'static str {
69        match self {
70            Self::BestEffort => "best-effort",
71            Self::HighAssuranceAttested => "high-assurance-attested",
72        }
73    }
74}
75
76/// Address-free allocation-presence posture.
77#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
78#[non_exhaustive]
79pub enum AllocationPosture {
80    /// The exact protected owner is live and addressable only through its type.
81    Present,
82    /// Teardown conclusively removed the allocation capability.
83    Absent,
84    /// Disposal became indeterminate and no address is retained.
85    UnknownNoAddressRetained,
86}
87
88impl AllocationPosture {
89    /// Returns the stable posture identifier.
90    #[must_use]
91    pub const fn as_str(self) -> &'static str {
92        match self {
93            Self::Present => "present",
94            Self::Absent => "absent",
95            Self::UnknownNoAddressRetained => "unknown-no-address-retained",
96        }
97    }
98}
99
100/// Token-scoped assurance report without allocation claims.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub struct AssuranceReport {
103    /// Ordinary encode backend selection.
104    pub encode_backend: OperationBackendReport,
105    /// Ordinary strict-decode backend selection.
106    pub strict_decode_backend: OperationBackendReport,
107    /// Secret decode backend selection.
108    pub secret_decode_backend: OperationBackendReport,
109    /// Independent context generations captured by the token.
110    pub generations: AssuranceGenerations,
111    /// Wipe primitive/barrier posture.
112    pub wipe_posture: WipePosture,
113    /// Secret result-gate posture.
114    pub result_gate_posture: CtGatePosture,
115    /// Token attestation posture.
116    pub attestation_posture: AttestationPosture,
117    /// Secret policy posture.
118    pub secret_policy_posture: SecretPolicyPosture,
119    /// Wasm artifact posture inherited from ordinary runtime reporting.
120    pub wasm_artifact_posture: crate::runtime::WasmArtifactPosture,
121    /// Wasm host-runtime identification posture.
122    pub wasm_runtime_posture: crate::runtime::WasmRuntimePosture,
123}
124
125impl AssuranceReport {
126    /// Returns a stable logging snapshot.
127    #[must_use]
128    pub const fn snapshot(self) -> AssuranceSnapshot {
129        AssuranceSnapshot {
130            encode_backend: self.encode_backend.snapshot(),
131            strict_decode_backend: self.strict_decode_backend.snapshot(),
132            secret_decode_backend: self.secret_decode_backend.snapshot(),
133            ordinary_backend_generation: self.generations.ordinary_backend,
134            secret_algorithm_generation: self.generations.secret_algorithm,
135            wipe_barrier_generation: self.generations.wipe_barrier,
136            speculation_generation: self.generations.speculation,
137            wipe_posture: self.wipe_posture.as_str(),
138            result_gate_posture: self.result_gate_posture.as_str(),
139            attestation_posture: self.attestation_posture.as_str(),
140            secret_policy_posture: self.secret_policy_posture.as_str(),
141            wasm_artifact_posture: self.wasm_artifact_posture.as_str(),
142            wasm_runtime_posture: self.wasm_runtime_posture.as_str(),
143        }
144    }
145}
146
147/// Stable token-scoped logging snapshot.
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub struct AssuranceSnapshot {
150    /// Stable encode backend snapshot.
151    pub encode_backend: crate::runtime::OperationBackendSnapshot,
152    /// Stable strict-decode backend snapshot.
153    pub strict_decode_backend: crate::runtime::OperationBackendSnapshot,
154    /// Stable secret-decode backend snapshot.
155    pub secret_decode_backend: crate::runtime::OperationBackendSnapshot,
156    /// Ordinary backend generation.
157    pub ordinary_backend_generation: usize,
158    /// Secret algorithm generation.
159    pub secret_algorithm_generation: usize,
160    /// Wipe/barrier generation.
161    pub wipe_barrier_generation: usize,
162    /// Result-gate/speculation generation.
163    pub speculation_generation: usize,
164    /// Stable wipe posture.
165    pub wipe_posture: &'static str,
166    /// Stable result-gate posture.
167    pub result_gate_posture: &'static str,
168    /// Stable attestation posture.
169    pub attestation_posture: &'static str,
170    /// Stable secret-policy posture.
171    pub secret_policy_posture: &'static str,
172    /// Stable Wasm artifact posture.
173    pub wasm_artifact_posture: &'static str,
174    /// Stable Wasm host-runtime posture.
175    pub wasm_runtime_posture: &'static str,
176}
177
178/// Report for the exact allocation participating in one assured operation.
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub struct ProtectedOperationReport {
181    /// Token and backend posture.
182    pub assurance: AssuranceReport,
183    /// Secret operation associated with this allocation.
184    pub operation: SecretOperation,
185    /// Wipe evidence currently established for this live allocation.
186    pub wipe: WipeEvidence,
187    /// Physical posture reported for this exact allocation.
188    pub physical_protection: PhysicalProtection,
189    /// Conservative resource-accounting posture.
190    pub accounting: AccountingPosture,
191    /// Allocation lifecycle, independently reported.
192    pub lifecycle: LifecyclePosture,
193    /// Address-free allocation-presence posture.
194    pub allocation: AllocationPosture,
195    /// Provider health at observation time.
196    pub provider_health: ProviderHealth,
197    /// Provider health generation.
198    pub health_generation: usize,
199    /// Provider protection generation.
200    pub protection_generation: usize,
201}
202
203impl ProtectedOperationReport {
204    pub(crate) const fn live(
205        assurance: AssuranceReport,
206        operation: SecretOperation,
207        physical_protection: PhysicalProtection,
208        provider_health: ProviderHealth,
209        health_generation: usize,
210        protection_generation: usize,
211    ) -> Self {
212        Self {
213            assurance,
214            operation,
215            wipe: WipeEvidence::WipeNotCompleted,
216            physical_protection,
217            accounting: AccountingPosture::Charged,
218            lifecycle: LifecyclePosture::Live,
219            allocation: AllocationPosture::Present,
220            provider_health,
221            health_generation,
222            protection_generation,
223        }
224    }
225
226    /// Returns a stable, redacted logging snapshot.
227    #[must_use]
228    pub const fn snapshot(self) -> ProtectedOperationSnapshot {
229        ProtectedOperationSnapshot {
230            assurance: self.assurance.snapshot(),
231            operation: self.operation.as_str(),
232            wipe: wipe_id(self.wipe),
233            physical_protection: physical_id(self.physical_protection),
234            accounting: accounting_id(self.accounting),
235            lifecycle: lifecycle_id(self.lifecycle),
236            allocation: self.allocation.as_str(),
237            provider_health: provider_health_id(self.provider_health),
238            health_generation: self.health_generation,
239            protection_generation: self.protection_generation,
240        }
241    }
242}
243
244/// Stable redacted snapshot of one participating protected allocation.
245#[derive(Clone, Copy, Debug, Eq, PartialEq)]
246pub struct ProtectedOperationSnapshot {
247    /// Token and backend snapshot.
248    pub assurance: AssuranceSnapshot,
249    /// Stable secret operation identifier.
250    pub operation: &'static str,
251    /// Stable wipe evidence identifier.
252    pub wipe: &'static str,
253    /// Stable physical protection identifier.
254    pub physical_protection: &'static str,
255    /// Stable accounting identifier.
256    pub accounting: &'static str,
257    /// Stable lifecycle identifier.
258    pub lifecycle: &'static str,
259    /// Stable allocation-presence identifier.
260    pub allocation: &'static str,
261    /// Stable provider-health identifier.
262    pub provider_health: &'static str,
263    /// Provider health generation.
264    pub health_generation: usize,
265    /// Provider protection generation.
266    pub protection_generation: usize,
267}
268
269/// Stable redacted teardown snapshot.
270#[derive(Clone, Copy, Debug, Eq, PartialEq)]
271pub struct CleanupSnapshot {
272    /// Stable wipe evidence identifier.
273    pub wipe: &'static str,
274    /// Stable physical protection identifier.
275    pub physical_protection: &'static str,
276    /// Stable accounting identifier.
277    pub accounting: &'static str,
278    /// Stable lifecycle identifier.
279    pub lifecycle: &'static str,
280    /// Address-free allocation-presence identifier.
281    pub allocation: &'static str,
282    /// Earliest pending teardown stage, if any.
283    pub pending_stage: Option<&'static str>,
284    /// Redacted provider substage, if any.
285    pub pending_substage: Option<&'static str>,
286    /// Provider health after failure, if applicable.
287    pub provider_health: Option<&'static str>,
288    /// Retry attempt, if applicable.
289    pub retry_attempt: Option<usize>,
290}
291
292/// Stable redacted provider-wide resource snapshot.
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294pub struct ProviderSnapshot {
295    /// Stable provider-health identifier.
296    pub health: &'static str,
297    /// Provider health generation.
298    pub health_generation: usize,
299    /// Provider protection generation.
300    pub protection_generation: usize,
301    /// Active and reserved identities.
302    pub active_and_reserved: usize,
303    /// Retryable quarantined identities.
304    pub quarantined: usize,
305    /// Permanently quarantined identities.
306    pub permanently_quarantined: usize,
307    /// Address-free tombstone identities.
308    pub tombstoned: usize,
309    /// Conservatively charged logical bytes.
310    pub charged_logical_bytes: usize,
311    /// Conservatively charged effective pages.
312    pub charged_effective_pages: usize,
313}
314
315impl super::ProviderReport {
316    /// Returns a stable provider snapshot without allocation addresses.
317    #[must_use]
318    pub const fn snapshot(self) -> ProviderSnapshot {
319        ProviderSnapshot {
320            health: provider_health_id(self.health),
321            health_generation: self.health_generation,
322            protection_generation: self.protection_generation,
323            active_and_reserved: self.active_and_reserved,
324            quarantined: self.quarantined,
325            permanently_quarantined: self.permanently_quarantined,
326            tombstoned: self.tombstoned,
327            charged_logical_bytes: self.charged_logical_bytes,
328            charged_effective_pages: self.charged_effective_pages,
329        }
330    }
331}
332
333impl<Level: AssuranceLevel> AssuranceToken<'_, Level> {
334    /// Reports token-scoped posture without claiming allocation protection.
335    #[must_use]
336    pub fn report(&self) -> AssuranceReport {
337        let runtime = crate::runtime::backend_report();
338        let mut encode_backend = runtime.encode_backend;
339        let mut strict_decode_backend = runtime.strict_decode_backend;
340        let mut secret_decode_backend = runtime.secret_decode_backend;
341        let generations = self.generations();
342        encode_backend.health_generation = generations.ordinary_backend;
343        strict_decode_backend.health_generation = generations.ordinary_backend;
344        secret_decode_backend.health_generation = generations.secret_algorithm;
345        let evidence = self.evidence();
346        AssuranceReport {
347            encode_backend,
348            strict_decode_backend,
349            secret_decode_backend,
350            generations,
351            wipe_posture: evidence.map_or(
352                runtime.wipe_posture,
353                super::context::AttestationEvidence::wipe_posture,
354            ),
355            result_gate_posture: evidence.map_or(
356                runtime.ct_gate_posture,
357                super::context::AttestationEvidence::speculation_posture,
358            ),
359            attestation_posture: if evidence.is_some() {
360                AttestationPosture::Attested
361            } else {
362                AttestationPosture::NotAttested
363            },
364            secret_policy_posture: if Self::requires_attestation() {
365                SecretPolicyPosture::HighAssuranceAttested
366            } else {
367                SecretPolicyPosture::BestEffort
368            },
369            wasm_artifact_posture: runtime.wasm_artifact_posture,
370            wasm_runtime_posture: runtime.wasm_runtime_posture,
371        }
372    }
373}
374
375impl CleanupReport {
376    /// Returns a stable snapshot without retaining allocation identity.
377    #[must_use]
378    pub const fn snapshot(self) -> CleanupSnapshot {
379        CleanupSnapshot {
380            wipe: wipe_id(self.wipe),
381            physical_protection: physical_id(self.physical_protection),
382            accounting: accounting_id(self.accounting),
383            lifecycle: lifecycle_id(self.lifecycle),
384            allocation: AllocationPosture::Absent.as_str(),
385            pending_stage: None,
386            pending_substage: None,
387            provider_health: None,
388            retry_attempt: None,
389        }
390    }
391}
392
393impl CleanupError {
394    /// Returns a stable redacted snapshot without an allocation address.
395    #[must_use]
396    pub const fn snapshot(self) -> CleanupSnapshot {
397        let allocation = if matches!(self.lifecycle, LifecyclePosture::Tombstoned { .. }) {
398            AllocationPosture::UnknownNoAddressRetained
399        } else {
400            AllocationPosture::Present
401        };
402        CleanupSnapshot {
403            wipe: wipe_id(self.wipe),
404            physical_protection: physical_id(self.physical_protection),
405            accounting: accounting_id(self.accounting),
406            lifecycle: lifecycle_id(self.lifecycle),
407            allocation: allocation.as_str(),
408            pending_stage: Some(pending_stage_id(self.pending_stage)),
409            pending_substage: Some(journal_id(self.pending_substage)),
410            provider_health: Some(provider_health_id(self.provider_health)),
411            retry_attempt: Some(self.retry_attempt),
412        }
413    }
414}
415
416const fn wipe_id(value: WipeEvidence) -> &'static str {
417    match value {
418        WipeEvidence::WipeNotCompleted => "wipe-not-completed",
419        WipeEvidence::WipedBestEffort => "wiped-best-effort",
420        WipeEvidence::WipedAttested => "wiped-attested",
421    }
422}
423
424const fn physical_id(value: PhysicalProtection) -> &'static str {
425    match value {
426        PhysicalProtection::ProtectionAttested => "protection-attested",
427        PhysicalProtection::ProtectionConfirmedAbsent => "protection-confirmed-absent",
428        PhysicalProtection::ProtectionUnknown => "protection-unknown",
429    }
430}
431
432const fn accounting_id(value: AccountingPosture) -> &'static str {
433    match value {
434        AccountingPosture::Charged => "charged",
435        AccountingPosture::Reconciled => "reconciled",
436    }
437}
438
439const fn lifecycle_id(value: LifecyclePosture) -> &'static str {
440    match value {
441        LifecyclePosture::Live => "live",
442        LifecyclePosture::Closing { .. } => "closing",
443        LifecyclePosture::Quarantined { .. } => "quarantined",
444        LifecyclePosture::PermanentlyQuarantined { .. } => "permanently-quarantined",
445        LifecyclePosture::Tombstoned { .. } => "tombstoned",
446        LifecyclePosture::Closed => "closed",
447    }
448}
449
450const fn pending_stage_id(value: PendingStage) -> &'static str {
451    match value {
452        PendingStage::Wipe => "wipe",
453        PendingStage::ProtectionRemoval => "protection-removal",
454        PendingStage::AccountingReconciliation => "accounting-reconciliation",
455        PendingStage::Disposal => "disposal",
456    }
457}
458
459const fn journal_id(value: JournalDisposition) -> &'static str {
460    match value {
461        JournalDisposition::NotApplied => "not-applied",
462        JournalDisposition::Applied => "applied",
463        JournalDisposition::Indeterminate => "indeterminate",
464    }
465}
466
467const fn provider_health_id(value: ProviderHealth) -> &'static str {
468    match value {
469        ProviderHealth::Healthy => "healthy",
470        ProviderHealth::Degraded => "degraded",
471        ProviderHealth::Exhausted => "exhausted",
472        ProviderHealth::Shutdown => "shutdown",
473    }
474}