korium.io Docs
v0.8.38 Rust source ↗

Consumer guide · Korium 0.8.38

Build the application.
Let the fabric carry trust.

Korium gives Rust applications self-certifying identity, encrypted node transport, discovery, messaging, streams, pub-sub, tunnels, and optional service authority through one bounded runtime. Start with the interaction your application needs.

Cargo.toml
[dependencies]
korium = "0.8"
tokio = { version = "1", features = ["full"] }
anyhow = "1"

Requires stable Rust 1.97.1+ · Edition 2024 · default features: authz + mdns

01

Decision point

Choose the runtime posture first

There is no hidden protected-mode flag. The identity evidence supplied at build time determines admission behavior.

Default

DID-only node

Use for chat, collaboration, agent meshes, device networks, and other decentralized applications.

  • mTLS did:korium identity and key-bound PoW
  • Encrypted, bounded protocol handling
  • Your app owns contacts, invitations, membership, spam controls, and action policy
Node::builder().build().await?

Explicit evidence

Verified service authority

Use for APIs, gateways, CI/CD services, edge ingestion, and cross-organization service calls.

  • Everything in a DID-only node
  • Namespace-rooted exact-service binding and immutable source policy
  • Fail-closed bi-stream admission plus optional L7 invocation grants
.with_service_authority(namespace_roots, bundle)
If you need…Start withPrimary API
Small RPC callsDID-only or service-authorizedsend / incoming_requests
Custom or long-lived protocolsDID-only or service-authorizedopen_stream / incoming_streams
Many-to-many eventsDID-only nodesubscribe / publish
Local node discoveryDefault-feature nodestart_mdns / stop_mdns
Public-network introductionExplicit opt-in or pinned seedsbootstrap(true) / join
Stable service or resource namesEither postureannounce_* / lookup_*
Legacy TCP connectivityTunnel-enabled nodestart_tunnel_entry
DNS-native service lookupDNS-enabled node or gatewaystart_dns_listener_at
Service-scoped authorizationService-authorized nodeverify_action

02

Foundation

Configure one node

Construction starts the fabric plus bounded local mDNS browsing and advertising when the default mdns feature is compiled. Public DNS bootstrap remains off unless bootstrap(true) is selected; its attempt is best-effort, so lookup or route-validation failure does not prevent construction or local mDNS. Persist both the keypair and its mined identity proof when identity must survive restarts; restoring a key without its matching proof fails the build.

src/main.rs
use anyhow::Result;
use korium::Node;

#[tokio::main]
async fn main() -> Result<()> {
    let node = Node::builder()
        .bind_port(4433)
        .build()
        .await?;

    println!("DID: {}", node.self_did());
    println!("identity hex: {}", node.identity());
    println!("listening: {}", node.local_addr()?);

    tokio::signal::ctrl_c().await?;
    node.shutdown().await;
    Ok(())
}
DID and identity hex are different.

self_did() is the public did:korium URI. identity() is the 64-character routing identity expected by the node APIs.

bind_addr exact socket contact restore identity + proof mdns(false) disable automatic multicast stop_mdns() pause local discovery without dropping transport bootstrap(true) opt into public DNS hints bootstrap_route_validation_timeout bound the join wait tunnel enable tunnel exit

03

Scenario

Choose local, pinned, or public discovery

Default-feature library nodes browse and advertise Korium nodes over bounded IPv4 and IPv6 mDNS. Local discovery can stop and restart while transport sessions remain alive. Wider networks can use operator-pinned identities and literal socket addresses, or explicitly opt into bounded public DNS bootstrap. Every discovered address remains a reachability hint; the connection still authenticates the remote node with signed transport validation, mTLS, DID binding, and PoW.

Local mDNS modes
use korium::{MdnsMode, Node};

// Default: browse and advertise during construction.
let local_node = Node::builder().build().await?;

// Browse without advertising this node.
let browse_only = Node::builder()
    .mdns(false)
    .build()
    .await?;
browse_only.start_mdns(MdnsMode::Browse).await?;

// Stop multicast without disconnecting transport sessions, then restart
// in either supported mode.
browse_only.stop_mdns().await?;
browse_only
    .start_mdns(MdnsMode::BrowseAndAdvertise)
    .await?;
