Skip to main content

arkhe_forge_core/
lib.rs

1//! # ArkheForge Runtime — L1 Primitives (`arkhe-forge-core`)
2//!
3//! Core 5 primitive types (User / Actor / Space / Entry / Activity), sealed
4//! `ArkheComponent` / `ArkheAction` / `ArkheEvent` traits, `ShellBrand<'s>`
5//! invariant-lifetime isolation, deterministic entity-id derivation.
6//!
7//! Pure compute — no I/O, no time, no randomness (L0 A11 succession). Only
8//! depends on `arkhe-kernel` (L0) and `arkhe-macros` (derive provider).
9//! `arkhe-forge-platform` must not be pulled in (layer-independence directive).
10
11#![forbid(unsafe_code)]
12#![warn(missing_docs)]
13
14// Self-alias so macro-generated `::arkhe_forge_core::...` paths resolve when
15// the derive is applied inside this crate itself.
16extern crate self as arkhe_forge_core;
17
18pub mod action;
19pub mod activity;
20pub mod actor;
21pub mod brand;
22pub mod bridge;
23pub mod component;
24pub mod context;
25pub mod entry;
26pub mod event;
27pub mod pii;
28pub mod pipeline;
29// Internal sealed-trait machinery — `__sealed::__Sealed` is exposed only so
30// the runtime derive macros can emit `impl __Sealed for UserType {}`.
31// Downstream crates must never reference this module; the dylint CI gate
32// flags any such usage. See `sealed.rs` for the soft-seal rationale.
33#[doc(hidden)]
34#[path = "sealed.rs"]
35pub mod __sealed;
36pub mod space;
37pub mod typecode;
38pub mod user;
39
40/// Re-exports of the Runtime-layer derive + attribute macros from
41/// `arkhe-forge-macros`. `#[arkhe_pure]` is the E14.L1 Subset-Rust
42/// purity attribute for `Action::compute` bodies.
43pub use arkhe_forge_macros::{arkhe_pure, ArkheAction, ArkheComponent, ArkheEvent};
44
45use arkhe_kernel::abi::{EntityId, InstanceId, Tick, TypeCode};
46
47/// ArkheForge Runtime semver triple — matches the repo release.
48pub const RUNTIME_SEMVER: (u16, u16, u16) = (0, 13, 0);
49
50/// Maximum retry count for zero-digest collision avoidance.
51pub const MAX_ID_DERIVE_RETRIES: u32 = 16;
52
53/// Deterministic entity-id derivation. Pure — no hidden state.
54///
55/// Given a 256-bit non-exportable `world_seed`, a per-runtime `instance_id`,
56/// the primitive's `type_code`, and the spawning `tick` / `seq`, returns a
57/// collision-resistant `EntityId`. Zero-valued outputs are retried by
58/// incrementing `seq` up to [`MAX_ID_DERIVE_RETRIES`]; exhaustion returns
59/// `None` so the caller can emit an `IdExhaustion` Failure event.
60#[must_use]
61pub fn derive_entity_id(
62    world_seed: &[u8; 32],
63    instance_id: InstanceId,
64    type_code: TypeCode,
65    tick: Tick,
66    seq: u32,
67) -> Option<EntityId> {
68    let domain_key = blake3::derive_key("arkhe-forge-entity-id", world_seed);
69    for bump in 0..MAX_ID_DERIVE_RETRIES {
70        let candidate_seq = seq.wrapping_add(bump);
71        let mut h = blake3::Hasher::new_keyed(&domain_key);
72        h.update(&instance_id.get().to_be_bytes());
73        h.update(&type_code.0.to_be_bytes());
74        h.update(&tick.0.to_be_bytes());
75        h.update(&candidate_seq.to_be_bytes());
76        let out = h.finalize();
77        let digest = out.as_bytes();
78        let mut first_eight = [0u8; 8];
79        first_eight.copy_from_slice(&digest[..8]);
80        let raw = u64::from_be_bytes(first_eight);
81        if let Some(id) = EntityId::new(raw) {
82            return Some(id);
83        }
84    }
85    None
86}
87
88#[cfg(test)]
89#[allow(clippy::unwrap_used, clippy::expect_used)]
90mod lib_tests {
91    use super::*;
92
93    fn instance(v: u64) -> InstanceId {
94        InstanceId::new(v).unwrap()
95    }
96
97    #[test]
98    fn derive_entity_id_is_deterministic() {
99        let seed = [0x11u8; 32];
100        let iid = instance(1);
101        let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(42), 0);
102        let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(42), 0);
103        assert_eq!(a, b);
104        assert!(a.is_some());
105    }
106
107    #[test]
108    fn derive_entity_id_differs_across_type_code() {
109        let seed = [0x11u8; 32];
110        let iid = instance(1);
111        let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 0);
112        let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0002), Tick(1), 0);
113        assert_ne!(a, b);
114    }
115
116    #[test]
117    fn derive_entity_id_differs_across_instance() {
118        let seed = [0x11u8; 32];
119        let a = derive_entity_id(&seed, instance(1), TypeCode(0x0001_0001), Tick(1), 0);
120        let b = derive_entity_id(&seed, instance(2), TypeCode(0x0001_0001), Tick(1), 0);
121        assert_ne!(a, b);
122    }
123
124    #[test]
125    fn derive_entity_id_differs_across_world_seed() {
126        let s1 = [0x01u8; 32];
127        let s2 = [0x02u8; 32];
128        let iid = instance(1);
129        let a = derive_entity_id(&s1, iid, TypeCode(0x0001_0001), Tick(1), 0);
130        let b = derive_entity_id(&s2, iid, TypeCode(0x0001_0001), Tick(1), 0);
131        assert_ne!(a, b);
132    }
133
134    #[test]
135    fn derive_entity_id_seq_changes_output() {
136        let seed = [0x11u8; 32];
137        let iid = instance(1);
138        let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 0);
139        let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 1);
140        assert_ne!(a, b);
141    }
142}