rvm_types/witness.rs
1//! Witness record types for the audit subsystem.
2//!
3//! Every privileged action in RVM emits a compact, immutable audit record.
4//! This is a core invariant (INV-3): **no witness, no mutation**.
5//!
6//! The witness record is exactly 64 bytes, cache-line aligned, with FNV-1a
7//! hash chaining for tamper evidence. See ADR-134 for the full specification.
8
9/// A single witness record. Exactly 64 bytes, cache-line aligned.
10///
11/// All fields are little-endian. The record is `#[repr(C, align(64))]` to
12/// guarantee layout and alignment on all target architectures (`AArch64`,
13/// RISC-V, x86-64).
14///
15/// # Layout
16///
17/// | Offset | Size | Field | Description |
18/// |--------|------|----------------------|-------------|
19/// | 0 | 8 | `sequence` | Monotonic sequence number |
20/// | 8 | 8 | `timestamp_ns` | Nanosecond timestamp |
21/// | 16 | 1 | `action_kind` | Privileged action discriminant |
22/// | 17 | 1 | `proof_tier` | Proof tier (1, 2, or 3) |
23/// | 18 | 1 | `flags` | Action-specific flags |
24/// | 19 | 1 | `_reserved` | Reserved (must be zero) |
25/// | 20 | 4 | `actor_partition_id` | Actor partition |
26/// | 24 | 8 | `target_object_id` | Target object |
27/// | 32 | 4 | `capability_hash` | Truncated cap hash |
28/// | 36 | 8 | `payload` | Action-specific data |
29/// | 44 | 4 | `prev_hash` | FNV-1a chain link |
30/// | 48 | 4 | `record_hash` | FNV-1a self-integrity |
31/// | 52 | 8 | `aux` | Secondary payload / TEE sig |
32/// | 60 | 4 | `_pad` | Padding to 64 bytes |
33#[derive(Debug, Clone, Copy)]
34#[repr(C, align(64))]
35pub struct WitnessRecord {
36 /// Monotonic sequence number. Provides global ordering of all privileged actions.
37 pub sequence: u64,
38 /// Nanosecond timestamp from the system timer (`CNTVCT_EL0` / `rdtsc`).
39 pub timestamp_ns: u64,
40 /// Which privileged action was performed (see [`ActionKind`]).
41 pub action_kind: u8,
42 /// Which proof tier authorized this action (1 = P1, 2 = P2, 3 = P3).
43 pub proof_tier: u8,
44 /// Action-specific flags (interpretation varies by `action_kind`).
45 pub flags: u8,
46 /// Reserved for future use. Must be zero.
47 reserved: u8,
48 /// Partition that performed the action.
49 pub actor_partition_id: u32,
50 /// Object acted upon: partition, region, capability, etc.
51 pub target_object_id: u64,
52 /// Truncated FNV-1a hash of the capability used (not the full token).
53 pub capability_hash: u32,
54 /// Action-specific data, packed by kind.
55 ///
56 /// Examples:
57 /// - `PartitionSplit`: `new_id_a` in bytes \[0..4\], `new_id_b` in bytes \[4..8\].
58 /// - `RegionTransfer`: `from_partition` in bytes \[0..4\], `to_partition` in bytes \[4..8\].
59 pub payload: [u8; 8],
60 /// FNV-1a hash of the previous record (chain link for tamper evidence).
61 pub prev_hash: u32,
62 /// FNV-1a hash of bytes \[0..44\] of this record (self-integrity).
63 pub record_hash: u32,
64 /// Secondary payload or TEE signature fragment.
65 pub aux: [u8; 8],
66 /// Padding to guarantee 64-byte total size.
67 pad: [u8; 4],
68}
69
70// Compile-time size assertion: the record MUST be exactly 64 bytes.
71const _: () = {
72 assert!(core::mem::size_of::<WitnessRecord>() == 64);
73};
74
75impl WitnessRecord {
76 /// Create a zeroed witness record (genesis / placeholder).
77 #[must_use]
78 pub const fn zeroed() -> Self {
79 Self {
80 sequence: 0,
81 timestamp_ns: 0,
82 action_kind: 0,
83 proof_tier: 0,
84 flags: 0,
85 reserved: 0,
86 actor_partition_id: 0,
87 target_object_id: 0,
88 capability_hash: 0,
89 payload: [0; 8],
90 prev_hash: 0,
91 record_hash: 0,
92 aux: [0; 8],
93 pad: [0; 4],
94 }
95 }
96}
97
98/// A 256-bit witness commitment hash.
99///
100/// Used to anchor state transitions in the RVM witness trail. This is
101/// a fixed-size value type suitable for embedding in `no_std` contexts
102/// without heap allocation.
103#[derive(Clone, Copy, PartialEq, Eq, Hash)]
104pub struct WitnessHash {
105 bytes: [u8; 32],
106}
107
108impl WitnessHash {
109 /// The zero hash, used as a sentinel for the genesis state.
110 pub const ZERO: Self = Self { bytes: [0u8; 32] };
111
112 /// Create a witness hash from raw bytes.
113 #[must_use]
114 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
115 Self { bytes }
116 }
117
118 /// Return the raw byte representation.
119 #[must_use]
120 pub const fn as_bytes(&self) -> &[u8; 32] {
121 &self.bytes
122 }
123
124 /// Check whether this is the zero (genesis) hash.
125 #[must_use]
126 pub const fn is_zero(&self) -> bool {
127 let mut i = 0;
128 while i < 32 {
129 if self.bytes[i] != 0 {
130 return false;
131 }
132 i += 1;
133 }
134 true
135 }
136}
137
138impl core::fmt::Debug for WitnessHash {
139 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140 write!(f, "WitnessHash(")?;
141 for byte in &self.bytes[..4] {
142 write!(f, "{byte:02x}")?;
143 }
144 write!(f, "..)")
145 }
146}
147
148impl core::fmt::Display for WitnessHash {
149 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150 for byte in &self.bytes {
151 write!(f, "{byte:02x}")?;
152 }
153 Ok(())
154 }
155}
156
157/// Privileged actions that produce witness records (ADR-134, Section 2).
158///
159/// Organized by subsystem. Hex values allow easy filtering by prefix in
160/// audit queries (0x0_ = partition, 0x1_ = capability, 0x2_ = memory, etc.).
161///
162/// If a privileged action exists without a corresponding kind, the system
163/// has an audit gap.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165#[repr(u8)]
166pub enum ActionKind {
167 // --- Partition lifecycle (0x01-0x0F) ---
168 /// A new partition was created.
169 PartitionCreate = 0x01,
170 /// A partition was destroyed and its resources freed.
171 PartitionDestroy = 0x02,
172 /// A partition was suspended (tasks paused).
173 PartitionSuspend = 0x03,
174 /// A suspended partition was resumed.
175 PartitionResume = 0x04,
176 /// A partition was split along a mincut boundary.
177 PartitionSplit = 0x05,
178 /// Two partitions were merged into one.
179 PartitionMerge = 0x06,
180 /// A partition was hibernated to dormant/cold storage.
181 PartitionHibernate = 0x07,
182 /// A hibernated partition was reconstructed from its receipt.
183 PartitionReconstruct = 0x08,
184 /// A partition was migrated to another node.
185 PartitionMigrate = 0x09,
186
187 // --- Capability operations (0x10-0x1F) ---
188 /// A capability was granted (copied) to another partition.
189 CapabilityGrant = 0x10,
190 /// A capability was revoked.
191 CapabilityRevoke = 0x11,
192 /// A capability was delegated (with depth decrement).
193 CapabilityDelegate = 0x12,
194 /// Delegation depth was increased (escalation).
195 CapabilityEscalate = 0x13,
196 /// Capability was attenuated during a partition split (DC-8).
197 CapabilityAttenuated = 0x14,
198
199 // --- Memory operations (0x20-0x2F) ---
200 /// A memory region was created.
201 RegionCreate = 0x20,
202 /// A memory region was destroyed.
203 RegionDestroy = 0x21,
204 /// A memory region was transferred to another partition.
205 RegionTransfer = 0x22,
206 /// A memory region was shared (read-only) with another partition.
207 RegionShare = 0x23,
208 /// A shared memory region was unshared.
209 RegionUnshare = 0x24,
210 /// A memory region was promoted to a warmer tier.
211 RegionPromote = 0x25,
212 /// A memory region was demoted to a colder tier.
213 RegionDemote = 0x26,
214 /// A stage-2 mapping was added for a memory region.
215 RegionMap = 0x27,
216 /// A stage-2 mapping was removed for a memory region.
217 RegionUnmap = 0x28,
218
219 // --- Communication (0x30-0x3F) ---
220 /// A communication edge was created between two partitions.
221 CommEdgeCreate = 0x30,
222 /// A communication edge was destroyed.
223 CommEdgeDestroy = 0x31,
224 /// An IPC message was sent.
225 IpcSend = 0x32,
226 /// An IPC message was received.
227 IpcReceive = 0x33,
228 /// A zero-copy memory share was established.
229 ZeroCopyShare = 0x34,
230 /// A notification signal was sent.
231 NotificationSignal = 0x35,
232
233 // --- Device operations (0x40-0x4F) ---
234 /// A device lease was granted.
235 DeviceLeaseGrant = 0x40,
236 /// A device lease was revoked.
237 DeviceLeaseRevoke = 0x41,
238 /// A device lease expired (time-bounded).
239 DeviceLeaseExpire = 0x42,
240 /// A device lease was renewed.
241 DeviceLeaseRenew = 0x43,
242
243 // --- Proof verification (0x50-0x5F) ---
244 /// A P1 capability check passed.
245 ProofVerifiedP1 = 0x50,
246 /// A P2 policy validation passed.
247 ProofVerifiedP2 = 0x51,
248 /// A P3 deep proof passed.
249 ProofVerifiedP3 = 0x52,
250 /// A proof was rejected.
251 ProofRejected = 0x53,
252 /// A proof was escalated to a higher tier.
253 ProofEscalated = 0x54,
254
255 // --- Scheduler decisions (0x60-0x6F) ---
256 /// Scheduler epoch boundary (bulk switch summary per DC-10).
257 SchedulerEpoch = 0x60,
258 /// Scheduler mode switched (Reflex / Flow / Recovery).
259 SchedulerModeSwitch = 0x61,
260 /// A task was spawned within a partition.
261 TaskSpawn = 0x62,
262 /// A task was terminated.
263 TaskTerminate = 0x63,
264 /// Scheduler triggered a structural split.
265 StructuralSplit = 0x64,
266 /// Scheduler triggered a structural merge.
267 StructuralMerge = 0x65,
268
269 // --- Recovery actions (0x70-0x7F) ---
270 /// System entered recovery mode.
271 RecoveryEnter = 0x70,
272 /// System exited recovery mode.
273 RecoveryExit = 0x71,
274 /// A recovery checkpoint was created.
275 CheckpointCreated = 0x72,
276 /// A recovery checkpoint was restored.
277 CheckpointRestored = 0x73,
278 /// Mincut budget was exceeded, stale cut used (DC-2 fallback).
279 MinCutBudgetExceeded = 0x74,
280 /// System entered degraded mode (DC-6).
281 DegradedModeEntered = 0x75,
282 /// System exited degraded mode.
283 DegradedModeExited = 0x76,
284
285 // --- Boot and attestation (0x80-0x8F) ---
286 /// Boot attestation record (genesis witness).
287 BootAttestation = 0x80,
288 /// Boot sequence completed successfully.
289 BootComplete = 0x81,
290 /// TEE-backed attestation record.
291 TeeAttestation = 0x82,
292
293 // --- Vector/Graph mutations (0x90-0x9F) ---
294 /// A vector was inserted into the coherence graph.
295 VectorPut = 0x90,
296 /// A vector was deleted from the coherence graph.
297 VectorDelete = 0x91,
298 /// A graph mutation occurred.
299 GraphMutation = 0x92,
300 /// Coherence scores were recomputed.
301 CoherenceRecomputed = 0x93,
302
303 // --- VMID management (0xA0-0xAF) ---
304 /// A physical VMID was reclaimed from a hibernated partition (DC-12).
305 VmidReclaim = 0xA0,
306 /// Migration timed out and was aborted (DC-7).
307 MigrationTimeout = 0xA1,
308
309 // --- External anchoring (0xB0-0xBF) ---
310 /// A commitment to an external, service-side record (e.g. a ruflo
311 /// ADR-322C evaluation receipt) was anchored into the witness chain
312 /// after independent verification (ADR-156).
313 ///
314 /// Anchoring records provenance only: the anchored record keeps the
315 /// assurance level it was produced under and does not acquire the
316 /// witness chain's guarantees (ADR-285 discipline).
317 AnchorExternalReceipt = 0xB0,
318
319 // --- Governed context namespace (0xC0-0xCF) ---
320 /// A versionless `ruv://` name was resolved to an immutable RVF revision.
321 ContextResolve = 0xC0,
322 /// A progressive context representation was read.
323 ContextRead = 0xC1,
324 /// An authorized context search enumerated candidate results.
325 ContextSearch = 0xC2,
326 /// A new immutable context revision was registered.
327 ContextPut = 0xC3,
328 /// A versionless alias was changed with compare-and-swap.
329 ContextAliasUpdate = 0xC4,
330 /// A context alias was tombstoned and its payload became unreachable.
331 ContextForget = 0xC5,
332 /// Execution of pinned context content was authorized.
333 ContextExecute = 0xC6,
334 /// A cryptographic receipt sealed a context witness epoch.
335 ContextEpochSeal = 0xC7,
336}
337
338impl ActionKind {
339 /// Return the subsystem prefix for this action kind.
340 ///
341 /// Useful for filtering audit queries by subsystem:
342 /// 0 = partition, 1 = capability, 2 = memory, 3 = communication,
343 /// 4 = device, 5 = proof, 6 = scheduler, 7 = recovery,
344 /// 8 = boot, 9 = graph, 0xA = VMID management,
345 /// 0xB = external anchoring, 0xC = governed context.
346 #[must_use]
347 pub const fn subsystem(self) -> u8 {
348 (self as u8) >> 4
349 }
350}
351
352/// FNV-1a hash over a byte slice.
353///
354/// Chosen for speed (< 50 ns for 64 bytes), not cryptographic strength.
355/// For tamper resistance against a capable adversary, use the optional
356/// TEE-backed `WitnessSigner` (ADR-134, Section 9).
357///
358/// Unrolls the per-byte loop by 8 for inputs >= 8 bytes while preserving
359/// standard FNV-1a byte-order sensitivity. The remainder is handled
360/// one byte at a time.
361#[inline]
362#[must_use]
363pub fn fnv1a_64(data: &[u8]) -> u64 {
364 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
365 const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
366
367 let mut hash: u64 = FNV_OFFSET;
368 let len = data.len();
369 let mut i = 0;
370
371 // Process 8 bytes at a time (unrolled), preserving standard FNV-1a
372 // per-byte XOR-then-multiply semantics for hash compatibility.
373 while i + 8 <= len {
374 hash ^= u64::from(data[i]);
375 hash = hash.wrapping_mul(FNV_PRIME);
376 hash ^= u64::from(data[i + 1]);
377 hash = hash.wrapping_mul(FNV_PRIME);
378 hash ^= u64::from(data[i + 2]);
379 hash = hash.wrapping_mul(FNV_PRIME);
380 hash ^= u64::from(data[i + 3]);
381 hash = hash.wrapping_mul(FNV_PRIME);
382 hash ^= u64::from(data[i + 4]);
383 hash = hash.wrapping_mul(FNV_PRIME);
384 hash ^= u64::from(data[i + 5]);
385 hash = hash.wrapping_mul(FNV_PRIME);
386 hash ^= u64::from(data[i + 6]);
387 hash = hash.wrapping_mul(FNV_PRIME);
388 hash ^= u64::from(data[i + 7]);
389 hash = hash.wrapping_mul(FNV_PRIME);
390 i += 8;
391 }
392
393 // Handle remaining bytes one at a time.
394 while i < len {
395 hash ^= u64::from(data[i]);
396 hash = hash.wrapping_mul(FNV_PRIME);
397 i += 1;
398 }
399
400 hash
401}
402
403/// FNV-1a hash truncated to 32 bits.
404#[inline]
405#[must_use]
406#[allow(clippy::cast_possible_truncation)]
407pub fn fnv1a_32(data: &[u8]) -> u32 {
408 // Intentional truncation: 64-bit hash folded to 32 bits.
409 fnv1a_64(data) as u32
410}
411
412/// Default witness ring buffer capacity in records.
413///
414/// 16 MiB / 64 bytes = 262,144 records.
415/// At 100,000 privileged actions per second this gives approximately 2.6
416/// seconds of hot storage before overflow drain is needed.
417pub const WITNESS_RING_CAPACITY: usize = 262_144;
418
419/// Witness record size in bytes.
420pub const WITNESS_RECORD_SIZE: usize = 64;