Opt-in public bootstrap
// Default is false; opt in independently of mDNS.
let public_node = Node::builder()
    .bootstrap(true)
    .build()
    .await?;
Public DNS supplies hints, never authority.

Opt-in bootstrap resolves bootstrap.korium.io TXT records through the system DNS configuration with a seven-second lookup timeout. Korium examines at most 16 records and eight literal endpoints per identity, trying returned seeds until one passes signed path validation. If lookup or every validation fails, construction continues with a warning and default local mDNS remains available. DNS grants no identity, routing, service, or application authority.

Explicit library introduction
let node = Node::builder()
    .mdns(false)
    .build()
    .await?;

node.join(
    "0123...64-hex-characters...cdef",
    &["203.0.113.10:4433".to_string()],
).await?;

node.connect("0123...64-hex-characters...cdef").await?;
Explicit CLI seeds
# /etc/korium/seeds.toml
[seed.primary]
identity = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
endpoints = ["203.0.113.10:4433", "[2001:db8::10]:4433"]
required = true

# No seed file is loaded unless its path is explicit.
korium --seeds-file /etc/korium/seeds.toml

# A repeatable best-effort alternative:
korium --join 203.0.113.10:4433/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

# Or opt into bounded public DNS hints:
korium --bootstrap
mDNS discovers a route, not a trusted node.

Korium admits mDNS traffic only from UDP source port 5353 at the expected multicast destinations, then validates the advertised route before PoW-valid mTLS. IPv6 link scope is retained for connection attempts; scoped link-local routes never become remembered bootstrap seeds.

stop_mdns().await cooperatively waits for the active standalone or combined mDNS listener to release its sockets, without shutting down transport sessions. A later start_mdns may select browse-only or browse-and-advertise mode. Stopping a combined DNS/mDNS listener also stops its regular-DNS socket, so restart the combined listener when both services are required.

Korium handles

Identity verification, path validation, encrypted transport, bounded routing, and discovery records.

Your app handles

Who should be contacted, invitations and blocklists, node roles, and user-facing trust decisions.

04

Scenario

Request / response

Use PLAIN request streams for bounded request/response work. Requests are capped at 64 KiB; callers always provide their own end-to-end timeout.

Caller
use std::time::Duration;

let reply = node.send(
    &remote_identity_hex,
    br#"{"op":"status"}"#.to_vec(),
    Duration::from_secs(5),
).await?;
Async server
let mut requests = node.incoming_requests().await?;

while let Some((from, pubkey, body, ctx, reply)) = requests.recv().await {
    // Inspect ctx and authorize before changing state.
    let response = handle(from, pubkey, body, ctx).await;
    let _ = reply.send(response);
}

incoming_requests is take-once and is the only request-serving API; the synchronous set_request_handler shortcut was removed in 0.8.28. recv() yields the five-field IncomingRequest tuple; inspect the context before handling the body, then send the response through the returned channel. Own the receiving task, and keep draining it.

05

Scenario

Raw bidirectional streams

Use RAW streams for custom framing, file transfer, interactive sessions, or protocols that need full duplex. Korium authenticates and bounds stream admission; your protocol owns framing and payload limits.

Open and accept
use tokio::io::{AsyncReadExt, AsyncWriteExt};

// Caller
let (mut send, mut recv) = node.open_stream(&remote_identity_hex).await?;
send.write_all(b"hello").await?;
send.finish()?;
let reply = recv.read_to_end(64 * 1024).await?;

// Receiver: take this once, then keep draining it.
let mut incoming = node.incoming_streams().await?;
while let Some((from, mut stream)) = incoming.recv().await {
    if stream.kind() != IncomingStreamKind::Raw { continue; }
    let body = stream.recv.read_to_end(64 * 1024).await?;
    stream.send.write_all(&process(from, body)).await?;
    stream.send.shutdown().await?;
}
Keep the guard alive.

GuardedRawStream owns the per-node admission permit. Do not discard it while retaining only detached I/O handles unless you also retain the guard returned by into_parts().

06

Scenario

Signed pub-sub

Use GossipSub for many-to-many events where duplicate, delayed, or reordered delivery is acceptable. Messages are signed and replay-bounded; publication does not grant application membership.

