Kernel/App Separation Architecture
Status: Living Document
Last Updated: 2026-02-01
Related Issues: Phase 19+ (Trust Extraction, PolicyOracle Migration), #1007 (Wave 1 Firewall Contract)
Executive Summary
ICN is a constraint engine: apps translate meaning into constraints; the kernel enforces constraints without understanding meaning.
ICN's kernel/app separation architecture enforces a strict boundary—the Meaning Firewall—between infrastructure mechanisms (kernel) and domain semantics (apps). This document describes the architectural principles, implementation patterns, and migration path for this separation.
Key Insight: The kernel enforces constraints WITHOUT understanding their semantic origin. Trust scores, governance rules, and membership criteria are domain concepts that apps translate into generic constraints the kernel can enforce blindly.
This is the constraint engine model: Policy Oracles (apps) decide constraint values → Kernel enforces constraints without knowing why.
Firewall Contract (Non-Negotiable Invariants)
Status: Enforced via CI
Tracking Issue: #1007 (Wave 1)
These invariants are mechanically enforced to ensure the kernel remains semantically blind.
Invariant 1: Kernel Must Not Import Domain Crates
Rule: Kernel crates (icn-core, icn-kernel-api, icn-net, icn-gossip, icn-store) must NOT depend on:
- Domain crates:
icn-trust,icn-governance,icn-ledger(internals) - App crates: runtime app crates under
icn/apps/*(canonical) or legacy top-levelapps/*during migration
Rationale: Direct imports create compile-time coupling. If kernel code can import TrustGraph, it can call domain-specific methods, violating the meaning firewall.
Enforcement: CI checks dependency closure via cargo metadata (see .github/scripts/firewall_denylist.py).
Example Violation:
// ❌ FORBIDDEN in icn-core
use icn_trust::TrustGraph;
fn select_replica(&self, candidates: &[Did]) -> Did {
let score = self.trust_graph.compute_trust_score(&candidates[0]);
// Kernel now "knows" what trust scores mean
}
Correct Pattern:
// ✅ ALLOWED - kernel reads typed ConstraintSet fields, not custom keys
fn select_replica(&self, candidates: &[Did], constraints: &ConstraintSet) -> Did {
let min_score = constraints
.replica_selection
.min_trust_score
.unwrap_or(0.0);
// Kernel enforces the constraint without knowing WHY min_score=0.7
}
Invariant 2: Kernel Only Accepts Pure Data Types
Rule: Kernel entrypoints must accept only:
- Cryptographic proofs:
IdentityProof, Ed25519 signatures - Pure data:
ConstraintSet,CapabilityToken,RateLimitPolicy - Kernel primitives:
Did,Timestamp,Hash
Rationale: Pure data types have no behavior. Accepting &TrustGraph or GovernanceConfig allows kernel code to call semantic methods.
Example Violation:
// ❌ FORBIDDEN - accepting domain type
pub fn authorize_action(
&self,
actor: &Did,
trust_graph: &TrustGraph, // Domain type with semantic methods
) -> bool {
trust_graph.compute_trust_score(actor) >= 0.4
}
Correct Pattern:
// ✅ ALLOWED - kernel uses PolicyOracle decision, not domain lookups
pub fn authorize_action(
&self,
actor: &Did,
decision: &PolicyDecision, // Pre-computed by PolicyOracle
) -> bool {
matches!(decision, PolicyDecision::Allow { .. })
}
Invariant 3: All Semantic Lookup Confined to PolicyOracle
Rule: Any decision based on trust scores, governance rules, membership status, or credit limits MUST happen in a PolicyOracle implementation, NOT in kernel code.
Rationale: PolicyOracles are the translation boundary. They convert domain semantics into ConstraintSet values the kernel can enforce blindly.
Example Violation:
// ❌ FORBIDDEN in icn-core
fn rate_limit_for(&self, actor: &Did) -> RateLimit {
let score = self.trust_graph.compute_trust_score(actor);
if score >= 0.7 { // Kernel "knows" 0.7 means "Federated"
RateLimit::new(200, 50)
} else {
RateLimit::new(20, 5)
}
}
Correct Pattern:
// ✅ In icn/apps/trust/src/oracle.rs (or apps/trust/src/oracle.rs during migration)
impl PolicyOracle for TrustPolicyOracle {
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision {
let score = self.graph.compute_trust_score(&request.actor);
// Semantic logic ENDS here ═══════════════════════════
// Convert to pure constraints below the firewall
let rate_limit = if score >= 0.7 {
RateLimit::new(200, 50)
} else {
RateLimit::new(20, 5)
};
PolicyDecision::allow_with(
ConstraintSet::new().with_rate_limit(rate_limit)
)
}
}
// ✅ In icn-core (kernel code)
fn apply_rate_limit(&self, actor: &Did, constraints: &ConstraintSet) {
if let Some(limit) = constraints.rate_limit {
self.rate_limiter.apply(&actor, limit);
// Kernel doesn't know WHY limit is 200/50 vs 20/5
}
}
Invariant 4: Kernel Must Not Pattern-Match on ConstraintSet.custom Keys
Rule: Kernel enforcement logic must ONLY use typed ConstraintSet fields:
rate_limit: Option<RateLimit>max_topics: Option<u32>max_connections: Option<u32>- etc.
Kernel code MUST NOT:
- Check for specific
customkeys (e.g.,constraints.custom.get("trust_score")) - Branch on
customkey presence - Extract
customvalues to affect enforcement
Rationale: custom is an escape hatch for app-specific data. If kernel code depends on custom keys, it's indirectly depending on app semantics.
Example Violation:
// ❌ FORBIDDEN - kernel branching on custom key
fn should_replicate(&self, constraints: &ConstraintSet) -> bool {
if let Some(trust_score) = constraints.custom.get("trust_score") {
trust_score.as_float().unwrap() >= 0.4 // Kernel "knows" trust_score semantics
} else {
false
}
}
Correct Pattern:
// ✅ ALLOWED - PolicyOracle sets typed field
impl PolicyOracle for TrustPolicyOracle {
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision {
let score = self.graph.compute_trust_score(&request.actor);
PolicyDecision::allow_with(
ConstraintSet::new()
.with_custom("can_replicate", ConstraintValue::Bool(score >= 0.4))
)
}
}
// ✅ Kernel checks typed field (if added to ConstraintSet)
// OR kernel remains completely agnostic and lets app handle it
fn should_replicate(&self, constraints: &ConstraintSet) -> bool {
// Option A: Add typed field to ConstraintSet
constraints.can_replicate.unwrap_or(false)
// Option B: Don't check at all - let replication be a higher-level decision
}
Invariant 5: Kernel Contains Enforcement Mechanisms, Not Policy Decisions
Rule: Kernel provides enforcement infrastructure. Apps provide policy values.
| Component | Kernel Owns (Mechanism) | Apps Own (Value) | Notes |
|---|---|---|---|
| Rate limiting | RateLimiter struct, token bucket algorithm |
rate_limit: RateLimit(messages_per_second, burst_size) in ConstraintSet |
Kernel enforces rate; app decides rate |
| Credit gating | CreditGate check logic (balance >= ceiling?) |
credit_ceiling: 1000 in ConstraintSet |
Kernel checks ceiling; app computes ceiling |
| Capability gating | CapabilityGate check logic (caps.contains(required)?) |
capabilities: [Read, Write] in ConstraintSet |
Kernel checks presence; app grants capabilities |
| Topic access | Topic subscription acceptance logic | allowed_topics: Vec<String> in ConstraintSet |
Kernel enforces list; app computes list |
| Replay guard | ReplayGuard sequence tracking and rejection logic |
N/A (no tunable values) | Pure mechanism, no policy knobs |
| Transport auth | TLS certificate verification, DID-TLS binding | N/A (no tunable values) | Pure cryptographic check, no policy |
Example: Rate limiting mechanism vs value
// ✅ Kernel provides the mechanism
pub struct RateLimiter {
limiters: HashMap<Did, TokenBucket>,
}
impl RateLimiter {
pub fn check(&mut self, actor: &Did, limit: &RateLimit) -> Result<(), RateLimitError> {
let bucket = self.limiters.entry(*actor).or_insert_with(|| {
TokenBucket::new(limit.messages_per_second, limit.burst_size)
});
bucket.consume(1) // Mechanism: token bucket algorithm
}
}
// ✅ App provides the value
impl PolicyOracle for TrustPolicyOracle {
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision {
let score = self.graph.compute_trust_score(&request.actor);
// App decides the rate based on trust class
let rate = if score >= 0.7 { RateLimit::new(200, 50) }
else if score >= 0.4 { RateLimit::new(100, 25) }
else { RateLimit::new(20, 5) };
PolicyDecision::allow_with(ConstraintSet::new().with_rate_limit(rate))
}
}
Violation Examples (What NOT to Do)
❌ Direct trust-score call in kernel:
// icn/crates/icn-core/src/supervisor/init_gossip.rs
fn select_anti_entropy_peers(&self) -> Vec<Did> {
self.peers.iter()
.filter(|peer| {
let score = self.trust_graph.compute_trust_score(peer); // VIOLATION
score >= 0.4
})
.collect()
}
❌ Importing domain types in kernel:
// icn/crates/icn-net/src/rate_limit.rs
use icn_trust::{TrustGraph, TrustClass}; // VIOLATION
❌ Hardcoded policy constants in kernel:
// icn/crates/icn-core/src/supervisor/mod.rs
const MIN_TRUST_FOR_REPLICATION: f64 = 0.4; // VIOLATION - kernel defines policy
❌ Pattern-matching on custom keys:
// icn/crates/icn-core/src/supervisor/replication.rs
fn should_replicate(&self, constraints: &ConstraintSet) -> bool {
if let Some(class) = constraints.custom.get("trust_class") { // VIOLATION
class.as_string() == Some("Partner")
}
}
Migration Path
- Wave 1 (This Issue): Document contract, add CI enforcement
- Waves 2-6: Migrate existing violations to PolicyOracle pattern
- Enforcement: CI reports violations as advisory during migration (
continue-on-error: true). After all waves complete, the job becomes a hard gate (continue-on-errorremoved)
Table of Contents
- Firewall Contract
- The Meaning Firewall
- Architectural Layers
- Core Abstractions
- Request Flow
- Implementation Guide
- Migration Status
- Testing Patterns
- FAQ
1. The Meaning Firewall
1.1 Core Principle
The Meaning Firewall is the conceptual boundary where domain semantics end and kernel enforcement begins.
┌─────────────────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ │
│ ┌───────────┐ ┌────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Trust │ │ Governance │ │ Ledger │ │ Membership│ │
│ │ Graph │ │ Rules │ │ Policy │ │ Roles │ │
│ └─────┬─────┘ └──────┬─────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ PolicyOracle │ │
│ │ (Translates domain semantics → ConstraintSet) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
└───────────────────────────────────┼──────────────────────────────────────┘
│
════════════════════════════════════╪══════════════════════════════════════
MEANING FIREWALL
(Domain semantics END here)
════════════════════════════════════╪══════════════════════════════════════
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ KERNEL LAYER │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ConstraintSet │ │
│ │ - rate_limit: RateLimit │ │
│ │ - credit_multiplier: f64 │ │
│ │ - max_topics: u32 │ │
│ │ - custom: HashMap<String, ConstraintValue> │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ Rate │ │ Credit │ │ Topic │ │ Route │ │
│ │ Limiting │ │ Control │ │ Access │ │ Priority │ │
│ └─────────────┘ └────────────┘ └─────────────┘ └────────────┘ │
│ │
│ The kernel enforces these WITHOUT understanding WHY │
└───────────────────────────────────────────────────────────────────────────┘
1.2 Why This Matters
Before (Tight Coupling):
// ❌ Kernel code directly using domain types
use icn_trust::{TrustGraph, TrustClass};
fn select_replica_candidates(&self) -> Vec<Did> {
let score = self.trust_graph.compute_trust_score(&peer);
if TrustClass::from_score(score) >= TrustClass::Partner {
// Kernel now "knows" what Partner means
candidates.push(peer);
}
}
After (Meaning Firewall):
// ✅ Kernel using abstract interfaces
use icn_kernel_api::services::TrustService;
fn select_replica_candidates(&self) -> Vec<Did> {
let score = self.trust_service.trust_score(&peer);
if score >= self.config.min_trust_score { // Just a number!
candidates.push(peer);
}
}
1.3 Decision Criteria
Before adding code to kernel crates, ask:
| Question | If YES → |
|---|---|
| Does this interpret domain semantics? | Must be an app |
| Does this hardcode a schema? | Must be an app |
| Does this privilege a specific application? | Must be an app |
| Does this require knowing what a trust score means? | Must be an app |
The kernel is deliberately dumb. It provides pipes, not policies.
2. Architectural Layers
2.1 Crate Classification
KERNEL CRATES (domain-agnostic) APP CRATES (domain-specific)
──────────────────────────────── ────────────────────────────
icn-kernel-api ← Trait definitions icn-trust ← Trust graph
icn-core ← Runtime, supervisor icn-governance ← Voting, proposals
icn-net ← QUIC, mDNS, sessions icn-ledger ← Credit, accounts
icn-gossip ← Topics, vector clocks icn-ccl ← Contract execution
icn-store ← Sled persistence icn-entity ← Organizations
icn-obs ← Metrics, tracing icn-community ← Membership
icn-federation ← Cross-coop
2.2 Dependency Rules
┌─────────────────┐
│ icn-kernel-api │ ← Trait definitions
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ icn-core │ │ icn-net │ │icn-gossip│
└────┬─────┘ └──────────┘ └──────────┘
│
│ ❌ FORBIDDEN: Direct imports of domain crates
│
│ ✅ ALLOWED: Through TrustService, PolicyOracle, etc.
│
CI Enforcement: The kernel-deps CI job checks that kernel crates don't depend on forbidden domain crates:
# From .github/workflows/ci.yml
kernel-deps:
name: Kernel Forbidden Dependencies
steps:
- name: Check kernel crates for forbidden deps
run: |
KERNEL_CRATES=("icn-core" "icn-net" "icn-gossip")
FORBIDDEN=("icn-trust" "icn-governance" "icn-ledger" ...)
for crate in "${KERNEL_CRATES[@]}"; do
for dep in "${FORBIDDEN[@]}"; do
if cargo tree -p "$crate" | grep -q "${dep}"; then
echo "Forbidden dependency: ${crate} -> ${dep}"
exit 1
fi
done
done
3. Core Abstractions
3.1 PolicyOracle
The PolicyOracle trait is the primary interface for authorization decisions. Apps implement it to translate domain-specific rules into generic constraints.
/// File: icn/crates/icn-kernel-api/src/authz.rs
pub trait PolicyOracle: Send + Sync {
/// Evaluate a policy request and return a decision
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision;
/// Domain this oracle handles (e.g., "trust", "governance")
fn domain(&self) -> Domain;
}
pub enum PolicyDecision {
/// Allow with constraints
Allow { constraints: ConstraintSet },
/// Deny with reason
Deny { reason: String },
/// Abstain (let other oracles decide)
Abstain,
}
Example Implementation (TrustPolicyOracle):
/// File: icn/crates/icn-gateway/src/trust_policy.rs
impl PolicyOracle for TrustPolicyOracle {
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision {
// 1. Extract actor from request
let actor = &request.actor;
// 2. Compute trust score (domain logic)
let score = self.graph.compute_trust_score(actor);
// ════════════════════════════════════════════
// ▼▼▼ MEANING FIREWALL CROSSING ▼▼▼
// Trust semantics END here
// ════════════════════════════════════════════
// 3. Convert to generic constraints (kernel-safe)
let constraints = ConstraintSet::new()
.with_rate_limit(score_to_rate_limit(score))
.with_credit_multiplier(score)
.with_custom("trust_score", ConstraintValue::Float(score));
PolicyDecision::Allow { constraints }
}
fn domain(&self) -> Domain {
Domain::Trust
}
}
fn score_to_rate_limit(score: f64) -> RateLimit {
// Trust class boundaries, but kernel sees only numbers
if score >= 0.7 { // Federated
RateLimit { messages_per_sec: 200, burst: 50 }
} else if score >= 0.4 { // Partner
RateLimit { messages_per_sec: 100, burst: 25 }
} else if score >= 0.1 { // Known
RateLimit { messages_per_sec: 20, burst: 5 }
} else { // Isolated
RateLimit { messages_per_sec: 10, burst: 2 }
}
}
3.2 TrustService
The TrustService trait provides trust-related functionality without exposing TrustGraph, TrustClass, or other domain types.
/// File: icn/crates/icn-kernel-api/src/services.rs
pub trait TrustService: Send + Sync {
/// Get the PolicyOracle for this trust service
fn oracle(&self) -> Arc<dyn PolicyOracle>;
/// Get trust score for an actor (opaque value 0.0-1.0)
fn trust_score(&self, actor: &Did) -> f64;
/// Check if actor meets minimum trust threshold
fn meets_trust_threshold(&self, actor: &Did, min_score: f64) -> bool {
self.trust_score(actor) >= min_score
}
/// Record a trust-affecting event
fn record_event(&self, actor: &Did, event: TrustEvent);
}
Kernel-Level TrustClass:
For convenience, the kernel defines its own TrustClass enum that apps can use for boundary conversions:
/// File: icn/crates/icn-kernel-api/src/services.rs
pub const TRUST_THRESHOLD_KNOWN: f64 = 0.1;
pub const TRUST_THRESHOLD_PARTNER: f64 = 0.4;
pub const TRUST_THRESHOLD_FEDERATED: f64 = 0.7;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TrustClass {
#[default]
Isolated = 0, // score < 0.1
Known = 1, // 0.1 <= score < 0.4
Partner = 2, // 0.4 <= score < 0.7
Federated = 3, // score >= 0.7
}
impl TrustClass {
pub fn from_score(score: f64) -> Self { /* ... */ }
pub fn min_score(&self) -> f64 { /* ... */ }
}
3.3 ServiceRegistry
The ServiceRegistry allows the daemon to inject domain services into the kernel at startup:
/// File: icn/crates/icn-kernel-api/src/services.rs
pub struct ServiceRegistry {
trust: Option<Arc<dyn TrustService>>,
security: Option<Arc<dyn SecurityService>>,
governance: Option<Arc<dyn GovernanceService>>,
ledger: Option<Arc<dyn LedgerService>>,
}
impl ServiceRegistry {
pub fn new() -> Self;
pub fn with_trust(self, service: Arc<dyn TrustService>) -> Self;
pub fn with_security(self, service: Arc<dyn SecurityService>) -> Self;
pub fn with_governance(self, service: Arc<dyn GovernanceService>) -> Self;
pub fn with_ledger(self, service: Arc<dyn LedgerService>) -> Self;
}
Concrete domain objects that need to be shared with the supervisor (ledger handle,
contract runtime, dispute/treasury managers, etc.) are passed via typed
BootstrapHandles instead of type-erased raw_handle() calls:
/// File: icn/crates/icn-core/src/supervisor/actors.rs
pub struct BootstrapHandles {
pub ledger: Arc<RwLock<Ledger>>,
pub ledger_store: Arc<SledStore>,
pub dispute_manager: Arc<RwLock<DisputeManager>>,
pub treasury_manager: Arc<RwLock<TreasuryManager>>,
pub contract_runtime: Arc<RwLock<ContractRuntime>>,
pub contract_actor: Arc<RwLock<ContractActor>>,
pub protocol_parameter_store: Arc<dyn ProtocolParameterStore>,
}
Usage in icnd:
// In icnd main.rs — trait-based services go in ServiceRegistry
let trust_service = icn_trust_app::create_service_tokio(trust_graph_handle.clone());
let registry = ServiceRegistry::new().with_trust(trust_service);
let runtime = Runtime::new(config, identity)
.with_services(registry);
// Concrete domain handles go in BootstrapHandles
let handles = BootstrapHandles {
ledger: ledger_handle,
ledger_store,
dispute_manager,
treasury_manager,
contract_runtime,
contract_actor,
protocol_parameter_store,
};
let runtime = runtime.with_bootstrap_handles(handles);
Why the daemon passes stores separately: Sled uses exclusive file locking (flock on Linux). Opening the same sled path twice in the same process fails. The daemon creates stores once and passes them through BootstrapHandles so the supervisor can reuse them for ancillary services (DisputeManager, TreasuryManager) without re-opening.
3.4 ConstraintSet
The ConstraintSet is the sole output format for PolicyOracle. It's designed to be domain-blind:
/// File: icn/crates/icn-kernel-api/src/authz.rs
pub struct ConstraintSet {
pub rate_limit: Option<RateLimit>,
pub credit_multiplier: Option<f64>,
pub max_topics: Option<u32>,
pub priority_class: Option<u8>,
pub custom: HashMap<String, ConstraintValue>,
}
pub struct RateLimit {
pub messages_per_sec: u32,
pub burst: u32,
}
pub enum ConstraintValue {
Bool(bool),
Int(i64),
Float(f64),
String(String),
List(Vec<ConstraintValue>),
}
The kernel enforces these without knowing whether they came from trust scores, governance rules, or membership status.
3.5 OracleRegistry
The OracleRegistry is the centralized authorization gateway that routes policy requests to domain-specific oracles. It implements PolicyOracle so kernel components (GossipActor, NetworkActor, RPC server) can use it as their single authorization endpoint.
Key features:
- Per-domain routing: Requests are dispatched to the oracle registered for the request's domain (e.g.,
trust,ledger,governance) - Bootstrap phases: Genesis (allow all) → CoreApps (allow all) → Running (deny unknown domains)
- Decision caching: TTL-based cache with automatic invalidation on oracle swap
- Atomic oracle replacement: Uses
ArcSwapfor lock-free reads during hot path evaluation
Supervisor lifecycle integration:
1. Supervisor creates OracleRegistry (Genesis phase - AllowAll)
2. Daemon registers TrustPolicyOracle for "trust" domain
3. Registry transitions to CoreApps phase
4. All actors initialize with OracleRegistry as their PolicyOracle
5. Registry transitions to Running phase (deny-by-default for unknown domains)
Usage in kernel components:
// The supervisor passes OracleRegistry as Arc<dyn PolicyOracle>
let oracle_registry = Arc::new(OracleRegistry::new());
oracle_registry.register(Domain::trust(), trust_oracle);
oracle_registry.set_phase(BootstrapPhase::Running);
// Kernel components receive it as a generic PolicyOracle
let oracle: Arc<dyn PolicyOracle> = oracle_registry;
let gossip = GossipActor::new(did, Some(oracle));
Location: icn-kernel-api/src/bootstrap.rs
4. Request Flow
4.1 TrustPolicyOracle Flow Diagram
┌──────────────────────────────────────────────────────────────────────────┐
│ API Request Arrives │
└───────────────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ TrustPolicyOracle │
│ ───────────────── │
│ fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision │
└───────────────────────────────────┬──────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────────┐
│ domain check │ │ domain != "trust" │
│ domain == │ │ │
│ "trust" │ │ Return: Abstain │
└───────┬───────┘ └───────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Parse Actor DID │
│ ┌─────────────────────────────────────────┐ │
│ │ Invalid DID? → Return score = 0.0 │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Trust Graph Lookup │
│ ──────────────────────────── │
│ graph.compute_trust_score(actor) → f64 │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Lock Strategy: │ │
│ │ 1. Try try_read() first │ │
│ │ 2. If contention → block_in_place() │ │
│ │ 3. Increment counter metric │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────┬───────────────────────┘
│
│ trust_score: f64
│
════════════════════════════════════════╪═══════════════════════════════════
MEANING FIREWALL
Trust semantics END here
════════════════════════════════════════╪═══════════════════════════════════
│
▼
┌───────────────────────────────────────────────────┐
│ score_to_constraints(score: f64) │
│ ───────────────────────────────────────────── │
│ │
│ ConstraintSet { │
│ rate_limit: score_to_rate_limit(score), │
│ credit_multiplier: score, │
│ max_topics: score_to_max_topics(score), │
│ custom: { "trust_score": Float(score) } │
│ } │
└───────────────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Return: PolicyDecision::Allow { constraints } │
└───────────────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ KERNEL │
│ ──────────────────────────────── │
│ Enforces rate_limit, credit_multiplier, etc. │
│ WITHOUT knowing they came from trust scores │
└───────────────────────────────────────────────────┘
4.2 Component Integration
The following components have been migrated to use TrustService:
| Component | Location | Before | After |
|---|---|---|---|
| ReplicationManager | icn-core/replication | Arc<RwLock<TrustGraph>> |
Arc<dyn TrustService> |
| ForkResolver | icn-ledger | Arc<RwLock<TrustGraph>> |
Option<Arc<dyn TrustService>> |
| TrustManager | icn-gateway | Arc<RwLock<TrustGraph>> |
Option<Arc<dyn TrustService>> |
| ChallengeScheduler | icn-core | Arc<RwLock<TrustGraph>> |
Option<Arc<dyn TrustService>> |
| PolicySource | icn-core/policy | icn_trust::TrustClass |
icn_kernel_api::TrustClass |
| IdentityHandle | icn-core/identity | icn_trust::TrustClass |
icn_kernel_api::TrustClass |
5. Implementation Guide
5.1 Adding a New PolicyOracle
- Define your domain in
icn-kernel-api/src/authz.rs:
pub enum Domain {
Trust,
Governance,
Ledger,
Membership, // ← Add new domain
}
- Implement PolicyOracle in your app crate:
// In icn/apps/membership/src/oracle.rs
use icn_kernel_api::{PolicyOracle, PolicyRequest, PolicyDecision, ConstraintSet, Domain};
pub struct MembershipOracle {
membership_db: Arc<MembershipDb>,
}
impl PolicyOracle for MembershipOracle {
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision {
if request.domain != Domain::Membership {
return PolicyDecision::Abstain;
}
let actor = &request.actor;
let role = self.membership_db.get_role(actor);
// ═══ MEANING FIREWALL ═══
// Convert role → constraints
let constraints = role_to_constraints(role);
PolicyDecision::Allow { constraints }
}
fn domain(&self) -> Domain {
Domain::Membership
}
}
- Register in ServiceRegistry:
// In icnd or your app's initialization
let membership_oracle = Arc::new(MembershipOracle::new(db));
let registry = registry.with_oracle(membership_oracle);
5.2 Migrating a Component from TrustGraph to TrustService
Step 1: Add optional TrustService field:
pub struct MyComponent {
// Keep during transition
trust_graph: Option<Arc<RwLock<TrustGraph>>>,
// New: prefer this
trust_service: Option<Arc<dyn TrustService>>,
}
Step 2: Add helper method that prefers TrustService:
impl MyComponent {
fn get_trust_score(&self, did: &Did) -> f64 {
// Prefer TrustService (kernel/app separation)
if let Some(ref service) = self.trust_service {
let kernel_did = icn_kernel_api::types::Did::from(did.to_string());
return service.trust_score(&kernel_did);
}
// Fallback to TrustGraph (deprecated path)
if let Some(ref graph) = self.trust_graph {
if let Ok(guard) = graph.try_read() {
return guard.compute_trust_score(did);
}
}
0.0 // Default if neither available
}
}
Step 3: Update callers to pass TrustService:
// In supervisor/lifecycle.rs
let trust_service = registry.trust().cloned();
let component = MyComponent::new()
.with_trust_service(trust_service);
Step 4: Remove TrustGraph field once migration is complete.
5.3 Converting TrustClass Types
When migrating from icn_trust::TrustClass to icn_kernel_api::TrustClass:
// Before
use icn_trust::TrustClass;
fn check_access(&self, score: f64) -> bool {
TrustClass::from_score(score) >= TrustClass::Partner
}
// After
use icn_kernel_api::TrustClass;
fn check_access(&self, score: f64) -> bool {
TrustClass::from_score(score) >= TrustClass::Partner
}
// Or more directly:
fn check_access(&self, score: f64) -> bool {
score >= icn_kernel_api::TRUST_THRESHOLD_PARTNER // 0.4
}
6. Migration Status
6.0 Wave-Based Migration Progress
Current Status: Wave 1 Complete ✅
The kernel/app separation is being executed in waves, with each wave building on previous work:
| Wave | Tracking Issue | Status | Description |
|---|---|---|---|
| Wave 0 | #1006 | ✅ Complete | Canonical Mental Model documentation |
| Wave 1 | #1007 | ✅ Complete | Firewall Contract documentation + CI enforcement |
| Wave 2 | #1008 | ⏳ Planned | Scope-Bounded Trust: structural centralization prevention |
| Wave 3 | #1009 | ⏳ Planned | Attestation Model: canonical schema and dispute pathway |
| Wave 4 | #1010 | ⏳ Planned | Adversarial Model: threat documentation and chaos harness |
| Wave 5 | #1011 | ⏳ Planned | Constitutional Genesis: canonical bootstrap documentation |
| Wave 6 | #1012 | ⏳ Planned | Legibility Dashboards: UX spec for constraint visibility |
Wave 1 Deliverables (✅ Complete as of 2026-02-01):
- ✅ Firewall Contract section added to this document
- ✅ 5 non-negotiable invariants documented with rationale
- ✅ Mechanism vs value distinction table
- ✅ Violation examples (what NOT to do)
- ✅
.github/scripts/firewall_denylist.pyCI enforcement script - ✅ CI job integrated (non-blocking until migrations complete)
Known Violations (to be resolved in Waves 2-6):
Currently detected by firewall_denylist.py:
icn-core -> icn-cclicn-core -> icn-communityicn-core -> icn-computeicn-core -> icn-coopicn-core -> icn-entityicn-core -> icn-federationicn-core -> icn-governanceicn-core -> icn-ledgericn-core -> icn-stewardicn-core -> icn-trust
Definition of Done for Complete Separation:
- CI
firewall-contractjob passes without violations -
continue-on-errorremoved from CI job - All
raw_handlesremoved from ServiceRegistry (completed in #911) - No kernel crate depends on any domain/app crate
6.0.1 Enforcement Mechanism Status
The table below shows the current state of kernel enforcement mechanisms and policy value sources:
| Enforcement | Kernel-Pure? | Status | Migration Target |
|---|---|---|---|
| Transport auth | ✅ Yes | Complete | No changes needed - pure cryptographic check |
| Replay guard | ✅ Yes | Complete | No changes needed - pure sequence tracking |
| Rate limiter (mechanism) | ✅ Yes | Complete | Kernel provides token bucket algorithm |
| Rate limit values | 🟡 Partial | Wave 2 | Some hardcoded, needs full migration to ConstraintSet |
| Capability gate (mechanism) | ✅ Yes | Complete | Kernel checks capability presence |
| Capability grants | 🟡 Partial | Wave 3 | Needs migration to PolicyOracle-provided values |
| Credit gate (mechanism) | ✅ Yes | Complete | Kernel enforces ceiling checks |
| Credit ceiling values | 🟡 Partial | Wave 4 | Needs migration to ConstraintSet |
| Topic access control | 🟡 Partial | Wave 2 | Some direct trust checks, needs PolicyOracle |
| Replication selection | ❌ No | Wave 2 | Direct trust-score calls need migration |
Legend:
- ✅ Kernel-Pure: Mechanism and values properly separated
- 🟡 Partial: Mechanism separated, but some values still hardcoded in kernel
- ❌ No: Still has semantic knowledge, needs migration
6.1 Completed Migrations
| Component | Migration Date | Notes |
|---|---|---|
| ReplicationManager → TrustService | 2026-01-27 | Full migration |
| ForkResolver → TrustService | 2026-01-27 | Optional field, fallback preserved |
| TrustManager (gateway) → TrustService | 2026-01-27 | Optional field |
| ChallengeScheduler → TrustService | 2026-01-28 | Full migration |
| PolicySource → kernel TrustClass | 2026-01-28 | Type migration |
| IdentityHandle → kernel TrustClass | 2026-01-28 | Type migration |
| GossipDeps → PolicyOracle | 2026-01-27 | Removed TrustGraphOracle fallback |
| GovernanceService wired in icnd | 2026-01-30 | Daemon owns SledParameterStore lifecycle |
| LedgerService wired in icnd | 2026-01-30 | Daemon owns Ledger + SledStore lifecycle |
| MisbehaviorDetector → TrustService | 2026-01-30 | Full migration (#910) |
| Ledger → TrustService (no TrustGraph) | 2026-01-30 | icn-trust moved to dev-dep (#867) |
| OracleRegistry wired in supervisor | 2026-01-30 | Bootstrap phases, domain routing (#869) |
| Network rate limiting → OracleRegistry | 2026-01-30 | Wired OracleRegistry as PolicyOracle; functional when trust oracle registered (#869) |
| RPC PolicyOracle → OracleRegistry | 2026-01-30 | Passed to RpcDeps; rate-limiting wiring TBD (#869) |
| AllowAllOracle fallback removed | 2026-01-30 | Gossip uses OracleRegistry (#869) |
6.2 Remaining Work
| Component | Location | Status | Notes |
|---|---|---|---|
| GatewayServer trust routes | icn-gateway | Partial | Some endpoints still use TrustGraph |
| ContractActor | icn-ccl | Needs TrustService | Still accepts Option<Arc<RwLock<TrustGraph>>> |
| icn-trust dep in icn-core | icn-core/Cargo.toml | #912 | Kernel crate still imports domain crate |
| icn-governance dep in icn-core | icn-core/Cargo.toml | #913 | Kernel crate still imports domain crate |
| icn-ledger dep in icn-core | icn-core/Cargo.toml | #914 | Kernel crate still imports domain crate |
6.3 Transition Handles (Completed)
The raw_handles transition mechanism has been fully removed (#911). All domain
objects are now provided via:
BootstrapHandles(icn-core/src/supervisor/actors.rs) -- Typed handles for ledger, contract runtime, dispute/treasury managers, and protocol parameter store. Passed from daemon to supervisor viaRuntime::with_bootstrap_handles().ServiceRegistry(icn-kernel-api/src/services.rs) -- Trait-based service injection for TrustService, GovernanceService, LedgerService, SecurityService. Passed viaRuntime::with_services().
7. Testing Patterns
7.1 Mock TrustService
/// For tests that don't need trust-gated behavior
pub struct MockTrustService {
default_score: f64,
}
impl MockTrustService {
pub fn new(default_score: f64) -> Self {
Self { default_score }
}
pub fn fully_trusted() -> Self {
Self::new(1.0)
}
pub fn untrusted() -> Self {
Self::new(0.0)
}
}
impl TrustService for MockTrustService {
fn oracle(&self) -> Arc<dyn PolicyOracle> {
Arc::new(AllowAllOracle::default())
}
fn trust_score(&self, _actor: &Did) -> f64 {
self.default_score
}
fn record_event(&self, _actor: &Did, _event: TrustEvent) {
// No-op for tests
}
}
7.2 AllowAllOracle
For tests that don't need authorization enforcement:
use icn_kernel_api::AllowAllOracle;
let deps = GossipDeps {
policy_oracle: Some(Arc::new(AllowAllOracle::default())),
trust_service: None,
// ...
};
7.3 Integration Test Pattern
#[tokio::test]
async fn test_component_with_trust_service() {
// Create mock trust service
let trust_service = Arc::new(MockTrustService::new(0.5));
// Create component with injected service
let component = MyComponent::new()
.with_trust_service(Some(trust_service));
// Test behavior
let result = component.do_something(&test_did).await;
assert!(result.is_ok());
}
8. FAQ
Q: Why not just pass TrustGraph everywhere?
A: Direct TrustGraph access creates tight coupling:
- Kernel code "knows" about trust class semantics
- Cannot swap trust implementations
- Testing requires real TrustGraph instances
- Violates separation of concerns
Q: What if a component needs both TrustService and TrustGraph?
A: During transition, use both:
struct Component {
trust_service: Option<Arc<dyn TrustService>>, // Preferred
trust_graph: Option<Arc<RwLock<TrustGraph>>>, // Fallback
}
When fully migrated, remove trust_graph.
Q: How do I know if my code crosses the Meaning Firewall?
A: Your code crosses the firewall when:
- It converts domain types to kernel types (✅ correct direction)
- It converts kernel types to domain types (❌ wrong direction)
Example of correct crossing:
// In app code:
let trust_score = graph.compute_trust_score(&did);
let constraints = ConstraintSet::new()
.with_rate_limit(score_to_limit(trust_score));
// ↑ trust_score (domain) → rate_limit (kernel)
Q: What's the difference between icn_trust::TrustClass and icn_kernel_api::TrustClass?
A: They have identical values but different purposes:
icn_trust::TrustClass- Domain concept, used in trust graph operationsicn_kernel_api::TrustClass- Kernel abstraction, used for policy decisions
Kernel code should only use icn_kernel_api::TrustClass.
Q: When were raw_handles removed?
A: Completed in #911. All components now use typed BootstrapHandles or trait-based ServiceRegistry injection.
Appendix A: Store Path Structure
The daemon creates sled-backed stores under config.store_path() (default: ~/.local/share/icn/store/ on Linux):
| Path | Owner | Contents |
|---|---|---|
store/trust/ |
Daemon → TrustGraph | Trust attestations, scores |
store/protocol_params/ |
Daemon → GovernanceService | Protocol parameter values |
store/ledger/ |
Daemon → Ledger + DisputeManager + TreasuryManager | Double-entry transactions, disputes |
Each path is opened exactly once by the daemon. The supervisor receives handles via BootstrapHandles to avoid double-opening sled databases.
Helper methods on Config:
config.store_path()→ base store directoryconfig.protocol_params_path()→store/protocol_params/config.ledger_store_path()→store/ledger/
Appendix B: Key Files
| File | Purpose |
|---|---|
icn/crates/icn-kernel-api/src/lib.rs |
Kernel API trait exports |
icn/crates/icn-kernel-api/src/authz.rs |
PolicyOracle, ConstraintSet |
icn/crates/icn-kernel-api/src/services.rs |
TrustService, ServiceRegistry |
icn/crates/icn-core/src/supervisor/lifecycle.rs |
Service injection |
icn/crates/icn-core/src/supervisor/init_trust.rs |
Trust initialization |
icn/crates/icn-core/src/supervisor/init_governance.rs |
Governance initialization |
icn/crates/icn-core/src/supervisor/init_ledger.rs |
Ledger initialization |
icn/crates/icn-gateway/src/trust_policy.rs |
TrustPolicyOracle implementation |
icn/apps/* |
Canonical runtime app implementations |
apps/* |
Legacy runtime app location during migration |
CLAUDE.md |
Agent guidance (Kernel/App section) |