Skip to content

Latest commit

 

History

History
97 lines (70 loc) · 4.37 KB

File metadata and controls

97 lines (70 loc) · 4.37 KB
id ADR-014
title Sealed kernel traits with explicit adapter registration
status accepted
date 2026-04-14
phase Phase 2 (first non-mock impl)
deciders
@ThibautMelen
tags
kernel
sealed-traits
api-stability
affects_crates
nika-kernel
nika-kernel-mock
affects_layers
L0.5
supersedes
superseded_by
related
ADR-006
ADR-012
ADR-016
ADR-020
ADR-028
ADR-034
ADR-039
ADR-040
ADR-041
ADR-078
ADR-079
ADR-080
ADR-081
requires
ADR-006
enables
amends
fci
FCI-006
inv
shadow_zones
nika_codes
timeline v0.80.0, Phase 2 -- sealing before first L1 admission
follow_ups
Apply sealed pattern in single commit before Phase 2 Session 1

ADR-014: Sealed kernel traits with explicit adapter registration

Context

nika-kernel (L0.5) defines ~20 public async traits — Provider, MemoryStore, ToolExecutor, FsRead, HttpGet, ShellExecutor, etc. Today only nika-kernel-mock implements them (L0.5 test). As Phase 2 admits L1 effect crates (nika-http, nika-fs, nika-process), each trait gains real implementors.

The open question: can an external crate (outside supernovae-st/nika workspace) implement these traits? If yes, every method signature becomes load-bearing across semver — adding a default-method parameter is a breaking change for external implementors. If no, we preserve flexibility to evolve method signatures as we learn.

2026 Rust practice (axum, serde, uv) uses the sealed-trait pattern (pub trait Foo: private::Sealed { ... }) to prevent external impls while keeping the trait public for users who depend on it via dyn Foo. rust-analyzer, rustls, tokio all seal core traits.

Decision

All public traits in nika-kernel are sealed via the private::Sealed supertrait pattern:

// nika-kernel/src/lib.rs
mod private {
    pub trait Sealed {}
}

// nika-kernel/src/provider.rs
pub trait ProviderInfer: private::Sealed + Send + Sync { /* ... */ }

// nika-kernel/src/provider.rs (internal, for blanket Sealed impls)
impl<T: crate::Provider + ?Sized> private::Sealed for T {}

External crates cannot impl ProviderInfer for MyType (compile error: Sealed not implemented, and Sealed is private). Instead, external integrations route through:

  1. Adapter macros (nika_kernel::provider_impl!) for workspace-local crates (nika-provider-rig, nika-provider-native, nika-provider-mock).
  2. nika-builtin-* wrappers for ecosystem integrations (GitHub, Cloud, Workspace — ADR tbd).
  3. Future pck packages (plugin protocol, Phase 4+) for community extensions.

Adding a trait method still requires care, but the set of implementors is finite and controlled by the workspace.

Consequences

Positive

  • Method signatures and default methods can evolve within v0.x without breaking external code (there isn't any external impl by construction).
  • Clear path for ecosystem extensions (adapter macros > plugin protocol > pck) — enforced architecturally.
  • Matches Rust 2026 canonical pattern.

Negative

  • Some friction for hypothetical out-of-workspace adapters — but there are none today, and when they emerge they belong as nika-builtin-* or pck packages per ADR-004's crate-count discipline.
  • Adds a private::Sealed module + blanket impl per trait group.

Neutral

  • Language-level sealed keyword RFC is still unmerged as of 2026-Q1 — the supertrait pattern is idiomatic.

Evidence

  • Pattern sources: axum, serde (private::Sealed), rustls (server::ServerConfig sealed), tokio-stream
  • crates/nika-kernel/src/lib.rs:19-34 — trait hierarchy (to be updated with sealed pattern)

Alternatives considered

Alt A — Open traits (no sealing)

Rejected — every method change becomes potential breakage.

Alt B — Opaque impl trait only (no trait at all)

Rejected — loses dyn Trait + breaks test substitutability (kernel-mock).

Alt C — #[non_exhaustive] on the trait

Not valid in Rust — #[non_exhaustive] is structs/enums only.

Related

  • ADR-006 — kernel ISP traits (which this seals)
  • ADR-012 — typestate runtime (uses sealed kernel via bind_kernel(&dyn Kernel))

Notes

Migration: sealing can be added to nika-kernel in a single commit before any L1 effect crate admits. No breaking change to nika-kernel-mock (workspace-local, blanket-Sealed applies). Plan for Phase 2 Session 1.