Subscribe and publish
node.subscribe("events/alerts").await?;
let mut messages = node.messages().await?; // take once

node.publish(
    "events/alerts",
    b"system update available".to_vec(),
).await?;

while let Some(message) = messages.recv().await {
    consume(message).await?;
}
Good fit

Presence, invalidation, event fan-out, replicated notifications, and eventually consistent signals.

Use RPC instead

Commands that require a direct result, request-specific authorization, or business-level success semantics.

07

Scenario

Discover stable services

Service URNs stay stable while node identities rotate. Exact-service announcements publish signed, expiring liveness; optional namespace memberships provide a separate, bounded directory of service names. Neither lookup grants authority.

Service liveness
let service = "urn:korium:acme/payments";

// Serving node
node.announce_service(service).await?;

// Consumer
for (identity, addresses) in node.lookup_service_endpoints(service).await? {
    println!("{} at {:?}", identity.to_hex(), addresses);
}
Namespace membership
use korium::NamespaceDiscoveryTrust;

let discovery = NamespaceDiscoveryTrust::new(
    "urn:korium:acme",
    namespace_discovery_root,
)?;

for member in node.lookup_namespace(&discovery).await? {
    println!("{} expires at {}", member.service_urn, member.expires_unix_seconds);
    let instances = node.lookup_service(&member.service_urn).await?;
    // Authenticate each instance; membership is discovery evidence only.
    println!("{} live candidates", instances.len());
}
Discovery is not authorization.

A fresh service record proves which node published the liveness hint. A namespace membership proves only that a service URN was listed by the trusted discovery root. Neither proves permission, health, or network authority; connect and enforce the appropriate exact-service and application policy.

NamespaceDiscoveryTrust is request-scoped and may accept a primary plus bounded retiring discovery root. There is no public announce_namespace operation: a service receives a signed NamespaceServiceMembershipSpec through with_namespace_service_membership, then announce_service publishes the membership beside exact service liveness.

08

Scenario

Locate resources without a registry

Publish exact resources or bounded parent prefixes under a namespace or service URN. Consumers always query an exact resource and receive bounded fresh candidates.

Resource discovery
use korium::ResourceSelector;

let context = "urn:korium:acme/storage";

node.announce_resource(
    context,
    ResourceSelector::prefix("repos/project"),
).await?;

if let Some(found) = node
    .lookup_resource(context, "repos/project/readme")
    .await?
{
    for candidate in found.candidates {
        node.connect(&candidate.identity.to_hex()).await?;
    }
}
  • Resource strings are canonical printable ASCII and capped at 1024 bytes.
  • No list-all, wildcard, regex, glob, or prefix-enumeration lookup exists.
  • Use opaque, keyed, or tenant-scoped names when discovery subjects are sensitive.
  • unannounce_resource stops republishing; old records expire naturally.

09

Scenario

Bridge a TCP application

TUNNEL lets a local TCP client reach an in-process stream handler on a destination node. The entry tries native TLS 1.3 mTLS/TCP first and falls back to a QUIC TUNNEL stream through the fabric.

Destination and entry
use std::net::SocketAddr;
use korium::{IncomingStreamKind, Node};

// Destination: enable the exit and consume Tunnel streams.
let destination = Node::builder().tunnel().build().await?;
let mut incoming = destination.incoming_streams().await?;

// Entry: bind a local port for the legacy TCP client.
let entry = Node::new().await?;
let listen: SocketAddr = "127.0.0.1:13306".parse()?;
let (handle, shutdown) = entry
    .start_tunnel_entry(listen, &destination_identity_hex)
    .await?;

// Later: stop accepting and await clean termination.
shutdown.notify_one();
handle.await??;
Destination contract

Drain incoming_streams, select IncomingStreamKind::Tunnel, and map each accepted stream to local application state.

Operational bounds

256 concurrent sessions globally, 16 streams per remote node, 5-minute per-direction idle timeout, and a 1-hour session cap.

10

Kubernetes scenario

Expose Korium discovery through DNS

