# 12-gate admission Source: https://docs.nika.sh/architecture/admission Twelve gates every crate must pass before joining the Nika workspace. No exceptions, no late fixes. A crate does not join the Nika workspace by being merged. It joins by **passing all twelve gates in the same commit**. No "we'll fix Gate 5 later." No "the spec is obvious." A crate admission is one atomic commit. **Canonical source:** [`RUST_ENFORCEMENT.md`](https://github.com/supernovae-st/nika/blob/main/RUST_ENFORCEMENT.md) §12 Gates · checker `scripts/ci/check-crate-gates.sh` (machine-readable JSON output). **Live progress:** crates admitted · WIP () · projected horizon (cap ). ## The 12 gates A crate spec exists at `docs/crate-specs/.md`. It declares: purpose, layer (L0/L0.5/L1/L2/L3/L4), LOC budget, public API surface, security axes, tracked ADRs. No code before spec. Tests written before implementation. The commit history shows **RED before GREEN**: failing tests on one commit, minimal implementation on the next. Reviewed in gate 11. Minimal implementation. Compiles with `cargo build --workspace`. Every test green with `cargo test --workspace --lib`. No `# TEMP` without a removal plan. No `#[allow(dead_code)]`. No files >1500 LOC. `cargo clippy --workspace --all-targets -- -D warnings`: zero warnings. `unwrap_used = "deny"` enforced workspace-wide. Zero `.unwrap()` / `.expect(` in `src/` outside `#[cfg(test)]`. `cargo mutants -p `: at least 90% of mutants killed. Catches untested branches, "happy path only" tests, dead clauses. Data-heavy crates (e.g., `nika-catalog`) may use 85% with justification in spec. Property tests via `proptest` for any crate touching: security, parsers, encoding, taint propagation, capability resolution. Regression seeds checked into `proptest-regressions/`. `benches/` directory with `criterion` if the crate is on a hot path (parsers, catalog lookup, template resolution). Waivable for pure-types crates with justification in spec. `cargo doc --no-deps --document-private-items`: zero warnings. All public items documented. `#[doc(hidden)]` used sparingly and with reason. A `.nika.yaml` canary workflow under `tests/canary-.nika.yaml` exercises the crate end-to-end against the rest of the workspace. Waivable for L0 foundation crates with no runtime surface. Golden test comparing output against the legacy v0.79 implementation (read via `git show main:path/to/file.rs`). Flags behavior drift from v0.79 for conscious review. Landing from Phase 5+. Three parallel agents review the PR: `spn-nika:code-reviewer` + `spn-rust:rust-pro` + `feature-dev:code-reviewer`. Every P0 and P1 finding must be fixed in the same session before merge. Single commit. Message: ``` feat(): admit to workspace — all 12 gates passed Gate 1 SPEC ✅ docs/crate-specs/.md Gate 2 TDD ✅ RED before GREEN confirmed Gate 3 IMPL ✅ LOC, compiles, tests pass ... Co-Authored-By: Nika 🦋 ``` ## Why twelve Each gate catches a failure mode seen in production Rust codebases at scale. Dropping any one means accepting a known regression vector. | Gate | Failure mode caught | | ---------- | -------------------------------------------------------------------- | | SPEC | Scope creep, missing layer contract, undeclared axes | | TDD | Tests that retrofit the implementation (always green from commit 1) | | IMPL | Oversized files, dead code, TEMP debt | | CLIPPY | Panic-prone patterns, hidden bugs flagged by lints | | MUTATION | "100% test coverage" with zero real assertions | | PROPERTY | Edge cases in parsers / taint / crypto that example-based tests miss | | BENCHMARKS | Silent perf regression on hot paths | | DOCS | Undocumented public API, broken intra-doc links | | CANARY | Integration breaks hidden by unit-test success | | PARITY | Silent behavior drift from the v0.79 baseline | | REVIEW | Single-reviewer blind spots | | ATOMIC | Merge-commit noise, non-bisectable history | ## Exemptions A gate is **genuinely not applicable** for some crate types: * `benches/` for a pure-types crate with no hot path. * Canary E2E for L0 foundation (nothing to execute end-to-end). * PARITY for crates with no v0.79 counterpart (Diamond-original code). **Exemptions are documented in `docs/crate-specs/.md` with a 1-paragraph justification.** They are not defaults. They are deliberate. ## Admission cadence One crate at a time. No parallel admission. No "let's admit these 3 together." Each crate commit is atomic, bisectable, and reviewable in isolation. The rhythm: **1 crate admission = 1 commit = 1 tag increment** a per-admission alpha tag (`v0.8x.0-alpha.N`). Every 4 weeks, a blog post or dev log entry. Public accountability. ## See also Six layers. Mechanical sort test to pick the right layer for a new crate. Eight patterns. The public-API contract every admitted crate honors. Q1-Q13 locked: the per-crate decisions that shaped today's L0. Live progress: admitted, WIP, planned. # ADR index Source: https://docs.nika.sh/architecture/adrs The Architecture Decision Record index — every significant engineering decision in Nika Diamond, versioned + auditable. Live counts derive from the status snapshot. Every significant engineering decision in Nika Diamond lives as an ADR, a short markdown file in [`docs/adr/`](https://github.com/supernovae-st/nika/tree/main/docs/adr) with status, date, context, decision, consequences. ** total**: Accepted, Proposed. **Canonical source:** [`docs/adr/index.json`](https://github.com/supernovae-st/nika/blob/main/docs/adr/index.json) (machine-readable) + [`docs/adr/README.md`](https://github.com/supernovae-st/nika/blob/main/docs/adr/README.md) (human index). One file per ADR, immutable once Accepted (superseded rather than edited). ## At a glance decision records locked in review ## By theme How Nika runs itself as a project: version cadence, admission, ADRs-on-ADRs. | ID | Title | Status | | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------- | | [ADR-001](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-001-diamond-orphan-branch.md) | Diamond orphan branch rewrite instead of iterative refactor | ✅ Accepted | | [ADR-002](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-002-forever-v0x.md) | Real semver toward 1.0: 0.91.0 release-candidate, 1.0 public API lock when gates are green | 🔄 amended (D-2026-06-20-N1) | | [ADR-003](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-003-12-gate-admission.md) | 12-gate crate admission protocol | ✅ Accepted | | [ADR-009](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-009-adr-process.md) | ADR process and hook discipline | ✅ Accepted | | [ADR-011](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-011-cargo-xtask.md) | cargo xtask as canonical automation surface | ✅ Accepted | The six-layer crate discipline + physical workspace layout. | ID | Title | Status | | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | ---------- | | [ADR-004](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-004-context-window-sized-crates.md) | Context-window-sized crate architecture | ✅ Accepted | | [ADR-006](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-006-layered-kernel-isp-traits.md) | Layered architecture with kernel ISP atomic traits + `trait_variant` | ✅ Accepted | | [ADR-022](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-022-foundation-crate-layout-v081.md) | Foundation crate layout v0.81: 14 crates, `publish = false` | ✅ Accepted | | [ADR-023](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-023-file-modularity-discipline.md) | File modularity: 800 LOC warn / 1500 fail / 3000 fail-with-ADR | ✅ Accepted | | [ADR-026](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-026-workspace-toml-ssot.md) | `workspace.toml` single source of truth + auto-generated `crate.md` | ✅ Accepted | | [ADR-027](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-027-strict-l0-tiers-cargo-timings.md) | Strict L0 sublayer tiers + max 3 sibling deps | ✅ Accepted | How Nika stays additive across decades without breaking changes. | ID | Title | Status | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------- | | [ADR-007](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-007-forward-compat-invariants.md) | Forward-compat invariants with `#[non_exhaustive]`, `new()`, pre-planted reservations | ✅ Accepted | | [ADR-014](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-014-sealed-kernel-traits.md) | Sealed kernel traits with explicit adapter registration | ✅ Accepted | | [ADR-025](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-025-per-crate-semver-release-plz.md) | Per-crate semver via `release-plz` (publishable crates only) | ✅ Accepted | | [ADR-028](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-028-forward-compat-reservation-policy.md) | Forward-compat reservation policy: seams now, crates later | ✅ Accepted | How `NikaError` is shaped + how failures surface to users. | ID | Title | Status | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- | ---------- | | [ADR-005](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-005-error-hierarchy.md) | Trait-based error hierarchy with `NikaErrorCode` + `Box` | ✅ Accepted | | [ADR-010](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-010-miette-diagnostic-layer.md) | `miette` as the L4 diagnostic presentation layer | ✅ Accepted | Cancellation, streaming, sync primitives, retry / timeout ownership. | ID | Title | Status | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ---------- | | [ADR-012](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-012-typestate-runtime.md) | Typestate for `nika-runtime` workflow lifecycle | ✅ Accepted | | [ADR-016](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-016-cancellation-model.md) | Cancellation model: future-drop primary + `CancelCtx` cooperative module | ✅ Accepted | | [ADR-017](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-017-streaming-policy.md) | Streaming policy: bounded mpsc(32), `ReceiverStream` at kernel boundary | ✅ Accepted | | [ADR-018](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-018-runtime-sync-primitives.md) | Runtime + sync primitives: single Tokio rt, `parking_lot` default | ✅ Accepted | | [ADR-019](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-019-retry-timeout-ownership.md) | Retry + timeout ownership: stratified by layer | ✅ Accepted | How the engine verifies itself beyond unit tests. | ID | Title | Status | | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ---------- | | [ADR-013](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-013-loom-concurrency-verification.md) | Loom-based concurrency verification for L0.5 + L3 | ✅ Accepted | | [ADR-015](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-015-expect-test-inline-snapshots.md) | `expect-test` for inline snapshot assertions on rendered output | ✅ Accepted | The data-driven provider catalog and the workflow envelope convention. | ID | Title | Status | | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ---------- | | [ADR-008](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-008-toml-driven-catalog.md) | TOML-driven catalog with build-time codegen + perfect-hash lookup | ✅ Accepted | | [ADR-021](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-021-yaml-envelope-convention.md) | YAML envelope: `apiVersion + kind + metadata + spec` | ✅ Accepted | The v0.100 plugin boundary, pre-planted at v0.80. | ID | Title | Status | | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ---------- | | [ADR-020](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-020-wasm-plugin-boundary.md) | WASM plugin boundary + Sandbox: trait stubs now, dual timeout later | ✅ Accepted | Cross-cutting idiom adoption. | ID | Title | Status | | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------- | | [ADR-024](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-024-sota-rust-patterns.md) | Adopt SOTA Rust patterns: `bon` Builder, `Arc`, `camino`, sealed modules | ✅ Accepted | Late-added Accepted ADRs covering foundational types + kernel expansion. | ID | Title | Status | | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------- | | [ADR-033](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-033-l0-foundational-types.md) | L0 foundational types: Cost, UUIDv7, TrustLevel, TokenUsage evolution | ✅ Accepted | | [ADR-034](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-034-l05-kernel-traits-expansion.md) | L0.5 kernel traits expansion: 6 new traits scoped to v0.81+ | ✅ Accepted | Seed-level reservations ahead of their implementing crates. | ID | Title | Status | | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------- | | [ADR-029](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-029-embedding-spec-reservation.md) | EmbeddingSpec value-type reservation (memory subsystem seed) | 🕒 Proposed | | [ADR-030](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-030-memory-frame-trust.md) | `MemoryFrameRef.trust` reservation (Shield gate seed) | 🕒 Proposed | | [ADR-031](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-031-recall-query-tenant-keyspace.md) | `RecallQuery.tenant` reservation (multi-tenant keyspace seed) | 🕒 Proposed | | [ADR-032](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-032-wasm-plugin-context.md) | `WasmPluginError::OutOfFuel` + `Trap` + `PluginCallContext` reservation | 🕒 Proposed | | [ADR-035](https://github.com/supernovae-st/nika/blob/main/docs/adr/adr-035-telemetry-seams.md) | Telemetry seams: `SpanGuard.parent_span_id` + `SpanRef` (OTel-ready) | 🕒 Proposed | ## How ADRs work Any significant engineering decision starts as a Proposed ADR: a short markdown file in `docs/adr/adr-NNN-.md` with frontmatter: `id`, `title`, `status: Proposed`, `date`, `tags`, `related`. Writer describes the context, options considered, and recommended decision. Proposed ADRs are discussed in PRs. Others get `related:` / `supersedes:` / `requires:` links. Status can flip to Accepted or Rejected. The ADR index (`docs/adr/index.json`) is auto-regenerated. Once Accepted, the ADR is **immutable**. Any revision happens via a new ADR that `supersedes:` the old one. This keeps decision history auditable: `git log` tells you when and why a given rule came in. Rare, but when a decision proves wrong, a new Accepted ADR supersedes it. The superseded ADR stays in the tree with `status: superseded` and `superseded_by:`. It has happened once so far: **ADR-021** (the K8s-style `apiVersion` envelope) was superseded by **ADR-082**, which locked the single `nika: v1` version marker. One ADR is also Rejected outright (**ADR-089**) and kept in the tree as the record of a road not taken. ## Metadata fields Every ADR carries rich metadata in both its frontmatter and `index.json`: | Field | Purpose | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `ADR-NNN` · assigned in ascending order, never reused. Not gap-free: ranges are reserved in blocks, so the tree currently runs `ADR-001`..`ADR-106` with 043-077 and 101-104 unallocated | | `title` | One-line decision summary | | `status` | `Proposed` / `Accepted` / `Rejected` / `Superseded` | | `date` | ISO date of last status change | | `tags` | Free-form (e.g., `architecture`, `runtime`, `forward-compat`) | | `affects_crates` | List of crate names the decision constrains | | `affects_layers` | `L0`..`L5` · which layers feel the effect | | `supersedes` | IDs this ADR replaces | | `superseded_by` | ID that replaces this ADR | | `related` | Sibling ADRs in the same design arc | | `requires` | Prerequisite ADRs (this one makes no sense without them) | | `enables` | Downstream ADRs this one unlocks | ## Gate 12 integration Admission Gate 12 fails when a crate's commit changes a public API surface without either (a) being purely additive under `#[non_exhaustive]` rules, or (b) referencing an Accepted ADR that authorizes the break. The ADR trail is how breaking changes earn the right to exist. ## See also The 8 patterns that keep most ADRs additive. Q1-Q13: the foundation decisions that became ADRs 033, 034 + others. Gate 12 requires ADR alignment for breaking change. Browse the full ADR tree, index.json, and README. # Forward-compat invariants Source: https://docs.nika.sh/architecture/forward-compat-invariants Eight patterns + five decisions that protect Nika's public API against breaking change. LOCKED at v0.80. Nika follows **real semver toward 1.0**. Features ship incrementally across 1.x minors over years. The 1.0 architecture must accommodate later minors (memory subsystem, agent-v2; WASM plugins, observability), and features we haven't imagined **without breaking changes to the public API**. **Status: LOCKED at v0.80.** Every crate admitted to the Diamond workspace must comply before passing Gate 12. Patterns distilled from Rust (2015→), Tokio (0.1→1.x), Deno, Cargo, Serde (9+ years stable), Axum/Tower, Bevy. **Canonical source:** [`docs/architecture/forward-compat-invariants.md`](https://github.com/supernovae-st/nika/blob/main/docs/architecture/forward-compat-invariants.md). ## The 8 patterns Define traits in `nika-kernel` at v0.90 for subsystems that ship later. Default methods return `Err(Unsupported)` until real implementations land. ```rust theme={"system"} #[trait_variant::make(MemoryStoreDyn: Send)] pub trait MemoryStore: Send + Sync + 'static { async fn put(&self, frame: MemoryFrame) -> Result; async fn query(&self, q: &MemoryQuery) -> Result>; async fn forget(&self, _r: &MemoryFrameRef) -> Result<()> { Err(NikaError::Unsupported("forget")) } } ``` **Locked traits (v0.90):** `MemoryStore`, `EmbeddingProvider`, `ToolExecutor`, `WasmPluginHost`, `MetricsExporter` + `TracerProvider` + `AuditSink` + `EventSink` + `BillingSink`, `Sandbox`. Stubs in `src/plugin/`, `src/infra/`. **Impact:** v0.95 lights up the memory subsystem by adding `nika-memory-oxigraph` that implements `MemoryStore`. **Zero v0.90 code modification.** Every public type carries `#[non_exhaustive]` so adding a field or variant is always additive. `new()` constructors provide ergonomic construction without exposing struct literal syntax. ```rust theme={"system"} #[non_exhaustive] pub struct InferRequest { pub provider: ProviderId, pub model: ModelId, pub messages: Vec, // adding fields here never breaks downstream } impl InferRequest { pub fn new(provider: ProviderId, model: ModelId) -> Self { /* ... */ } } ``` Invariant #19: every `#[non_exhaustive]` struct ships a `new()` constructor. A workflow pins its contract with **one header line** · `nika: v1` (a single version marker · the language name as key, the contract version as value · supersedes the older K8s `apiVersion:` and `schema: nika/workflow@X` forms). Parsers reject any other value (`v1.0` · `v2` …) with a clear error. ```yaml illustration · the envelope pin theme={"system"} nika: v1 workflow: id: my-flow tasks: { ... } ``` `v1` is the only value for the lifetime of the v1 contract: minor additions are **additive** and never change it. There is no `nika: v2` — ever: pre-1.0 grammar changes happen inside `v1` (the pre-1.0 stability contract) · after engine 1.0.0 the grammar is additive only. Non-workflow artifacts (package manifests, events) carry their own schema version field. Public schemas reserve two namespaces: **`nika.*`** for core additions (authoritative, versioned) and **`x-*`** for community overlays (no stability guarantee, no collision with core). **Scope note** · in the workflow language both are RESERVED, not current: unknown top-level fields are rejected at v0.1, and the tool namespace set is closed at `nika:` / `mcp:` (engine-specific tools route through `mcp:`). The live `x-*` surface today is the pck registry. ```yaml illustration · RESERVED future extension fields (rejected today) theme={"system"} nika: v1 workflow: id: my-flow nika.cache: { ttl: 3600 } # nika.* core extension · a future additive minor x-my-org-metric: 42 # x-* community overlay · reserved · NOT valid v0.1 ``` Two error surfaces, one rule each. The WORKFLOW-VISIBLE surface is the spec's `NIKA--` taxonomy ( registered codes across namespaces · [`reference/error-codes`](/reference/error-codes)). Namespaces own `001-099` ranges, codes are never repurposed. The ENGINE-INTERNAL registry (`nika_error::codes` · `NIKA-1000+` blocks per L1 effect crate) is diagnostics machinery: reserved per crate, never reassigned, and never leaked into workflow-visible errors. Core kernel traits (e.g., `Provider`, `EventSink`, `Sandbox`) are **sealed** via a private supertrait: only Nika-owned crates can implement them. Extension traits (e.g., `MemoryStore`) are open for community crates to implement. ```rust theme={"system"} mod sealed { pub trait Sealed {} } pub trait Provider: sealed::Sealed + Send + Sync { /* ... */ } ``` Enforced by ADR-014. New capability = new `feature` flag, OFF by default. After 1-2 minor cycles of stability, flips to default-ON. Never remove a default feature (breaking change): deprecate and redirect. ```toml theme={"system"} [features] default = ["rustls-tls"] rustls-tls = ["dep:rustls"] native-tls = ["dep:native-tls"] # alternative, off by default ``` Three tools on every CI run: * **`cargo public-api`**: detects API surface changes per crate. * **`cargo semver-checks`**: verifies SemVer compatibility. * **`cargo deny`**: enforces license + advisories + layer bans. Gate 12 fails on any unintentional public API drift. `.public-api.json` snapshots are checked into the repo per crate. ## The 5 locked decisions (v0.80) | ID | Decision | Impact | | ----------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | **FCI-009** | `EventKind` = scoped sub-enums (Pattern B), \~22 categories | Emit site is compile-time checked; sub-enums evolve independently | | **FCI-010** | `InferRequest` / `InferResponse` fields reserved: `memory`, `budget`, `baggage`, `trust_level`, `trace_id` | v0.95 adds memory without touching kernel | | **FCI-011** | `schema:` field mandatory day-1 | Every on-disk artifact versioned from v0.90 forward | | **FCI-012** | `Provider` trait shape frozen (4 methods, 1 sealed supertrait) | Cross-provider parity testable (shadow zone 2 + 7) | | **FCI-013** | Telemetry = 5 sibling sinks (Event, Metrics, Trace, Audit, Billing) | OTel GenAI semconv bridged via typed `GenAiAttrs` (Q13 rev.3) | ## The 10 rules Every crate admission answers "yes" to each. `cargo public-api` + `cargo semver-checks` catch violations before merge. 1. All public types carry `#[non_exhaustive]`. 2. All public structs with `#[non_exhaustive]` ship a `new()` constructor. 3. All `async` trait methods use `trait_variant::make` for dynamic dispatch. 4. All core traits seal via private supertrait. 5. All error types expose a `NIKA-XXX` code via `NikaError::code()`. 6. All on-disk schemas carry `schema: "nika/@"`. 7. All DTO fields reserve forward-compat extension slots (see FCI-010). 8. All feature flags default to a stable subset: new caps ship OFF. 9. All public API changes emit a `cargo public-api` diff in the PR. 10. All breaking changes require ADR Accepted before merge. ## See also Six layers + L0.5, mechanical sort test, security axes. Q1-Q13 locked 2026-04-16: proc macros, kernel prelude, transform crate, memory sinks. How a crate earns a seat at the workspace: SPEC through ATOMIC commit. 35 decision records (30 Accepted + 5 Proposed). # L0 foundation decisions Source: https://docs.nika.sh/architecture/l0-decisions Q1-Q13: the 13 architectural decisions that lock the L0 + L0.5 layer shape. Thirteen questions, thirteen locks. These decisions shape the **L0 foundation** ( admitted + remaining through Round 4) and the **L0.5 kernel contract**. Revised twice by swarm audit: current state is rev. 3. **Canonical source:** [`docs/architecture/l0-l05-architecture-decisions.md`](https://github.com/supernovae-st/nika/blob/main/docs/architecture/l0-l05-architecture-decisions.md). Full rationale (3-4 agents per question) lives in the engine repo. ## Decision index | Q | Decision | Status | | ------- | ------------------------------------------------------------------- | ----------------------- | | **Q1** | No proc macros in L0 · manual impl + `macro_rules!` | LOCKED | | **Q2** | No `nika-stdx`; split `nika-error` → `nika-types` + `nika-error` | LOCKED · executed | | **Q3** | Extract `nika-catalog-codegen` NOW (testable + reusable) | LOCKED | | **Q4** | `nika-event`: 3-layer split (L0 types + L1 store + L2 export) | LOCKED | | **Q5** | Admission order: schema → codegen → event → binding → pck-manifest | LOCKED | | **Q6** | `EventKind` = scoped sub-enums (Pattern B, \~22 categories) | LOCKED | | **Q7** | `nika-kernel` prelude re-export hub (4 lines, 0 new deps) | LOCKED | | **Q8** | `nika-transform` = standalone L0 crate (rev.2: 2 consumers found) | LOCKED rev.2 | | **Q9** | `Timestamp` + `WallDuration` = module in `nika-types` | LOCKED rev.2 | | **Q10** | Canonical-JSON (RFC 8785) = module in `nika-types` | LOCKED rev.2 | | **Q11** | Token-streaming cardinality = delta-batching, not 1-event-per-token | LOCKED rev.3 | | **Q12** | Drop `ObservabilitySink` + add `AuditSink` (5 sibling channels) | LOCKED rev.3 · executed | | **Q13** | Bridge OTel GenAI semconv via typed `GenAiAttrs` on Infer | LOCKED rev.3 · executed | ## Why these decisions exist L0 is the foundation. A wrong choice at L0 propagates through 40+ crates. These decisions were made BEFORE any L1 / L2 admission to avoid costly migrations. Each was validated by ≥3 Rust council agents + cross-checked against 17 Phase-C research agents. ## Deep dives **Rationale.** Proc macros add compile-time dependency on `syn` + `quote` * `proc-macro2` (\~50 MLOC compile graph). L0 crates must compile in seconds for CI parity testing. Manual `impl Trait for Struct` + `macro_rules!` covers every pattern we've needed at L0. **Escape hatch.** Proc macros allowed at L2+ (e.g., `#[builtin_tool]` attribute macro in `nika-verb-invoke`). L0 stays procmacro-free forever. **Impact.** `nika-types` compile time stays \< 2s; downstream crates don't pay proc-macro cost transitively. **Rationale.** A generic `nika-stdx` crate devolves into a kitchen-sink (anti-pattern #1 from layer-registry). Instead, foundation types live in `nika-types` (splitable when cohesion demands), error infra in `nika-error`, lookup tables in `nika-catalog`. Purpose-named, not kitchen-sink. **Executed.** `nika-types` admitted as L0 foundation (no I/O, no async, Cow-heavy). `nika-error` re-exports the subset it needs. **Rationale.** TOML → Rust codegen is testable logic that today lives in `build.rs`. Extract it so: * `build.rs` remains \<50 LOC (delegates). * Codegen gets its own unit tests + snapshot tests. * Community overlays (`nika-catalog-cn`) reuse the same codegen. Targets admission Round 3 post-nika-schema. **Rationale.** Event emission (L0 types + scoped sub-enums) is concept separate from storage (L1 `nika-event-store` writes NDJSON / SQLite) which is separate from export (L2 `nika-event-export` pushes OTel / Datadog / Honeycomb). Each layer tested in isolation. **Mapping**: L0 `nika-event` (\~2.5k LOC) types + sub-enums · L1 `nika-event-store` (\~3k LOC, future) · L2 `nika-event-export` (\~2k LOC, future). **Order.** schema → catalog-codegen → event → binding → pck-manifest. **Why this order.** * `nika-schema` unblocks the rest (it defines the AST). * `catalog-codegen` independent (purely build-time). * `nika-event` needs types (done) and schema (for correlation IDs). * `nika-binding` needs event + transform. * `nika-pck-manifest` is standalone and can run in parallel with any of the above. **Rationale.** A single `EventKind` enum with 22 variants becomes a merge-conflict magnet and bloats every consumer's match arm. Instead, \~22 sub-enums grouped by emission site (`ExecEvent`, `InferEvent`, `ProviderEvent`, etc.), unified under an `Event` sum type via the `event_categories!` macro. **Impact.** Each verb crate emits its own sub-enum variants; consumers match on the category they care about; rest are transparent. **Rationale.** L2+ verb crates today `use nika_types::*; use nika_error::*; use nika_kernel::traits::*;` (3 imports every file). Instead, `nika-kernel` ships `pub mod prelude` that re-exports from `nika-types` * `nika-error` + its own traits. Verb crates: `use nika_kernel::prelude::*;`. **Cost.** 4 lines of code. Zero new deps (already transitive). **Original decision.** Inline `nika-transform` into `nika-binding` (single consumer). **Reversion rationale.** Swarm audit found a **second consumer**: `nika-builtin-*` crates need `nika-transform` filters (json, base64, shell\_quote) for `invoke:builtin:` parameter processing. Inlining would force `nika-binding` dependency on every builtin crate (wrong coupling). **Final.** `nika-transform` stays L0, 65 transforms in 7 sub-modules, two consumers (binding + builtin-\*). **Rationale rev.2.** Both were initially planned as standalone L0 crates (`nika-time`, `nika-canonical`). Audit found each has zero non-foundation dependencies and tight coupling to core types. They become modules inside `nika-types`: * `nika_types::timestamp`: `Timestamp` + `WallDuration` (monotonic + RFC3339) * `nika_types::hash::canonical`: RFC 8785 canonical-JSON for hash stability **Impact.** `nika-types` gains \~300 LOC, stays well under 15k crate cap. **Rationale.** Emitting 1 `InferEvent::Token` per LLM token would flood the event bus (2k tokens/s on fast providers). Instead, batch into deltas: emit `InferEvent::Delta { text, token_count, duration }` every 50ms or 32 tokens, whichever first. **Impact.** Event rate capped at \~20 Hz regardless of provider speed. **Rationale rev.3.** The original `ObservabilitySink` god-trait would have forced every sink implementation to handle 5 concerns at once. Instead, 5 sibling traits live alongside each other: * `EventSink`: structured events (NDJSON, OpenTelemetry) * `MetricsExporter`: Prometheus / StatsD / OTel metrics * `TracerProvider`: OTel traces (W3C TraceContext) * `AuditSink`: compliance audit log (tamper-evident, separate from EventSink) * `BillingSink`: cost accounting (\$ per provider call) **Impact.** Each sink tested independently. Implementations can mix-and-match (e.g., Honeycomb for trace, Prometheus for metrics, local NDJSON for audit). **Rationale rev.3.** OpenTelemetry defines [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) for LLM attributes (`gen_ai.system`, `gen_ai.request.model`, etc.). Rather than stuff strings into a `HashMap`, expose a typed `GenAiAttrs` struct on `InferRequest` / `InferResponse`. **Impact.** Consumers get IDE completion + compile-time safety. Non-breaking: `GenAiAttrs` is `#[non_exhaustive]`. New attrs are additive. ## See also How these L0 crates fit into the 6-layer pyramid. The 8 patterns that make these decisions safe to evolve. How each L0 crate earns its seat. Live state: which L0 crates are admitted today. # Layer registry Source: https://docs.nika.sh/architecture/layers Six layers (L0 → L5) + L0.5 for trait-only crates. Upward dependencies are a CI hard failure. Nika Diamond is admitted crates organized into **six layers**, with a half-layer (**L0.5**) for trait-only crates. Every workspace-local dependency points strictly downward. A CI script (`check-layering.sh`) fails the build on any upward import. **Canonical source:** [`docs/architecture/crate-layer-registry.md`](https://github.com/supernovae-st/nika/blob/main/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. → **L5** (the binary) → **L4** (interfaces) → **L3** (runtime + policy + sandbox) → **L2** (verbs + services) → **L1** (effect impls) → **L0** (pure, sync, zero I/O). If it's trait-only but async is OK, → **L0.5**. ## The pyramid ```mermaid theme={"system"} %%{init: {'theme':'dark','themeVariables':{'background':'transparent','mainBkg':'transparent'}}}%% flowchart TB classDef layer0 fill:#1e3a8a,color:#fff,stroke:#3b82f6 classDef layer05 fill:#6d28d9,color:#fff,stroke:#8b5cf6 classDef layer1 fill:#065f46,color:#fff,stroke:#10b981 classDef layer2 fill:#92400e,color:#fff,stroke:#f59e0b classDef layer3 fill:#9a3412,color:#fff,stroke:#f97316 classDef layer4 fill:#991b1b,color:#fff,stroke:#ef4444 classDef layer5 fill:#4c1d95,color:#fff,stroke:#a78bfa,font-weight:bold L5["L5 — `nika` binary (<500 LOC)"]:::layer5 L4["L4 — interfaces · cli · serve · mcp · lsp · sdk"]:::layer4 L3["L3 — runtime · policy · shield · sandbox"]:::layer3 L2["L2 — verbs · builtins · pck · memory · observability"]:::layer2 L1["L1 — effects · fs · http · process · keys · provider impls"]:::layer1 L05["L0.5 — kernel traits + mock (async OK, zero I/O)"]:::layer05 L0["L0 — pure types · sync · zero I/O · zero deps on runtimes"]:::layer0 L5 --> L4 --> L3 --> L2 --> L1 --> L05 --> L0 ``` Arrows read "depends on". Upward arrows are forbidden: `check-layering.sh` fails on any workspace-local dep that points up. ## Layer table | Layer | Role | Allowed I/O | Allowed deps | Example crates | | -------- | -------------------------------------------------------------------- | ------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **L0** | Pure types, lookup tables, sync-only APIs | none | (leaf) | `nika-types`, `nika-error`, `nika-catalog`, `nika-schema`, `nika-event`, `nika-binding`, `nika-transform`, `nika-pck-manifest`, `nika-catalog-codegen` | | **L0.5** | Kernel trait definitions + companions (mock) · async OK | none (traits only) | L0 | `nika-kernel` (facade hub), `nika-kernel-core`, `nika-kernel-ai`, `nika-kernel-runtime`, `nika-kernel-plugin`, `nika-kernel-mock` | | **L1** | Effect implementations · async, per-crate capability axis | declared axes only (fs / net / exec / env) | L0, L0.5 | `nika-fs`, `nika-http`, `nika-process`, `nika-git`, `nika-keys-*`, `nika-pck-registry`, `nika-pck-store`, `nika--*` | | **L2** | Verbs + domain services · orchestrates L1 impls behind kernel traits | via L1 traits only | L0, L0.5, L1 | `nika-pck`, `nika-verb-*`, `nika-policy`, `nika-connectome`, `nika-observability`, `nika-builtin-{github,cloud,workspace}` | | **L3** | Runtime + policy + sandbox · enforces execution contracts | via L2 | L0..L2 | `nika-runtime`, `nika-shield`, `nika-wasm-host` (v0.100), `nika-sandbox` (v0.100) | | **L4** | Interfaces · transport / UI surfaces (libraries) | via L3 | L0..L3 | `nika-cli`, `nika-serve`, `nika-mcp`, `nika-lsp`, `nika-sdk`, `nika-catalog-verify` | | **L5** | The binary · sole `[[bin]]` composition root | via L4 | L0..L4 | `nika` (\<500 LOC) | `nika-http` is the **L1 HTTP client** (used by `fetch` / catalog sync). The HTTP server surface of `nika serve` lives **inline inside `nika-serve` L4**: no separate server crate. ## Admitted today (v) **L0** · Foundation value types. Leaf of the DAG. **L0** · NIKA-XXX error hierarchy + `miette` integration. **L0** · Provider / capability TOML catalog, phf lookup. **L0.5** · 40 ISP traits, sealed supertrait, prelude re-export hub. **L0.5** · Pure-memory trait mocks for testing. **L4** · Build-only catalog validation tool. **WIP:** `nika-schema` (parser scaffolding, Round 4 admission gate). ## Security axes Every L1 crate declares the capabilities it exercises in `docs/architecture/security-axes.toml`. Reserving an axis costs nothing today and forbids its silent later use. | Axis | Meaning | | --------------------- | ---------------------------------------------------------------- | | `reads-env` | Reads process environment variables | | `reads-fs` | Reads filesystem (read-only) | | `rw-fs` | Reads and writes filesystem | | `exec-shell` | Spawns subprocesses | | `net-egress` | Initiates outbound network connections | | `net-ingress` | Accepts inbound connections | | `spawns-thread` | Uses `std::thread::spawn` or `tokio::task::spawn_blocking` | | `mutates-global` | Mutates process-global state (env, CWD, signals) | | `panics-allowed` | Explicit opt-out from zero-panic discipline (rare) | | `reads-secrets` | Reads `Secret` / `SecretRef` material | | `time-mutation` | Manipulates clocks (`MockClock`, `tokio::time::pause`) | | `allocator-sensitive` | Imposes allocator constraints (pre-flight for WASM v0.100) | | `thread-local-state` | Uses `thread_local!` / static `RefCell` (incompatible with WASM) | 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): greps `async 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 Seven patterns that tank the layer contract. If you catch yourself writing one, stop and re-ask the mechanical sort test. 1. **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. 2. **Facade mega-crate**: "Re-export everything from `nika` for convenience" collapses the layer contract and tanks compile time. 3. **Upward re-exports**: an L1 crate `pub use nika_pck::...` turns its API into an L2 consumer and breaks mechanical sorting. 4. **`async` in L0**: forces a runtime choice on every L1 / L2 consumer; makes the crate unusable in build scripts. 5. **`anyhow::Error` in library code**: L5 binary only. L0..L4 uses the typed error hierarchy. 6. **`Box` in public API**: hygiene vector 19 bans it. Swap to the typed error hierarchy. 7. **Runtime imports in L0**: `tokio::spawn` in `nika-error` is a layer violation even if it compiles. ## See also * [Forward-compat invariants](/architecture/forward-compat-invariants), the 8 patterns + 5 decisions that protect the public API * [L0 foundation decisions](/architecture/l0-decisions): Q1 through Q13, locked * [12-gate admission](/architecture/admission), how a crate earns a seat at the workspace * [Constellation](/reference/constellation), live map of admitted / WIP / planned crates # Releases Source: https://docs.nika.sh/changelog/releases The full arc — the exploration (summer 2025), the brouillon era (v0.1 → v0.79), the Diamond rewrite, and every public release since v0.90.0. One source of truth: `CHANGELOG.md` on the `main` branch. Nika follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [real semver toward 1.0](/changelog/roadmap). The history below spans three eras, and every entry is dated from the git record: 1. **✧ Exploration** (summer 2025 → end 2025) — private prototypes before the repo existed: the idea, tested in code that was never meant to survive. 2. **✎ Brouillon** (January 1 → April 13, 2026) — the named draft: v0.1 → v0.79.3, a 14-week private sprint that grew to a 322k-LOC engine and taught the lesson Diamond is built on. The era is called what its git branch is called: `brouillon`. 3. **◆ Diamond** (April 13, 2026 →) — a ground-up rewrite, one crate at a time, 12 gates each; tagged binaries and real semver since v0.90.0 (June 21, 2026). **Canonical source:** [`CHANGELOG.md`](https://github.com/supernovae-st/nika/blob/main/CHANGELOG.md) on the `main` branch. This page curates the milestones; the full history lives in the repo. Current HEAD `{STATUS.head}`. Filter entries with the tags, or subscribe via [RSS](https://docs.nika.sh/changelog/releases/rss.xml). The era model itself is machine-verified: [nika.sh/timeline](https://nika.sh/timeline) renders the spec's [`timeline/timeline.yaml`](https://github.com/supernovae-st/nika-spec/blob/main/timeline/timeline.yaml), and every provable claim in it is re-proven in CI against crates.io and GitHub. ## Cadence **Since v0.90.0 (June 21, 2026 · the first public release), every release is a real-semver tag with binaries**: Homebrew, the install script, a GitHub Release, and a curated `CHANGELOG.md` section. Public accountability, no hidden work. Between release tags, progress lands on `main` as: 1. **Crate admission commits** (e.g., `feat(nika-X): admit to workspace — all 12 gates passed`). 2. **Hygiene vector deployments** ( active vectors guard the workspace on every push). 3. **ADR promotions** ( total: Accepted, Proposed). ## History An outside audit measured the **experience** instead of the feature list — five waves, seventy-two receipts, twenty-two P0s — and the arc closed every one. Consent means consent: a non-affirmative answer fires **zero effects** (`NIKA-SEC-014`, minted spec-first with its conformance fixtures). The hooks' judge moved into the binary (`nika guard`, fail-visible). Autonomy is graded before it is colored — the risk rung never renders green past Supervised. The data journey names sources, destinations and secrets **before** the run, and redaction works by provenance: a one-byte secret dies the same death as a long one. **The teaching grammar collapses to three doors.** `nika try [slug]` is the showroom — offline by default (the mock rehearsal · zero keys · zero flags), nothing written, nothing owned; bare `nika try` lists what there is to see. `nika new` takes ONE positional gesture: plain words route across the whole catalog, an example slug lands verbatim WITH its ingredients, a skeleton name instantiates (`nika new '?'` lists the set). `nika init` founds. The old doors die loudly: | Dead door | The 0.107 door | | ----------------------------------- | --------------------------------------------- | | `nika examples` (the verb tree) | `nika try` to see · `nika new ` to take | | `nika new --from