Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
ikigai-dev-server
A standalone, linkage-gated ikigai server for
development tooling. Its Cargo.toml is the module manifest: the binary
composes a curated space and links only what it serves.
ikigai-dev [socket] [flags] # default: ~/.ikigai/dev.sock
ikigai --connect <socket> # drive it from the REPL
ikigai-dev --help # flags + the config-file grammar
What it serves
| resource | from |
|---|---|
urn:system:exec, urn:repo:* |
ikigai-repo — git/gh/cargo as capability-gated resources |
urn:rdf:* |
ikigai-rdf — graph union/diff/transrept |
urn:sparql:* |
ikigai-sparql — query |
urn:repo:{repo}:tree/file/state/hash/explain/…, urn:iki:annotation:* |
ikigai-browse — the browse family, when configured (see below) |
urn:llm:* |
ikigai-llm — the explain seam's derivation engine, mounted only alongside browse |
Run it in a project directory and it serves that project's git state
(source urn:repo:status, :log, :branch), or pass dir= for another repo.
The browse family (opt-in)
This server owns the browse store on a machine (decision of record): the
persistent explanation/annotation archive is RocksDB under an exclusive lock,
so ONE process holds it and everyone else prefer-mounts this server's socket —
which is why the default socket is the stable ~/.ikigai/dev.sock rather than
something under $TMPDIR that churns across reboots.
Configure it in ~/.config/ikigai/dev.toml (flags override the file; there is
deliberately no environment-variable channel):
= "~/.ikigai/dev.sock" # this IS the default
# One line per root; the URN repo name is the directory's basename.
= "~/git-personal/ikigai-core"
= "~/git-personal/ikigai-cli"
# Optional once a root exists:
= "~/.ikigai/browse-store" # this IS the default
= "coder" # urn:llm:coder:ask
= "ask" # urn:llm:ask
= 400
= 600
= "coder" # review pass (urn:repo:{repo}:review:{path})
= 800 # findings carry quotes — raise for big files/PRs
= "coder" # pull-request explain (the PR family)
= 600
= "qwen3:30b" # operator overrides folded into version tags;
= "qwen3:30b" # unset, the true model id resolves at derive time
= "big" # ALSO selectable by an explain `provider=`
= "ollama" # (repeatable; same spellings; default: none)
browse.allow_model is the only key here that grants reach rather than tuning
it. Browse's explain takes provider={iri} — derive THIS explanation against a
named backend — and the option menu beside the explain button offers exactly the
set this host accepts: the two configured tiers (file_model, dir_model) plus
whatever is allow-listed. Anything else is Denied, never a silent fall back.
The list is the operator's because explain declares one capability
(urn:cap:net:*) and a capability cannot vary by argument value — it says "may
derive", not "may derive against the metered vendor" — so a request argument must
not be able to spend on the operator's behalf. Unset, the menu offers the two
tiers alone and this server behaves exactly as it did before the key existed.
Configuring the review or PR provider does not widen the set: those derive
other actions, and granting explain authority as a side effect of an unrelated
line is the surprise this key exists to avoid.
No browse.root ⇒ no browse family at all (and no urn:llm:*) — the original
curated surface, unchanged. A browse.* tuning without a root fails loud at
startup: half-configured must never come up looking healthy. With roots, the
store opens (or fails loud — never a silent in-memory archive), the bundled
vocabulary loads, and urn:sparql:* binds over the same store, so one
query joins ik:Explanation and oa:Annotation rows live.
Every other process on the machine reaches the family through this socket
(in the main host's ~/.config/ikigai/config.toml):
= "prefer urn:repo:=~/.ikigai/dev.sock"
= "prefer urn:iki:annotation=~/.ikigai/dev.sock"
⚠ The annotation line carries no trailing colon, deliberately. Mounts
match by plain string prefix, and urn:iki:annotation covers the slug family
(urn:iki:annotation:{id}) and the bare urn:iki:annotation a Sink mints
under — the only write path the browse overlay has. Written
urn:iki:annotation:=, reads keep working and every new annotation silently
fails to route.
Upgrading a pre-0.3.0 store
ikigai-browse 0.3.0 moved the annotation namespace from urn:annotation:
to urn:iki:annotation:. A store written before that upgrade does not
migrate itself, and the failure is silent: browse's listing paths
(list_annotations, list_annotations_for_target, included_for_ids)
strip_prefix the stored subject IRI, so an old-prefix annotation is
skipped rather than erroring — the panel comes back empty and nothing says
why. Review passes lose their minted findings the same way, through the
prov:generated objects that name them.
Count what a store holds before upgrading (this query runs over the store this process owns, so ask it through the socket rather than opening the RocksDB directory yourself — the lock is exclusive):
A non-zero count means a one-time rename is owed, in three positions that
must all move together: annotation subjects, their :selector:quote /
:selector:position children, and every IRI object naming one
(oa:hasSelector, prov:generated). That is an operator step with this
server stopped — the store takes an exclusive lock, so nothing can rewrite
it while the socket is up.
The LLM provider registry is the shared ~/.config/ikigai/llm.json (absent ⇒
a local Ollama default; present-but-unparseable ⇒ loud).
Why linkage-gating
ikigai mcp --grant dev on the omnibus binary config-gates — it hides the
calendar tools, but their code is still linked and reachable in-process. This
binary doesn't compile them in at all: EventKit and the calendar are not
present, so a flaw in either cannot be reached. The security bound is the
dependency list, not a runtime check.
ikigai-llm is linked — deliberately, and only mounted when browse is
configured. The explain seam derives through urn:llm:*, and ikigai-llm is an
outbound HTTP client to local inference servers — not the EventKit/TCC class
of platform authority the gating posture exists to exclude. The rejected
alternative (mounting urn:llm: from the main host) would couple every fresh
explanation to the main host's uptime — the dev seam would degrade with the
very process it exists to stand apart from.
On top of that, the expensive seams are wrapped in a
RateLimit: 30 subprocess
spawns/min on exec, 120 reads/min on urn:repo:, and 30 asks/min on
urn:llm: (derivations are expensive; the limit governs explain's internal
asks too, since subrequests resolve back through the overlay). Pure local
graph ops pass unlimited.
Conformance, and what a mounting client actually gets
tests/conformance.rs runs ikigai-conformance
over the kernel this binary serves — the same ikigai_dev_server::compose that
main calls, not a re-creation of it. This crate binds zero endpoints of its
own, so the test is not "does my module conform" but three questions a
composing host is the only place to ask:
- Is the served catalog exactly what the manifest composes? Every pattern is pinned with the crate that owns it, so linkage-gating is a checked fact rather than a paragraph, and a dependency that grows an endpoint puts it on this socket only after someone classifies it.
- Does every entry answer
Verb::Metawith a real contract? This process is the peer on the other end of everyone else'smountline, and a peer with no Meta renderer does not fail — it degrades.ForwardingEndpoint::describeis best-effort and falls back toDescription::new("remote"), so a renderer-less peer shows up in a client's catalog as one anonymous, action-less row: a whole federated kernel reading as small rather than broken. The test pins the JSON Meta face specifically, because that is the one a mount parses. - What does a mount cost? Golden threads are
#[serde(skip)]and do not cross a wire, so a cacheable representation arrives at a mounting client with nothing that can ever cut it. Five resources this server serves have a thread to lose, and the test enumerates them:urn:repo:style, which hangs on the layereda11y.tomlfiles, andurn:llm:config/:models/:select/:ollama:model, which are config-derived and all hang on the one registry threadurn:llm:config. Everything the browse family reads out of a working tree isExpiry::Always, live by design: this process runs no filesystem watcher, and a server must not mint a thread no host keeps. ⚠ A named thread is not a cut thread — nothing in this process cuts any of the five today, so they are still served for the life of the daemon; what a name buys is something for a client to aim at.
Findings from the composed modules (ikigai-repo, ikigai-rdf,
ikigai-sparql, ikigai-browse, ikigai-llm) are recorded and attributed,
not fixed here — they belong to those repos. Today: 5. It was 103 when
this test was written, and every one of the 98 that went away went away in
another repo: each of those five crates adopted the suite itself, and this walk
picks their fixes up on a fresh resolve. That is the composed-host dividend — a
host that composes five modules gets five modules' conformance for the price of
stating its catalog. Zero are this crate's, and the test fails if a finding ever
names an endpoint the catalog table does not.
The declared capability gates are under test
A conformance walk fires endpoints, and this composition is a development seam:
a bare walk really does execute git, shell out to gh, and POST to whatever
is listening on the inference port. Those endpoints are waived — but
ikigai-conformance 0.2.0 waives per check, so the waivers now name the
checks that fire an endpoint under root and leave ENFORCED running. ENFORCED
resolves under a capability holding no grants, so on an action that declares a
requires the kernel refuses before dispatch and the endpoint is never entered.
Twenty-one endpoints moved from "checked for nothing but their descriptions" to
"their capability gate is exercised on every CI run": urn:system:exec, the four
urn:repo:* git readers, the ten gh-backed PR facades, and the outbound
inference family.
A typed Denied is not by itself proof that nothing ran first, so
tests/enforced.rs is the licence under that: a separate test binary that puts
shims for git and gh on PATH, points the LLM registry at a loopback
listener it counts connections on, and watches the annotation store's quad count
and the scratch tree. Every gated action is fired under no grants and must be
refused with all four witnesses unmoved — and each witness is deliberately
moved first, under a capability that grants the scope, so a witness that cannot
see is a red test rather than a quiet pass. The complement is pinned too: an
action declaring no requires is one ENFORCED resolves for real, so a module
that grows an ungated action fails that test instead of making the next walk act.
Security posture (honest)
Three real bounds today — linkage (only these modules exist),
rate-limiting (exec and llm are capped), and the transport's peercred
check (owner-only socket). A per-scope capability ceiling enforced
server-side is the "capability-on-the-wire" work; until then the local owner
is trusted and the reachable surface is bounded by what is linked and limited.
Browse's own capability story (urn:cap:browse:read:*, urn:cap:annotate)
comes with the crate — and as of the conformance work above, those declarations
are exercised rather than merely declared: every gated action in the composed
catalog is refused under no grants, with witnesses proving nothing ran first.
That is what makes the ceiling worth building on. An MCP face (so an agent
connects under a grant) is a fast-follow.
License
MIT OR Apache-2.0.