The regular DNS shim lets DNS-native workloads consume Korium identity and service discovery without embedding the Rust API. It is an authoritative, non-recursive UDP listener backed by the node's bounded DHT lookups—not a replacement for CoreDNS, a general-purpose resolver, or Korium's separate local-node mDNS service.

Pod-local

Sidecar resolver

Bind to loopback and configure one application or a local forwarding proxy to query port 5353.

  • Smallest network exposure
  • Korium identity and lifecycle stay with the workload
  • Best when the client supports a custom DNS server and port
--dns 127.0.0.1:5353

Namespace or cluster

DNS gateway

Run a persistent Korium node behind an internal UDP Service and query it explicitly.

  • One stable Kubernetes service address
  • Shared DHT view and bounded lookup concurrency
  • Keep access internal and enforce network-level source controls
--dns 0.0.0.0:5353
Build and run
# Default binary: CLI, regular DNS, mDNS, and authz.
cargo build --release --bin korium --features cli,dns,mdns

# Regular DNS defaults to loopback:5353 when --dns is omitted.
# CLI mDNS and public DNS bootstrap are separate explicit choices.
korium \
  --port 4433 \
  --dns 127.0.0.1:5353 \
  --mdns advertise \
  --bootstrap \
  --identity-file /var/lib/korium/identity
Library embedding
use std::net::SocketAddr;
use korium::MdnsMode;

// Reserve one lifecycle for separate regular-DNS and mDNS sockets.
let node = Node::builder().mdns(false).build().await?;

// On Unix: regular DNS on loopback:5353 plus local browsing/advertising.
let local_dns = node
    .start_default_dns_listener_with_mdns(MdnsMode::BrowseAndAdvertise)
    .await?;

// A regular-DNS-only gateway may use an explicit controlled address.
let gateway = Node::builder().mdns(false).build().await?;
let bind: SocketAddr = "0.0.0.0:5353".parse()?;
let gateway_dns = gateway.start_dns_listener_at(bind).await?;

Query service URNs

A single-segment service URN maps deterministically to a DNS owner. For example, urn:korium:acme.example/payments becomes _korium._udp.u1.acme.example._svc.payments.svc.

DNS client
# Ask the Kubernetes Service explicitly; do not replace cluster DNS.
dig @korium-dns.platform.svc.cluster.local \
  _korium._udp.u1.acme.example._svc.payments.svc. SRV

dig @korium-dns.platform.svc.cluster.local \
  _korium._udp.u1.acme.example._svc.payments.svc. SVCB
RecordConsumer useReturned evidence
SRVConnect to a node or stable servicePort, generated target, and A/AAAA additionals
SVCBDiscover Korium transport parametersALPN, port, IPv4 and IPv6 hints
TXT …contactFetch a node contact recordBounded postcard + base64url signed contact
TXT …didRead the self-certifying node URIdid=did:korium:…

Kubernetes gateway

The following skeleton keeps the Korium identity on persistent storage and publishes DNS only through an internal UDP Service. Replace the image and storage class for your environment.

korium-dns.yaml
apiVersion: v1
kind: Service
metadata:
  name: korium-dns
  namespace: platform
spec:
  selector:
    app: korium-dns
  ports:
    - name: dns
      port: 53
      targetPort: dns
      protocol: UDP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: korium-dns
  namespace: platform
spec:
  serviceName: korium-dns
  replicas: 1
  selector:
    matchLabels:
      app: korium-dns
  template:
    metadata:
      labels:
        app: korium-dns
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        fsGroupChangePolicy: OnRootMismatch
      containers:
        - name: korium
          image: registry.example/korium:0.8.38
          args:
            - --port
            - "4433"
            - --dns
            - 0.0.0.0:5353
            - --join
            - 203.0.113.10:4433/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
            - --identity-file
            - /var/lib/korium/identity
          ports:
            - name: fabric
              containerPort: 4433
              protocol: UDP
            - name: dns
              containerPort: 5353
              protocol: UDP
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: identity
              mountPath: /var/lib/korium
  volumeClaimTemplates:
    - metadata:
        name: identity
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi
Keep Kubernetes DNS and Korium DNS separate.

Query the gateway explicitly or configure a narrowly scoped local forwarder. Do not replace the Pod's normal nameserver or forward the broad .svc suffix: Kubernetes already owns *.svc.<cluster-domain>.

