use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use ikigai_conformance::{Check, Fixture, Report, Suite};
use ikigai_core::{ArgRef, Capability, Error, Expiry, Iri, Kernel, Representation, Request, Verb};
use ikigai_dev_server::{browse, compose, config::BrowseSettings, LIMITS};
use ikigai_sparql::Store;
const ROOT: &str = "demo";
const SERVED: &[(&str, &str, &str)] = &[
("urn:system:exec", "system-exec", "ikigai-repo"),
("urn:repo:status", "repo-status", "ikigai-repo"),
("urn:repo:log", "repo-log", "ikigai-repo"),
("urn:repo:branch", "repo-branch", "ikigai-repo"),
("urn:repo:list", "repo-list", "ikigai-repo"),
("urn:repo:pr:checks", "repo-pr-checks", "ikigai-repo"),
("urn:repo:pr:view", "repo-pr-view", "ikigai-repo"),
("urn:repo:pr:list", "repo-pr-list", "ikigai-repo"),
("urn:repo:pr:files", "repo-pr-files", "ikigai-repo"),
("urn:repo:pr:diff", "repo-pr-diff", "ikigai-repo"),
("urn:rdf:union", "rdf-union", "ikigai-rdf"),
("urn:rdf:diff", "rdf-diff", "ikigai-rdf"),
("urn:rdf:transrept", "rdf-transrept", "ikigai-rdf"),
("urn:sparql:select", "sparql-select", "ikigai-sparql"),
("urn:sparql:ask", "sparql-ask", "ikigai-sparql"),
("urn:sparql:describe", "sparql-describe", "ikigai-sparql"),
("urn:sparql:construct", "sparql-construct", "ikigai-sparql"),
("urn:sparql:update", "sparql-update", "ikigai-sparql"),
("urn:repo:demo:tree", "browse-tree", "ikigai-browse"),
("urn:repo:demo:tree:{path}", "browse-tree", "ikigai-browse"),
("urn:repo:demo:file:{path}", "browse-file", "ikigai-browse"),
("urn:repo:demo:state", "browse-state", "ikigai-browse"),
("urn:repo:demo:hash", "browse-hash", "ikigai-browse"),
("urn:repo:demo:hash:{path}", "browse-hash", "ikigai-browse"),
("urn:repo:style", "browse-style", "ikigai-browse"),
("urn:repo:demo:prs", "browse-prs", "ikigai-browse"),
(
"urn:repo:demo:prs:{path}",
"browse-prs-scoped",
"ikigai-browse",
),
("urn:repo:demo:pr:{n}", "browse-pr", "ikigai-browse"),
(
"urn:repo:demo:explain-versions",
"browse-explain-versions",
"ikigai-browse",
),
(
"urn:repo:demo:explain-versions:{path}",
"browse-explain-versions",
"ikigai-browse",
),
("urn:repo:demo:explain", "browse-explain", "ikigai-browse"),
(
"urn:repo:demo:explain:{path}",
"browse-explain",
"ikigai-browse",
),
(
"urn:repo:demo:review:{path}",
"browse-review",
"ikigai-browse",
),
(
"urn:repo:demo:pr:{n}:explain",
"browse-pr-explain",
"ikigai-browse",
),
(
"urn:repo:demo:pr:{n}:review",
"browse-pr-review",
"ikigai-browse",
),
("urn:iki:annotation:{id}", "annotation", "ikigai-browse"),
(
"urn:repo:demo:annotations",
"browse-annotations",
"ikigai-browse",
),
(
"urn:repo:demo:annotations:{path}",
"browse-annotations",
"ikigai-browse",
),
("urn:llm:ask", "llm-ask", "ikigai-llm"),
("urn:llm:config", "llm-config", "ikigai-llm"),
("urn:llm:models", "llm-models", "ikigai-llm"),
("urn:llm:select", "llm-select", "ikigai-llm"),
("urn:llm:ollama:ask", "llm-ollama-ask", "ikigai-llm"),
("urn:llm:ollama:up", "llm-ollama-up", "ikigai-llm"),
(
"urn:llm:ollama:installed",
"llm-ollama-installed",
"ikigai-llm",
),
("urn:llm:ollama:model", "llm-ollama-model", "ikigai-llm"),
];
const FAMILIES: &[&str] = &[
"urn:system:exec",
"urn:repo:",
"urn:rdf:",
"urn:sparql:",
"urn:llm:",
"urn:iki:annotation",
];
fn scratch_config_home() -> &'static Path {
static HOME: OnceLock<PathBuf> = OnceLock::new();
HOME.get_or_init(|| {
let dir =
std::env::temp_dir().join(format!("ikigai-dev-conformance-{}", std::process::id()));
std::fs::create_dir_all(dir.join("ikigai")).expect("scratch config home");
std::env::set_var("XDG_CONFIG_HOME", &dir);
dir
})
.as_path()
}
fn fixture_root() -> tempfile::TempDir {
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");
dir
}
fn browse_settings(root: PathBuf) -> BrowseSettings {
BrowseSettings {
roots: vec![(ROOT.to_string(), root)],
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(),
}
}
struct Served {
_dir: tempfile::TempDir,
kernel: Kernel,
}
const SEED_ID: &str = "seed";
fn served() -> Served {
scratch_config_home();
let dir = fixture_root();
let settings = browse_settings(dir.path().to_path_buf());
let store = Arc::new(Store::new().expect("in-memory store"));
let browse = browse::wire_with_store(&settings, store);
let kernel = compose(Some(browse), || {
let mut ollama = ikigai_llm::OpenAiConfig::ollama("fixture-model");
ollama.base_url = "http://127.0.0.1:1/v1".to_string();
ollama.caps.context = Some(4096);
ollama.caps.modalities = vec!["text".to_string()];
ikigai_llm::Registry::single(ollama)
});
issue(
&kernel,
request(
Verb::Sink,
&format!("urn:iki:annotation:{SEED_ID}"),
&[
("target", "urn:repo:demo:file:README.md"),
("body", "a seeded note"),
("exact", "A fixture tree."),
],
),
)
.expect("the annotation overlay's write path");
Served { _dir: dir, kernel }
}
const TURTLE: &str = "<http://example.org/a> <http://purl.org/dc/terms/title> \"demo\" .\n";
const WAIVED: &[(&str, &str)] = &[
(
"system-exec",
"spawns a subprocess: the bare walk executed `git`",
),
("repo-status", "runs git in the invoking working tree"),
("repo-log", "runs git in the invoking working tree"),
("repo-branch", "runs git in the invoking working tree"),
("repo-list", "enumerates repositories on the machine"),
("repo-pr-checks", "shells out to `gh`: network and auth"),
("repo-pr-view", "shells out to `gh`: network and auth"),
("repo-pr-list", "shells out to `gh`: network and auth"),
("repo-pr-files", "shells out to `gh`: network and auth"),
("repo-pr-diff", "shells out to `gh`: network and auth"),
("browse-prs", "shells out to `gh`: network and auth"),
("browse-prs-scoped", "shells out to `gh`: network and auth"),
("browse-pr", "shells out to `gh`: network and auth"),
(
"browse-explain",
"derives through urn:llm:*: a live inference server",
),
(
"browse-review",
"derives through urn:llm:*: a live inference server",
),
(
"browse-pr-explain",
"shells out to `gh`, then derives through urn:llm:*",
),
(
"browse-pr-review",
"shells out to `gh`, then derives through urn:llm:*",
),
("llm-ask", "POSTs to a live inference server"),
("llm-ollama-ask", "POSTs to a live inference server"),
("llm-ollama-up", "probes a live inference server"),
("llm-ollama-installed", "queries a live inference server"),
];
const RDF_FACED: &[&str] = &[
"browse-explain",
"browse-review",
"browse-pr-explain",
"browse-pr-review",
];
fn suite() -> Suite {
let mut suite = Suite::new()
.fixture(
Fixture::new("rdf-union", Verb::Source)
.arg("content", TURTLE)
.arg("with", TURTLE),
)
.fixture(
Fixture::new("rdf-diff", Verb::Source)
.arg("content", TURTLE)
.arg("with", TURTLE)
.arg("mode", "added"),
)
.fixture(
Fixture::new("rdf-transrept", Verb::Source)
.arg("content", TURTLE)
.arg("as", "application/n-triples"),
)
.fixture(
Fixture::new("sparql-select", Verb::Source)
.arg("query", "SELECT * WHERE { ?s ?p ?o } LIMIT 1"),
)
.fixture(Fixture::new("sparql-ask", Verb::Source).arg("query", "ASK { ?s ?p ?o }"))
.fixture(
Fixture::new("sparql-describe", Verb::Source).arg("query", "DESCRIBE <urn:demo:a>"),
)
.fixture(
Fixture::new("sparql-construct", Verb::Source)
.arg("query", "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o } LIMIT 1"),
)
.fixture(Fixture::new("browse-tree", Verb::Source).binding("path", "src"))
.fixture(Fixture::new("browse-file", Verb::Source).binding("path", "README.md"))
.fixture(Fixture::new("browse-hash", Verb::Source).binding("path", "README.md"))
.fixture(Fixture::new("browse-explain-versions", Verb::Source).binding("path", "README.md"))
.fixture(Fixture::new("browse-annotations", Verb::Source).binding("path", "README.md"))
.fixture(Fixture::new("llm-select", Verb::Source).arg("needs", "text"))
.fixture(Fixture::new("annotation", Verb::Source).binding("id", SEED_ID))
.pure("rdf-transrept")
.namespace("http://www.w3.org/ns/oa#")
;
for (id, reason) in WAIVED {
suite = suite
.opt_out_check(*id, Check::Outputs, *reason)
.opt_out_check(*id, Check::Cacheable, *reason);
if RDF_FACED.contains(id) {
suite = suite
.opt_out_check(*id, Check::SkolemRdf, *reason)
.opt_out_check(*id, Check::Vocabulary, *reason);
}
}
suite
}
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) -> Result<Representation, Error> {
futures::executor::block_on(kernel.issue(request, &Capability::root()))
}
fn text(repr: &Representation) -> String {
String::from_utf8(repr.bytes.clone()).expect("UTF-8")
}
fn walked(kernel: &Kernel) -> BTreeMap<String, String> {
kernel
.entries()
.expect("an enumerable root")
.iter()
.filter(|e| !e.pattern.starts_with("urn:kernel:"))
.map(|e| {
let id = kernel
.describe_pattern(&e.pattern)
.unwrap_or_else(|| panic!("`{}` describes itself", e.pattern))
.id;
(e.pattern.clone(), id)
})
.collect()
}
#[test]
fn the_served_catalog_is_exactly_what_the_manifest_composes() {
let served = served();
let expected: BTreeMap<String, String> = SERVED
.iter()
.map(|(pattern, id, _)| (pattern.to_string(), id.to_string()))
.collect();
assert_eq!(
walked(&served.kernel),
expected,
"the served catalog changed: add the new entry to SERVED with the crate that owns it"
);
for (pattern, _, _) in SERVED {
assert!(
FAMILIES.iter().any(|f| pattern.starts_with(f)),
"`{pattern}` is outside the URN families this server occupies: {FAMILIES:?}"
);
}
let bare = compose(None, || unreachable!("no browse, no registry read"));
let bare_ids: BTreeSet<String> = walked(&bare).into_values().collect();
assert!(
!bare_ids
.iter()
.any(|id| id.starts_with("browse-") || id.starts_with("llm-")),
"an unconfigured server serves the curated surface only: {bare_ids:?}"
);
}
#[test]
fn conforms() {
let served = served();
let report = suite().run_blocking(&served.kernel);
eprintln!("--- ikigai-dev-server, browse configured ---\n{report}");
let owners: BTreeMap<&str, &str> = SERVED.iter().map(|(_, id, owner)| (*id, *owner)).collect();
let unattributed: Vec<String> = report
.findings
.iter()
.filter(|f| !owners.contains_key(f.endpoint.as_str()))
.map(|f| format!("{} {}", f.endpoint, f.check.label()))
.collect();
assert!(
unattributed.is_empty(),
"every finding belongs to a module this crate composes; these name nothing in \
SERVED: {unattributed:?}\n{report}"
);
let mut by_owner: BTreeMap<&str, usize> = BTreeMap::new();
for finding in &report.findings {
*by_owner
.entry(owners[finding.endpoint.as_str()])
.or_default() += 1;
}
eprintln!("inherited findings by owning crate: {by_owner:?}");
assert!(
!by_owner.is_empty(),
"the walk found nothing at all, which means it walked nothing: {report}"
);
assert_eq!(
report.checks.skipped().count(),
0,
"every check runs:\n{report}"
);
}
#[test]
fn every_served_entry_answers_meta_with_a_real_contract() {
let served = served();
for (pattern, id, _) in SERVED {
let description = served
.kernel
.describe_pattern(pattern)
.unwrap_or_else(|| panic!("`{pattern}` describes itself"));
assert_eq!(&description.id, id, "{pattern}");
assert_ne!(
description.id, "remote",
"`{pattern}` is the anonymous fallback a renderer-less peer produces"
);
assert!(
!description.action_specs().is_empty(),
"`{pattern}` declares no action: a contract with no verbs is not one"
);
if pattern.contains('{') {
continue;
}
let json = issue(
&served.kernel,
request(Verb::Meta, pattern, &[("as", "application/json")]),
)
.unwrap_or_else(|e| panic!("`{pattern}` renders its JSON Meta face: {e}"));
let body = text(&json);
assert!(
body.trim_start().starts_with('{'),
"`{pattern}`: the JSON Meta face must be JSON, not the canonical Turtle a \
missing transreptor falls back to — that fallback is parsed with `.ok()` by \
`MountedRemote` and degrades to `Description::new(\"remote\")`:\n{body}"
);
assert!(body.contains(id), "`{pattern}`: {body}");
}
let catalog = issue(
&served.kernel,
request(Verb::Source, "urn:kernel:catalog", &[]),
)
.expect("the served kernel renders its own catalog");
let turtle = text(&catalog);
assert!(turtle.contains("ik:Endpoint"), "{turtle}");
for (_, id, _) in SERVED {
assert!(
turtle.contains(id),
"`{id}` is in the served catalog:\n{turtle}"
);
}
}
#[test]
fn the_threads_a_mount_would_erase_are_enumerated() {
let served = served();
let probes: &[(&str, &[(&str, &str)])] = &[
("urn:repo:demo:tree", &[]),
("urn:repo:demo:tree:src", &[]),
("urn:repo:demo:file:README.md", &[]),
("urn:repo:demo:state", &[]),
("urn:repo:demo:hash", &[]),
("urn:repo:demo:hash:README.md", &[]),
("urn:repo:demo:annotations", &[]),
("urn:repo:demo:annotations:README.md", &[]),
("urn:repo:demo:explain-versions", &[]),
("urn:repo:style", &[]),
("urn:llm:config", &[]),
("urn:llm:models", &[]),
("urn:llm:select", &[("needs", "text")]),
("urn:llm:ollama:model", &[]),
];
let mut threaded: BTreeMap<&str, Vec<String>> = BTreeMap::new();
let mut cached_without_a_thread: Vec<&str> = Vec::new();
for (target, args) in probes {
let repr = issue(&served.kernel, request(Verb::Source, target, args))
.unwrap_or_else(|e| panic!("`{target}` resolves in the scratch composition: {e}"));
let threads: Vec<String> = repr.threads().iter().map(|t| t.to_string()).collect();
if !threads.is_empty() {
threaded.insert(target, threads);
} else if repr.expiry != Expiry::Always {
cached_without_a_thread.push(target);
}
}
assert_eq!(
threaded.keys().copied().collect::<Vec<_>>(),
vec![
"urn:llm:config",
"urn:llm:models",
"urn:llm:ollama:model",
"urn:llm:select",
"urn:repo:style",
],
"the list of representations a mount would strip of their threads changed; \
update the table in this test's docs and tell the hub — it is the blast radius"
);
let home = scratch_config_home().join("ikigai");
assert_eq!(
threaded["urn:repo:style"],
vec![
format!("urn:file:{}/a11y.toml", home.display()),
format!("urn:file:{}/dev-server.a11y.toml", home.display()),
],
"and they are the layered a11y config files, under the config home"
);
for target in [
"urn:llm:config",
"urn:llm:models",
"urn:llm:ollama:model",
"urn:llm:select",
] {
assert_eq!(
threaded[target],
vec!["urn:llm:config".to_string()],
"`{target}` is config-derived, so it hangs on the one registry thread: cutting \
`urn:llm:config` recomputes all four, a mounted copy included"
);
}
assert!(
cached_without_a_thread.is_empty(),
"every cacheable representation this server serves now names a thread — one that \
arrived here cacheable with nothing to cut is exactly the mount disease, in-process: \
{cached_without_a_thread:?}"
);
}
#[test]
fn the_bare_annotation_minting_iri_is_bound_but_not_enumerated() {
let served = served();
assert!(
!walked(&served.kernel).contains_key("urn:iki:annotation"),
"the bare minting IRI is not an enumerated entry (it is why the mount line omits \
the trailing colon); if it became one, say so in the README"
);
let description = served
.kernel
.describe(&iri("urn:iki:annotation"))
.expect("the bare minting IRI is bound");
assert_eq!(description.id, "annotation");
assert!(
description
.action_specs()
.iter()
.any(|spec| spec.verb == Verb::Sink),
"and it is the write path"
);
}
#[test]
fn the_walk_stays_inside_the_rate_ceilings() {
let (_, repo_calls, _) = LIMITS[1];
let served = served();
let report = suite().run_blocking(&served.kernel);
let denied: Vec<String> = report
.findings
.iter()
.filter(|f| f.detail.contains("rate limit") || f.detail.contains("RateLimit"))
.map(|f| format!("{} {}", f.endpoint, f.check.label()))
.collect();
assert!(
denied.is_empty(),
"the walk exhausted the `urn:repo:` budget ({repo_calls}/min): {denied:?}\n{report}"
);
}
#[test]
fn the_walk_sees_every_endpoint_in_the_table() {
let served = served();
let report: Report = suite().run_blocking(&served.kernel);
let ids: BTreeSet<&str> = SERVED.iter().map(|(_, id, _)| *id).collect();
assert_eq!(
report.endpoints,
ids.len(),
"the walk saw {} of {} endpoints:\n{report}",
report.endpoints,
ids.len()
);
}