Skip to main content

kaish_kernel/
operation.rs

1//! The kernel's operation taxonomy — what class of effect a builtin can have.
2//!
3//! This is analysis vocabulary, not policy. A tool declares the operations it
4//! can perform via [`ToolSchema::with_operations`](kaish_types::ToolSchema::with_operations),
5//! and an embedder reading a plan learns that `rm` can remove a path without
6//! having to recognize the name `rm`. The kernel itself does not decide
7//! anything from these ids.
8//!
9//! In-tree operations come from a closed enum and the mapping to the dotted id
10//! is an exhaustive match, so a new effect site is a **compile error** until it
11//! names its operation — a string sniff on the command name would instead pick
12//! a plausible wrong default in silence. The `fs.` and `trash.` namespaces
13//! belong to the kernel.
14
15/// Every operation an in-tree builtin can declare.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum KernelOperation {
19    /// `rm` removing a path permanently — the trash did not catch it.
20    FsRemove,
21    /// A truncating overwrite of an existing file (`cp`, `dd`, `patch`,
22    /// `sed -i`, `tee`, `write`).
23    FsOverwrite,
24    /// `mv` replacing an existing destination.
25    FsRename,
26    /// `kaish-trash empty` discarding the recovery net itself.
27    TrashEmpty,
28}
29
30impl KernelOperation {
31    /// The dotted id this operation declares. Exhaustive by construction.
32    pub const fn as_str(self) -> &'static str {
33        match self {
34            Self::FsRemove => "fs.remove",
35            Self::FsOverwrite => "fs.overwrite",
36            Self::FsRename => "fs.rename",
37            Self::TrashEmpty => "trash.empty",
38        }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn every_operation_has_a_dotted_two_part_id() {
48        for op in [
49            KernelOperation::FsRemove,
50            KernelOperation::FsOverwrite,
51            KernelOperation::FsRename,
52            KernelOperation::TrashEmpty,
53        ] {
54            let id = op.as_str();
55            let parts: Vec<&str> = id.split('.').collect();
56            assert_eq!(parts.len(), 2, "{id} must be `namespace.verb`");
57            assert!(
58                parts.iter().all(|p| !p.is_empty()),
59                "{id} has an empty half"
60            );
61        }
62    }
63
64    #[test]
65    fn ids_are_distinct() {
66        let ids = [
67            KernelOperation::FsRemove.as_str(),
68            KernelOperation::FsOverwrite.as_str(),
69            KernelOperation::FsRename.as_str(),
70            KernelOperation::TrashEmpty.as_str(),
71        ];
72        let unique: std::collections::BTreeSet<_> = ids.iter().collect();
73        assert_eq!(unique.len(), ids.len(), "two operations share a dotted id");
74    }
75}