Same-port DNS and mDNS require an exact unicast DNS bind.

On Unix, the combined API can isolate regular DNS and multicast mDNS on UDP 5353 only when the regular DNS address is a specific unicast address such as 127.0.0.1. A wildcard gateway bind such as 0.0.0.0:5353 must remain regular-DNS-only; other platforms must place regular DNS on a different port when mDNS is active.

  • External DNS exposure requires an explicit non-loopback bind plus source-spoofing and rate controls.
  • The listener is UDP-only, authoritative, non-recursive, and accepts exactly one IN-class question per packet.
  • Regular DNS and mDNS reject oversized headers, malformed structure, unsupported record surfaces, and wrong destination or source-port semantics before deeper processing.
  • Responses are capped at 1232 bytes, in-flight queries at 64, and query work at 2 seconds.
  • Records use a 15-second TTL; service lookup gets a bounded 1.5-second DHT budget.
  • DNS output remains discovery evidence, not service authority or authorization. Korium mTLS and application policy still apply.
  • Persist the identity file with mode 0600. A new identity changes the gateway's node identity and requires new PoW.

11

Scenario

Run a service with namespace-rooted authority

A service-authorized node presents a bounded, offline-authored chain from a configured NamespaceAuthorityTrust to one exact service URN, an immutable service policy, a policy-bound node issuer, and the node's terminal mTLS key. Build fails if the chain is malformed, outside its scope or validity, changes policy, or binds a different keypair.

Author the service chain and policy
use korium::{
    authoring::{
        ExactServiceDelegationSpec, LeaseTerms, NamespaceAuthorityChain,
        NamespaceAuthorityKey, NamespaceAuthorityScope,
        PolicyBoundNodeIssuerGrantSpec, PolicyBoundNodeIssuerKey,
        ServiceAuthorityBundleSpec,
    },
    NamespaceAuthorityTrust,
};

let namespace_trust = NamespaceAuthorityTrust::new(
    "urn:korium:acme",
    namespace_root.public_bytes(),
    4,
)?;
let exact_service_chain = NamespaceAuthorityChain::root(&namespace_trust)?
    .delegate_exact_service(
        &namespace_trust,
        &namespace_root,
        ExactServiceDelegationSpec {
            child_public_key: service_issuer.public_bytes(),
            scope: NamespaceAuthorityScope::exact_service(
                "urn:korium:acme/payments",
            )?,
            terms: service_issuer_terms,
            nonce: exact_service_nonce,
        },
    )?;

let node_issuer_grant = PolicyBoundNodeIssuerGrantSpec {
    namespace_trust: namespace_trust.clone(),
    exact_service_chain,
    node_issuer_public_key: node_issuer.public_bytes(),
    node_issuer_terms,
    node_issuer_nonce,
    policy_terms,
    allow_sources,
    grant_issuers,
}
.sign(&service_issuer)?;

let signed_bundle = ServiceAuthorityBundleSpec {
    namespace_trust: namespace_trust.clone(),
    node_issuer_grant,
    node_public_key: node_key.public_key_bytes(),
    node_terms,
    node_nonce,
}
.sign(&node_issuer)?;

The root, subtree, and exact-service issuer keys are authoring-only. The policy-bound node issuer belongs in an isolated recovery or control-plane holder; it can issue terminal bindings only for the committed service policy.

Enable Korium's authoring feature in the offline control-plane tool that creates these artifacts; ordinary serving nodes need only the signed bundle and configured namespace roots.

Every entry in allow_sources must belong to an explicitly configured namespace authority. Namespace equality and discovery membership never create network trust; configure one NamespaceAuthorityTrust for each namespace that may authorize a source.

Service admission + L7 grant
let service = Node::builder()
    .contact(persisted_keypair, persisted_pow)
    .with_service_authority(vec![namespace_trust], signed_bundle)
    .build()
    .await?;

assert_eq!(service.service_urn(), Some("urn:korium:acme/payments"));

