use arkhe_kernel::abi::{CapabilityMask, InstanceId, Principal, Tick};
use arkhe_kernel::state::{ActionContext as KernelActionContext, Op};
use crate::action::ActionCompute;
use crate::context::ActionContext;
pub fn kernel_compute<A>(action: &A, kernel_ctx: &KernelActionContext<'_>) -> Vec<Op>
where
A: ActionCompute,
{
kernel_compute_inner(action, kernel_ctx.instance_id, kernel_ctx.now)
}
fn kernel_compute_inner<A>(action: &A, instance_id: InstanceId, now: Tick) -> Vec<Op>
where
A: ActionCompute,
{
let mut forge_ctx = ActionContext::new(
[0u8; 32],
instance_id,
now,
Principal::System,
CapabilityMask::SYSTEM,
);
if <A as ActionCompute>::compute(action, &mut forge_ctx).is_ok() {
forge_ctx.drain_ops()
} else {
Vec::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use arkhe_kernel::abi::{EntityId, TypeCode};
use crate::event::ArkheEvent as _;
use crate::event::UserErasureScheduled;
use crate::user::{
AuthCredential, AuthKind, GdprEraseUser, GdprStatus, KdfKind, KdfParams, RegisterUser,
UserId, UserProfile,
};
fn fixture_args() -> (InstanceId, Tick) {
(InstanceId::new(7).unwrap(), Tick(99))
}
#[test]
fn ok_compute_returns_drained_ops() {
let (iid, tick) = fixture_args();
let action = GdprEraseUser {
schema_version: 1,
user: UserId::new(EntityId::new(42).unwrap()),
};
let ops = kernel_compute_inner(&action, iid, tick);
assert_eq!(ops.len(), 1, "GdprEraseUser emits one event Op");
match &ops[0] {
Op::EmitEvent {
actor,
event_type_code,
event_bytes: _,
} => {
assert!(actor.is_none());
assert_eq!(*event_type_code, TypeCode(UserErasureScheduled::TYPE_CODE));
}
other => panic!("expected EmitEvent, got {:?}", other),
}
}
#[test]
fn err_compute_returns_empty_vec() {
let (iid, tick) = fixture_args();
let action = RegisterUser {
schema_version: 1,
profile: UserProfile {
schema_version: 1,
created_tick: Tick(0),
primary_auth_kind: AuthKind::Passkey,
gdpr_status: GdprStatus::Active,
},
credential: AuthCredential {
schema_version: 1,
kind: AuthKind::Passkey,
kdf: KdfKind::Argon2id,
salt: [0u8; 16],
credential_hash: [0u8; 32],
kdf_params: KdfParams {
m_cost: 1024,
t_cost: 1,
p_cost: 1,
},
expires_tick: None,
bound_tick: Tick(0),
},
};
let ops = kernel_compute_inner(&action, iid, tick);
assert!(
ops.is_empty(),
"weak-KDF RegisterUser must collapse to empty Op vec",
);
}
#[test]
fn determinism_same_input_same_ops() {
let (iid, tick) = fixture_args();
let action = GdprEraseUser {
schema_version: 1,
user: UserId::new(EntityId::new(101).unwrap()),
};
let a = kernel_compute_inner(&action, iid, tick);
let b = kernel_compute_inner(&action, iid, tick);
assert_eq!(a.len(), b.len(), "Op count must match");
for (op_a, op_b) in a.iter().zip(b.iter()) {
let bytes_a = postcard::to_allocvec(op_a).expect("encode Op a");
let bytes_b = postcard::to_allocvec(op_b).expect("encode Op b");
assert_eq!(
bytes_a, bytes_b,
"bridge output must be byte-identical across runs",
);
}
}
}