ICN Kernel Contracts Specification
Version: 0.1.0 Status: Draft Last Updated: 2025-01-25
This document defines the contracts for ICN kernel primitives. It is the constitutional document for the kernel architecture - all kernel code must conform to these contracts.
Table of Contents
- Contract Rules
- Common Types
- Identity Primitive
- Authorization Primitive
- State Primitive
- Compute Primitive
- Communication Primitive
- Time Primitive
- Coordination Primitive
- Naming Primitive
- App Runtime Contract
0. Contract Rules
0.1 Stability Policy
The kernel follows semantic versioning for API stability:
| Version Type | Allowed Changes |
|---|---|
| Patch (0.1.x) | Bugfixes only. No API or wire format changes. |
| Minor (0.x.0) | Additive changes only. New methods, new optional fields. |
| Major (x.0.0) | Breaking changes allowed with migration path. |
Wire format stability: All serialized formats (events, capabilities, messages) MUST be versioned and support at least N-2 versions for reading.
0.2 The Meaning Firewall
The kernel provides mechanisms, not semantics.
The kernel MUST NOT:
- Define domain schemas (ledger transactions, governance proposals, membership records)
- Embed trust computation logic (trust scores, reputation algorithms)
- Embed membership logic (who's in an org, role definitions)
- Privilege "official" apps over third-party apps
- Interpret the meaning of events or credentials
The kernel MUST:
- Provide generic, domain-agnostic primitives
- Treat all apps equally (same APIs for core and third-party apps)
- Use policy oracles for authorization decisions
- Accept arbitrary bytes/schemas without interpretation
Litmus test: "Could a developer build a DIFFERENT economic model / governance system / trust algorithm using these primitives?" If no, the primitives are too specific.
0.3 Extension Points
Each primitive MUST define:
- Adapter interfaces: Pluggable implementations (e.g., different storage backends, different DID methods)
- Policy oracle hooks: Points where apps can inject authorization decisions
- Resource accounting hooks: Metering points for quotas and limits
0.4 PR Checklist
Every PR touching kernel code MUST answer:
- Does this add a new kernel operation? → Update
kernel_surface.toml - Does this interpret domain semantics? → Must be an app, not kernel
- Does this hardcode a schema? → Must be an app, not kernel
- Does this privilege a specific application? → Reject or use oracle pattern
- Does this add policy logic? → Must use policy oracle hook
- Does this break wire format compatibility? → Requires major version bump
1. Common Types
All primitives share these foundational types:
// === Identifiers ===
/// Decentralized Identifier (e.g., "did:icn:z6MkABC...")
pub type Did = String;
/// Node identifier (unique per physical/virtual node)
pub type NodeId = String;
/// Organization identifier (maps to a DID)
pub type OrgId = Did;
/// Namespace identifier (hierarchical: "/org/app/sub")
pub type NamespaceId = String;
/// Application identifier
pub type AppId = String;
/// Capability token identifier
pub type CapabilityId = String;
// === Time and Ordering ===
/// Logical timestamp for causal ordering
/// Monotonically increasing within a node, comparable across nodes via vector clocks
pub type LogicalTimestamp = u64;
/// Version number for optimistic concurrency (CAS operations)
pub type Version = u64;
/// Offset position in a log
pub type Offset = u64;
// === Storage ===
/// Content hash for content-addressed storage (SHA-256)
pub type Hash = [u8; 32];
/// Generic key for KV operations
pub type Key = Vec<u8>;
/// Generic value for KV operations
pub type Value = Vec<u8>;
// === Namespace ===
/// Hierarchical namespace for resource isolation
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Namespace {
/// Organization that owns this namespace
pub org: OrgId,
/// Application within the org
pub app: AppId,
/// Optional sub-namespace for further isolation
pub sub: Option<String>,
}
impl Namespace {
/// Creates a new namespace: /<org>/<app>/
pub fn new(org: impl Into<String>, app: impl Into<String>) -> Self {
Self {
org: org.into(),
app: app.into(),
sub: None,
}
}
/// Adds a sub-namespace: /<org>/<app>/<sub>/
pub fn with_sub(mut self, sub: impl Into<String>) -> Self {
self.sub = Some(sub.into());
self
}
/// Returns the full path as a string
pub fn to_path(&self) -> String {
match &self.sub {
Some(s) => format!("/{}/{}/{}", self.org, self.app, s),
None => format!("/{}/{}", self.org, self.app),
}
}
}
// === Errors ===
/// Standard error structure for kernel operations
#[derive(Debug, Clone)]
pub struct KernelError {
/// Error code for programmatic handling
pub code: ErrorCode,
/// Human-readable message
pub message: String,
/// Whether the operation can be retried
pub retryable: bool,
/// Who is at fault: caller, kernel, or adapter
pub blame: Blame,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
// Authorization errors
Unauthorized,
CapabilityExpired,
CapabilityRevoked,
InsufficientCapability,
// State errors
NotFound,
AlreadyExists,
VersionMismatch,
NamespaceNotFound,
// Resource errors
QuotaExceeded,
RateLimited,
// System errors
Internal,
Unavailable,
Timeout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Blame {
/// Caller made an invalid request
Caller,
/// Kernel had an internal error
Kernel,
/// External adapter/service failed
Adapter,
}
2. Identity Primitive
The Identity primitive provides decentralized identity management without credential semantics.
2.1 Contract
Provides:
- DID creation, rotation, and deactivation
- Cryptographic signing and verification
- DID document resolution via pluggable resolvers
- Keystore interface (sign without key export)
Does NOT provide:
- Credential semantics (what credentials mean)
- Membership (who's in an org)
- Trust/reputation scores
- Steward ceremonies or verification rituals
2.2 Trait Definitions
/// Core identity operations
pub trait IdentityService: Send + Sync {
/// Create a new DID with the specified method
fn did_create(&self, method: DidMethod) -> Result<(Did, DidDocument), IdentityError>;
/// Rotate keys for an existing DID
fn did_rotate(&self, did: &Did, new_keys: KeyBundle) -> Result<DidDocument, IdentityError>;
/// Deactivate a DID (mark as no longer valid)
fn did_deactivate(&self, did: &Did) -> Result<(), IdentityError>;
/// Sign arbitrary bytes with a DID's signing key
fn sign(&self, did: &Did, payload: &[u8]) -> Result<Signature, IdentityError>;
/// Verify a signature against a DID's public key
fn verify(&self, did: &Did, payload: &[u8], signature: &Signature) -> Result<bool, IdentityError>;
}
/// DID document resolution
pub trait DidResolver: Send + Sync {
/// Resolve a DID to its document
fn resolve(&self, did: &Did) -> Result<DidDocument, ResolveError>;
/// Check if a DID is still active
fn is_active(&self, did: &Did) -> Result<bool, ResolveError>;
}
/// Keystore for secure key management
pub trait Keystore: Send + Sync {
/// Sign payload using a key (key never exported)
fn sign(&self, key_id: &KeyId, payload: &[u8]) -> Result<Signature, KeystoreError>;
/// Get public key for a key ID
fn public_key(&self, key_id: &KeyId) -> Result<PublicKey, KeystoreError>;
/// List all key IDs in the keystore
fn list_keys(&self) -> Result<Vec<KeyId>, KeystoreError>;
// Note: No export_private_key - signing without exposure
}
/// Supported DID methods
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DidMethod {
/// did:key - self-certifying, no network lookup
Key,
/// did:icn - ICN-native, resolved via ICN network
Icn,
/// did:web - web-based, requires HTTP lookup
Web { domain: String },
}
/// DID Document structure
#[derive(Debug, Clone)]
pub struct DidDocument {
pub id: Did,
pub verification_methods: Vec<VerificationMethod>,
pub authentication: Vec<KeyId>,
pub assertion_method: Vec<KeyId>,
pub key_agreement: Vec<KeyId>,
pub created: LogicalTimestamp,
pub updated: LogicalTimestamp,
}
/// Key bundle for rotation
#[derive(Debug, Clone)]
pub struct KeyBundle {
pub signing_key: PublicKey,
pub encryption_key: Option<PublicKey>,
}
/// Cryptographic signature
#[derive(Debug, Clone)]
pub struct Signature {
pub algorithm: SignatureAlgorithm,
pub value: Vec<u8>,
pub key_id: KeyId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureAlgorithm {
Ed25519,
// Future: post-quantum algorithms
}
2.3 Design Notes
- DID resolution for networked methods (did:web) runs as an adapter/service with egress capability
- Pure methods (did:key, did:icn) can resolve in deterministic compute
- Credentials are "signed blobs with schema reference" - kernel verifies signature, apps interpret meaning
- Steward ceremonies and identity verification are apps built on top, not kernel features
3. Authorization Primitive
The Authorization primitive provides object-capability tokens and policy oracle integration.
3.1 Contract
Provides:
- Object-capability tokens (NOT RBAC)
- Issue, verify, delegate, revoke capabilities
- Policy oracle interface for external decisions
- Rate limiting hooks based on oracle decisions
Does NOT provide:
- Role hierarchies
- Trust scores or reputation
- Predefined capability taxonomies
- Any policy logic (just enforcement)
3.2 Trait Definitions
/// Capability token granting specific access
#[derive(Clone, Debug)]
pub struct Capability {
/// Unique identifier for this capability
pub id: CapabilityId,
/// What resource this grants access to
pub resource: String,
/// What action is permitted
pub action: String,
/// Additional constraints on use
pub constraints: Constraints,
/// If set, holder must co-sign operations
pub holder: Option<Did>,
/// Who issued this capability
pub issuer: Did,
/// When this capability expires
pub expiration: LogicalTimestamp,
/// Cryptographic proof of issuance
pub signature: Signature,
}
/// Constraints on capability use
#[derive(Clone, Debug, Default)]
pub struct Constraints {
/// Maximum amount (for numeric resources)
pub max_amount: Option<u64>,
/// Maximum number of uses
pub max_uses: Option<u32>,
/// Restrict to specific targets
pub allowed_targets: Option<Vec<String>>,
/// Additional app-defined constraints (opaque to kernel)
pub custom: Option<Vec<u8>>,
}
/// Result of a policy evaluation
#[derive(Clone, Debug)]
pub enum PolicyDecision {
/// Request is allowed to proceed
Allow,
/// Request is denied
Deny { reason: String },
/// Request is allowed but rate-limited
RateLimit { class: RateLimitClass },
}
/// Rate limit classification
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum RateLimitClass {
/// No rate limiting applied
Unlimited,
/// Standard rate limits
Standard,
/// Reduced limits (e.g., low trust)
Throttled,
/// Severely restricted
Restricted,
}
/// Context for policy evaluation
#[derive(Clone, Debug)]
pub struct PolicyContext {
/// Who is making the request
pub actor: Did,
/// What resource is being accessed
pub resource: String,
/// What action is being performed
pub action: String,
/// Namespace context (if applicable)
pub namespace: Option<Namespace>,
/// Whether this is a cross-org request
pub cross_org: bool,
/// Source org for cross-org requests
pub source_org: Option<OrgId>,
/// App-specific metadata
pub metadata: std::collections::HashMap<String, String>,
}
/// Request being evaluated
#[derive(Clone, Debug)]
pub struct PolicyRequest {
pub context: PolicyContext,
pub capability: Option<Capability>,
}
/// Policy oracle interface - implemented by apps (e.g., trust app)
pub trait PolicyOracle: Send + Sync {
/// Evaluate whether a request should be allowed
fn evaluate(&self, request: &PolicyRequest) -> PolicyDecision;
/// Get cache TTL for decisions (allows kernel to cache)
fn cache_ttl(&self) -> std::time::Duration {
std::time::Duration::from_secs(60)
}
/// Whether this oracle handles cross-org requests
fn handles_cross_org(&self) -> bool {
false
}
}
/// Default oracle that allows everything (for bootstrap and simple deployments)
pub struct AllowAllOracle;
impl PolicyOracle for AllowAllOracle {
fn evaluate(&self, _request: &PolicyRequest) -> PolicyDecision {
PolicyDecision::Allow
}
}
/// Capability management engine
pub trait CapabilityEngine: Send + Sync {
/// Issue a new capability
fn issue(
&self,
resource: &str,
action: &str,
holder: Option<&Did>,
constraints: Constraints,
expiration: LogicalTimestamp,
) -> Result<Capability, CapabilityError>;
/// Check if a capability authorizes a request
fn check(
&self,
capability: &Capability,
resource: &str,
action: &str,
) -> Result<(), CapabilityError>;
/// Delegate a capability with additional restrictions
fn delegate(
&self,
capability: &Capability,
to: &Did,
restrictions: Constraints,
) -> Result<Capability, CapabilityError>;
/// Revoke a capability by ID
fn revoke(&self, capability_id: &CapabilityId) -> Result<(), CapabilityError>;
/// Check if a capability has been revoked
fn is_revoked(&self, capability_id: &CapabilityId) -> Result<bool, CapabilityError>;
}
#[derive(Debug)]
pub enum CapabilityError {
NotFound,
Expired,
Revoked,
Unauthorized,
InvalidDelegation,
ConstraintViolation(String),
Internal(String),
}
3.3 Bootstrap Sequence
The capability bootstrapping paradox (trust is an app, but apps need capabilities) is solved via genesis bootstrap:
/// Bootstrap phases for node startup
pub enum BootstrapPhase {
/// Phase 0: Kernel only, no apps loaded
/// - Node identity established
/// - Genesis capabilities issued to operator DID
/// - AllowAllOracle active
Genesis,
/// Phase 1: Core apps loading
/// - Trust app loads with genesis capabilities
/// - Trust app registers as PolicyOracle
/// - Other core apps load
CoreApps,
/// Phase 2: Normal operation
/// - PolicyOracle now active
/// - User apps can load
/// - Full capability checking enabled
Running,
}
/// Genesis capabilities issued to node operator
/// These CANNOT be revoked by apps
pub struct GenesisCapabilities {
/// Can install/uninstall apps
pub app_management: Capability,
/// Can issue capabilities to apps
pub capability_issuance: Capability,
/// Can configure node settings
pub node_admin: Capability,
}
3.4 Design Notes
- Bearer tokens for automation; holder-bound tokens for humans (require co-signature)
- Short expirations + refresh model because revocation lists don't scale globally
- Trust computation is an APP that implements PolicyOracle; kernel just enforces decisions
- Default oracle = AllowAllOracle for simple single-coop deployments
4. State Primitive
The State primitive provides namespaced, typed storage without domain semantics.
4.1 Contract
Provides:
- Namespaced storage:
/<org>/<app>/<sub>/ - Three storage types: Logs (append-only), Blobs (content-addressed), KV (mutable)
- Replication policies per namespace
- Cross-namespace access via capability only
Does NOT provide:
- Projections/materialized views (runtime layer)
- Search/indexing (runtime layer)
- Domain-specific schemas
- "Ledger" or "governance" data types
4.2 Trait Definitions
/// Append-only, ordered event streams
pub trait LogService: Send + Sync {
/// Create a new log in a namespace
fn create(
&self,
namespace: &Namespace,
name: &str,
schema: Option<SchemaRef>,
replication: ReplicationPolicy,
) -> Result<LogId, StateError>;
/// Append an event to a log
fn append(&self, log: &LogId, event: &[u8]) -> Result<Offset, StateError>;
/// Read events from a log
fn read(&self, log: &LogId, from: Offset, limit: usize) -> Result<Vec<LogEntry>, StateError>;
/// Get current log length
fn length(&self, log: &LogId) -> Result<Offset, StateError>;
/// Subscribe to new events
fn subscribe(&self, log: &LogId, from: Offset) -> Result<Subscription<LogEntry>, StateError>;
}
/// Content-addressed immutable blob storage
pub trait BlobService: Send + Sync {
/// Store a blob, returns content hash
fn put(&self, namespace: &Namespace, data: &[u8]) -> Result<Hash, StateError>;
/// Retrieve a blob by hash
fn get(&self, hash: &Hash) -> Result<Vec<u8>, StateError>;
/// Check if a blob exists
fn exists(&self, hash: &Hash) -> Result<bool, StateError>;
/// Delete a blob (if permitted by retention policy)
fn delete(&self, hash: &Hash) -> Result<(), StateError>;
}
/// Mutable key-value storage with optimistic concurrency
pub trait KvService: Send + Sync {
/// Get a value and its version
fn get(&self, namespace: &Namespace, key: &[u8]) -> Result<(Value, Version), StateError>;
/// Put a value with CAS (compare-and-swap)
fn put(
&self,
namespace: &Namespace,
key: &[u8],
value: &[u8],
expected_version: Version,
) -> Result<Version, StateError>;
/// Delete a key with CAS
fn delete(
&self,
namespace: &Namespace,
key: &[u8],
expected_version: Version,
) -> Result<(), StateError>;
/// List keys with a prefix
fn list(&self, namespace: &Namespace, prefix: &[u8]) -> Result<Vec<Key>, StateError>;
/// Watch for changes to a key
fn watch(&self, namespace: &Namespace, key: &[u8]) -> Result<Subscription<KvChange>, StateError>;
}
/// Log entry returned from reads
#[derive(Clone, Debug)]
pub struct LogEntry {
pub offset: Offset,
pub timestamp: LogicalTimestamp,
pub author: Did,
pub data: Vec<u8>,
}
/// KV change notification
#[derive(Clone, Debug)]
pub struct KvChange {
pub key: Key,
pub old_value: Option<Value>,
pub new_value: Option<Value>,
pub new_version: Version,
}
/// Replication policy for storage
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum ReplicationPolicy {
/// Single node, no replication
LocalOnly,
/// Consensus group, linearizable reads/writes
ClusterStrong,
/// Gossip/CRDT, eventually consistent
FederationEventual,
/// Durable archive, retained indefinitely
Archive,
}
/// Reference to a schema (for type checking)
#[derive(Clone, Debug)]
pub struct SchemaRef {
pub name: String,
pub version: String,
pub hash: Option<Hash>,
}
/// Unique log identifier
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LogId {
pub namespace: Namespace,
pub name: String,
}
#[derive(Debug)]
pub enum StateError {
NotFound,
AlreadyExists,
VersionMismatch { expected: Version, actual: Version },
NamespaceNotFound,
QuotaExceeded,
Unauthorized,
Internal(String),
}
4.3 Ordering Guarantees
- Logs are ordered per-writer by default (cheap, scalable)
- Total ordering requires consensus group (
ClusterStrong) - opt-in, expensive - Apps that need global order must explicitly request
ClusterStrongreplication
5. Compute Primitive
The Compute primitive provides WASM execution with metering and scheduling.
5.1 Contract
Provides:
- WASM execution with resource metering
- Two execution modes: deterministic and service
- Scheduling (immediate, triggered, periodic)
- Resource quotas per app
Does NOT provide:
- Domain-specific libraries
- Privileged execution for "official" apps
- Any interpretation of what code does
5.2 Trait Definitions
/// Compute execution engine
pub trait ComputeEngine: Send + Sync {
/// Execute deterministic computation (no I/O, reproducible)
fn execute_deterministic(
&self,
module: &WasmModule,
function: &str,
input: &[u8],
fuel: u64,
) -> Result<ExecutionResult, ComputeError>;
/// Start a long-running service
fn start_service(
&self,
module: &WasmModule,
config: ServiceConfig,
quota: ResourceQuota,
) -> Result<ServiceId, ComputeError>;
/// Stop a running service
fn stop_service(&self, id: &ServiceId) -> Result<(), ComputeError>;
/// Get service status
fn service_status(&self, id: &ServiceId) -> Result<ServiceStatus, ComputeError>;
/// Schedule a job
fn schedule(&self, job: Job, trigger: Trigger) -> Result<JobId, ComputeError>;
/// Cancel a scheduled job
fn cancel(&self, job_id: &JobId) -> Result<(), ComputeError>;
}
/// Result of deterministic execution
#[derive(Clone, Debug)]
pub struct ExecutionResult {
pub output: Vec<u8>,
pub fuel_consumed: u64,
pub logs: Vec<String>,
}
/// WASM module reference
#[derive(Clone, Debug)]
pub struct WasmModule {
pub hash: Hash,
pub abi: AbiVersion,
}
/// ABI version for WASM modules
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum AbiVersion {
/// Deterministic compute: no I/O, no wall clock, no randomness
/// Same input → same output, always. Can be replayed to verify.
/// Use for: reducers, validators, governance tallying
IcnDeterministicV1,
/// Service compute: capability-gated I/O allowed
/// Long-running processes with network/storage access.
/// Use for: indexers, bridges, WebSocket handlers
IcnServiceV1,
}
/// Configuration for a service
#[derive(Clone, Debug)]
pub struct ServiceConfig {
pub name: String,
pub entry_point: String,
pub environment: std::collections::HashMap<String, String>,
}
/// Resource quota for compute
#[derive(Clone, Debug)]
pub struct ResourceQuota {
pub max_memory_bytes: u64,
pub max_cpu_ms_per_second: u64,
pub max_storage_bytes: u64,
pub max_network_bytes_per_second: u64,
}
/// Service status
#[derive(Clone, Debug)]
pub enum ServiceStatus {
Starting,
Running { uptime_seconds: u64 },
Stopping,
Stopped,
Failed { error: String },
}
/// Job definition for scheduling
#[derive(Clone, Debug)]
pub struct Job {
pub module: WasmModule,
pub function: String,
pub input: Vec<u8>,
pub fuel_limit: u64,
}
/// Trigger for scheduled jobs
#[derive(Clone, Debug)]
pub enum Trigger {
/// Execute immediately
Immediate,
/// Execute when an event appears on a log
OnEvent { log: LogId, filter: Option<String> },
/// Execute at a specific logical time
AtTime { time: LogicalTimestamp },
/// Execute periodically
Every { interval: std::time::Duration },
}
#[derive(Debug)]
pub enum ComputeError {
ModuleNotFound,
FuelExhausted,
MemoryExceeded,
Timeout,
ServiceNotFound,
QuotaExceeded,
InvalidAbi,
ExecutionFailed(String),
Internal(String),
}
5.3 Design Notes
- ABIs are versioned and stable (like syscalls) - never break without major version
- Scheduling is at-least-once - exactly-once requires coordination pattern at app layer
- All apps use the same compute APIs - no privileged syscalls for "core" functionality
6. Communication Primitive
The Communication primitive provides messaging without message semantics.
6.1 Contract
Provides:
- Pub/sub with at-least-once delivery
- Request/response routing
- Long-lived bidirectional streams
- Adapter interface for external protocols
Does NOT provide:
- Message semantics (what messages mean)
- Moderation/filtering logic (apps via oracles)
- Exactly-once delivery (apps build this if needed)
6.2 Trait Definitions
/// Publish-subscribe messaging
pub trait PubSub: Send + Sync {
/// Publish a message to a topic
fn publish(&self, topic: &TopicId, message: &[u8]) -> Result<MessageId, CommsError>;
/// Subscribe to a topic
fn subscribe(&self, topic: &TopicId, from: Offset) -> Result<Subscription<Message>, CommsError>;
/// Unsubscribe from a topic
fn unsubscribe(&self, subscription: SubscriptionId) -> Result<(), CommsError>;
}
/// Request-response pattern
pub trait RequestResponse: Send + Sync {
/// Register a handler for a path
fn register(&self, path: &Path, handler: HandlerId) -> Result<(), CommsError>;
/// Unregister a handler
fn unregister(&self, path: &Path) -> Result<(), CommsError>;
/// Send a request and await response
fn request(
&self,
endpoint: &Endpoint,
request: &[u8],
timeout: std::time::Duration,
) -> Result<Vec<u8>, CommsError>;
}
/// Bidirectional streaming
pub trait Streams: Send + Sync {
/// Open a bidirectional stream to an endpoint
fn open(&self, endpoint: &Endpoint) -> Result<BidirectionalStream, CommsError>;
/// Accept incoming streams on a path
fn accept(&self, path: &Path) -> Result<Subscription<BidirectionalStream>, CommsError>;
}
/// External protocol adapters
pub trait Adapter: Send + Sync {
/// Send via external protocol (email, push, webhooks)
fn send(
&self,
protocol: Protocol,
destination: &str,
payload: &[u8],
) -> Result<(), CommsError>;
/// Receive from external protocol
fn receive(&self, protocol: Protocol) -> Result<Subscription<ExternalMessage>, CommsError>;
}
/// Message from pub/sub
#[derive(Clone, Debug)]
pub struct Message {
pub id: MessageId,
pub topic: TopicId,
pub author: Did,
pub timestamp: LogicalTimestamp,
pub data: Vec<u8>,
pub signature: Signature,
}
/// Topic identifier
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TopicId {
pub namespace: Namespace,
pub name: String,
}
/// Endpoint for request/response and streams
#[derive(Clone, Debug)]
pub struct Endpoint {
pub node: Option<NodeId>,
pub org: OrgId,
pub path: Path,
}
/// Path for routing
pub type Path = String;
/// Bidirectional stream handle
pub struct BidirectionalStream {
// Implementation details hidden
}
impl BidirectionalStream {
pub fn send(&self, data: &[u8]) -> Result<(), CommsError>;
pub fn recv(&self) -> Result<Vec<u8>, CommsError>;
pub fn close(&self) -> Result<(), CommsError>;
}
/// External protocol types
#[derive(Clone, Debug)]
pub enum Protocol {
Email,
WebPush,
Webhook { url: String },
Custom(String),
}
#[derive(Debug)]
pub enum CommsError {
TopicNotFound,
EndpointUnreachable,
Timeout,
StreamClosed,
Unauthorized,
Internal(String),
}
6.3 Ordering Guarantees
- Per-topic partition ordering (not global)
- Message IDs are stable and signed (can verify authenticity)
- Consumer offsets are explicit (client tracks position)
- Replay is first-class (projections can rebuild from any offset)
7. Time Primitive
The Time primitive provides logical clocks and scheduling.
7.1 Contract
Provides:
- Logical clocks for causal ordering
- Scheduling interface for deferred execution
- Leases for distributed coordination
Does NOT provide:
- Wall-clock truth (impossible in distributed systems)
- Calendar/timezone concepts
- "Vote ends at 5pm" semantics (app layer)
7.2 Trait Definitions
/// Time services
pub trait TimeService: Send + Sync {
/// Get current logical timestamp
fn now(&self) -> LogicalTimestamp;
/// Compare two timestamps (happens-before relationship)
fn compare(&self, a: LogicalTimestamp, b: LogicalTimestamp) -> Ordering;
/// Schedule a callback at a logical time
fn schedule_at(
&self,
time: LogicalTimestamp,
callback: CallbackId,
) -> Result<ScheduleId, TimeError>;
/// Schedule a callback after a duration
fn schedule_after(
&self,
duration: std::time::Duration,
callback: CallbackId,
) -> Result<ScheduleId, TimeError>;
/// Cancel a scheduled callback
fn cancel(&self, id: ScheduleId) -> Result<(), TimeError>;
/// Acquire a lease on a resource
fn acquire_lease(
&self,
resource: &str,
duration: std::time::Duration,
) -> Result<Lease, TimeError>;
/// Renew an existing lease
fn renew_lease(
&self,
lease: &Lease,
duration: std::time::Duration,
) -> Result<Lease, TimeError>;
/// Release a lease early
fn release_lease(&self, lease: Lease) -> Result<(), TimeError>;
}
/// Lease for distributed coordination
#[derive(Clone, Debug)]
pub struct Lease {
pub id: LeaseId,
pub resource: String,
pub holder: Did,
pub expires: LogicalTimestamp,
}
pub type LeaseId = String;
pub type ScheduleId = String;
pub type CallbackId = String;
#[derive(Debug)]
pub enum TimeError {
LeaseNotAvailable,
LeaseExpired,
LeaseNotHeld,
ScheduleNotFound,
Internal(String),
}
8. Coordination Primitive
The Coordination primitive provides consensus and CRDTs.
8.1 Contract
Provides:
- Scoped consensus groups (Raft-like)
- CRDT replication for eventual consistency
- Atomic compare-and-swap operations
- Leader election with leases
Does NOT provide:
- Global consensus (no global chain)
- Cross-scope transactions (app layer via sagas)
- Arbitrary merge functions (security risk)
8.2 Trait Definitions
/// Coordination services
pub trait Coordination: Send + Sync {
// === Consensus Groups ===
/// Create a consensus group with specified members
fn create_group(
&self,
members: Vec<NodeId>,
config: ConsensusConfig,
) -> Result<GroupId, CoordError>;
/// Propose a value to a consensus group
fn propose(&self, group: &GroupId, value: &[u8]) -> Result<Decision, CoordError>;
/// Get current group status
fn group_status(&self, group: &GroupId) -> Result<GroupStatus, CoordError>;
// === CRDTs ===
/// Create a CRDT in a namespace
fn create_crdt(
&self,
namespace: &Namespace,
name: &str,
crdt_type: CrdtType,
) -> Result<CrdtId, CoordError>;
/// Apply an operation to a CRDT
fn update_crdt(&self, id: &CrdtId, op: CrdtOp) -> Result<(), CoordError>;
/// Read current CRDT value
fn read_crdt(&self, id: &CrdtId) -> Result<CrdtValue, CoordError>;
// === Leader Election ===
/// Elect a leader for a group
fn elect_leader(
&self,
group: &GroupId,
lease_duration: std::time::Duration,
) -> Result<LeaderLease, CoordError>;
/// Check if we hold leadership
fn is_leader(&self, group: &GroupId) -> Result<bool, CoordError>;
}
/// Consensus group configuration
#[derive(Clone, Debug)]
pub struct ConsensusConfig {
pub min_quorum: usize,
pub election_timeout: std::time::Duration,
pub heartbeat_interval: std::time::Duration,
}
/// Consensus decision
#[derive(Clone, Debug)]
pub enum Decision {
Accepted { value: Vec<u8>, index: u64 },
Rejected { reason: String },
}
/// Group status
#[derive(Clone, Debug)]
pub struct GroupStatus {
pub leader: Option<NodeId>,
pub members: Vec<NodeId>,
pub commit_index: u64,
}
/// Supported CRDT types (finite set for security)
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum CrdtType {
/// Grow-only counter
GCounter,
/// Positive-negative counter
PNCounter,
/// Observed-remove set
OrSet,
/// Last-writer-wins register
LwwRegister,
}
/// CRDT operation
#[derive(Clone, Debug)]
pub enum CrdtOp {
Increment(u64),
Decrement(u64),
Add(Vec<u8>),
Remove(Vec<u8>),
Set(Vec<u8>),
}
/// CRDT value
#[derive(Clone, Debug)]
pub enum CrdtValue {
Counter(i64),
Set(Vec<Vec<u8>>),
Register(Vec<u8>),
}
#[derive(Debug)]
pub enum CoordError {
GroupNotFound,
NotLeader,
QuorumNotReached,
CrdtNotFound,
InvalidOperation,
Internal(String),
}
8.3 Critical Design Rule
Consensus is always scoped (per namespace, per dataset). No global chain. This prevents:
- Imperial scaling (one chain to rule them all)
- Single points of failure
- Unbounded coordination costs
9. Naming Primitive
The Naming primitive provides name resolution and service discovery.
9.1 Contract
Provides:
- Name records (name → target, signed by authority)
- Resolution within scopes (local, org, federation)
- Service discovery and announcement
- Watch for changes
Does NOT provide:
- Human-readable name policies
- Dispute resolution (just enforces signatures)
- Global namespace (scoped by design)
9.2 Trait Definitions
/// Name resolution service
pub trait NamingService: Send + Sync {
/// Register a name record
fn register(
&self,
name: &Name,
target: Target,
authority: &Did,
signature: &Signature,
ttl: std::time::Duration,
) -> Result<(), NamingError>;
/// Resolve a name to its target
fn resolve(&self, name: &Name) -> Result<ResolvedTarget, NamingError>;
/// Update an existing name record
fn update(
&self,
name: &Name,
new_target: Target,
signature: &Signature,
) -> Result<(), NamingError>;
/// Delete a name record
fn delete(&self, name: &Name, signature: &Signature) -> Result<(), NamingError>;
/// Watch for changes to a name
fn watch(&self, name: &Name) -> Result<Subscription<NameChange>, NamingError>;
/// List names under a prefix
fn list(&self, prefix: &Name, scope: Scope) -> Result<Vec<Name>, NamingError>;
}
/// Service discovery
pub trait Discovery: Send + Sync {
/// Announce a service
fn announce(
&self,
scope: Scope,
service_type: &str,
endpoint: Endpoint,
metadata: DiscoveryMetadata,
) -> Result<AnnouncementId, NamingError>;
/// Discover services of a type
fn discover(
&self,
scope: Scope,
service_type: &str,
) -> Result<Vec<DiscoveredService>, NamingError>;
/// Stop announcing a service
fn withdraw(&self, id: AnnouncementId) -> Result<(), NamingError>;
}
/// Hierarchical name (e.g., "/org-123/apps/ledger")
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Name(pub String);
impl Name {
pub fn new(path: impl Into<String>) -> Self {
Self(path.into())
}
pub fn join(&self, segment: &str) -> Self {
Self(format!("{}/{}", self.0, segment))
}
}
/// Target of a name record
#[derive(Clone, Debug)]
pub enum Target {
/// Service endpoint
Service { endpoint: Endpoint },
/// Content-addressed blob
Blob { hash: Hash },
/// Namespace reference
Namespace { ns: Namespace },
/// Alias to another name
Alias { name: Name },
}
/// Name record with metadata
#[derive(Clone, Debug)]
pub struct NameRecord {
pub name: Name,
pub target: Target,
pub authority: Did,
pub signature: Signature,
pub ttl: std::time::Duration,
pub created: LogicalTimestamp,
pub updated: LogicalTimestamp,
}
/// Resolved target with resolution path
#[derive(Clone, Debug)]
pub struct ResolvedTarget {
pub target: Target,
pub path: Vec<Name>, // Resolution path for debugging
pub cached: bool,
}
/// Resolution scope
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum Scope {
/// This node only
Local,
/// Within organization
Org,
/// Across federated orgs
Federation,
}
/// Metadata for service discovery
#[derive(Clone, Debug, Default)]
pub struct DiscoveryMetadata {
pub version: Option<String>,
pub protocols: Vec<String>,
pub capabilities: Vec<String>,
pub custom: std::collections::HashMap<String, String>,
}
/// Discovered service
#[derive(Clone, Debug)]
pub struct DiscoveredService {
pub endpoint: Endpoint,
pub metadata: DiscoveryMetadata,
pub announced_by: Did,
pub last_seen: LogicalTimestamp,
}
#[derive(Debug)]
pub enum NamingError {
NotFound,
AlreadyExists,
Unauthorized,
InvalidSignature,
ResolutionLoop,
Internal(String),
}
9.3 Why Naming Matters
Without naming, apps hardcode addresses. With naming:
- Apps compose via stable names that survive scaling, failover, and federation
- Service discovery enables dynamic topology
- Aliases enable gradual migrations
10. App Runtime Contract
This section defines how applications interact with kernel primitives.
10.1 App Manifest Schema (v0)
# Every app—official or third-party—uses this format
name: my-app
version: 1.0.0
publisher: did:icn:publisher-did
# What this app needs from other apps/kernel
capabilities_required:
- identity:read
- state:logs:append:self # Can append to own logs
- state:kv:write:self # Can write to own KV
- comms:publish:my-app:* # Can publish to own topics
# What this app provides to others
capabilities_provided:
- my-app:read
- my-app:write
- my-app:admin
# Dependencies on other apps
dependencies:
- app: trust
capabilities_needed:
- trust:query:score
required: true # App won't start without this
- app: membership
capabilities_needed:
- membership:read
required: false # Graceful degradation if unavailable
fallback:
strategy: cache_then_error
cache_ttl: 300
# Event types this app produces
event_types:
MyEvent:
fields:
foo: string
bar: integer
# State this app uses
state:
logs:
- name: events
schema: MyEvent
replication: cluster_strong
kv:
- name: cache
replication: local_only
blobs:
- name: attachments
replication: federation_eventual
# Compute handlers
handlers:
reducers: # Deterministic (icn_abi_v1)
- trigger: on_event(events)
module: reducer.wasm
services: # Long-running (icn_service_abi_v1)
- name: indexer
module: indexer.wasm
endpoints: # Request/response
- path: /query
module: query_handler.wasm
# Resource requirements
resources:
compute:
cpu_limit: 100ms_per_event
memory_limit: 64mb
storage:
log_retention: 90d
blob_quota: 1gb
# Scaling intent (not topology-specific)
placement:
replicas: 3
locality: org_scope
10.2 App Lifecycle
pub enum AppState {
/// Manifest loaded, not yet configured
Installed,
/// Capabilities granted, ready to start
Configured,
/// Handlers running, processing events
Running,
/// Temporarily stopped
Stopped,
/// Upgrade in progress
Upgrading { from: Version, to: Version },
/// Uninstall in progress
Uninstalling,
}
pub trait AppRuntime: Send + Sync {
/// Install an app from manifest
fn install(&self, manifest: AppManifest) -> Result<AppId, RuntimeError>;
/// Configure an installed app (grant capabilities)
fn configure(&self, app: &AppId, config: AppConfig) -> Result<(), RuntimeError>;
/// Start an app
fn start(&self, app: &AppId) -> Result<(), RuntimeError>;
/// Stop an app
fn stop(&self, app: &AppId) -> Result<(), RuntimeError>;
/// Upgrade an app to a new version
fn upgrade(&self, app: &AppId, new_manifest: AppManifest) -> Result<(), RuntimeError>;
/// Uninstall an app
fn uninstall(&self, app: &AppId) -> Result<(), RuntimeError>;
/// Get app state
fn state(&self, app: &AppId) -> Result<AppState, RuntimeError>;
}
10.3 Isolation Guarantees
- Namespace isolation: Each app gets
/<org>/<app>/ - Cross-namespace access: Requires explicit capability
- Resource quotas: Enforced per-app
- Private state: Apps cannot access other apps' private state
- Public streams: Apps CAN read other apps' public event streams (with capability)
10.4 Handler Contracts
/// Reducer (deterministic) handler contract
/// Called for each event on subscribed logs
pub trait Reducer {
/// Process an event, return state updates
/// MUST be deterministic: same input → same output
fn reduce(&self, event: &LogEntry) -> Result<Vec<StateUpdate>, ReducerError>;
}
/// Service handler contract
/// Long-running process with I/O capabilities
pub trait Service {
/// Initialize the service
fn init(&mut self, config: ServiceConfig) -> Result<(), ServiceError>;
/// Handle incoming request
fn handle(&mut self, request: &[u8]) -> Result<Vec<u8>, ServiceError>;
/// Periodic tick (for background work)
fn tick(&mut self) -> Result<(), ServiceError>;
/// Shutdown gracefully
fn shutdown(&mut self) -> Result<(), ServiceError>;
}
Appendix A: Capability Naming Convention
Standard capability format: <domain>:<resource>:<action>
Examples:
identity:read- Read any identity infostate:logs:append:self- Append to own logsstate:kv:write:/org-123/ledger/*- Write to specific namespacecomms:publish:my-app:*- Publish to own topicstrust:query:score- Query trust scoresgovernance:propose- Create proposals
Special scopes:
:self- Own namespace only:*- All resources in category:<path>- Specific resource path
Appendix B: Wire Format Versioning
All serialized formats include version information:
/// Envelope for all wire messages
pub struct WireEnvelope {
/// Format version
pub version: u8,
/// Message type
pub message_type: String,
/// Serialized payload
pub payload: Vec<u8>,
/// Signature over (version || message_type || payload)
pub signature: Signature,
}
Version compatibility:
- Readers MUST handle versions N, N-1, N-2
- Writers SHOULD use latest version
- Unknown versions MUST be rejected with clear error
Appendix C: Error Handling Guidelines
- Errors include blame: Caller, Kernel, or Adapter
- Errors indicate retryability: Can this be retried?
- Error codes are stable: Numeric codes don't change
- Messages are for humans: May change across versions
// Example error handling
match result {
Err(e) if e.retryable => {
// Retry with backoff
}
Err(e) if e.blame == Blame::Caller => {
// Invalid request, don't retry
}
Err(e) => {
// Log and escalate
}
Ok(v) => { /* proceed */ }
}
Document History
| Version | Date | Changes |
|---|---|---|
| 0.1.0 | 2025-01-25 | Initial draft |
This document is the constitutional foundation for ICN kernel development. All kernel code must conform to these contracts.