let mut requests = service.incoming_requests().await?;
while let Some((_from, _pubkey, body, ctx, reply)) = requests.recv().await {
    let request = parse_request(body)?;
    let grant = service.verify_action(
        &ctx,
        request.bearer_token.as_deref(),
        "payment.capture",
        &format!("payments/{}", request.payment_id),
    )?;

    let _ = reply.send(capture(request, grant).await?);
}
1mTLSCaller controls its DID key
2Namespace-rooted chainIssuer links reach one exact URN
3L4 admissionConfigured roots and source policy apply
4L7 grantIssuer, audience, service, action, resource, expiry
The consumer defines authorization semantics; namespace membership does not authorize a service.

A NamespaceDiscoveryKey only signs membership records. A NamespaceAuthorityKey roots service authority, and the two domains are separate. Korium defines no action vocabulary, so canonicalize the resource before calling verify_action. L7 Biscuit grants remain single-issuer, single-audience, terminal, non-delegating, and capped at 300 seconds.

Build bounded issuer links

Use subtree delegation when one control plane should administer a bounded namespace or service subtree. Use exact-service delegation for a single service. Scope is parsed by URN segments, never by globs or byte prefixes, and every link carries its own validity, nonce, predecessor digest, signature domain, and remaining depth.

Optional subtree issuer
use korium::authoring::{
    NamespaceSubtreeDelegationSpec, NamespaceAuthorityScope,
};

let subtree_chain = NamespaceAuthorityChain::root(&namespace_trust)?
    .delegate_subtree(
        &namespace_trust,
        &namespace_root,
        NamespaceSubtreeDelegationSpec {
            child_public_key: platform_issuer.public_bytes(),
            scope: NamespaceAuthorityScope::subtree(
                "urn:korium:acme/platform",
            )?,
            terms: subtree_terms,
            nonce: subtree_nonce,
            remaining_depth: 1,
        },
    )?;

An exact-service issuer can create the policy-bound node-issuer grant, but the grant commits the exact service URN, allow_sources, and grant_issuers. The node issuer cannot widen scope or change policy.

Redeploy with a fresh terminal binding

Keep the stable service URN and the authoring-only issuer chain. The isolated policy-bound node issuer can sign a new short-lived ServiceAuthorityBundleSpec for a replacement or overlapping replica DID without entering the serving node.

Replacement node integration
let replacement_bundle = ServiceAuthorityBundleSpec {
    namespace_trust: namespace_trust.clone(),
    node_issuer_grant: node_issuer_grant.clone(),
    node_public_key: replacement_key.public_key_bytes(),
    node_terms: LeaseTerms {
        not_before_unix_seconds: now,
        expires_unix_seconds: now + 3_600,
    },
    node_nonce: replacement_nonce,
}
.sign(&node_issuer)?;

let replacement = Node::builder()
    .contact(replacement_key, replacement_pow)
    .with_service_authority(vec![namespace_trust], replacement_bundle)
    .build()
    .await?;
  • Terminal node bindings are short-lived and capped by the enclosing chain, policy, node issuer, and certificate validity.
  • Overlapping replacement or replica DIDs are valid while each has terminal-key possession and an unexpired chain.
  • A node-issuer compromise is bounded by its lease; it cannot change the service URN, source allowlist, or grant-issuer policy.
  • Replacing a compromised namespace or delegated issuer is a deployment-configured hard trust reset. Stop renewal, replace the pinned trust and affected chains, and terminate local sessions when immediate containment is required.
  • There is no live service-root rollover, epoch, dual-root state, or implicit authority checkpoint. Namespace discovery root rotation remains independent and affects only lookup_namespace.

12

Consumer contract

Know what the proof means

Authenticated node

Key possession

The live node controls the private key bound to its did:korium identity. This says nothing about permission or behavior.

Discovery record

Signed hint

The named node published fresh, bounded reachability or liveness data. The data is not membership or authority.

Service authority

Right to speak as one URN

A bounded chain from a configured NamespaceAuthorityTrust binds this node key to one exact service URN and an immutable source policy until the earliest expiry in the chain.

Invocation grant

Exact authorized action

An approved issuer granted one audience an action on a canonical resource for one service until a short deadline. The terminal L7 Biscuit does not authorize further grants.

