#[cfg(not(target_family = "wasm"))]
mod clipboard;
#[cfg(feature = "quic")]
mod quic;
mod repl;
#[cfg(feature = "web")]
mod route_load;
#[cfg(not(target_family = "wasm"))]
mod tui;
const USAGE: &str = "\
ikigai — resource-resolution REPL
usage:
ikigai start the interactive REPL (full-screen on a terminal)
ikigai --plain force the line REPL (also used automatically when piped)
ikigai --demo mount the interactive runbook (urn:runbook:*); off by default
ikigai --connect [<target>] attach the REPL to a kernel server (a Unix path, or quic://host:port)
ikigai --mount <pfx>=<tgt> compose a remote kernel at prefix <pfx> (<tgt> = Unix path or quic://host:port)
ikigai serve [<target>] run a kernel server (a Unix socket path, or quic://addr to bind)
ikigai serve <q> --cap <s> serve under a fixed capability ceiling <s> every client is clamped to
(with clients.json below, the OUTER bound each grant narrows within)
ikigai serve <q> per-identity authority when ~/.config/ikigai/clients.json enrols
certificates — it maps a client's SHA-256 fingerprint to a grant
named in grants.json, so each gets its own scopes, an unenrolled
one is REFUSED, and editing the file revokes on the next
connection. `cert add-client` prints the fingerprint to enrol.
ikigai serve <q> --announce also advertise this kernel on the local network (mDNS), so clients
can mount it by name; `source urn:peer:list` shows who is out there
ikigai serve <s> --prefer … a served host may take --mount/--override/--prefer too, so IT owns
the topology: a local client reaches a peer through this socket
without holding its certs or finding it itself
ikigai serve … --code-signer <k> accept SIGNED programs (urn:lisp:run) vouched for by key
resource <k> (repeatable; --code-signers-dir sets where
urn:codekey:{file} reads from). Without it the door is unbound.
ikigai serve … --eval-timeout <s> wall-clock ceiling for a served eval (default 10s)
ikigai serve --http <port> serve the inbound HTTP face (loopback; front with TLS at your proxy)
[--trust-proxy: honor X-Forwarded-*; --cors-origin <o>: allow a CORS origin;
--routes <iri>: load routes from an RDF or plain-JSON resource
(a urn:file: route hot-reloads); --routes-only: un-routed → 404;
--max-body <bytes>: largest accepted request body, default 1048576]
ikigai --daemon headless: timers, the watcher, and the standing sync — for launchd
ikigai --name <instance> name this instance (scopes <name>.* config properties; defaults
repl / daemon / serve by mode)
ikigai --scheduler <spec> fan-out width: single | pool | pool:N (any mode). Also
`scheduler = \"pool:N\"` in the config home (instance-scoped as
<name>.scheduler); IKIGAI_SCHEDULER still works, deprecated.
`source urn:kernel:scheduler` reports the width AND the channel
ikigai --width-routing <on|off>
route a fan-out by the width it ACHIEVES: at 2+ concurrent
requests, append needs=batchAt<=W for targets that read it
(urn:llm:ask). OFF by default; also `width-routing = \"on\"`
in the config home (instance-scoped as <name>.width-routing).
An explicit provider= or needs= always wins
that set it — the DEFAULT `single` runs every fan-out serially
ikigai --mount <p>=<t> graft a remote namespace at prefix <p> (ALIAS: <p>rest → urn:rest
on the remote, tried after local; --cert-dir after it is ITS cert set)
ikigai --override <p>=<t> the SAME namespace, served remotely: IRIs forward unchanged and win
over local. <p> may be a whole IRI, so a single resource can be
rerouted; the most specific override wins
ikigai --prefer <p>=<t> like --override, but falls back to the LOCAL binding when the peer
is unreachable (transient failures only; denials still propagate)
<t> = peer:<name> find that peer on the local network (mDNS) instead of naming an
address; needs a pinned cert at <config>/ikigai/quic-<name>/
ikigai --no-config-mounts compose ZERO mounts: decline the machine's topology instead of
enumerating a replacement. Honoured by every mode that builds a
kernel (REPL, -c, --daemon, serve, mcp); refuses to start beside
--mount/--override/--prefer, which declare a topology of their own
ikigai --react run the space reactor in this session — claims and executes tuples
dropped in the workspace. OFF by default; the daemon is the worker
ikigai cert generate create the pinned QUIC certificates (--dir <d> for a dedicated set)
ikigai cert add-client <n> mint an extra client identity into clients/<n>.{crt,key}
ikigai -c '<command>' ... run command(s) non-interactively, then exit
ikigai -e '<sexpr>' ... evaluate a Lisp s-expression (urn:lisp:eval), then exit
ikigai --load <uri> [--cap <s>] read a script resource and evaluate it as Lisp (--cap narrows first)
ikigai -h | --help show this help
ikigai -V | --version print `ikigai <x.y.z>` on one line and exit — answered in
every mode, before anything is built or connected
QUIC: --server-cert/--server-key name the server's identity, --client-cert/--client-key the client's
inside the REPL: source, describe, help, quit (type `help` for details)";
const VERSION_LINE: &str = concat!("ikigai ", env!("CARGO_PKG_VERSION"));
use ikigai_engine::Engine;
#[derive(Default, Clone)]
struct Certs {
cert_dir: Option<String>,
server_cert: Option<String>,
server_key: Option<String>,
client_cert: Option<String>,
client_key: Option<String>,
}
enum Mode {
Version,
Repl(ReplArgs),
Daemon {
mounts: Mounts,
},
Serve {
target: Option<String>,
certs: Certs,
caps: Vec<String>,
http: Option<String>,
trust_proxy: bool,
cors_origins: Vec<String>,
routes: Option<String>,
routes_only: bool,
max_body: Option<usize>,
announce: bool,
mounts: Mounts,
},
Mcp {
grants: Vec<String>,
scopes: Vec<String>,
mounts: Mounts,
},
CertGenerate {
force: bool,
dir: Option<String>,
},
CertAddClient {
name: String,
cert_dir: Option<String>,
force: bool,
},
}
#[derive(Default, Clone)]
struct ReplArgs {
plain: bool,
demo: bool,
commands: Vec<String>,
connect: Option<Option<String>>,
mounts: Mounts,
certs: Certs,
react: bool,
}
#[derive(Clone)]
struct Mount {
prefix: String,
target: String,
certs: Certs,
kind: ikigai_embedded::MountKind,
}
#[derive(Default, Clone)]
struct Mounts {
flags: Vec<Mount>,
declined: bool,
}
impl Mounts {
fn refuse_conflict(&self) -> Result<(), String> {
match self.flags.first() {
Some(mount) if self.declined => Err(format!(
"--no-config-mounts and {flag} cannot be combined: --no-config-mounts composes \
ZERO mounts and does not read the config home, while {flag} `{prefix}={target}` \
declares the whole topology. Pass one or the other.",
flag = mount_flag(mount.kind),
prefix = mount.prefix,
target = mount.target,
)),
_ => Ok(()),
}
}
}
fn mount_mode(kind: ikigai_embedded::MountKind) -> &'static str {
match kind {
ikigai_embedded::MountKind::Alias => "alias",
ikigai_embedded::MountKind::Override => "override",
ikigai_embedded::MountKind::Prefer => "prefer",
}
}
fn mount_posture(mounts: &[Mount], declined: bool) -> ikigai_embedded::posture::MountPosture {
if declined {
return ikigai_embedded::posture::MountPosture::Declined;
}
ikigai_embedded::posture::MountPosture::Composed(
mounts
.iter()
.map(|mount| ikigai_embedded::posture::ComposedMount {
mode: mount_mode(mount.kind),
prefix: mount.prefix.clone(),
target: mount.target.clone(),
cert_dir: mount.certs.cert_dir.clone(),
})
.collect(),
)
}
fn mount_lines(mounts: &[Mount], declined: bool) -> Vec<String> {
ikigai_embedded::posture::mount_lines(&mount_posture(mounts, declined))
}
fn announce_mounts(who: &str, mounts: &[Mount], declined: bool) {
for line in mount_lines(mounts, declined) {
eprintln!("{who}: {line}");
}
}
#[cfg_attr(
not(all(feature = "embedded", feature = "web")),
allow(
dead_code,
reason = "the web-less build hands this to a stub that exits"
)
)]
struct HttpDoor<'a> {
bind: &'a str,
caps: &'a [String],
trust_proxy: bool,
cors_origins: &'a [String],
routes: Option<&'a str>,
routes_only: bool,
max_body: Option<usize>,
mounts: Mounts,
}
fn is_quic(target: &str) -> bool {
target.starts_with("quic://")
}
fn cert_flag(
arg: &str,
argv: &mut impl Iterator<Item = String>,
certs: &mut Certs,
) -> Result<bool, String> {
let slot = match arg {
"--cert-dir" => &mut certs.cert_dir,
"--server-cert" => &mut certs.server_cert,
"--server-key" => &mut certs.server_key,
"--client-cert" => &mut certs.client_cert,
"--client-key" => &mut certs.client_key,
_ => return Ok(false),
};
*slot = Some(
argv.next()
.ok_or_else(|| format!("{arg} requires a path"))?,
);
Ok(true)
}
fn scheduler_flag(arg: &str, argv: &mut impl Iterator<Item = String>) -> Result<bool, String> {
if arg != "--scheduler" {
return Ok(false);
}
let spec = argv
.next()
.ok_or_else(|| "--scheduler needs <single|pool|pool:N>".to_string())?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_scheduler_spec(spec.as_str()).map_err(|e| format!("--scheduler: {e}"))?;
#[cfg(not(feature = "embedded"))]
let _ = spec;
Ok(true)
}
fn width_routing_flag(arg: &str, argv: &mut impl Iterator<Item = String>) -> Result<bool, String> {
if arg != "--width-routing" {
return Ok(false);
}
let value = argv
.next()
.ok_or_else(|| "--width-routing needs <on|off>".to_string())?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_width_routing(&value).map_err(|e| format!("--width-routing: {e}"))?;
#[cfg(not(feature = "embedded"))]
let _ = value;
Ok(true)
}
fn version_requested<'a>(mut args: impl Iterator<Item = &'a str>) -> bool {
args.any(|arg| arg == "-V" || arg == "--version")
}
fn parse_args() -> Result<Option<Mode>, String> {
parse_argv(std::env::args().skip(1))
}
fn parse_argv(args: impl Iterator<Item = String>) -> Result<Option<Mode>, String> {
let args: Vec<String> = args.collect();
if version_requested(args.iter().map(String::as_str)) {
return Ok(Some(Mode::Version));
}
let mut argv = args.into_iter().peekable();
if argv.peek().map(String::as_str) == Some("cert") {
argv.next();
return match argv.next().as_deref() {
Some("generate") => {
let mut force = false;
let mut dir = None;
while let Some(arg) = argv.next() {
match arg.as_str() {
"--force" => force = true,
"--dir" => {
dir = Some(
argv.next()
.ok_or_else(|| "--dir needs a path".to_string())?,
)
}
other => {
return Err(format!("unknown argument after `cert generate`: {other}"))
}
}
}
Ok(Some(Mode::CertGenerate { force, dir }))
}
Some("add-client") => {
let mut name = None;
let mut cert_dir = None;
let mut force = false;
while let Some(arg) = argv.next() {
match arg.as_str() {
"--force" => force = true,
"--cert-dir" => {
cert_dir = Some(
argv.next()
.ok_or_else(|| "--cert-dir needs a path".to_string())?,
)
}
other if other.starts_with('-') => {
return Err(format!(
"unknown argument after `cert add-client`: {other}"
))
}
_ if name.is_none() => name = Some(arg),
other => {
return Err(format!(
"unexpected argument after `cert add-client`: {other}"
))
}
}
}
let name =
name.ok_or_else(|| "usage: `ikigai cert add-client <name>`".to_string())?;
Ok(Some(Mode::CertAddClient {
name,
cert_dir,
force,
}))
}
Some(other) => Err(format!("unknown `cert` subcommand: {other}")),
None => {
Err("usage: `ikigai cert generate` | `ikigai cert add-client <name>`".to_string())
}
};
}
if argv.peek().map(String::as_str) == Some("serve") {
argv.next();
let mut target = None;
let mut certs = Certs::default();
let mut caps = Vec::new();
let mut code_signers: Vec<String> = Vec::new();
let mut http = None;
let mut trust_proxy = false;
let mut cors_origins = Vec::new();
let mut routes = None;
let mut routes_only = false;
let mut max_body = None;
let mut announce = false;
let mut mounts = Mounts::default();
while let Some(arg) = argv.next() {
if cert_flag(&arg, &mut argv, &mut certs)? {
if let Some(mount) = mounts.flags.last_mut() {
mount.certs = certs.clone();
}
continue;
}
if scheduler_flag(&arg, &mut argv)? || width_routing_flag(&arg, &mut argv)? {
continue;
}
if arg == "--announce" {
announce = true;
continue;
}
if arg == "--no-config-mounts" {
mounts.declined = true;
continue;
}
if let Some(kind) = match arg.as_str() {
"--mount" => Some(ikigai_embedded::MountKind::Alias),
"--override" => Some(ikigai_embedded::MountKind::Override),
"--prefer" => Some(ikigai_embedded::MountKind::Prefer),
_ => None,
} {
let spec = argv
.next()
.ok_or_else(|| format!("{arg} needs <prefix>=<target>"))?;
let (prefix, target) = spec
.split_once('=')
.ok_or_else(|| format!("{arg} expects <prefix>=<target>, got `{spec}`"))?;
mounts.flags.push(Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs: certs.clone(),
kind,
});
continue;
}
if arg == "--name" {
let name = argv
.next()
.ok_or_else(|| "--name needs a value".to_string())?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_instance_name(name);
continue;
}
if arg == "--cap" {
caps.push(
argv.next()
.ok_or_else(|| "--cap needs a capability IRI".to_string())?,
);
continue;
}
if arg == "--code-signer" {
let signer = argv
.next()
.ok_or_else(|| "--code-signer needs a key resource IRI".to_string())?;
code_signers.push(signer);
continue;
}
if arg == "--code-signers-dir" {
let dir = argv
.next()
.ok_or_else(|| "--code-signers-dir needs a path".to_string())?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_code_signers_dir(std::path::PathBuf::from(dir));
#[cfg(not(feature = "embedded"))]
let _ = dir;
continue;
}
if arg == "--eval-timeout" {
let secs = argv
.next()
.ok_or_else(|| "--eval-timeout needs seconds".to_string())?
.parse::<u64>()
.map_err(|_| "--eval-timeout needs a whole number of seconds".to_string())?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_eval_timeout_secs(secs);
#[cfg(not(feature = "embedded"))]
let _ = secs;
continue;
}
if arg == "--http" {
http = Some(
argv.next()
.ok_or_else(|| "--http needs a port or host:port".to_string())?,
);
continue;
}
if arg == "--trust-proxy" {
trust_proxy = true;
continue;
}
if arg == "--cors-origin" {
cors_origins.push(
argv.next()
.ok_or_else(|| "--cors-origin needs an origin (or `*`)".to_string())?,
);
continue;
}
if arg == "--routes" {
routes = Some(
argv.next()
.ok_or_else(|| "--routes needs a resource IRI".to_string())?,
);
continue;
}
if arg == "--routes-only" {
routes_only = true;
continue;
}
if arg == "--max-body" {
let raw = argv
.next()
.ok_or_else(|| "--max-body needs a size in bytes".to_string())?;
max_body = Some(
raw.parse::<usize>()
.map_err(|_| format!("--max-body wants a size in bytes ({raw})"))?,
);
continue;
}
if arg.starts_with('-') {
return Err(format!("unknown argument: {arg}"));
} else if target.is_none() {
target = Some(arg);
} else {
return Err(format!("unexpected argument after `serve`: {arg}"));
}
}
mounts.refuse_conflict()?;
#[cfg(feature = "embedded")]
ikigai_embedded::set_code_signers(code_signers);
#[cfg(not(feature = "embedded"))]
let _ = code_signers;
return Ok(Some(Mode::Serve {
target,
certs,
caps,
http,
trust_proxy,
cors_origins,
routes,
routes_only,
max_body,
announce,
mounts,
}));
}
if argv.peek().map(String::as_str) == Some("mcp") {
argv.next();
let mut grants = Vec::new();
let mut scopes = Vec::new();
let mut certs = Certs::default();
let mut mounts = Mounts::default();
while let Some(arg) = argv.next() {
if cert_flag(&arg, &mut argv, &mut certs)? {
if let Some(mount) = mounts.flags.last_mut() {
mount.certs = certs.clone();
}
continue;
}
if let Some(kind) = match arg.as_str() {
"--mount" => Some(ikigai_embedded::MountKind::Alias),
"--override" => Some(ikigai_embedded::MountKind::Override),
"--prefer" => Some(ikigai_embedded::MountKind::Prefer),
_ => None,
} {
let spec = argv
.next()
.ok_or_else(|| format!("{arg} needs <prefix>=<target>"))?;
let (prefix, target) = spec
.split_once('=')
.ok_or_else(|| format!("{arg} expects <prefix>=<target>, got `{spec}`"))?;
mounts.flags.push(Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs: certs.clone(),
kind,
});
continue;
}
if scheduler_flag(&arg, &mut argv)? || width_routing_flag(&arg, &mut argv)? {
continue;
}
match arg.as_str() {
"--no-config-mounts" => mounts.declined = true,
"--grant" => grants.push(
argv.next()
.ok_or_else(|| "--grant needs a name".to_string())?,
),
"--scope" => scopes.push(
argv.next()
.ok_or_else(|| "--scope needs a capability IRI".to_string())?,
),
other => return Err(format!("unknown argument after `mcp`: {other}")),
}
}
mounts.refuse_conflict()?;
return Ok(Some(Mode::Mcp {
grants,
scopes,
mounts,
}));
}
let mut repl = ReplArgs::default();
let mut daemon = false;
while let Some(arg) = argv.next() {
let cert_target = match repl.mounts.flags.last_mut() {
Some(mount) => &mut mount.certs,
None => &mut repl.certs,
};
if cert_flag(&arg, &mut argv, cert_target)? {
continue;
}
if scheduler_flag(&arg, &mut argv)? || width_routing_flag(&arg, &mut argv)? {
continue;
}
match arg.as_str() {
"-h" | "--help" => return Ok(None),
"--plain" => repl.plain = true,
"--demo" => repl.demo = true,
"--daemon" => daemon = true,
"--name" => {
let name = argv
.next()
.ok_or_else(|| "--name needs a value".to_string())?;
ikigai_embedded::set_instance_name(name);
}
"--connect" => {
let target = match argv.peek() {
Some(next) if !next.starts_with('-') => argv.next(),
_ => None,
};
repl.connect = Some(target);
}
"--mount" => {
let spec = argv
.next()
.ok_or_else(|| "--mount needs <prefix>=<socket>".to_string())?;
let (prefix, socket) = spec
.split_once('=')
.ok_or_else(|| format!("--mount expects <prefix>=<socket>, got `{spec}`"))?;
repl.mounts.flags.push(Mount {
prefix: prefix.to_string(),
target: socket.to_string(),
certs: repl.certs.clone(),
kind: ikigai_embedded::MountKind::Alias,
});
}
"--override" => {
let spec = argv
.next()
.ok_or_else(|| "--override needs <prefix>=<target>".to_string())?;
let (prefix, target) = spec
.split_once('=')
.ok_or_else(|| format!("--override expects <prefix>=<target>, got `{spec}`"))?;
repl.mounts.flags.push(Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs: repl.certs.clone(),
kind: ikigai_embedded::MountKind::Override,
});
}
"--no-config-mounts" => {
repl.mounts.declined = true;
}
"--react" => {
repl.react = true;
}
"--prefer" => {
let spec = argv
.next()
.ok_or_else(|| "--prefer needs <prefix>=<target>".to_string())?;
let (prefix, target) = spec
.split_once('=')
.ok_or_else(|| format!("--prefer expects <prefix>=<target>, got `{spec}`"))?;
repl.mounts.flags.push(Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs: repl.certs.clone(),
kind: ikigai_embedded::MountKind::Prefer,
});
}
"-c" | "--command" => {
let command = argv
.next()
.ok_or_else(|| format!("{arg} requires a command argument"))?;
repl.commands.push(command);
}
"-e" | "--eval" => {
let sexpr = argv
.next()
.ok_or_else(|| format!("{arg} requires an s-expression argument"))?;
repl.commands.push(sexpr);
}
"--load" => {
let uri = argv
.next()
.ok_or_else(|| "--load requires a <uri> argument".to_string())?;
let mut command = format!(":load {uri}");
if argv.peek().map(String::as_str) == Some("--cap") {
argv.next();
let scope = argv
.next()
.ok_or_else(|| "--cap requires a capability scope".to_string())?;
command.push_str(&format!(" cap={scope}"));
}
repl.commands.push(command);
}
other => return Err(format!("unknown argument: {other}")),
}
}
repl.mounts.refuse_conflict()?;
if daemon {
return Ok(Some(Mode::Daemon {
mounts: repl.mounts,
}));
}
Ok(Some(Mode::Repl(repl)))
}
#[cfg(feature = "embedded")]
fn main() {
let mode = match parse_args() {
Ok(Some(mode)) => mode,
Ok(None) => {
println!("{USAGE}");
return;
}
Err(e) => {
eprintln!("ikigai: {e}\n\n{USAGE}");
std::process::exit(2);
}
};
#[cfg(feature = "embedded")]
ikigai_embedded::set_instance_name(match &mode {
Mode::Daemon { .. } => "daemon",
Mode::Serve { .. } => "serve",
Mode::Mcp { .. } => "mcp",
_ => "repl",
});
match mode {
Mode::Version => println!("{VERSION_LINE}"),
Mode::Daemon { mounts } => daemon(mounts),
Mode::Mcp {
grants,
scopes,
mounts,
} => mcp(grants, scopes, mounts),
Mode::CertGenerate { force, dir } => cert_generate(force, dir),
Mode::CertAddClient {
name,
cert_dir,
force,
} => cert_add_client(&name, cert_dir, force),
Mode::Serve {
target,
certs,
caps,
announce,
mounts,
http,
trust_proxy,
cors_origins,
routes,
routes_only,
max_body,
} => match (http, target.as_deref()) {
(Some(bind), _) => serve_http(HttpDoor {
bind: &bind,
caps: &caps,
trust_proxy,
cors_origins: &cors_origins,
routes: routes.as_deref(),
routes_only,
max_body,
mounts,
}),
(None, Some(t)) if is_quic(t) => serve_quic(t, &certs, &caps, announce, mounts),
(None, _) if !caps.is_empty() => {
eprintln!("ikigai: --cap sets a per-connection ceiling and needs a quic:// target");
std::process::exit(2);
}
(None, _) => serve_ipc(target, mounts),
},
Mode::Repl(args) => {
if args.demo {
ikigai_embedded::demo_flag().store(true, std::sync::atomic::Ordering::SeqCst);
}
let (engine, topology) =
build_engine(args.connect, args.mounts, &args.certs, args.react).unwrap_or_else(
|e| {
eprintln!("ikigai: {e}");
std::process::exit(1);
},
);
run_repl(engine, args.plain, &args.commands, &topology);
}
}
}
#[cfg(feature = "embedded")]
fn daemon(mounts: Mounts) {
let declined = mounts.declined;
let mounts = match mounts_or_config(mounts) {
Ok(mounts) => mounts,
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
};
announce_mounts("ikigai", &mounts, declined);
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: "none — the writer daemon serves no transport; it holds the reactive kernel"
.to_string(),
mounts: mount_posture(&mounts, declined),
clients: Vec::new(),
surface: None,
authority: Some("root — this process resolves under the identity it runs as".to_string()),
reloads: Vec::new(),
});
let kernel = if mounts.is_empty() {
ikigai_embedded::reactive_kernel_with_mounts(Vec::new())
} else {
let mut resolved = Vec::new();
for mount in mounts {
match resolve_mount(mount) {
Ok(spec) => resolved.push(spec),
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
}
}
ikigai_embedded::reactive_kernel_with_mounts(resolved)
};
let name = ikigai_embedded::instance_name();
match ikigai_embedded::standing_sync_interval() {
Some(every) => eprintln!(
"ikigai: daemon up — instance \"{name}\": standing sync every {}s + watchers (Ctrl-C to stop)",
every.as_secs()
),
None => eprintln!(
"ikigai: daemon up — instance \"{name}\": no \"{name}.derive_every\" in calendar.json — IDLE (Ctrl-C to stop)"
),
}
ikigai_embedded::startup_derive(&kernel);
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
#[cfg(not(feature = "embedded"))]
fn daemon(_mounts: Mounts) {
eprintln!("ikigai: --daemon requires the embedded feature");
std::process::exit(2);
}
#[cfg(feature = "embedded")]
fn mcp_capability(grants: &[String], scopes: &[String]) -> ikigai_core::Capability {
let mut union: Vec<String> = scopes.to_vec();
for name in grants {
union.extend(ikigai_embedded::grant_scopes(name));
}
union.sort();
union.dedup();
if union.is_empty() {
ikigai_core::Capability::root()
} else {
ikigai_core::Capability::scoped(union)
}
}
#[cfg(feature = "embedded")]
fn mcp_filter(grants: &[String]) -> ikigai_mcp::ToolFilter {
let mut filter = ikigai_mcp::ToolFilter::default();
for name in grants {
let (show, hide) = ikigai_embedded::grant_visibility(name);
filter.show.extend(show);
filter.hide.extend(hide);
}
filter
}
#[cfg(feature = "embedded")]
fn mcp(grants: Vec<String>, scopes: Vec<String>, mounts: Mounts) {
use ikigai_mcp::server::handle;
use std::io::{BufRead, Write};
use std::sync::{Arc, Mutex, RwLock};
let capability = Arc::new(RwLock::new(mcp_capability(&grants, &scopes)));
let filter = Arc::new(RwLock::new(mcp_filter(&grants)));
match capability.read().expect("cap lock").scopes() {
None => eprintln!("ikigai mcp: no --grant/--scope — running UNRESTRICTED (root)"),
Some(s) => eprintln!(
"ikigai mcp: serving the manifold under {} scope(s)",
s.len()
),
}
{
let f = filter.read().expect("filter lock");
if !f.show.is_empty() || !f.hide.is_empty() {
eprintln!(
"ikigai mcp: tool visibility — {} shown, {} hidden pattern(s)",
f.show.len(),
f.hide.len()
);
}
}
let declined = mounts.declined;
let mounts = match mounts_or_config(mounts) {
Ok(mounts) => mounts,
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
};
announce_mounts("ikigai mcp", &mounts, declined);
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: "stdio (MCP)".to_string(),
mounts: mount_posture(&mounts, declined),
clients: Vec::new(),
surface: None,
authority: Some(match capability.read().expect("cap lock").scopes() {
None => "root — no --grant/--scope was given (UNRESTRICTED)".to_string(),
Some(s) => format!("{} scope(s) from --grant/--scope", s.len()),
}),
reloads: vec![
"grants.json — the active grant's scopes are re-read while running, and the \
projected tool list is rebuilt live (no restart)"
.to_string(),
],
});
let kernel = if mounts.is_empty() {
ikigai_embedded::watched_kernel()
} else {
let mut resolved = Vec::new();
for mount in mounts {
match resolve_mount(mount) {
Ok(spec) => resolved.push(spec),
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
}
}
for spec in resolved
.iter()
.filter(|s| s.kind == ikigai_embedded::MountKind::Prefer)
{
let _ = spec.resolver.issue(ikigai_core::Request::new(
ikigai_core::Verb::Meta,
ikigai_core::Iri::parse("urn:kernel:catalog").expect("static IRI"),
));
let up = spec.resolver.entries().is_some();
eprintln!(
"ikigai mcp: {} peer is {}",
spec.prefix,
if up {
"up — its tools are projected"
} else {
"absent — its tools are omitted (relaunch when it is up)"
}
);
}
ikigai_embedded::watched_kernel_with_mounts(resolved)
};
let stdout = Arc::new(Mutex::new(std::io::stdout()));
if !grants.is_empty() {
if let Some(path) = ikigai_embedded::grants_path() {
let capability = Arc::clone(&capability);
let filter = Arc::clone(&filter);
let stdout = Arc::clone(&stdout);
std::thread::spawn(move || {
let mtime = || std::fs::metadata(&path).and_then(|m| m.modified()).ok();
let mut last = mtime();
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
let now = mtime();
if now == last {
continue;
}
last = now;
let fresh_cap = mcp_capability(&grants, &scopes);
let fresh_filter = mcp_filter(&grants);
let cap_changed =
fresh_cap.scopes() != capability.read().expect("cap lock").scopes();
let filter_changed = fresh_filter != *filter.read().expect("filter lock");
if cap_changed {
*capability.write().expect("cap lock") = fresh_cap;
}
if filter_changed {
*filter.write().expect("filter lock") = fresh_filter;
}
if cap_changed || filter_changed {
let note =
"{\"jsonrpc\":\"2.0\",\"method\":\"notifications/tools/list_changed\"}";
let mut out = stdout.lock().expect("stdout lock");
let _ = writeln!(out, "{note}");
let _ = out.flush();
eprintln!("ikigai mcp: grant changed — tool list re-emitted");
}
}
});
}
}
let stdin = std::io::stdin();
for line in stdin.lock().lines() {
let Ok(line) = line else { break };
if line.trim().is_empty() {
continue;
}
let Ok(msg) = serde_json::from_str::<serde_json::Value>(&line) else {
continue;
};
let response = {
let cap = capability.read().expect("cap lock");
let filt = filter.read().expect("filter lock");
handle(&kernel, &cap, &filt, &msg)
};
if let Some(response) = response {
let mut out = stdout.lock().expect("stdout lock");
if writeln!(
out,
"{}",
serde_json::to_string(&response).unwrap_or_default()
)
.is_err()
{
break;
}
let _ = out.flush();
}
}
}
#[cfg(not(feature = "embedded"))]
fn mcp(_grants: Vec<String>, _scopes: Vec<String>, _mounts: Mounts) {
eprintln!("ikigai: mcp requires the embedded feature");
std::process::exit(2);
}
#[cfg(feature = "embedded")]
fn with_profiles(engine: Engine) -> Engine {
engine.define_cap_profile("freebusy", ["urn:cap:personal:calendar:read:freebusy"]);
let root = ikigai_embedded::file_root();
let root = root.display();
let read = format!("urn:cap:fs:read:{root}");
let write = format!("urn:cap:fs:write:{root}");
let delete = format!("urn:cap:fs:delete:{root}");
engine.define_cap_profile("read-only", [read.clone()]);
engine.define_cap_profile("read", [read.clone()]);
engine.define_cap_profile("write", [read.clone(), write.clone()]);
engine.define_cap_profile("delete", [read.clone(), write, delete]);
engine.define_cap_profile(
"agent",
["urn:cap:personal:calendar:read:freebusy".to_string(), read],
);
engine.define_cap_profile("lisp", ["urn:cap:lisp"]);
engine
}
#[cfg(feature = "embedded")]
fn build_engine(
connect: Option<Option<String>>,
mounts: Mounts,
certs: &Certs,
react: bool,
) -> Result<(Engine, Vec<String>), String> {
match connect {
None => {
let declined = mounts.declined;
let mounts = mounts_or_config(mounts)?;
let composed = mount_posture(&mounts, declined);
let topology = ikigai_embedded::posture::mount_lines(&composed);
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: "none — in-process (this REPL/one-shot kernel)".to_string(),
mounts: composed,
clients: Vec::new(),
surface: None,
authority: Some(
"root — the running user IS the owner (`cap` attenuates it voluntarily)"
.to_string(),
),
reloads: Vec::new(),
});
let kernel = if mounts.is_empty() {
if react {
ikigai_embedded::reactive_kernel_with_mounts(Vec::new())
} else {
ikigai_embedded::watched_kernel()
}
} else {
let mut resolved = Vec::new();
for mount in mounts {
resolved.push(resolve_mount(mount)?);
}
if react {
ikigai_embedded::reactive_kernel_with_mounts(resolved)
} else {
ikigai_embedded::watched_kernel_with_mounts(resolved)
}
};
Ok((
with_profiles(
Engine::new(kernel)
.with_spawner(std::sync::Arc::new(ikigai_embedded::scheduler()))
.with_width_routing(ikigai_embedded::width_routing()),
),
topology,
))
}
Some(target) => {
if !mounts.flags.is_empty() {
return Err("--mount composes into the embedded kernel; drop --connect".to_string());
}
if mounts.declined {
return Err(
"--no-config-mounts declines the config home for a kernel this process \
BUILDS; a --connect client composes no mounts anyway (the host it attaches \
to owns the topology) — drop one of the two"
.to_string(),
);
}
match target.as_deref() {
Some(t) if is_quic(t) => connect_quic(t, certs),
_ => connect_ipc(target),
}
.map(|engine| (engine, Vec::new()))
}
}
}
#[cfg(feature = "embedded")]
fn mounts_or_config(mounts: Mounts) -> Result<Vec<Mount>, String> {
resolve_mounts(mounts, config_mounts)
}
fn resolve_mounts(
mounts: Mounts,
config: impl FnOnce() -> Result<Vec<Mount>, String>,
) -> Result<Vec<Mount>, String> {
mounts.refuse_conflict()?;
if mounts.declined {
Ok(Vec::new())
} else if mounts.flags.is_empty() {
config()
} else {
Ok(mounts.flags)
}
}
#[cfg(feature = "embedded")]
fn config_mounts() -> Result<Vec<Mount>, String> {
mounts_from_config_lines(ikigai_embedded::config::all("mount"))
}
fn mounts_from_config_lines(lines: Vec<String>) -> Result<Vec<Mount>, String> {
lines
.into_iter()
.map(|line| {
let mut parts = line.split_whitespace();
let mode = parts
.next()
.ok_or_else(|| format!("mount `{line}`: expected <mode> <prefix>=<target>"))?;
let kind = match mode {
"alias" | "mount" => ikigai_embedded::MountKind::Alias,
"override" => ikigai_embedded::MountKind::Override,
"prefer" => ikigai_embedded::MountKind::Prefer,
other => {
return Err(format!(
"mount `{line}`: unknown mode `{other}` (alias | override | prefer)"
))
}
};
let spec = parts
.next()
.ok_or_else(|| format!("mount `{line}`: expected <prefix>=<target>"))?;
let (prefix, target) = spec.split_once('=').ok_or_else(|| {
format!("mount `{line}`: expected <prefix>=<target>, got `{spec}`")
})?;
let mut certs = Certs::default();
if let Some(dir) = parts.next() {
certs.cert_dir = Some(shellexpand_home(dir));
}
Ok(Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs,
kind,
})
})
.collect()
}
fn shellexpand_home(path: &str) -> String {
match path.strip_prefix("~/") {
Some(rest) => std::path::PathBuf::from(std::env::var("HOME").unwrap_or_default())
.join(rest)
.display()
.to_string(),
None => path.to_string(),
}
}
fn resolve_mount(mount: Mount) -> Result<ikigai_embedded::MountSpec, String> {
let Mount {
prefix,
target,
certs,
kind,
} = mount;
let resolver: std::sync::Arc<dyn ikigai_resolve::Resolver> =
if kind == ikigai_embedded::MountKind::Prefer {
std::sync::Arc::new(LazyResolver {
target: target.clone(),
certs,
inner: std::sync::Mutex::new(None),
entries_failed: std::sync::Mutex::new(None),
})
} else {
connect_mount(&target, &certs, kind)?
};
Ok(ikigai_embedded::MountSpec {
prefix,
origin: target,
resolver,
kind,
})
}
struct LazyResolver {
target: String,
certs: Certs,
inner: std::sync::Mutex<Option<std::sync::Arc<dyn ikigai_resolve::Resolver>>>,
entries_failed: std::sync::Mutex<Option<std::time::Instant>>,
}
const ENTRIES_REDIAL_AFTER: std::time::Duration = std::time::Duration::from_secs(30);
impl LazyResolver {
fn get(&self) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, ikigai_core::Error> {
if let Some(resolver) = self.inner.lock().unwrap().clone() {
return Ok(resolver);
}
let resolver = connect_mount(
&self.target,
&self.certs,
ikigai_embedded::MountKind::Prefer,
)
.map_err(|e| ikigai_core::Error::Unavailable(format!("{}: {e}", self.target)))?;
*self.inner.lock().unwrap() = Some(std::sync::Arc::clone(&resolver));
Ok(resolver)
}
}
#[async_trait::async_trait]
impl ikigai_resolve::Resolver for LazyResolver {
fn issue(
&self,
request: ikigai_core::Request,
) -> Result<(ikigai_core::Representation, ikigai_resolve::CacheStatus), ikigai_core::Error>
{
self.get()?.issue(request)
}
fn issue_as(
&self,
request: ikigai_core::Request,
capability: &ikigai_core::Capability,
) -> Result<(ikigai_core::Representation, ikigai_resolve::CacheStatus), ikigai_core::Error>
{
self.get()?.issue_as(request, capability)
}
async fn issue_as_async(
&self,
request: ikigai_core::Request,
capability: &ikigai_core::Capability,
) -> Result<(ikigai_core::Representation, ikigai_resolve::CacheStatus), ikigai_core::Error>
{
let resolver = self.get()?;
resolver.issue_as_async(request, capability).await
}
fn is_cached(
&self,
request: &ikigai_core::Request,
capability: &ikigai_core::Capability,
) -> bool {
match self.inner.lock().unwrap().clone() {
Some(resolver) => resolver.is_cached(request, capability),
None => false,
}
}
fn entries(&self) -> Option<Vec<ikigai_core::SpaceEntry>> {
self.try_entries().ok().flatten()
}
fn try_entries(&self) -> Result<Option<Vec<ikigai_core::SpaceEntry>>, ikigai_core::Error> {
if let Some(resolver) = self.inner.lock().unwrap().clone() {
return resolver.try_entries();
}
{
let failed = self.entries_failed.lock().unwrap();
if let Some(at) = *failed {
if at.elapsed() < ENTRIES_REDIAL_AFTER {
return Ok(None); }
}
}
match self.get() {
Ok(resolver) => {
*self.entries_failed.lock().unwrap() = None;
resolver.try_entries()
}
Err(_) => {
#[allow(clippy::disallowed_methods)]
let failed_at = std::time::Instant::now();
*self.entries_failed.lock().unwrap() = Some(failed_at);
Ok(None)
}
}
}
fn transport(&self) -> String {
match self.inner.lock().unwrap().clone() {
Some(resolver) => resolver.transport(),
None => format!("{} · not connected", self.target),
}
}
}
fn connect_mount(
target: &str,
certs: &Certs,
kind: ikigai_embedded::MountKind,
) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, String> {
let flag = mount_flag(kind);
if let Some(name) = target.strip_prefix("peer:") {
let (resolved, certs) = resolve_peer(name, certs)?;
return connect_mount_quic(&resolved, &certs, flag);
}
if is_quic(target) {
connect_mount_quic(target, certs, flag)
} else {
connect_mount_ipc(target, kind, flag)
}
}
fn mount_flag(kind: ikigai_embedded::MountKind) -> &'static str {
match kind {
ikigai_embedded::MountKind::Alias => "--mount",
ikigai_embedded::MountKind::Override => "--override",
ikigai_embedded::MountKind::Prefer => "--prefer",
}
}
#[cfg(all(feature = "embedded", feature = "quic"))]
const PEER_DISCOVERY_WAIT: std::time::Duration = std::time::Duration::from_millis(1500);
#[cfg(all(feature = "embedded", feature = "quic"))]
fn resolve_peer(name: &str, certs: &Certs) -> Result<(String, Certs), String> {
let browser = ikigai_discovery::Browser::start()
.map_err(|e| format!("peer:{name}: could not browse this network: {e}"))?;
std::thread::sleep(PEER_DISCOVERY_WAIT);
let peer = browser.peer(name).ok_or_else(|| {
format!(
"peer:{name}: no peer is announcing under that name \
(`source urn:peer:list` shows who is). The peer must serve with `--announce`."
)
})?;
let addr = peer
.socket_addr()
.ok_or_else(|| format!("peer:{name}: announced no usable address"))?;
let mut certs = certs.clone();
if certs.cert_dir.is_none() && certs.server_cert.is_none() {
let dir = peer_cert_dir(name);
if !dir.join("server.crt").exists() {
return Err(format!(
"peer:{name}: found it at {addr}, but this machine holds no pinned \
certificate for it ({}/server.crt). Discovery supplies an address, never \
trust — enrol the peer first (on {name}: `ikigai cert add-client <this \
host>`, then copy its server.crt here), or pass --cert-dir.",
dir.display()
));
}
certs.cert_dir = Some(dir.display().to_string());
}
Ok((format!("quic://{addr}"), certs))
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn peer_cert_dir(name: &str) -> std::path::PathBuf {
ikigai_embedded::config::config_home()
.unwrap_or_default()
.join(format!("quic-{name}"))
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn resolve_peer(name: &str, _certs: &Certs) -> Result<(String, Certs), String> {
Err(format!(
"peer:{name}: mounting a discovered peer needs the `quic` feature"
))
}
#[cfg(feature = "embedded")]
fn describe_timeout(default: std::time::Duration) -> Option<std::time::Duration> {
match ikigai_embedded::config::get("describe.timeout")
.and_then(|v| v.trim().parse::<u64>().ok())
{
Some(0) => None,
Some(secs) => Some(std::time::Duration::from_secs(secs)),
None => Some(default),
}
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn quic_idle_timeout() -> std::time::Duration {
ikigai_embedded::config::get("quic.timeout")
.and_then(|s| s.parse::<u64>().ok())
.map(std::time::Duration::from_secs)
.unwrap_or(ikigai_quic::DEFAULT_IDLE_TIMEOUT)
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn connect_mount_quic(
target: &str,
certs: &Certs,
flag: &'static str,
) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, String> {
let addr = quic::parse_addr(target)?;
let identity = quic::client_identity(certs)?;
let trusted = quic::trusted_server_cert(certs)?;
let resolver = ikigai_quic::connect_with(addr, &identity, &trusted, quic_idle_timeout())
.map_err(|e| format!("{flag}: connect {target}: {e}"))?
.with_describe_timeout(describe_timeout(ikigai_quic::DEFAULT_DESCRIBE_TIMEOUT));
Ok(std::sync::Arc::new(resolver))
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn connect_mount_quic(
_target: &str,
_certs: &Certs,
flag: &'static str,
) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, String> {
Err(format!(
"{flag} of a quic:// target needs the `quic` feature"
))
}
#[cfg(all(feature = "embedded", feature = "ipc"))]
fn connect_mount_ipc(
socket: &str,
kind: ikigai_embedded::MountKind,
flag: &'static str,
) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, String> {
let mode = match kind {
ikigai_embedded::MountKind::Alias => ikigai_ipc::HelloMode::Alias,
_ => ikigai_ipc::HelloMode::Verbatim,
};
let resolver = ikigai_ipc::connect_as(std::path::Path::new(socket), mode)
.map_err(|e| format!("{flag}: connect {socket}: {e}"))?
.with_describe_timeout(describe_timeout(ikigai_ipc::DEFAULT_DESCRIBE_TIMEOUT));
Ok(std::sync::Arc::new(resolver))
}
#[cfg(all(feature = "embedded", not(feature = "ipc")))]
fn connect_mount_ipc(
_socket: &str,
_kind: ikigai_embedded::MountKind,
flag: &'static str,
) -> Result<std::sync::Arc<dyn ikigai_resolve::Resolver>, String> {
Err(format!(
"{flag} of a Unix socket needs the `ipc` feature (Unix only)"
))
}
#[cfg(feature = "embedded")]
fn run_repl(engine: Engine, plain: bool, commands: &[String], topology: &[String]) {
if !commands.is_empty() {
#[cfg(not(target_family = "wasm"))]
{
use std::io::{IsTerminal, Read};
if !std::io::stdin().is_terminal() {
engine.set_piped_input_with(|| {
let mut buf = Vec::new();
let _ = std::io::stdin().read_to_end(&mut buf);
buf
});
}
}
std::process::exit(repl::run_commands(engine, commands));
}
#[cfg(not(target_family = "wasm"))]
{
use std::io::IsTerminal;
if !plain && std::io::stdin().is_terminal() && std::io::stdout().is_terminal() {
if let Err(e) = tui::run(engine, ikigai_engine::config::keybindings(), topology) {
eprintln!("ikigai: tui error: {e}");
std::process::exit(1);
}
return;
}
repl::run(engine, topology);
}
#[cfg(target_family = "wasm")]
repl::run(engine, topology);
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn cert_generate(force: bool, dir: Option<String>) -> ! {
match quic::generate(force, dir.map(std::path::PathBuf::from)) {
Ok(dir) => {
println!(
"wrote server.{{crt,key}} and client.{{crt,key}} to {}",
dir.display()
);
println!(
"to attach a client on another machine, copy client.crt, client.key, and \
server.crt there."
);
std::process::exit(0);
}
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(1);
}
}
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn cert_add_client(name: &str, cert_dir: Option<String>, force: bool) -> ! {
let certs = Certs {
cert_dir,
..Default::default()
};
match quic::add_client(name, &certs, force) {
Ok(path) => {
println!("wrote a new client identity to {}", path.display());
println!(
"the server trusts it on next start (it reads clients/*.crt); to use it, copy \
{name}.crt, {name}.key, and server.crt to the client machine."
);
if let Ok(pem) = std::fs::read_to_string(&path) {
if let Ok(fingerprint) = ikigai_quic::fingerprint_of_pem(&pem) {
println!(
"fingerprint: {fingerprint}\n \
to give it its own authority, add it to the `clients` map in \
{}: \"{fingerprint}\": {{\"grant\": \"<grant>\", \"label\": \"{name}\"}}",
ikigai_embedded::clients::clients_path()
.map_or_else(|| "clients.json".into(), |p| p.display().to_string())
);
}
}
std::process::exit(0);
}
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(1);
}
}
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn cert_generate(_force: bool, _dir: Option<String>) -> ! {
eprintln!("ikigai: `cert generate` needs the `quic` feature");
std::process::exit(1);
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn cert_add_client(_name: &str, _cert_dir: Option<String>, _force: bool) -> ! {
eprintln!("ikigai: `cert add-client` needs the `quic` feature");
std::process::exit(1);
}
#[cfg(all(feature = "embedded", feature = "quic"))]
const FINGERPRINT_SHOWN: usize = 16;
#[cfg(all(feature = "embedded", feature = "quic"))]
fn short_fingerprint(full: &str) -> &str {
&full[..full.len().min(FINGERPRINT_SHOWN)]
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn trusted_identities(
trusted: &[quic::TrustedClient],
) -> Vec<ikigai_embedded::posture::TrustedIdentity> {
trusted
.iter()
.map(|client| ikigai_embedded::posture::TrustedIdentity {
label: client.label.clone(),
fingerprint: ikigai_quic::fingerprint_of_pem(&client.pem).map_or_else(
|_| "unreadable".to_string(),
|f| short_fingerprint(&f).to_string(),
),
path: client.path.display().to_string(),
})
.collect()
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn serve_quic(target: &str, certs: &Certs, caps: &[String], announce: bool, mounts: Mounts) -> ! {
let caps = caps.to_vec();
let result = (|| -> Result<(), String> {
let addr = quic::parse_addr(target)?;
let identity = quic::server_identity(certs)?;
let trusted = quic::trusted_clients(certs)?;
let declined = mounts.declined;
let mounts = mounts_or_config(mounts)?;
let announced = announce.then(ikigai_embedded::instance_name);
let mounts: Vec<Mount> = mounts
.into_iter()
.filter(|mount| {
let own = is_own_quic_addr(&mount.target, addr, announced);
if own {
eprintln!(
"ikigai: mount `{}={}` targets this process's own serve address — \
skipped (this instance IS the server; that line is for the other \
processes on this machine)",
mount.prefix, mount.target
);
}
!own
})
.collect();
announce_mounts("ikigai", &mounts, declined);
let composed = mount_posture(&mounts, declined);
let mut resolved = Vec::new();
for mount in mounts {
resolved.push(resolve_mount(mount)?);
}
let enrolment = ikigai_embedded::clients::enrolment()?;
let file_root = ikigai_embedded::file_root();
let declared: Vec<String> = caps
.iter()
.cloned()
.chain(
enrolment
.iter()
.flat_map(|e| e.grant_names())
.flat_map(|name| ikigai_embedded::grant_scopes(&name)),
)
.collect();
let unaddressable = ikigai_embedded::tenant::unaddressable_fs_scopes(&declared, &file_root);
if !unaddressable.is_empty() {
return Err(format!(
"these file scopes name paths no client of this server can address:\n \
{}\n \
a served connection reaches only {}/<its segment>/… — the file module is \
jailed there and refuses everything outside it, capability or not.\n \
write the path RELATIVE to the client's own workspace (`urn:cap:fs:read:notes` \
grants the `notes` it addresses as `urn:file:notes/…`), or point IKIGAI_FILES \
at the tree you meant to serve.",
unaddressable.join("\n "),
file_root.display()
));
}
let minter: ikigai_quic::Minter = if enrolment.is_some() {
let ceiling = if caps.is_empty() {
ikigai_core::Capability::root()
} else {
ikigai_core::Capability::scoped(caps.clone())
};
let path = ikigai_embedded::clients::clients_path()
.map_or_else(|| "clients.json".into(), |p| p.display().to_string());
let root = file_root.clone();
std::sync::Arc::new(move |peer: &ikigai_quic::PeerIdentity| {
match ikigai_embedded::clients::authority(&peer.fingerprint, &ceiling) {
Ok((grant, capability)) => {
let capability = ikigai_embedded::tenant::root_fs_scopes(
&capability,
&ikigai_embedded::tenant::tenant_root(&root, &peer.segment_id),
);
eprintln!(
"ikigai: client {} → grant \"{grant}\" ({})",
short_fingerprint(&peer.fingerprint),
match capability.scopes() {
None => "unrestricted".to_string(),
Some(s) => format!("{} scope(s)", s.len()),
}
);
Some(ikigai_quic::Session {
capability,
file_segment: peer.segment_id.clone(),
})
}
Err(why) => {
eprintln!(
"ikigai: REFUSED a trusted client certificate — {why}\n \
fingerprint: {}\n \
to enrol it, add it to the `clients` map in {path}",
peer.fingerprint
);
None
}
}
})
} else if caps.is_empty() {
let root = file_root.clone();
std::sync::Arc::new(move |peer: &ikigai_quic::PeerIdentity| {
let segment = ikigai_embedded::tenant::tenant_root(&root, &peer.segment_id);
let _ = std::fs::create_dir_all(&segment); let seg = segment.display();
Some(ikigai_quic::Session {
capability: ikigai_core::Capability::root().attenuate([
format!("urn:cap:fs:read:{seg}"),
format!("urn:cap:fs:write:{seg}"),
format!("urn:cap:fs:delete:{seg}"),
]),
file_segment: peer.segment_id.clone(),
})
})
} else {
let ceiling = ikigai_core::Capability::scoped(caps.clone());
let root = file_root.clone();
std::sync::Arc::new(move |peer: &ikigai_quic::PeerIdentity| {
Some(ikigai_quic::Session {
capability: ikigai_embedded::tenant::root_fs_scopes(
&ceiling,
&ikigai_embedded::tenant::tenant_root(&root, &peer.segment_id),
),
file_segment: peer.segment_id.clone(),
})
})
};
let posture = match (&enrolment, caps.is_empty()) {
(Some(e), true) => format!("per-identity grants: {} enrolled", e.len()),
(Some(e), false) => format!(
"per-identity grants: {} enrolled, under ceiling: {}",
e.len(),
caps.join(", ")
),
(None, true) => "per-client workspaces".to_string(),
(None, false) => format!("fixed ceiling: {}", caps.join(", ")),
};
if let Some(default_grant) = enrolment.as_ref().and_then(|e| e.default_grant()) {
eprintln!(
"ikigai: warning: clients.json sets an explicit shared default grant \
\"{default_grant}\" — every trusted certificate that is not enrolled \
individually gets it"
);
}
let surface_caps: Vec<String> = {
let mut union = caps.clone();
for grant in enrolment.iter().flat_map(|e| e.grant_names()) {
union.extend(ikigai_embedded::grant_scopes(&grant));
}
union.sort();
union.dedup();
union
};
let surface = ikigai_embedded::ServedSurface {
personal: surface_caps
.iter()
.any(|c| c.starts_with("urn:cap:personal:")),
wire_eval: surface_caps
.iter()
.any(|c| c == "urn:cap:lisp" || c == "urn:cap:lisp:run"),
llm: surface_caps.iter().any(|c| c.starts_with("urn:cap:net:")),
};
let kernel = ikigai_embedded::served_kernel_with_mounts("Remote (QUIC)", surface, resolved);
let signed_door = ikigai_embedded::code_signers_configured();
let mut faces = vec![if surface.personal {
"calendar-only"
} else {
"host + fs"
}];
if surface.llm {
faces.push("llm");
}
if surface.wire_eval {
faces.push("governed eval");
}
if surface.wire_eval && signed_door {
faces.push("signed-run");
}
let surface = faces.join(" + ");
let identities = trusted_identities(&trusted);
for line in ikigai_embedded::posture::client_lines(&identities) {
eprintln!("ikigai: {line}");
}
eprintln!("ikigai: serving on {target} ({posture}; surface: {surface}) (Ctrl-C to stop)");
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: target.to_string(),
mounts: composed,
clients: identities,
surface: Some(surface.clone()),
authority: Some(posture.clone()),
reloads: if enrolment.is_some() {
vec![
"clients.json (and the grants it names) — re-read on EVERY connection, \
so which authority each certificate gets is decided then, not now; an \
edit revokes on the next call. The set of certificates that may \
connect at all is frozen at startup."
.to_string(),
]
} else {
Vec::new()
},
});
let _announcement = if announce {
let name = ikigai_embedded::instance_name();
let wire_version = ikigai_wire::PROTOCOL_VERSION.to_string();
match ikigai_discovery::announce_with(
name,
addr.port(),
&[
(ikigai_discovery::TXT_SURFACE, surface.as_str()),
(ikigai_discovery::TXT_CEILING, posture.as_str()),
(ikigai_discovery::TXT_VERSION, wire_version.as_str()),
],
|event| eprintln!("ikigai: {event}"),
) {
Ok(handle) => Some(handle),
Err(e) => {
eprintln!("ikigai: warning: could not announce on this network: {e}");
None
}
}
} else {
None
};
let pems: Vec<String> = trusted.into_iter().map(|client| client.pem).collect();
ikigai_quic::serve_with(kernel, addr, &identity, &pems, minter, quic_idle_timeout())
.map_err(|e| e.to_string())
})();
match result {
Ok(()) => std::process::exit(0),
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(1);
}
}
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn connect_quic(target: &str, certs: &Certs) -> Result<Engine, String> {
let addr = quic::parse_addr(target)?;
let identity = quic::client_identity(certs)?;
let trusted = quic::trusted_server_cert(certs)?;
let resolver = ikigai_quic::connect_with(addr, &identity, &trusted, quic_idle_timeout())
.map_err(|e| format!("connect {target}: {e}"))?
.with_describe_timeout(describe_timeout(ikigai_quic::DEFAULT_DESCRIBE_TIMEOUT));
Ok(with_profiles(Engine::new(resolver)))
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn serve_quic(
_target: &str,
_certs: &Certs,
_caps: &[String],
_announce: bool,
_mounts: Mounts,
) -> ! {
eprintln!("ikigai: `quic://` needs the `quic` feature");
std::process::exit(1);
}
#[cfg(all(feature = "embedded", not(feature = "quic")))]
fn connect_quic(_target: &str, _certs: &Certs) -> Result<Engine, String> {
Err("`quic://` needs the `quic` feature".to_string())
}
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
fn serve_ipc(path: Option<String>, mounts: Mounts) -> ! {
let socket = ipc_socket(path);
if let Some(e) = socket_path_error(&socket) {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
let declined = mounts.declined;
let mounts = match mounts_or_config(mounts) {
Ok(mounts) => mounts,
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
};
let mounts: Vec<Mount> = mounts
.into_iter()
.filter(|mount| {
let own = is_own_socket(&mount.target, &socket);
if own {
eprintln!(
"ikigai: mount `{}={}` targets this process's own serve socket — \
skipped (this instance IS the server; that line is for the other \
processes on this machine)",
mount.prefix, mount.target
);
}
!own
})
.collect();
announce_mounts("ikigai", &mounts, declined);
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: socket.display().to_string(),
mounts: mount_posture(&mounts, declined),
clients: Vec::new(),
surface: None,
authority: Some(
"root — every request on this socket resolves as the owner; the socket's file \
permissions are the boundary"
.to_string(),
),
reloads: Vec::new(),
});
let mut resolved = Vec::new();
for mount in mounts {
match resolve_mount(mount) {
Ok(spec) => resolved.push(spec),
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
}
}
eprintln!("ikigai: serving on {} (Ctrl-C to stop)", socket.display());
let kernel = ikigai_embedded::trusted_kernel_with_mounts("Remote (IPC)", resolved);
match ikigai_ipc::serve(kernel, &socket) {
Ok(()) => std::process::exit(0),
Err(e) => {
eprintln!("ikigai: serve error: {e}");
std::process::exit(1);
}
}
}
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
fn connect_ipc(path: Option<String>) -> Result<Engine, String> {
let socket = ipc_socket(path);
let timeout = ikigai_embedded::config::get("ipc.timeout")
.and_then(|v| v.trim().parse::<u64>().ok())
.map(std::time::Duration::from_secs)
.unwrap_or(ikigai_ipc::DEFAULT_TIMEOUT);
let resolver = ikigai_ipc::connect_with_timeout(&socket, Some(timeout))
.map_err(|e| format!("connect {}: {e}", socket.display()))?
.with_describe_timeout(describe_timeout(ikigai_ipc::DEFAULT_DESCRIBE_TIMEOUT));
Ok(with_profiles(Engine::new(resolver)))
}
#[cfg(all(feature = "embedded", feature = "quic"))]
fn is_own_quic_addr(target: &str, bind: std::net::SocketAddr, announced: Option<&str>) -> bool {
if let Some(name) = target.strip_prefix("peer:") {
return announced == Some(name);
}
if !is_quic(target) {
return false; }
let Ok(addr) = quic::parse_addr(target) else {
return false;
};
if addr.port() != bind.port() {
return false;
}
addr.ip() == bind.ip()
|| (bind.ip().is_unspecified()
&& (addr.ip().is_loopback() || std::net::UdpSocket::bind((addr.ip(), 0)).is_ok()))
}
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
fn is_own_socket(target: &str, socket: &std::path::Path) -> bool {
if is_quic(target) || target.starts_with("peer:") {
return false;
}
let absolute = |p: std::path::PathBuf| std::path::absolute(&p).unwrap_or(p);
absolute(std::path::PathBuf::from(shellexpand_home(target))) == absolute(socket.to_path_buf())
}
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
const SUN_PATH_CAPACITY: usize = if cfg!(target_os = "linux") { 108 } else { 104 };
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
fn socket_path_error(socket: &std::path::Path) -> Option<String> {
use std::os::unix::ffi::OsStrExt;
let len = socket.as_os_str().as_bytes().len();
(len >= SUN_PATH_CAPACITY).then(|| {
format!(
"socket path is {len} bytes, but a Unix socket path fits {} on this \
platform — serve at a shorter path: {}",
SUN_PATH_CAPACITY - 1,
socket.display()
)
})
}
#[cfg(all(feature = "embedded", feature = "ipc", unix))]
fn ipc_socket(path: Option<String>) -> std::path::PathBuf {
path.map(std::path::PathBuf::from)
.or_else(ikigai_ipc::default_socket_path)
.unwrap_or_else(|| {
eprintln!("ikigai: no socket path given and no runtime directory to default to");
std::process::exit(2);
})
}
#[cfg(all(feature = "embedded", not(all(feature = "ipc", unix))))]
fn serve_ipc(_path: Option<String>, _mounts: Mounts) -> ! {
eprintln!("ikigai: a Unix-socket server needs the `ipc` feature on a Unix platform");
std::process::exit(1);
}
#[cfg(all(feature = "embedded", feature = "web"))]
fn serve_http(door: HttpDoor<'_>) -> ! {
let HttpDoor {
bind,
caps,
trust_proxy,
cors_origins,
routes,
routes_only,
max_body,
mounts,
} = door;
use std::net::SocketAddr;
let addr: SocketAddr = if let Ok(port) = bind.parse::<u16>() {
SocketAddr::from(([127, 0, 0, 1], port))
} else {
match bind.parse() {
Ok(a) => a,
Err(e) => {
eprintln!("ikigai: --http wants a port or host:port ({bind}: {e})");
std::process::exit(2);
}
}
};
let declined = mounts.declined;
let mounts = match mounts_or_config(mounts) {
Ok(mounts) => mounts,
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
};
announce_mounts("ikigai", &mounts, declined);
let composed = mount_posture(&mounts, declined);
let mut resolved = Vec::new();
for mount in mounts {
match resolve_mount(mount) {
Ok(spec) => resolved.push(spec),
Err(e) => {
eprintln!("ikigai: {e}");
std::process::exit(2);
}
}
}
let kernel = std::sync::Arc::new(ikigai_embedded::kernel_for_with_mounts(
"Remote (HTTP)",
resolved,
));
let (cap_fn, posture) = if caps.is_empty() {
(ikigai_web::public_cap(), "public cap".to_string())
} else {
(
ikigai_web::fixed_cap(caps.to_vec()),
format!("fixed ceiling: {}", caps.join(", ")),
)
};
let mut config = ikigai_web::EdgeConfig {
trust_proxy,
routes_only,
..Default::default()
};
if let Some(max) = max_body {
config.max_body_bytes = max;
}
config.cors.allowed_origins = cors_origins.to_vec();
let runtime = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
{
Ok(r) => r,
Err(e) => {
eprintln!("ikigai: could not start the async runtime: {e}");
std::process::exit(1);
}
};
let mut routes_watched = false;
let route_note = match routes {
Some(iri) => {
let loader = ikigai_embedded::kernel();
let table = match runtime.block_on(route_load::load_route_table(
&loader,
iri,
&ikigai_core::Capability::root(),
)) {
Ok(t) => t,
Err(e) => {
eprintln!("ikigai: route load failed ({e})");
std::process::exit(1);
}
};
let n = table.routes.len();
let live = ikigai_web::live_routes(table);
config.live_routes = Some(live.clone());
if let Some(path) = route_load::watch_path(iri, &ikigai_embedded::file_root()) {
let iri_owned = iri.to_string();
runtime.spawn(async move {
let mtime =
|p: &std::path::Path| std::fs::metadata(p).and_then(|m| m.modified()).ok();
let mut last = mtime(&path);
loop {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let now = mtime(&path);
if now == last {
continue;
}
last = now;
let loader = ikigai_embedded::kernel();
match route_load::load_route_table(
&loader,
&iri_owned,
&ikigai_core::Capability::root(),
)
.await
{
Ok(t) => {
let m = t.routes.len();
ikigai_web::swap_routes(&live, t);
eprintln!("ikigai: reloaded {m} route(s) from {iri_owned}");
}
Err(e) => eprintln!(
"ikigai: route reload failed ({e}) — keeping the current table"
),
}
}
});
routes_watched = true;
format!("{n} route(s) from {iri}, watching")
} else {
format!("{n} route(s) from {iri}")
}
}
None => "mechanical routing".to_string(),
};
let route_note = if routes_only {
format!("{route_note}, routes-only (un-routed → 404)")
} else {
route_note
};
let cors_note = if cors_origins.is_empty() {
"CORS closed".to_string()
} else {
format!("CORS: {}", cors_origins.join(", "))
};
let proxy_note = if trust_proxy {
"trusting X-Forwarded-*"
} else {
"no proxy trust"
};
eprintln!(
"ikigai: serving HTTP on {addr} ({posture}; {route_note}; {cors_note}; {proxy_note}; terminate TLS at your proxy) (Ctrl-C to stop)"
);
ikigai_embedded::posture::set_posture(ikigai_embedded::posture::Posture {
door: format!("http://{addr}"),
mounts: composed,
clients: Vec::new(),
surface: Some(route_note.clone()),
authority: Some(posture.clone()),
reloads: if routes_watched {
vec![
"the route table — the watched route resource is re-queried when the file \
changes and swapped live, so the route count above is the startup reading"
.to_string(),
]
} else {
Vec::new()
},
});
match runtime.block_on(ikigai_web::serve_with(kernel, cap_fn, addr, config)) {
Ok(()) => std::process::exit(0),
Err(e) => {
eprintln!("ikigai: HTTP serve error: {e}");
std::process::exit(1);
}
}
}
#[cfg(not(all(feature = "embedded", feature = "web")))]
fn serve_http(_door: HttpDoor<'_>) -> ! {
eprintln!("ikigai: the inbound HTTP face needs the `web` feature (build with --features web)");
std::process::exit(1);
}
#[cfg(all(feature = "embedded", not(all(feature = "ipc", unix))))]
fn connect_ipc(_path: Option<String>) -> Result<Engine, String> {
Err("attaching to a Unix socket needs the `ipc` feature on a Unix platform".to_string())
}
#[cfg(not(feature = "embedded"))]
fn main() {
if matches!(parse_args(), Ok(Some(Mode::Version))) {
println!("{VERSION_LINE}");
return;
}
eprintln!(
"ikigai {}: built without a transport. Rebuild with a transport feature, e.g. `--features embedded`.",
env!("CARGO_PKG_VERSION")
);
std::process::exit(1);
}
#[cfg(test)]
mod mount_posture_tests {
use super::*;
fn argv(args: &[&str]) -> Vec<String> {
args.iter().map(|s| s.to_string()).collect()
}
fn mount(kind: ikigai_embedded::MountKind) -> Mount {
Mount {
prefix: "urn:x:".to_string(),
target: "/tmp/x.sock".to_string(),
certs: Certs::default(),
kind,
}
}
fn posture(args: &[&str]) -> Result<(usize, bool), String> {
let mounts = match parse_argv(argv(args).into_iter())? {
Some(Mode::Repl(repl)) => repl.mounts,
Some(Mode::Daemon { mounts }) => mounts,
Some(Mode::Serve { mounts, .. }) => mounts,
Some(Mode::Mcp { mounts, .. }) => mounts,
_ => panic!("expected a kernel-building mode"),
};
Ok((mounts.flags.len(), mounts.declined))
}
#[test]
fn declining_composes_zero_and_never_reads_the_config_home() {
let declined = Mounts {
flags: Vec::new(),
declined: true,
};
let resolved = resolve_mounts(declined, || {
panic!("--no-config-mounts must not read the config home")
})
.expect("declining is not an error");
assert!(resolved.is_empty(), "declining composes nothing");
}
#[test]
fn no_flags_still_compose_the_machines_topology() {
let resolved = resolve_mounts(Mounts::default(), || {
Ok(vec![mount(ikigai_embedded::MountKind::Prefer)])
})
.expect("a parsing config home is not an error");
assert_eq!(resolved.len(), 1, "the config home is the default posture");
}
#[test]
fn mount_flags_win_wholesale_over_the_config_home() {
let flagged = Mounts {
flags: vec![mount(ikigai_embedded::MountKind::Prefer)],
declined: false,
};
let resolved = resolve_mounts(flagged, || panic!("flags win wholesale"))
.expect("flags are not an error");
assert_eq!(resolved.len(), 1);
assert_eq!(resolved[0].prefix, "urn:x:");
}
#[test]
fn declining_beside_a_mount_flag_refuses_to_start() {
let mount_flags = [
("--mount", "urn:a:=/tmp/a.sock"),
("--override", "urn:b:=/tmp/b.sock"),
("--prefer", "urn:c:=/tmp/c.sock"),
];
let modes: [&[&str]; 4] = [&[], &["--daemon"], &["serve", "/tmp/s.sock"], &["mcp"]];
for prefix in modes {
for (flag, spec) in mount_flags {
for order in [
vec!["--no-config-mounts", flag, spec],
vec![flag, spec, "--no-config-mounts"],
] {
let args: Vec<&str> = prefix.iter().copied().chain(order).collect();
let e = posture(&args).expect_err(&format!("{args:?} must refuse to start"));
assert!(
e.contains("--no-config-mounts") && e.contains(flag),
"the refusal must name BOTH flags, got: {e}"
);
}
}
}
}
#[test]
fn every_kernel_building_mode_honours_the_flag() {
for args in [
vec!["--no-config-mounts"],
vec!["--daemon", "--no-config-mounts"],
vec!["serve", "/tmp/s.sock", "--no-config-mounts"],
vec![
"serve",
"quic://0.0.0.0:4433",
"--no-config-mounts",
"--announce",
],
vec!["serve", "--http", "8642", "--no-config-mounts"],
vec!["mcp", "--no-config-mounts"],
] {
assert_eq!(
posture(&args).unwrap_or_else(|e| panic!("{args:?}: {e}")),
(0, true),
"{args:?} must reach its kernel builder as the DECLINE posture"
);
}
}
#[test]
fn declining_beside_connect_refuses_rather_than_doing_nothing() {
let declined = Mounts {
flags: Vec::new(),
declined: true,
};
let e = match build_engine(
Some(Some("/tmp/ikigai-no-such-socket".to_string())),
declined,
&Certs::default(),
false,
) {
Err(e) => e,
Ok(_) => panic!("a flag with nothing to do must say so"),
};
assert!(
e.contains("--no-config-mounts") && e.contains("--connect"),
"the refusal must name both, got: {e}"
);
}
fn a_mount(prefix: &str, target: &str) -> Mount {
Mount {
prefix: prefix.to_string(),
target: target.to_string(),
certs: Certs::default(),
kind: ikigai_embedded::MountKind::Prefer,
}
}
#[test]
fn the_banner_distinguishes_declined_from_simply_none() {
let none = mount_lines(&[], false);
assert_eq!(none.len(), 1);
assert!(
none[0].contains("none composed"),
"an empty topology says so in words: {none:?}"
);
let declined = mount_lines(&[a_mount("urn:x:", "/tmp/x.sock")], true);
assert_eq!(declined.len(), 1);
assert!(
declined[0].contains(ikigai_embedded::posture::MOUNTS_DECLINED),
"the decline names itself: {declined:?}"
);
assert!(
!declined[0].contains("urn:x:"),
"a declined door composed NOTHING and must name no mount: {declined:?}"
);
assert_ne!(none[0], declined[0], "the two postures never read alike");
}
#[test]
fn the_banner_names_every_mount_rather_than_counting_them() {
let lines = mount_lines(
&[
a_mount("urn:iki:store:", "/Users/x/.ikigai/gonk.sock"),
a_mount("urn:llm:", "quic://plasma.local:4433"),
],
false,
);
assert_eq!(
lines.len(),
2,
"one line per mount, never a tally: {lines:?}"
);
assert_eq!(
lines[0],
"mount prefer urn:iki:store: -> /Users/x/.ikigai/gonk.sock"
);
assert_eq!(
lines[1],
"mount prefer urn:llm: -> quic://plasma.local:4433"
);
assert!(
!lines.iter().any(|line| line.contains("2 mount")),
"no count survives anywhere in the block: {lines:?}"
);
}
#[test]
fn a_mounts_own_certificates_are_named_on_its_line() {
let mut mount = a_mount("urn:cal:", "quic://bug.local:4433");
mount.certs.cert_dir = Some("/Users/x/.config/ikigai/quic-bug".to_string());
assert_eq!(
mount_lines(&[mount], false),
vec!["mount prefer urn:cal: -> quic://bug.local:4433 [certs /Users/x/.config/ikigai/quic-bug]"]
);
}
#[cfg(feature = "quic")]
#[test]
fn the_banner_names_each_trusted_certificate_and_labels_the_base() {
let base = ikigai_quic::generate();
let peer = ikigai_quic::generate();
let full =
ikigai_quic::fingerprint_of_pem(&peer.cert_pem).expect("a generated cert parses");
let lines = ikigai_embedded::posture::client_lines(&trusted_identities(&[
quic::TrustedClient {
label: "base".to_string(),
path: "/Users/x/.config/ikigai/quic/client.crt".into(),
pem: base.cert_pem.clone(),
},
quic::TrustedClient {
label: "plasma".to_string(),
path: "/Users/x/.config/ikigai/quic/clients/plasma.crt".into(),
pem: peer.cert_pem.clone(),
},
]));
assert_eq!(lines.len(), 2, "one line per certificate: {lines:?}");
assert!(
lines[0].starts_with("client base "),
"the base cert is labelled as the base, not listed as a peer: {lines:?}"
);
assert_eq!(
lines[1],
format!(
"client plasma {} /Users/x/.config/ikigai/quic/clients/plasma.crt",
&full[..16]
)
);
assert_eq!(full.len(), 64, "the full id stays the SHA-256 hex");
assert!(
full.starts_with(short_fingerprint(&full)),
"what the banner shows is a PREFIX of what `cert add-client` prints"
);
assert!(
!lines.iter().any(|line| line.contains("cert(s)")),
"no count survives anywhere in the block: {lines:?}"
);
}
#[cfg(feature = "quic")]
#[test]
fn an_unparseable_certificate_still_gets_a_line() {
let lines =
ikigai_embedded::posture::client_lines(&trusted_identities(&[quic::TrustedClient {
label: "junk".to_string(),
path: "/tmp/junk.crt".into(),
pem: "not a certificate".to_string(),
}]));
assert_eq!(lines, vec!["client junk unreadable /tmp/junk.crt"]);
}
#[test]
fn the_mode_word_matches_the_config_lines_grammar() {
for (kind, word) in [
(ikigai_embedded::MountKind::Alias, "alias"),
(ikigai_embedded::MountKind::Override, "override"),
(ikigai_embedded::MountKind::Prefer, "prefer"),
] {
assert_eq!(mount_mode(kind), word);
let parsed = mounts_from_config_lines(vec![format!("{word} urn:x:=/tmp/x.sock")])
.expect("the mode word round-trips through the config grammar");
assert_eq!(parsed[0].kind, kind);
}
}
#[test]
fn a_config_home_mount_line_still_parses() {
let mounts = mounts_from_config_lines(vec![
"prefer urn:iki:store:=/Users/x/.ikigai/gonk.sock".to_string(),
"alias urn:cal:=quic://bug.local:4433 ~/.config/ikigai/quic-bug".to_string(),
])
.expect("both lines parse");
assert_eq!(mounts.len(), 2);
assert_eq!(mounts[0].kind, ikigai_embedded::MountKind::Prefer);
assert_eq!(mounts[0].prefix, "urn:iki:store:");
assert_eq!(mounts[1].kind, ikigai_embedded::MountKind::Alias);
assert!(
mounts[1]
.certs
.cert_dir
.as_deref()
.is_some_and(|dir| !dir.starts_with('~')),
"a config line's `~` is expanded"
);
}
}
#[cfg(test)]
mod mount_cert_tests {
use super::*;
fn argv(args: &[&str]) -> Vec<String> {
args.iter().map(|s| s.to_string()).collect()
}
fn mounts_of(args: &[&str]) -> Vec<Mount> {
match parse_argv(argv(args).into_iter()) {
Ok(Some(Mode::Repl(repl))) => repl.mounts.flags,
Ok(Some(_)) => panic!("expected a repl mode, got another mode"),
Ok(None) => panic!("expected a repl mode, got no mode"),
Err(e) => panic!("parse failed: {e}"),
}
}
#[test]
fn each_mount_keeps_its_own_certificates() {
let mounts = mounts_of(&[
"--mount",
"urn:personal:=quic://localhost:4433",
"--cert-dir",
"/certs/peer",
"--mount",
"urn:edge:=quic://edge.example:4433",
"--cert-dir",
"/certs/edge",
]);
assert_eq!(mounts.len(), 2);
assert_eq!(mounts[0].prefix, "urn:personal:");
assert_eq!(mounts[0].certs.cert_dir.as_deref(), Some("/certs/peer"));
assert_eq!(mounts[1].prefix, "urn:edge:");
assert_eq!(
mounts[1].certs.cert_dir.as_deref(),
Some("/certs/edge"),
"the second mount must NOT inherit the first mount's cert dir"
);
}
#[test]
fn certs_before_a_mount_are_the_default_inherited_by_mounts() {
let mounts = mounts_of(&[
"--cert-dir",
"/certs/default",
"--mount",
"urn:a:=quic://a.example:4433",
"--mount",
"urn:b:=quic://b.example:4433",
"--cert-dir",
"/certs/b",
]);
assert_eq!(mounts[0].certs.cert_dir.as_deref(), Some("/certs/default"));
assert_eq!(
mounts[1].certs.cert_dir.as_deref(),
Some("/certs/b"),
"a mount's own cert flag overrides the inherited default"
);
}
#[test]
fn mount_override_and_prefer_are_distinct_kinds() {
let mounts = mounts_of(&[
"--mount",
"urn:cal:=quic://a.example:4433",
"--override",
"urn:personal:=quic://b.example:4433",
"--prefer",
"urn:llm:=quic://plasma.local:4433",
"--cert-dir",
"/certs/plasma",
]);
assert_eq!(mounts[0].kind, ikigai_embedded::MountKind::Alias);
assert_eq!(mounts[1].kind, ikigai_embedded::MountKind::Override);
assert_eq!(mounts[2].kind, ikigai_embedded::MountKind::Prefer);
assert_eq!(mounts[2].prefix, "urn:llm:");
assert_eq!(mounts[2].target, "quic://plasma.local:4433");
assert_eq!(mounts[2].certs.cert_dir.as_deref(), Some("/certs/plasma"));
}
#[test]
fn a_prefer_mounts_entries_dial_a_live_peer() {
use ikigai_core::{builtins, EndpointSpace, Exact, Kernel};
let path =
std::env::temp_dir().join(format!("ikigai-prefer-list-{}.sock", std::process::id()));
let _ = std::fs::remove_file(&path);
let kernel = Kernel::new(std::sync::Arc::new(
EndpointSpace::new().bind(Exact::new("urn:test:upper"), builtins::to_upper()),
));
let served = path.clone();
std::thread::spawn(move || {
let _ = ikigai_ipc::serve(kernel, &served);
});
for _ in 0..50 {
if path.exists() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(20));
}
let mount = Mount {
prefix: "urn:test:".to_string(),
target: path.display().to_string(),
certs: Certs::default(),
kind: ikigai_embedded::MountKind::Prefer,
};
let spec = resolve_mount(mount).expect("prefer resolves lazily");
let entries = spec
.resolver
.entries()
.expect("entries() dials the live peer");
assert!(
entries.iter().any(|e| e.endpoint == "toUpper"),
"the peer's catalog is visible without any prior resolution: {entries:?}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn mcp_takes_mounts_with_their_own_certificates() {
let parsed = parse_argv(
argv(&[
"mcp",
"--grant",
"cal",
"--prefer",
"urn:llm:=peer:plasma",
"--mount",
"urn:cal:=quic://bug.local:4433",
"--cert-dir",
"/certs/bug",
])
.into_iter(),
);
let Ok(Some(Mode::Mcp { grants, mounts, .. })) = parsed else {
panic!("expected mcp mode");
};
assert_eq!(grants, vec!["cal".to_string()]);
let mounts = mounts.flags;
assert_eq!(mounts.len(), 2);
assert_eq!(mounts[0].kind, ikigai_embedded::MountKind::Prefer);
assert_eq!(mounts[0].target, "peer:plasma");
assert!(
mounts[0].certs.cert_dir.is_none(),
"a cert flag after the SECOND mount must not leak onto the first"
);
assert_eq!(mounts[1].kind, ikigai_embedded::MountKind::Alias);
assert_eq!(mounts[1].certs.cert_dir.as_deref(), Some("/certs/bug"));
}
#[test]
#[allow(clippy::disallowed_methods)]
fn a_prefer_mount_does_not_connect_at_startup() {
let mount = Mount {
prefix: "urn:llm:".to_string(),
target: "quic://127.0.0.1:1".to_string(),
certs: Certs::default(),
kind: ikigai_embedded::MountKind::Prefer,
};
let spec = resolve_mount(mount).expect("a prefer mount must not fail at startup");
assert!(
spec.resolver.transport().contains("not connected"),
"transport should say the peer is not connected, got: {}",
spec.resolver.transport()
);
let start = std::time::Instant::now();
assert!(
spec.resolver.entries().is_none(),
"a dead peer yields no catalog"
);
assert!(
spec.resolver.entries().is_none() && start.elapsed() < ENTRIES_REDIAL_AFTER,
"the second probe rides the negative cache"
);
}
#[test]
fn per_file_cert_overrides_are_also_per_mount() {
let mounts = mounts_of(&[
"--mount",
"urn:a:=quic://a.example:4433",
"--server-cert",
"/certs/a-server.crt",
"--mount",
"urn:b:=quic://b.example:4433",
"--server-cert",
"/certs/b-server.crt",
]);
assert_eq!(
mounts[0].certs.server_cert.as_deref(),
Some("/certs/a-server.crt")
);
assert_eq!(
mounts[1].certs.server_cert.as_deref(),
Some("/certs/b-server.crt")
);
}
}
#[cfg(all(test, feature = "embedded", feature = "ipc", unix))]
mod own_socket_tests {
use super::is_own_socket;
use std::path::Path;
#[test]
fn own_socket_is_detected_across_spellings() {
let socket = Path::new("/tmp/ikigai-test/serve.sock");
assert!(is_own_socket("/tmp/ikigai-test/serve.sock", socket));
assert!(is_own_socket("/tmp/ikigai-test/./serve.sock", socket));
assert!(!is_own_socket("/tmp/ikigai-test/other.sock", socket));
assert!(!is_own_socket("quic://plasma.local:4433", socket));
assert!(!is_own_socket("peer:plasma", socket));
}
#[test]
fn tilde_spelling_matches_the_expanded_socket() {
let home = std::env::var("HOME").expect("HOME set in test env");
let socket = std::path::PathBuf::from(home).join(".ikigai-test.sock");
assert!(is_own_socket("~/.ikigai-test.sock", &socket));
}
}
#[cfg(all(test, feature = "embedded", feature = "quic"))]
mod own_quic_addr_tests {
use super::is_own_quic_addr;
#[test]
fn own_quic_address_is_detected() {
let wildcard: std::net::SocketAddr = "0.0.0.0:4433".parse().unwrap();
assert!(is_own_quic_addr("quic://127.0.0.1:4433", wildcard, None));
assert!(!is_own_quic_addr("quic://127.0.0.1:4434", wildcard, None));
assert!(!is_own_quic_addr("/tmp/ikigai.sock", wildcard, None));
let specific: std::net::SocketAddr = "127.0.0.1:4433".parse().unwrap();
assert!(is_own_quic_addr("quic://127.0.0.1:4433", specific, None));
}
#[test]
fn a_peer_target_is_own_only_under_our_announced_name() {
let bind: std::net::SocketAddr = "0.0.0.0:4433".parse().unwrap();
assert!(!is_own_quic_addr("peer:plasma", bind, None));
assert!(is_own_quic_addr("peer:plasma", bind, Some("plasma")));
assert!(!is_own_quic_addr("peer:bug", bind, Some("plasma")));
}
}
#[cfg(all(test, feature = "embedded", feature = "ipc", unix))]
mod socket_preflight_tests {
use super::{socket_path_error, SUN_PATH_CAPACITY};
#[test]
fn the_preflight_mirrors_the_binds_length_boundary() {
let of_len = |n: usize| std::path::PathBuf::from(format!("/{}", "x".repeat(n - 1)));
assert!(socket_path_error(&of_len(SUN_PATH_CAPACITY - 1)).is_none());
let e = socket_path_error(&of_len(SUN_PATH_CAPACITY)).expect("over the sun_path capacity");
assert!(e.contains("shorter path"), "{e}");
assert!(socket_path_error(std::path::Path::new("/tmp/ikigai.sock")).is_none());
}
}
#[cfg(all(test, feature = "embedded", feature = "ipc", unix))]
mod mount_flag_tests {
use super::*;
#[test]
fn a_down_prefer_peer_says_prefer_not_mount() {
let absent = "/tmp/ikigai-test-absent-peer.sock";
let spec = resolve_mount(Mount {
prefix: "urn:repo:".to_string(),
target: absent.to_string(),
certs: Certs::default(),
kind: ikigai_embedded::MountKind::Prefer,
})
.expect("a prefer mount resolves lazily");
let err = spec
.resolver
.issue(ikigai_core::Request::new(
ikigai_core::Verb::Source,
ikigai_core::Iri::parse("urn:repo:x").expect("static IRI"),
))
.expect_err("nothing listens at the absent socket")
.to_string();
assert!(err.contains("--prefer: connect"), "{err}");
assert!(!err.contains("--mount:"), "{err}");
let Err(err) = connect_mount(absent, &Certs::default(), ikigai_embedded::MountKind::Alias)
else {
panic!("nothing listens at the absent socket");
};
assert!(err.contains("--mount: connect"), "{err}");
}
}
#[cfg(test)]
mod scheduler_flag_tests {
use super::*;
fn parse(args: &[&str]) -> Result<Option<Mode>, String> {
parse_argv(args.iter().map(|s| s.to_string()))
}
#[cfg(feature = "embedded")]
#[test]
fn an_invalid_scheduler_spec_stops_the_process_in_every_mode() {
for args in [
vec!["--scheduler", "pool:xyz"],
vec!["--daemon", "--scheduler", "pool:xyz"],
vec!["serve", "--scheduler", "pool:xyz"],
vec!["mcp", "--scheduler", "pool:xyz"],
] {
let Err(e) = parse(&args) else {
panic!("a typo'd spec must not fall back to single: {args:?}")
};
assert!(
e.contains("--scheduler") && e.contains("pool:xyz"),
"the error must name the flag and the bad value, got: {e}"
);
}
assert!(parse(&["--scheduler", "nonsense"]).is_err());
}
#[test]
fn the_scheduler_flag_requires_a_spec() {
let Err(e) = parse(&["--scheduler"]) else {
panic!("a flag with no value is an error")
};
assert!(e.contains("--scheduler"), "{e}");
}
#[test]
fn a_valid_scheduler_spec_parses_in_every_mode() {
assert!(matches!(
parse(&["--scheduler", "pool:2"]),
Ok(Some(Mode::Repl(_)))
));
assert!(matches!(
parse(&["--daemon", "--scheduler", "pool"]),
Ok(Some(Mode::Daemon { .. }))
));
assert!(matches!(
parse(&["serve", "--scheduler", "single"]),
Ok(Some(Mode::Serve { .. }))
));
assert!(matches!(
parse(&["mcp", "--scheduler", "single"]),
Ok(Some(Mode::Mcp { .. }))
));
}
#[test]
fn the_width_routing_flag_parses_in_every_mode() {
assert!(matches!(
parse(&["--width-routing", "off"]),
Ok(Some(Mode::Repl(_)))
));
assert!(matches!(
parse(&["--daemon", "--width-routing", "off"]),
Ok(Some(Mode::Daemon { .. }))
));
assert!(matches!(
parse(&["serve", "--width-routing", "off"]),
Ok(Some(Mode::Serve { .. }))
));
assert!(matches!(
parse(&["mcp", "--width-routing", "off"]),
Ok(Some(Mode::Mcp { .. }))
));
}
#[cfg(feature = "embedded")]
#[test]
fn an_invalid_width_routing_value_stops_the_process() {
let Err(e) = parse(&["--width-routing", "yes"]) else {
panic!("`yes` is a typo, not a synonym for on")
};
assert!(e.contains("--width-routing") && e.contains("yes"), "{e}");
let Err(e) = parse(&["--width-routing"]) else {
panic!("a flag with no value is an error")
};
assert!(e.contains("--width-routing"), "{e}");
}
}
#[cfg(test)]
mod version_flag_tests {
use super::*;
fn parse(args: &[&str]) -> Result<Option<Mode>, String> {
parse_argv(args.iter().map(|s| s.to_string()))
}
#[test]
fn the_version_line_is_one_undecorated_line() {
assert_eq!(
VERSION_LINE,
format!("ikigai {}", env!("CARGO_PKG_VERSION")),
"one version string for the whole binary"
);
assert!(
!VERSION_LINE.contains('\n'),
"a script reads one line: {VERSION_LINE:?}"
);
assert_eq!(
VERSION_LINE.split(' ').count(),
2,
"`ikigai <x.y.z>` and nothing else: {VERSION_LINE:?}"
);
}
#[test]
fn every_argument_path_answers_the_version_flag() {
for args in [
vec!["--version"],
vec!["-V"],
vec!["serve", "--version"],
vec!["serve", "-V"],
vec!["serve", "quic://127.0.0.1:4433", "--version"],
vec!["mcp", "--version"],
vec!["cert", "--version"],
vec!["cert", "generate", "--version"],
vec!["--daemon", "--version"],
vec!["--plain", "--version"],
vec!["--demo", "--plain", "-V"],
vec!["-c", "source urn:host:info", "--version"],
vec!["--version", "-c", "source urn:host:info"],
vec!["--connect", "--version"],
] {
assert!(
matches!(parse(&args), Ok(Some(Mode::Version))),
"the version flag must be answered in every arm, not just one: {args:?}"
);
}
}
#[test]
fn argv_without_the_flag_is_untouched() {
assert!(matches!(parse(&[]), Ok(Some(Mode::Repl(_)))));
assert!(matches!(parse(&["--plain"]), Ok(Some(Mode::Repl(_)))));
assert!(matches!(
parse(&["--daemon"]),
Ok(Some(Mode::Daemon { .. }))
));
assert!(matches!(parse(&["serve"]), Ok(Some(Mode::Serve { .. }))));
assert!(matches!(parse(&["mcp"]), Ok(Some(Mode::Mcp { .. }))));
assert!(
matches!(parse(&["-h"]), Ok(None)),
"help still prints usage"
);
assert!(
parse(&["--versionn"]).is_err(),
"a near-miss is still an unknown argument, not a version request"
);
}
}
#[cfg(all(test, feature = "embedded"))]
mod adapter_gate_tests {
use super::*;
use ikigai_core::{
ArgRef, Capability, Error, Iri, Kernel, Request, TraceEvent, Tracer, Verb, DENIED_NOTE,
};
use ikigai_engine::Action;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, MutexGuard};
#[derive(Default)]
struct Recorder(Mutex<Vec<TraceEvent>>);
impl Tracer for Recorder {
fn record(&self, event: TraceEvent) {
self.0.lock().expect("recorder").push(event);
}
}
impl Recorder {
fn events(&self) -> Vec<TraceEvent> {
self.0.lock().expect("recorder").clone()
}
}
static SESSION: Mutex<()> = Mutex::new(());
struct Session {
_lock: MutexGuard<'static, ()>,
root: PathBuf,
kernel: Arc<Kernel>,
engine: Engine,
trace: Arc<Recorder>,
}
fn session(name: &str) -> Session {
let lock = SESSION.lock().unwrap_or_else(|p| p.into_inner());
let scratch =
std::env::temp_dir().join(format!("ikigai-adapter-{}-{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&scratch);
std::fs::create_dir_all(scratch.join("config")).expect("scratch config home");
std::env::set_var("HOME", &scratch);
std::env::set_var("XDG_CONFIG_HOME", scratch.join("config"));
let root = scratch.join("workspace");
std::fs::create_dir_all(&root).expect("scratch workspace");
let root = root.canonicalize().expect("canonical workspace");
ikigai_embedded::set_file_root(root.clone());
ikigai_embedded::demo_flag().store(true, Ordering::SeqCst);
let kernel = Arc::new(ikigai_embedded::kernel());
let trace = Arc::new(Recorder::default());
kernel.set_tracer(trace.clone() as Arc<dyn Tracer>);
let engine = with_profiles(Engine::new(Arc::clone(&kernel)));
Session {
_lock: lock,
root,
kernel,
engine,
trace,
}
}
fn run(engine: &Engine, cmd: &str) -> Result<String, String> {
match engine.eval(cmd) {
Action::Output(entry) => entry.result,
Action::Clear => Ok(String::new()),
_ => panic!("`{cmd}` is not an output"),
}
}
fn unescape(s: &str) -> String {
s.replace(""", "\"")
.replace("<", "<")
.replace(">", ">")
.replace("&", "&")
}
fn steps(html: &str) -> Vec<String> {
let mut out = Vec::new();
let mut rest = html;
while let Some(at) = rest.find("hx-get=\"/k/") {
rest = &rest[at + "hx-get=\"/k/".len()..];
let end = rest.find('"').expect("a closed attribute");
let cmd = unescape(&rest[..end]);
rest = &rest[end..];
if cmd == "clear" || cmd.starts_with("source urn:runbook:") {
continue;
}
out.push(cmd);
}
out
}
fn typed_sink(kernel: &Kernel, target: &str, capability: &Capability) -> Error {
let request = Request::new(Verb::Sink, Iri::parse(target.to_string()).expect("iri"))
.with_arg("content", ArgRef::Inline(b"nope".to_vec()));
futures::executor::block_on(kernel.issue(request, capability))
.err()
.unwrap_or_else(|| panic!("`{target}` resolved under {capability:?}"))
}
fn read(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
}
#[test]
fn a_step_is_refused_before_dispatch_when_the_session_lacks_the_declared_family() {
let s = session("floor");
let tab = run(&s.engine, "source urn:runbook:zerotrust as=text/html")
.expect("the demo flag is on, so the tab renders");
let steps = steps(&tab);
assert_eq!(steps.len(), 10, "{steps:?}");
assert_eq!(steps[1], "cap read-only");
assert_eq!(steps[2], "sink urn:file:note.txt nope");
assert!(steps[7].contains("httpbin.org"), "{:?}", steps[7]);
assert!(steps[8].contains("w3id.org"), "{:?}", steps[8]);
run(&s.engine, &steps[0]).expect("1 · at root the write lands");
let note = s.root.join("note.txt");
assert_eq!(read(¬e), "remember the milk");
run(&s.engine, &steps[1]).expect("2 · cap read-only");
assert_eq!(
s.engine.capability(),
Capability::root().attenuate([format!("urn:cap:fs:read:{}", s.root.display())]),
"the profile `with_profiles` actually registers"
);
let before = s.trace.events().len();
let refused = run(&s.engine, &steps[2]).expect_err("3 · write → denied");
assert!(refused.starts_with("denied:"), "{refused}");
assert_eq!(read(¬e), "remember the milk", "the file is untouched");
let events = s.trace.events();
assert_eq!(
events.len(),
before + 1,
"one event, the denial: {events:?}"
);
let denial = &events[before];
assert_eq!(denial.target, "urn:file:note.txt");
assert_eq!(
denial.notes,
vec![(DENIED_NOTE.to_string(), "urn:cap:fs:write:*".to_string())],
"refused at the floor, for the scope the action declares"
);
assert!(
denial.started.is_none() && denial.ended.is_none(),
"nothing ran: {denial:?}"
);
assert!(
matches!(
typed_sink(&s.kernel, "urn:file:note.txt", &s.engine.capability()),
Error::Denied(_)
),
"typed at the kernel, under the session's own capability"
);
assert_eq!(
run(&s.engine, &steps[3]).expect("4 · read → ok"),
"remember the milk"
);
run(&s.engine, &steps[4]).expect_err("5 · the jail refuses `..`");
run(&s.engine, &steps[5]).expect("6 · cap reset");
assert_eq!(s.engine.capability(), Capability::root());
run(&s.engine, &steps[4]).expect_err("the jail holds at root too");
}
#[test]
fn a_step_outside_the_granted_segment_is_denied_and_never_reaches_the_disk() {
let s = session("acl");
let root = s.root.display();
let login = run(
&s.engine,
&format!(
"sink urn:host:login urn:cap:fs:read:{root}/mine urn:cap:fs:write:{root}/mine"
),
)
.expect("login is a session operation");
assert!(login.starts_with("logged in"), "{login}");
run(&s.engine, "sink urn:file:mine/secret.txt mine only")
.expect("a write inside the segment lands");
assert_eq!(read(&s.root.join("mine/secret.txt")), "mine only");
let before = s.trace.events().len();
let refused = run(&s.engine, "sink urn:file:someone-else/secret.txt nope")
.expect_err("outside the segment is refused");
assert!(refused.starts_with("denied:"), "{refused}");
assert!(
!s.root.join("someone-else").exists(),
"nothing was written outside the granted segment"
);
let after = s.trace.events();
assert_eq!(
after.len(),
before,
"the module's ACL refused inside invoke, which core does not trace: {after:?}"
);
assert!(
matches!(
typed_sink(
&s.kernel,
"urn:file:someone-else/secret.txt",
&s.engine.capability()
),
Error::Denied(_)
),
"the floor passed (a write grant IS held) and the endpoint's own rule refused"
);
}
}