use crate::action::{Action, ActionType, quote_cost};
use crate::errors::{KernelError, KernelResult};
pub fn validate_action(action: &Action) -> KernelResult<()> {
if action.actor_id.trim().is_empty() {
return Err(KernelError::PolicyViolation(
"actor_id cannot be empty".to_string(),
));
}
if action.target.trim().is_empty() {
return Err(KernelError::PolicyViolation(
"target cannot be empty".to_string(),
));
}
if action.action_type.is_state_changing() && quote_cost(action) == 0 {
return Err(KernelError::PolicyViolation(
"state-changing action must have positive cost".to_string(),
));
}
if matches!(action.action_type, ActionType::Observe) {
check_visibility(&action.actor_id, &action.target)?;
}
Ok(())
}
pub fn check_visibility(_actor_id: &str, _target: &str) -> KernelResult<()> {
Ok(())
}
pub fn check_read_access(_actor_id: &str, _query_kind: &str) -> KernelResult<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::action::{Action, ActionType};
use super::validate_action;
#[test]
fn reject_empty_actor() {
let action = Action {
actor_id: "".to_string(),
action_type: ActionType::Mutate,
target: "t".to_string(),
payload: json!({}),
timestamp: None,
};
assert!(validate_action(&action).is_err());
}
#[test]
fn reject_empty_target() {
let action = Action {
actor_id: "root".to_string(),
action_type: ActionType::Mutate,
target: "".to_string(),
payload: json!({}),
timestamp: None,
};
assert!(validate_action(&action).is_err());
}
#[test]
fn accept_valid_mutate() {
let action = Action {
actor_id: "root".to_string(),
action_type: ActionType::Mutate,
target: "workspace/a".to_string(),
payload: json!({ "name": "ok" }),
timestamp: None,
};
assert!(validate_action(&action).is_ok());
}
}