check-layering.sh)
fails the build on any upward import.
Canonical source:
docs/architecture/crate-layer-registry.md.
This page curates the layer model for readers; the authoritative manifest
lives in the engine repo next to the enforcement script.Mechanical sort test
Every new crate answers these questions in order. First hit wins.1
Does it produce the `nika` binary?
→ L5 (the binary)
2
Does it expose a transport / UI surface (CLI, HTTP, MCP, LSP, SDK)?
→ L4 (interfaces)
3
Does it enforce runtime policy, sandboxing, or orchestration?
→ L3 (runtime + policy + sandbox)
4
Does it implement a verb or a domain service?
→ L2 (verbs + services)
5
Is it a primitive with I/O (fs, net, exec, env)?
→ L1 (effect impls)
6
None of the above?
→ L0 (pure, sync, zero I/O). If it’s trait-only but async is OK, → L0.5.
The pyramid
Arrows read “depends on”. Upward arrows are forbidden:check-layering.sh fails on any workspace-local dep that points up.
Layer table
Admitted today (v)
nika-types
L0 · Foundation value types. Leaf of the DAG.
nika-error
L0 · NIKA-XXX error hierarchy +
miette integration.nika-catalog
L0 · Provider / capability TOML catalog, phf lookup.
nika-kernel
L0.5 · 40 ISP traits, sealed supertrait, prelude re-export hub.
nika-kernel-mock
L0.5 · Pure-memory trait mocks for testing.
nika-catalog-verify
L4 · Build-only catalog validation tool.
nika-schema (parser scaffolding, Round 4 admission gate).
Security axes
Every L1 crate declares the capabilities it exercises indocs/architecture/security-axes.toml. Reserving an axis costs nothing
today and forbids its silent later use.
The 12 capability axes
The 12 capability axes
How enforcement works
How enforcement works
Three CI vectors enforce layer discipline:
check-layering.sh(hygiene vector 11, P0, fail on violation): reads layer membership from[workspace.metadata.diamond.layers]and verifies every workspace-local dependency is equal or lower layer.check-security-axes.sh(vector 12, P1): cross-checks declared axes against static analysis of the source tree.check-no-async-in-l0.sh(vector 16, P1): grepsasync fn/async {in L0 source trees; fails on any match.
Forward compatibility
- The Connectome: 1 L2 orchestrator (
nika-connectome) + 10 L1 satellites (hnsw, bm25, rrf, rerank, fsrs, rdfs-reasoner, temporal, graph-algos, autodesc-minimal, autodesc-full). No renumber: they slot into L1 / L2 naturally. - v0.100 WASM plugin host + sandbox: L3 crates alongside
nika-runtime. No renumber.
Anti-patterns
- Kitchen-sink crate: “I’ll just add this utility to
nika-error” leads to a 10k-LOC bag-of-tricks every crate transitively pulls in. One caller = stays in the caller. - Facade mega-crate: “Re-export everything from
nikafor convenience” collapses the layer contract and tanks compile time. - Upward re-exports: an L1 crate
pub use nika_pck::...turns its API into an L2 consumer and breaks mechanical sorting. asyncin L0: forces a runtime choice on every L1 / L2 consumer; makes the crate unusable in build scripts.anyhow::Errorin library code: L5 binary only. L0..L4 uses the typed error hierarchy.Box<dyn Error>in public API: hygiene vector 19 bans it. Swap to the typed error hierarchy.- Runtime imports in L0:
tokio::spawninnika-erroris a layer violation even if it compiles.
See also
- Forward-compat invariants, the 8 patterns + 5 decisions that protect the public API
- L0 foundation decisions: Q1 through Q13, locked
- 12-gate admission, how a crate earns a seat at the workspace
- Constellation, live map of admitted / WIP / planned crates