Design for these realities

  • Delivery can be duplicated, delayed, replayed, reordered, or lost. Make application operations idempotent where needed.
  • Live transport does not depend on remote clock claims. Signed records and credentials use bounded local Unix-UTC validity checks.
  • There is no online per-node revocation. Normal redeployment uses the existing policy-bound node issuer to issue a fresh terminal binding; a compromised namespace or delegated issuer requires a deployment-configured hard trust reset. Lease expiry is a backstop, not instant removal.
  • Confidentiality is pinned to X25519 + ML-KEM-768 as the only offered group over TLS 1.3 on QUIC and direct TCP; there is no classical downgrade path. Identity, authority, and capability signatures remain Ed25519.
  • 0.8.34 hardening remains part of 0.8.38: Ed25519 public keys of small order, non-Ed25519 certificate key algorithms, and ambiguous authority encodings are rejected.
  • 0.8.38 validates DNS and mDNS packet shape before wire decoding, bounds retained name-service tasks, and requires authenticated route admission before an mDNS candidate enters the fabric.
  • Public bootstrap is default-off and treats system-DNS TXT answers as bounded, untrusted hints. A seed is accepted only after signed route validation; lookup or validation failure warns and allows construction to continue with local mDNS.
  • Audit events are structured where verified context exists; collection, retention, timestamps, integrity, and complete denial coverage are deployment responsibilities.

Normative detail: Security invariants ↗

13

Operate

Own lifecycle and observe pressure

Keep the node alive for as long as its receivers and sessions should run. Use stop_mdns().await when only local discovery should pause; transport sessions stay alive and mDNS can restart after its sockets are released. Prefer explicit shutdown().await for terminal node shutdown: it stops the active DNS/mDNS lifecycle before draining owned runtime work and cannot be reversed. Dropping a node aborts remaining work as a last-resort safety boundary.

Telemetry
let t = node.telemetry().await?;

metrics.gauge("korium.connected_peers", t.connected_peers);
metrics.gauge("korium.dht.pressure", t.pressure);
metrics.count("korium.transport.errors", t.transport_errors);
metrics.count("korium.transport.datagram_deliver_drops", t.transport_datagram_deliver_drops);
metrics.count("korium.transport.datagram_unknown_addr_drops", t.transport_datagram_unknown_addr_drops);
metrics.count("korium.transport.control_queue_drops", t.transport_control_queue_drops);
metrics.count("korium.gossip.delivery_drops", t.gossipsub_delivery_drops);
metrics.gauge("korium.tunnel.active", t.tunnel_active_sessions);

node.shutdown().await;
01

Drain take-once receivers continuously; backpressure and channel limits are intentional.

02

Persist the keypair and matching PoW proof together when identity continuity matters.

03

Use application deadlines around work beyond Korium’s built-in transport bounds.

04

Renew service authority and grants before expiry; never treat timeouts as consensus.

05

Alert on pressure, replay-capacity drops, transport errors, datagram and control-queue drops, and sustained tunnel saturation.

14

Reference

Public API map

Identity & node

Node NodeBuilder Identity Keypair IdentityProof Contact

Requests & streams

IncomingRequest IncomingRequests RequestContext GuardedRawStream IncomingStreamKind

Discovery

MdnsMode start_mdns stop_mdns ResourceSelector ResourceLookup ResourceCandidate ResourceMatchKind

Authorization

PeerDid VerifiedGrant VerifiedToken VerifiedAppClaims VerifiedServiceBinding VerifiedNamespaceService

Operations

TelemetrySnapshot TopicPeerInfo Message

Offline authoring

NamespaceAuthorityKey NamespaceDiscoveryKey PolicyBoundNodeIssuerKey LeaseTerms NamespaceAuthorityScope NamespaceAuthorityChain NamespaceSubtreeDelegationSpec ExactServiceDelegationSpec PolicyBoundNodeIssuerGrantSpec PolicyBoundNodeIssuerGrant ServiceAuthorityBundleSpec NamespaceServiceMembershipSpec — requires authoring

These references follow the immutable v0.8.38 tagged source, including restartable authenticated local mDNS, best-effort default-off bounded public DNS bootstrap, explicit pinned introductions, namespace-rooted exact-service authority, bounded issuer chains, the tuple request API, and transport telemetry fields in this release.