use std::collections::BTreeSet;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use ikigai_core::{
ArgRef, Bindings, Capability, Error, Iri, Kernel, Representation, Request, UriTemplate, Verb,
};
use ikigai_dev_server::{browse, compose, config::BrowseSettings};
use ikigai_sparql::Store;
const ROOT: &str = "demo";
const BINDINGS: &[(&str, &str)] = &[("path", "README.md"), ("n", "1"), ("id", "seed")];
fn spawn_log() -> &'static Path {
static LOG: OnceLock<PathBuf> = OnceLock::new();
LOG.get_or_init(|| {
let dir = std::env::temp_dir().join(format!("ikigai-dev-enforced-{}", std::process::id()));
let bin = dir.join("bin");
std::fs::create_dir_all(&bin).expect("shim bin dir");
let log = dir.join("spawns.log");
for tool in ["git", "gh"] {
let path = bin.join(tool);
std::fs::write(
&path,
format!(
"#!/bin/sh\na=$(printf '%s ' \"$@\" | tr '\\n' ' ')\n\
printf '{tool} %s\\n' \"$a\" >> '{}'\nexit 0\n",
log.display()
),
)
.expect("shim");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755))
.expect("shim is executable");
}
}
let existing = std::env::var("PATH").unwrap_or_default();
std::env::set_var("PATH", format!("{}:{existing}", bin.display()));
let config = dir.join("config");
std::fs::create_dir_all(config.join("ikigai")).expect("scratch config home");
std::env::set_var("XDG_CONFIG_HOME", &config);
std::fs::write(&log, "").expect("empty log");
log
})
.as_path()
}
fn spawns() -> Vec<String> {
std::fs::read_to_string(spawn_log())
.expect("the spawn log")
.lines()
.map(str::to_string)
.collect()
}
struct Listener {
base_url: String,
connections: Arc<AtomicUsize>,
}
fn listener() -> &'static Listener {
static LISTENER: OnceLock<Listener> = OnceLock::new();
LISTENER.get_or_init(|| {
let socket = std::net::TcpListener::bind("127.0.0.1:0").expect("a loopback port");
let addr = socket.local_addr().expect("the bound address");
let connections = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&connections);
std::thread::spawn(move || {
for stream in socket.incoming() {
counter.fetch_add(1, Ordering::SeqCst);
if let Ok(mut stream) = stream {
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf);
let _ = std::io::Write::write_all(
&mut stream,
b"HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n",
);
}
}
});
Listener {
base_url: format!("http://127.0.0.1:{}/v1", addr.port()),
connections,
}
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Witness {
spawns: Vec<String>,
connections: usize,
quads: usize,
tree: Vec<(String, u64, std::time::SystemTime)>,
}
fn snapshot(served: &Served) -> Witness {
Witness {
spawns: spawns(),
connections: listener().connections.load(Ordering::SeqCst),
quads: served.store.len().expect("the store's quad count"),
tree: tree_state(served.dir.path()),
}
}
fn tree_state(root: &Path) -> Vec<(String, u64, std::time::SystemTime)> {
fn walk(dir: &Path, root: &Path, out: &mut Vec<(String, u64, std::time::SystemTime)>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let meta = entry.metadata().expect("metadata");
if meta.is_dir() {
walk(&path, root, out);
} else {
out.push((
path.strip_prefix(root)
.unwrap_or(&path)
.display()
.to_string(),
meta.len(),
meta.modified().unwrap_or(std::time::UNIX_EPOCH),
));
}
}
}
let mut out = Vec::new();
walk(root, root, &mut out);
out.sort();
out
}
struct Served {
dir: tempfile::TempDir,
store: Arc<Store>,
kernel: Kernel,
}
fn served() -> Served {
spawn_log();
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("README.md"), "# demo\n\nA fixture tree.\n").expect("README");
std::fs::create_dir_all(dir.path().join("src")).expect("src");
std::fs::write(dir.path().join("src/lib.rs"), "pub fn demo() {}\n").expect("lib.rs");
let settings = BrowseSettings {
roots: vec![(ROOT.to_string(), dir.path().to_path_buf())],
store: PathBuf::new(),
file_model: "urn:llm:ollama:ask".to_string(),
dir_model: "urn:llm:ollama:ask".to_string(),
review_model: "urn:llm:ollama:ask".to_string(),
pr_model: "urn:llm:ollama:ask".to_string(),
file_max_tokens: None,
dir_max_tokens: None,
review_max_tokens: None,
pr_max_tokens: None,
review_model_label: None,
pr_model_label: None,
allow_models: Vec::new(),
};
let store = Arc::new(Store::new().expect("in-memory store"));
let browse = browse::wire_with_store(&settings, Arc::clone(&store));
let kernel = compose(Some(browse), || {
let mut ollama = ikigai_llm::OpenAiConfig::ollama("fixture-model");
ollama.base_url = listener().base_url.clone();
ollama.caps.context = Some(4096);
ollama.caps.modalities = vec!["text".to_string()];
ikigai_llm::Registry::single(ollama)
});
Served { dir, store, kernel }
}
fn serialized() -> std::sync::MutexGuard<'static, ()> {
static LOCK: Mutex<()> = Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
fn iri(s: &str) -> Iri {
Iri::parse(s.to_string()).unwrap_or_else(|e| panic!("`{s}` is a valid IRI: {e}"))
}
fn request(verb: Verb, target: &str, args: &[(&str, &str)]) -> Request {
let mut request = Request::new(verb, iri(target));
for (name, value) in args {
request = request.with_arg(*name, ArgRef::Inline(value.as_bytes().to_vec()));
}
request
}
fn issue(
kernel: &Kernel,
request: Request,
capability: &Capability,
) -> Result<Representation, Error> {
futures::executor::block_on(kernel.issue(request, capability))
}
fn concrete(pattern: &str) -> String {
if Iri::parse(pattern).is_ok() {
return pattern.to_string();
}
let template = UriTemplate::parse(pattern).expect("a pattern is an IRI or a URI template");
let mut bindings = Bindings::new();
for (name, value) in BINDINGS {
bindings.insert(*name, *value);
}
template
.expand(&bindings)
.unwrap_or_else(|| panic!("`{pattern}` has a variable BINDINGS does not name"))
}
fn actions(kernel: &Kernel) -> Vec<(String, String, Verb, Vec<String>)> {
kernel
.entries()
.expect("an enumerable root")
.iter()
.filter(|e| !e.pattern.starts_with("urn:kernel:"))
.flat_map(|e| {
let description = kernel
.describe_pattern(&e.pattern)
.unwrap_or_else(|| panic!("`{}` describes itself", e.pattern));
let id = description.id.clone();
description
.action_specs()
.into_iter()
.map(move |spec| (e.pattern.clone(), id.clone(), spec.verb, spec.requires))
.collect::<Vec<_>>()
})
.collect()
}
#[test]
fn the_witnesses_see_the_effects_they_are_watching_for() {
let _serial = serialized();
let served = served();
let before = snapshot(&served);
let exec = Capability::scoped(["urn:cap:exec:git"]);
issue(
&served.kernel,
request(
Verb::Source,
"urn:system:exec",
&[("tool", "git"), ("args", "--version")],
),
&exec,
)
.expect("the shim exits 0");
let after = snapshot(&served);
assert_eq!(
after.spawns.len(),
before.spawns.len() + 1,
"the spawn witness saw nothing: {after:?}"
);
assert!(after.spawns.last().expect("a line").starts_with("git "));
let before = snapshot(&served);
let gh = Capability::scoped(["urn:cap:exec:gh"]);
let _ = issue(
&served.kernel,
request(
Verb::Source,
"urn:repo:pr:list",
&[("repo", "ikigai-rs/ikigai-dev-server")],
),
&gh,
);
let after = snapshot(&served);
assert!(
after.spawns.len() > before.spawns.len()
&& after.spawns.iter().any(|line| line.starts_with("gh ")),
"the `gh` facade did not reach the shim: {after:?}"
);
let before = snapshot(&served);
let net = Capability::scoped(["urn:cap:net:127.0.0.1"]);
issue(
&served.kernel,
request(Verb::Source, "urn:llm:ollama:up", &[]),
&net,
)
.expect("liveness is a boolean, not an error");
let after = snapshot(&served);
assert_eq!(
after.connections,
before.connections + 1,
"the connection witness saw nothing"
);
let before = snapshot(&served);
let annotate = Capability::scoped(["urn:cap:annotate", "urn:cap:browse:read:*"]);
issue(
&served.kernel,
request(
Verb::Sink,
"urn:iki:annotation",
&[
("target", "urn:repo:demo:file:README.md"),
("body", "a witness note"),
("exact", "A fixture tree."),
],
),
&annotate,
)
.expect("the annotation overlay's write path");
let after = snapshot(&served);
assert!(
after.quads > before.quads,
"the store witness saw nothing: {} quad(s) before, {} after",
before.quads,
after.quads
);
}
#[test]
fn no_declared_gate_is_crossed_before_it_is_checked() {
let _serial = serialized();
let served = served();
let none = Capability::scoped(Vec::<String>::new());
let before = snapshot(&served);
let mut gated = 0usize;
for (pattern, id, verb, requires) in actions(&served.kernel) {
if requires.is_empty() {
continue;
}
gated += 1;
let target = concrete(&pattern);
let result = issue(&served.kernel, request(verb, &target, &[]), &none);
match result {
Err(Error::Denied(_)) => {}
other => panic!(
"`{id}` ({target}, {verb:?}) declares requires {requires:?} but under no grants \
returned {other:?}: declared is not enforced"
),
}
let after = snapshot(&served);
assert_eq!(
after, before,
"`{id}` ({target}, {verb:?}) refused, but something happened first — a typed \
`Denied` is not proof that nothing ran (conformance PENDING #117). Restore the \
whole-endpoint `Suite::opt_out` in tests/conformance.rs and report the endpoint."
);
}
assert!(
gated >= 20,
"only {gated} gated action(s): the walk is not covering the composition"
);
}
#[test]
fn the_ungated_actions_are_pinned_because_enforced_really_fires_them() {
let _serial = serialized();
let served = served();
let ungated: BTreeSet<String> = actions(&served.kernel)
.into_iter()
.filter(|(_, _, _, requires)| requires.is_empty())
.map(|(_, id, verb, _)| format!("{id} {verb:?}"))
.collect();
assert_eq!(
ungated,
[
"llm-config Source",
"llm-models Source",
"llm-ollama-model Source",
"llm-select Source",
"rdf-diff Source",
"rdf-transrept Source",
"rdf-union Source",
"sparql-ask Source",
"sparql-construct Source",
"sparql-describe Source",
"sparql-select Source",
]
.into_iter()
.map(str::to_string)
.collect::<BTreeSet<_>>(),
"the set of actions the kernel gates on nothing changed. ENFORCED resolves these \
for real, so anything with a side effect that appears here needs a whole-endpoint \
`Suite::opt_out` in tests/conformance.rs — and an undeclared capability an \
endpoint enforces at runtime makes the manifold over-offer, which is a finding \
about the owning module."
);
}