use arkhe_kernel::abi::{ExternalId, Tick, TypeCode};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use crate::actor::ActorId;
use crate::brand::ShellId;
use crate::component::BoundedString;
use crate::pii::DekId;
use crate::user::UserId;
use crate::ArkheEvent;
pub trait ArkheEvent:
crate::__sealed::__Sealed + Serialize + for<'de> Deserialize<'de> + 'static
{
const TYPE_CODE: u32;
const SCHEMA_VERSION: u16;
fn type_code() -> TypeCode {
TypeCode(Self::TYPE_CODE)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct SemVer {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl SemVer {
#[inline]
#[must_use]
pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
Self {
major,
minor,
patch,
}
}
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum RuntimeSignatureClass {
None = 0,
Ed25519 = 1,
MlDsa65 = 2,
Hybrid = 3,
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ComplianceTier {
Tier0 = 0,
Tier1 = 1,
Tier2 = 2,
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ObserverTrapClass {
Panic = 0,
BudgetExceeded = 1,
CapabilityDenied = 2,
Other = 3,
}
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ProgressScope {
Region(BoundedString<64>),
KmsIdentifier(BoundedString<64>),
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F01, schema_version = 1)]
pub struct RuntimeBootstrap {
pub schema_version: u16,
pub l0_semver: SemVer,
pub runtime_semver: SemVer,
pub manifest_digest: [u8; 32],
#[arkhe(canonical_sort)]
pub typecode_pins: Vec<TypeCode>,
pub bootstrap_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F02, schema_version = 1)]
pub struct UserErasureScheduled {
pub schema_version: u16,
pub user: UserId,
pub scheduled_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F03, schema_version = 1)]
pub struct UserErasureCompleted {
pub schema_version: u16,
pub user: UserId,
pub dek_shred_tick: Tick,
pub attestation_class: RuntimeSignatureClass,
pub attestation_bytes: Bytes,
pub transparency_log_index: u64,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F04, schema_version = 1)]
pub struct BackupErasurePropagated {
pub schema_version: u16,
pub user: UserId,
pub region: BoundedString<32>,
pub applied_tick: Tick,
pub receipt_class: RuntimeSignatureClass,
pub receipt_bytes: Bytes,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F05, schema_version = 1)]
pub struct GdprPolicyViolation {
pub schema_version: u16,
pub actor: ActorId,
pub attempted_tick: Tick,
pub action_type_code: TypeCode,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F06, schema_version = 1)]
pub struct SignatureClassPolicy {
pub schema_version: u16,
pub shell_id: ShellId,
pub class: RuntimeSignatureClass,
pub effective_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F07, schema_version = 1)]
pub struct CrossShellActivity {
pub schema_version: u16,
pub actor: ActorId,
pub target_shell_id: ShellId,
pub record_shell_id: ShellId,
pub detected_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F08, schema_version = 1)]
pub struct PerRegionErasureProgress {
pub schema_version: u16,
pub user: UserId,
pub scope: ProgressScope,
pub shred_tick: Tick,
pub attestation_class: RuntimeSignatureClass,
pub attestation_bytes: Bytes,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F09, schema_version = 1)]
pub struct DekMigrationCompleted {
pub schema_version: u16,
pub user: UserId,
pub old_dek_id: DekId,
pub new_dek_id: DekId,
pub migrated_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0A, schema_version = 1)]
pub struct ComplianceTierChange {
pub schema_version: u16,
pub old_tier: ComplianceTier,
pub new_tier: ComplianceTier,
pub effective_tick: Tick,
pub operator: ExternalId,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0B, schema_version = 1)]
pub struct HookModuleRegister {
pub schema_version: u16,
pub manifest_digest: [u8; 32],
pub module_digest: [u8; 32],
pub register_tick: Tick,
pub attestation_class: RuntimeSignatureClass,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0C, schema_version = 1)]
pub struct ObserverQuarantine {
pub schema_version: u16,
pub observer_module_digest: [u8; 32],
pub quarantine_tick: Tick,
pub trap_class: ObserverTrapClass,
pub attestation_class: RuntimeSignatureClass,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Attestation {
inner: Vec<u8>,
}
impl Attestation {
#[must_use]
pub fn from_bytes(bytes: [u8; 64]) -> Self {
Self {
inner: bytes.to_vec(),
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
}
impl Serialize for Attestation {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.inner.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Attestation {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let inner: Vec<u8> = Vec::deserialize(deserializer)?;
if inner.len() != 64 {
return Err(serde::de::Error::custom(format!(
"Attestation: expected 64 bytes, got {}",
inner.len()
)));
}
Ok(Self { inner })
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AttestationSignerPolicy {
Predecessor,
OperatorRoot,
SelfSigned,
}
#[cfg(feature = "federation-archive-hardened")]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0D, schema_version = 1)]
pub struct ReplicaIdAllocation {
pub schema_version: u16,
pub federation_id: [u8; 16],
pub replica_id: u64,
pub allocation_nonce: u32,
pub effective_tick: Tick,
pub registry_attestation: Attestation,
}
#[cfg(feature = "audit-receipt-key-identified")]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0E, schema_version = 1)]
pub struct AuditReceiptKeyPolicy {
pub schema_version: u16,
pub key_id: [u8; 16],
pub algorithm: RuntimeSignatureClass,
pub public_key: Bytes,
pub predecessor_key_id: Option<[u8; 16]>,
pub effective_tick: Tick,
pub retirement_tick: Option<Tick>,
pub signer_policy: AttestationSignerPolicy,
pub attestation: Attestation,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn runtime_bootstrap_serde_roundtrip() {
let rb = RuntimeBootstrap {
schema_version: 1,
l0_semver: SemVer::new(0, 11, 0),
runtime_semver: SemVer::new(0, 11, 0),
manifest_digest: [0xABu8; 32],
typecode_pins: vec![TypeCode(0x0003_0001), TypeCode(0x0003_0002)],
bootstrap_tick: Tick(1),
};
let bytes = postcard::to_stdvec(&rb).unwrap();
let back: RuntimeBootstrap = postcard::from_bytes(&bytes).unwrap();
assert_eq!(rb, back);
}
#[test]
fn per_region_progress_with_region_scope_roundtrip() {
let ev = PerRegionErasureProgress {
schema_version: 1,
user: crate::user::UserId::new(arkhe_kernel::abi::EntityId::new(42).unwrap()),
scope: ProgressScope::Region(BoundedString::<64>::new("eu-west-1").unwrap()),
shred_tick: Tick(100),
attestation_class: RuntimeSignatureClass::Ed25519,
attestation_bytes: Bytes::from_static(&[0u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: PerRegionErasureProgress = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[test]
fn core_event_type_code_pins_match_spec() {
assert_eq!(RuntimeBootstrap::TYPE_CODE, 0x0003_0F01);
assert_eq!(UserErasureScheduled::TYPE_CODE, 0x0003_0F02);
assert_eq!(UserErasureCompleted::TYPE_CODE, 0x0003_0F03);
assert_eq!(BackupErasurePropagated::TYPE_CODE, 0x0003_0F04);
assert_eq!(GdprPolicyViolation::TYPE_CODE, 0x0003_0F05);
assert_eq!(SignatureClassPolicy::TYPE_CODE, 0x0003_0F06);
assert_eq!(CrossShellActivity::TYPE_CODE, 0x0003_0F07);
assert_eq!(PerRegionErasureProgress::TYPE_CODE, 0x0003_0F08);
assert_eq!(DekMigrationCompleted::TYPE_CODE, 0x0003_0F09);
assert_eq!(ComplianceTierChange::TYPE_CODE, 0x0003_0F0A);
assert_eq!(HookModuleRegister::TYPE_CODE, 0x0003_0F0B);
assert_eq!(ObserverQuarantine::TYPE_CODE, 0x0003_0F0C);
#[cfg(feature = "federation-archive-hardened")]
assert_eq!(ReplicaIdAllocation::TYPE_CODE, 0x0003_0F0D);
#[cfg(feature = "audit-receipt-key-identified")]
assert_eq!(AuditReceiptKeyPolicy::TYPE_CODE, 0x0003_0F0E);
}
#[test]
fn hook_module_register_serde_roundtrip() {
let ev = HookModuleRegister {
schema_version: 1,
manifest_digest: [0xAAu8; 32],
module_digest: [0xBBu8; 32],
register_tick: Tick(123),
attestation_class: RuntimeSignatureClass::None,
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: HookModuleRegister = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[test]
fn hook_module_register_type_code_matches_typecode_constant() {
assert_eq!(
HookModuleRegister::TYPE_CODE,
crate::typecode::core_event::HOOK_MODULE_REGISTER
);
}
#[test]
fn observer_quarantine_serde_roundtrip() {
let ev = ObserverQuarantine {
schema_version: 1,
observer_module_digest: [0xCCu8; 32],
quarantine_tick: Tick(456),
trap_class: ObserverTrapClass::Panic,
attestation_class: RuntimeSignatureClass::None,
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: ObserverQuarantine = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[test]
fn observer_quarantine_type_code_matches_typecode_constant() {
assert_eq!(
ObserverQuarantine::TYPE_CODE,
crate::typecode::core_event::OBSERVER_QUARANTINE
);
}
#[test]
fn observer_trap_class_wire_discriminants_stable() {
for (variant, expected_disc) in [
(ObserverTrapClass::Panic, 0u8),
(ObserverTrapClass::BudgetExceeded, 1u8),
(ObserverTrapClass::CapabilityDenied, 2u8),
(ObserverTrapClass::Other, 3u8),
] {
let bytes = postcard::to_stdvec(&variant).unwrap();
assert_eq!(
bytes,
vec![expected_disc],
"ObserverTrapClass::{variant:?} discriminant drift"
);
}
}
#[test]
fn observer_quarantine_with_each_trap_class_roundtrips() {
for trap_class in [
ObserverTrapClass::Panic,
ObserverTrapClass::BudgetExceeded,
ObserverTrapClass::CapabilityDenied,
ObserverTrapClass::Other,
] {
let ev = ObserverQuarantine {
schema_version: 1,
observer_module_digest: [0u8; 32],
quarantine_tick: Tick(1),
trap_class,
attestation_class: RuntimeSignatureClass::None,
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: ObserverQuarantine = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev.trap_class, back.trap_class);
}
}
#[test]
fn semver_roundtrip_is_stable() {
let v = SemVer::new(0, 11, 0);
let a = postcard::to_stdvec(&v).unwrap();
let b = postcard::to_stdvec(&v).unwrap();
assert_eq!(a, b);
let back: SemVer = postcard::from_bytes(&a).unwrap();
assert_eq!(back, v);
}
#[cfg(feature = "federation-archive-hardened")]
#[test]
fn replica_id_allocation_serde_roundtrip() {
let ev = ReplicaIdAllocation {
schema_version: 1,
federation_id: [0xF1u8; 16],
replica_id: 7,
allocation_nonce: 0xCAFE_BABE,
effective_tick: Tick(1234),
registry_attestation: Attestation::from_bytes([0x55u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: ReplicaIdAllocation = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[cfg(feature = "federation-archive-hardened")]
#[test]
fn replica_id_allocation_high_replica_id_preserves_u64_width() {
let high_replica = 0x1234_5678_9ABC_DEF0u64;
assert!(high_replica > u64::from(u32::MAX));
let ev = ReplicaIdAllocation {
schema_version: 1,
federation_id: [0xA5u8; 16],
replica_id: high_replica,
allocation_nonce: 0xDEAD_BEEF,
effective_tick: Tick(99_999),
registry_attestation: Attestation::from_bytes([0x33u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: ReplicaIdAllocation = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
assert_eq!(back.replica_id, high_replica);
}
#[cfg(feature = "federation-archive-hardened")]
#[test]
fn replica_id_allocation_type_code_matches_typecode_constant() {
assert_eq!(
ReplicaIdAllocation::TYPE_CODE,
crate::typecode::core_event::REPLICA_ID_ALLOCATION
);
}
#[cfg(feature = "audit-receipt-key-identified")]
#[test]
fn audit_receipt_key_policy_serde_roundtrip_with_genesis_entry() {
let ev = AuditReceiptKeyPolicy {
schema_version: 1,
key_id: [0xABu8; 16],
algorithm: RuntimeSignatureClass::Ed25519,
public_key: Bytes::from_static(&[0u8; 32]),
predecessor_key_id: None,
effective_tick: Tick(0),
retirement_tick: None,
signer_policy: AttestationSignerPolicy::SelfSigned,
attestation: Attestation::from_bytes([0x77u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[cfg(feature = "audit-receipt-key-identified")]
#[test]
fn audit_receipt_key_policy_serde_roundtrip_with_rotation_entry() {
let ev = AuditReceiptKeyPolicy {
schema_version: 1,
key_id: [0xCDu8; 16],
algorithm: RuntimeSignatureClass::MlDsa65,
public_key: Bytes::from_static(&[0xEEu8; 1952]), predecessor_key_id: Some([0xABu8; 16]),
effective_tick: Tick(100),
retirement_tick: Some(Tick(1000)),
signer_policy: AttestationSignerPolicy::Predecessor,
attestation: Attestation::from_bytes([0x99u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
}
#[cfg(feature = "audit-receipt-key-identified")]
#[test]
fn audit_receipt_key_policy_distinct_bytes_preserve_full_16_byte_widths() {
let key_id: [u8; 16] = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let predecessor_key_id: [u8; 16] = [
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xF0, 0x0D, 0xFA, 0xCE, 0x12, 0x34,
0x56, 0x78,
];
let ev = AuditReceiptKeyPolicy {
schema_version: 1,
key_id,
algorithm: RuntimeSignatureClass::Ed25519,
public_key: Bytes::from_static(&[0xC3u8; 32]),
predecessor_key_id: Some(predecessor_key_id),
effective_tick: Tick(2_500),
retirement_tick: Some(Tick(5_000)),
signer_policy: AttestationSignerPolicy::OperatorRoot,
attestation: Attestation::from_bytes([0x44u8; 64]),
};
let bytes = postcard::to_stdvec(&ev).unwrap();
let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ev, back);
assert_eq!(back.key_id, key_id);
assert_eq!(back.predecessor_key_id, Some(predecessor_key_id));
assert_eq!(back.key_id[8..16], key_id[8..16]);
}
#[cfg(feature = "audit-receipt-key-identified")]
#[test]
fn audit_receipt_key_policy_type_code_matches_typecode_constant() {
assert_eq!(
AuditReceiptKeyPolicy::TYPE_CODE,
crate::typecode::core_event::AUDIT_RECEIPT_KEY_POLICY
);
}
#[cfg(all(
feature = "federation-archive-hardened",
feature = "audit-receipt-key-identified"
))]
#[test]
fn forward_looking_events_are_define_only() {
assert_eq!(ReplicaIdAllocation::SCHEMA_VERSION, 1);
assert_eq!(AuditReceiptKeyPolicy::SCHEMA_VERSION, 1);
assert_eq!(ReplicaIdAllocation::TYPE_CODE, 0x0003_0F0D);
assert_eq!(AuditReceiptKeyPolicy::TYPE_CODE, 0x0003_0F0E);
}
#[test]
fn attestation_serde_round_trip_preserves_64_bytes() {
let payload: [u8; 64] = [0xA1; 64];
let att = Attestation::from_bytes(payload);
let bytes = postcard::to_stdvec(&att).unwrap();
let back: Attestation = postcard::from_bytes(&bytes).unwrap();
assert_eq!(att, back);
assert_eq!(back.as_bytes(), &payload);
assert_eq!(back.as_bytes().len(), 64);
}
#[test]
fn attestation_wire_format_byte_identical_to_vec_u8_length_64() {
let payload: [u8; 64] = [0xC7; 64];
let att_bytes = postcard::to_stdvec(&Attestation::from_bytes(payload)).unwrap();
let vec_bytes = postcard::to_stdvec(&payload.to_vec()).unwrap();
assert_eq!(att_bytes, vec_bytes);
}
#[test]
fn attestation_deserialize_rejects_short_payload() {
let short_payload: Vec<u8> = vec![0xBB; 32];
let bytes = postcard::to_stdvec(&short_payload).unwrap();
let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
assert!(
result.is_err(),
"32-byte payload must be rejected as not-64-bytes"
);
}
#[test]
fn attestation_deserialize_rejects_long_payload() {
let long_payload: Vec<u8> = vec![0xCC; 65];
let bytes = postcard::to_stdvec(&long_payload).unwrap();
let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
assert!(
result.is_err(),
"65-byte payload must be rejected as not-64-bytes"
);
}
#[test]
fn attestation_deserialize_rejects_empty_payload() {
let empty_payload: Vec<u8> = Vec::new();
let bytes = postcard::to_stdvec(&empty_payload).unwrap();
let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
assert!(
result.is_err(),
"empty payload must be rejected as not-64-bytes"
);
}
#[test]
fn attestation_signer_policy_round_trip_all_three_variants() {
for variant in [
AttestationSignerPolicy::Predecessor,
AttestationSignerPolicy::OperatorRoot,
AttestationSignerPolicy::SelfSigned,
] {
let bytes = postcard::to_stdvec(&variant).unwrap();
let back: AttestationSignerPolicy = postcard::from_bytes(&bytes).unwrap();
assert_eq!(variant, back);
}
}
}