Merge route continuity C1: local route proof in the TypeScript Idunn runtime

The runtime splits into a signer (load, sign, answer a route challenge
locally, no Odin, no network) and an optional Odin presence publisher for
CultMesh-aware services. The signer owns health (reportHealth, starting at
warming), records every warming it signs as a lease target, allows one
signer per frozen authority, and ships a subpath export that does not
load the publisher. Rust judges TS output: committed vectors from
examples/idunn_runtime_fixture.rs, verified by the Rust authenticator and
correlator, including a stateful lease-bound set.

Soul passed it after two fix batches. cultnet-ts has 113/113 tests on
node:24. StreamPixels' web and service call sites move in C2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 03:27:19 +02:00
commit 30ee8b97ab
38 changed files with 1097 additions and 447 deletions

View file

@ -0,0 +1,359 @@
//! Generate the Idunn runtime fixtures that cultnet-ts is tested against.
//!
//! The Rust owner builds every input: real enrolled identities, an Expected that
//! passes `validate`, an Activation issued by `IdunnRuntimeActivationLaunch`, the
//! bundle files written through `SingleFileMessagePackBackingStore`, and
//! runtime-presence vectors signed by the Rust signers. Each vector is also run
//! through `authenticate_runtime_presence_claim` and
//! `correlate_runtime_presence_claim` before it is written, so the bytes
//! committed to the TypeScript tests are ones this verifier accepted (or, for
//! the low-capacity vector, ones it correlates to the typed capacity
//! disagreement). Ed25519 is deterministic, so the TypeScript signer must
//! reproduce each vector byte for byte from the same fields.
//!
//! The `service-stateful` set is the lease-bound shape: Expected carries a state
//! lineage and requires a write lease, and Rust issues `lease.cc` naming the
//! warming vector's digest. Its warming vector is sequence 1; the active and
//! degraded vectors are sequence 2 and bind to that lease. Two further leases are
//! deliberately wrong (`lease-other-warming.cc` names a different warming digest,
//! `lease-other-target.cc` names another target) for the TypeScript refusal tests.
//!
//! Linux only: the provider identity is bound to `/etc/machine-id`, and the
//! generated `machine-id` file records the value the identities were bound to.
//!
//! Usage: idunn_runtime_fixture <out-dir>
use std::fs;
use std::path::Path;
use anyhow::{Result, bail, ensure};
use cultcache_rs::{
CacheBackingStore, CultCacheEnvelope, DatabaseEntry, SingleFileMessagePackBackingStore,
};
use cultnet_rs::{
GameCultProviderHealthIdentity, GameCultRuntimeCapability,
GameCultRuntimePresenceHealthPurpose, GameCultRuntimePresenceHealthRecord,
IDUNN_EXPECTED_INCARNATION_SCHEMA, IDUNN_PROCESS_WRITE_LEASE_SCHEMA,
IDUNN_RUNTIME_ACTIVATION_SCHEMA, IdunnExpectedCapability, IdunnExpectedDependency,
IdunnExpectedIncarnationRecord, IdunnExpectedRoute, IdunnProcessWriteLeaseRecord,
IdunnRuntimeActivationLaunch, IdunnRuntimeActivationRecord,
IdunnRuntimeActivationSigner, IdunnServiceIdentity, RuntimePresenceAuthenticationContext,
authenticate_runtime_presence_claim, correlate_runtime_presence_claim,
enroll_service_identity_at, verify_runtime_authority,
};
/// The TypeScript tests pin the same values; drift shows up as a byte mismatch.
const OBSERVED_AT: u64 = 1_790_000_000_000;
const ISSUED_AT: u64 = OBSERVED_AT - 5_000;
const SEQUENCE: u64 = 1;
const DETAIL: &str = "route-observation:route-challenge-1";
const STORED_AT: &str = "2026-01-01T00:00:00.000Z";
struct Set {
dir: &'static str,
target: &'static str,
transport: &'static str,
stable: &'static str,
candidate: &'static str,
minimum_capacity: u32,
odin_dependency: bool,
stateful: bool,
}
const SETS: [Set; 4] = [
Set {
dir: "web",
target: "streampixels-web",
transport: "http",
stable: "http://127.0.0.1:8830",
candidate: "http://127.0.0.1:18830",
minimum_capacity: 1,
odin_dependency: false,
stateful: false,
},
Set {
dir: "service",
target: "streampixels-service",
transport: "tcp",
stable: "tcp://127.0.0.1:8831",
candidate: "tcp://127.0.0.1:18831",
minimum_capacity: 2,
odin_dependency: true,
stateful: false,
},
Set {
dir: "service-stateful",
target: "streampixels-service",
transport: "tcp",
stable: "tcp://127.0.0.1:8833",
candidate: "tcp://127.0.0.1:18833",
minimum_capacity: 2,
odin_dependency: true,
stateful: true,
},
Set {
dir: "rudp-route",
target: "streampixels-rudp",
transport: "rudp",
stable: "rudp://127.0.0.1:8832",
candidate: "rudp://127.0.0.1:18832",
minimum_capacity: 1,
odin_dependency: false,
stateful: false,
},
];
fn main() -> Result<()> {
let Some(out) = std::env::args().nth(1) else {
bail!("usage: idunn_runtime_fixture <out-dir>");
};
let machine_id = fs::read_to_string("/etc/machine-id")?;
fs::create_dir_all(&out)?;
fs::write(Path::new(&out).join("machine-id"), machine_id.trim())?;
let idunn = enroll_service_identity_at::<IdunnServiceIdentity>(
&Path::new(&out).join("idunn-identity.private"),
)?;
let idunn_anchor = idunn.trust_anchor()?;
fs::remove_file(Path::new(&out).join("idunn-identity.private"))?;
let _ = fs::remove_file(Path::new(&out).join("idunn-identity.private.lock"));
for set in &SETS {
build(Path::new(&out).join(set.dir).as_path(), set, &idunn, &idunn_anchor)?;
}
Ok(())
}
fn digest(byte: char) -> String {
format!("sha256-{}", byte.to_string().repeat(64))
}
fn build(
dir: &Path,
set: &Set,
idunn: &cultnet_rs::ServiceIdentitySigner<IdunnServiceIdentity>,
idunn_anchor: &cultnet_rs::ServiceIdentityTrustAnchor,
) -> Result<()> {
fs::create_dir_all(dir)?;
let provider_path = dir.join("provider-identity.credential");
let provider = enroll_service_identity_at::<GameCultProviderHealthIdentity>(&provider_path)?;
let provider_anchor = provider.trust_anchor()?;
let _ = fs::remove_file(dir.join("provider-identity.credential.lock"));
let capability = format!("{}.api", set.target);
let capability_schema = format!("{}.api.v1", set.target);
let expected = IdunnExpectedIncarnationRecord {
schema_version: IDUNN_EXPECTED_INCARNATION_SCHEMA.into(),
target: set.target.into(),
plan_id: digest('1'),
incarnation_id: format!("{}/incarnation-1", set.target),
sealed_release_id: digest('2'),
source_repository: "github.com/GameCult/StreamPixels".into(),
source_revision: "a".repeat(40),
recipe_sha256: digest('3'),
runtime_id: format!("{}-yggdrasil", set.target),
expected_signer_identity_id: provider_anchor.identity_id.clone(),
health_contract: format!("{}.runtime-health", set.target),
artifact_sha256: digest('4'),
state_schema_generation: set.stateful.then(|| "streampixels-state-v1".to_string()),
state_contract_sha256: set.stateful.then(|| digest('7')),
write_lease_required: set.stateful,
route: Some(IdunnExpectedRoute {
route_id: format!("{}-route", set.target),
transport: set.transport.into(),
stable_endpoint: set.stable.into(),
candidate_endpoint: set.candidate.into(),
}),
capabilities: vec![IdunnExpectedCapability {
capability: capability.clone(),
schema: capability_schema.clone(),
compatibility: "v1".into(),
minimum_capacity: set.minimum_capacity,
}],
dependencies: if set.odin_dependency {
vec![IdunnExpectedDependency {
kind: "shared-infrastructure".into(),
capability: "odin.verse-rendezvous".into(),
schema: "odin.verse-topology.v1".into(),
compatibility: "v1".into(),
minimum_capacity: 1,
startup: "before-promotion".into(),
provider_id: Some("odin".into()),
provider_authority: Some("managed-incarnation".into()),
provider_expected_projection_sha256: Some(digest('5')),
provider_endpoint: Some("rudp://127.0.0.1:47871".into()),
}]
} else {
Vec::new()
},
};
expected.validate()?;
let launch = IdunnRuntimeActivationLaunch::issue(&expected, digest('6'), ISSUED_AT, idunn)?;
let mut credential = Vec::new();
let activation = launch.write_credential(&mut credential)?;
ensure!(credential.len() == 32, "activation credential is a 32-byte seed");
fs::write(dir.join("activation-key.seed"), &credential)?;
let activation_signer = IdunnRuntimeActivationSigner::from_credential_reader(&credential[..])?;
write_record(
&dir.join("expected.cc"),
&expected.target,
IdunnExpectedIncarnationRecord::TYPE,
IDUNN_EXPECTED_INCARNATION_SCHEMA,
expected.canonical_bytes()?,
)?;
write_record(
&dir.join("activation.cc"),
&expected.target,
IdunnRuntimeActivationRecord::TYPE,
IDUNN_RUNTIME_ACTIVATION_SCHEMA,
activation.canonical_bytes()?,
)?;
let authority =
verify_runtime_authority(&expected, &activation, idunn_anchor, &provider_anchor.public_key)?;
let sign_vector = |name: &str,
state: &str,
capacity: u32,
sequence: u64,
write_lease_sha256: Option<String>,
disagreement: Option<&str>|
-> Result<String> {
let mut presence = GameCultRuntimePresenceHealthRecord {
schema_version: cultnet_rs::GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA.into(),
target: expected.target.clone(),
expected_projection_sha256: activation.expected_projection_sha256.clone(),
plan_id: expected.plan_id.clone(),
incarnation_id: expected.incarnation_id.clone(),
sealed_release_id: expected.sealed_release_id.clone(),
activation_witness_sha256: activation.canonical_sha256()?,
state_schema_generation: expected.state_schema_generation.clone(),
state_contract_sha256: expected.state_contract_sha256.clone(),
runtime_id: expected.runtime_id.clone(),
runtime_instance_id: activation.runtime_instance_id.clone(),
bound_endpoint: Some(set.candidate.into()),
capabilities: vec![GameCultRuntimeCapability {
capability: capability.clone(),
schema: capability_schema.clone(),
compatibility: "v1".into(),
capacity,
}],
health_contract: expected.health_contract.clone(),
state: state.into(),
detail: DETAIL.into(),
write_lease_sha256,
signer_identity_id: provider_anchor.identity_id.clone(),
publisher_sequence: sequence,
observed_at_unix_millis: OBSERVED_AT,
signature_algorithm: "ed25519".into(),
signature: Vec::new(),
activation_signer_identity_id: activation_signer.identity_id(),
activation_signature: Vec::new(),
};
let proof = presence.canonical_proof_payload()?;
presence.signature = provider
.sign::<GameCultRuntimePresenceHealthPurpose>(&proof)
.signature;
presence.activation_signature = activation_signer.sign_presence_proof(&presence)?;
let bytes = rmp_serde::to_vec(&presence)?;
let claim = authenticate_runtime_presence_claim(
&bytes,
&authority,
RuntimePresenceAuthenticationContext {
trusted_received_at_unix_millis: OBSERVED_AT + 1_000,
maximum_age_millis: 30_000,
maximum_future_skew_millis: 0,
},
)?;
let presence_sha256 = claim.signed_presence_sha256().to_string();
let correlation = correlate_runtime_presence_claim(claim, &authority)?;
let codes: Vec<&str> = correlation
.disagreements()
.iter()
.map(|entry| entry.code.as_str())
.collect();
match disagreement {
None => ensure!(codes.is_empty(), "{name}: unexpected disagreements {codes:?}"),
Some(code) => ensure!(codes == [code], "{name}: expected only {code}, got {codes:?}"),
}
fs::write(dir.join(name), bytes)?;
Ok(presence_sha256)
};
if !set.stateful {
sign_vector("presence-active.bin", "active", set.minimum_capacity, SEQUENCE, None, None)?;
sign_vector("presence-warming.bin", "warming", set.minimum_capacity, SEQUENCE, None, None)?;
if set.minimum_capacity > 1 {
sign_vector(
"presence-active-below-minimum.bin",
"active",
set.minimum_capacity - 1,
SEQUENCE,
None,
Some("expected-capability-000-capacity"),
)?;
}
return Ok(());
}
// Lease-bound set. The warming vector comes first because the lease names it.
let warming_sha256 =
sign_vector("presence-warming.bin", "warming", set.minimum_capacity, SEQUENCE, None, None)?;
let activation_sha256 = activation.canonical_sha256()?;
let lease_for = |target: &str, warming_presence_sha256: &str| IdunnProcessWriteLeaseRecord {
schema_version: IDUNN_PROCESS_WRITE_LEASE_SCHEMA.into(),
target: target.into(),
expected_projection_sha256: activation.expected_projection_sha256.clone(),
plan_id: expected.plan_id.clone(),
incarnation_id: expected.incarnation_id.clone(),
sealed_release_id: expected.sealed_release_id.clone(),
activation_witness_sha256: activation_sha256.clone(),
state_schema_generation: expected.state_schema_generation.clone().unwrap_or_default(),
state_contract_sha256: expected.state_contract_sha256.clone().unwrap_or_default(),
runtime_id: expected.runtime_id.clone(),
runtime_instance_id: activation.runtime_instance_id.clone(),
warming_presence_sha256: warming_presence_sha256.into(),
lease_epoch: 1,
issued_at_unix_millis: OBSERVED_AT - 1_000,
};
let lease = lease_for(set.target, &warming_sha256);
let other_warming = lease_for(set.target, &digest('d'));
let other_target = lease_for("streampixels-web", &warming_sha256);
for (name, record) in [
("lease.cc", &lease),
("lease-other-warming.cc", &other_warming),
("lease-other-target.cc", &other_target),
] {
let bytes = record.canonical_bytes()?;
ensure!(
IdunnProcessWriteLeaseRecord::decode_canonical(&bytes)? == *record,
"{name}: lease does not round-trip"
);
write_record(
&dir.join(name),
&record.target,
IdunnProcessWriteLeaseRecord::TYPE,
IDUNN_PROCESS_WRITE_LEASE_SCHEMA,
bytes,
)?;
}
let lease_sha256 = lease.canonical_sha256()?;
for (name, state) in [("presence-active.bin", "active"), ("presence-degraded.bin", "degraded")] {
sign_vector(name, state, set.minimum_capacity, SEQUENCE + 1, Some(lease_sha256.clone()), None)?;
}
Ok(())
}
fn write_record(path: &Path, key: &str, kind: &str, schema: &str, payload: Vec<u8>) -> Result<()> {
SingleFileMessagePackBackingStore::new(path).push(&CultCacheEnvelope {
key: key.into(),
r#type: kind.into(),
payload,
stored_at: STORED_AT.into(),
schema_id: Some(schema.into()),
})?;
let mut lock = path.as_os_str().to_owned();
lock.push(".lock");
let _ = fs::remove_file(lock);
Ok(())
}

View file

@ -15,6 +15,11 @@
"require": "./dist/contracts.js",
"default": "./dist/contracts.js"
},
"./idunn-runtime": {
"types": "./dist/idunn-runtime-authority.d.ts",
"require": "./dist/idunn-runtime-authority.js",
"default": "./dist/idunn-runtime-authority.js"
},
"./authority": {
"types": "./dist/cultmesh-authority.d.ts",
"require": "./dist/cultmesh-authority.js",

View file

@ -0,0 +1,244 @@
import dgram from "node:dgram";
import { decode, encode } from "@msgpack/msgpack";
import { encodeCultNetMessageForWire, parseCultNetMessage } from "./contracts";
import type { IdunnRuntimeSigner } from "./idunn-runtime-authority";
import { CultNetRudpSession, decodeRudpPacket, encodeRudpPacket } from "./rudp";
import type { CultNetRudpPacket } from "./rudp";
const ODIN_CAPABILITY = "odin.verse-rendezvous";
const MAX_LEASE_WAIT_ROUNDS = 720;
export type IdunnRuntimePresencePublisher = {
readonly runtimeId: string;
readonly runtimeInstanceId: string;
readonly requiresWriteLease: boolean;
/** Publishes the signer's presence in the health the signer reports. */
publish(detail: string): Promise<string>;
/**
* Publishes warming presence until Idunn's lease names one. Call it before
* `reportHealth('active')`: once the signer reports a non-warming health,
* `publish` inside this loop throws (it needs the lease) instead of polling.
*/
waitForWriteLease(options?: { pollIntervalMs?: number; signal?: AbortSignal }): Promise<string>;
assertWriteLease(): Promise<string>;
};
export type IdunnRuntimePresencePublisherOptions = {
signer: IdunnRuntimeSigner;
endpoint: string;
};
/**
* Publishes the signer's presence to Odin over RUDP, for services that are
* CultMesh-aware and declare `odin.verse-rendezvous` in Expected. The route
* proof never passes through here: `signer.answerRouteObservation` answers
* Idunn's challenge locally and needs no Odin. Capability shortfalls are not
* judged here either; the correlation owner records a typed disagreement.
*/
export function createIdunnRuntimePresencePublisher(
options: IdunnRuntimePresencePublisherOptions,
): IdunnRuntimePresencePublisher {
const { signer } = options;
const endpoint = parseRudpEndpoint(options.endpoint);
const odinDependency = signer.authority.expected.dependencies.find((dependency) => dependency.kind === "shared-infrastructure"
&& dependency.capability === ODIN_CAPABILITY);
if (!odinDependency || normalizeRudpEndpoint(odinDependency.providerEndpoint ?? "") !== normalizeRudpEndpoint(options.endpoint)) {
throw new Error("Configured Odin endpoint does not match Idunn Expected dependency authority.");
}
const connectionId = 0x0d1d0002;
let messageSequence = 0;
const publish = async (detail: string) => {
const signed = signer.sign(detail);
messageSequence += 1;
await publishDocument(endpoint, connectionId, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: `runtime-presence:${signer.authority.expected.target}:${signer.runtimeInstanceId}:${messageSequence}`,
document: signed.document,
});
return signed.canonicalSha256;
};
return {
runtimeId: signer.runtimeId,
runtimeInstanceId: signer.runtimeInstanceId,
requiresWriteLease: signer.requiresWriteLease,
publish,
async waitForWriteLease({ pollIntervalMs = 5000, signal } = {}) {
if (!signer.requiresWriteLease) return "";
let warmingSequence = 0;
while (!signal?.aborted) {
await publish("waiting-for-process-write-lease");
warmingSequence += 1;
try {
return signer.assertWriteLease();
} catch (error) {
if (warmingSequence > MAX_LEASE_WAIT_ROUNDS) throw new Error("Timed out waiting for Idunn process write lease.", { cause: error });
}
await abortableDelay(pollIntervalMs, signal);
}
throw signal?.reason ?? new Error("Aborted while waiting for Idunn process write lease.");
},
async assertWriteLease() {
return signer.assertWriteLease();
},
};
}
async function publishDocument(
endpoint: { host: string; port: number },
connectionId: number,
message: Record<string, unknown>,
): Promise<void> {
const socket = dgram.createSocket(endpoint.host.includes(":") ? "udp6" : "udp4");
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.bind(0, endpoint.host.includes(":") ? "::" : "0.0.0.0", () => {
socket.off("error", reject);
resolve();
});
});
const receiver = receivePackets(socket);
const session = new CultNetRudpSession({ connectionId, initialSequence: 1, resendDelayMs: 100 });
try {
await sendPacket(socket, endpoint, session.createConnect(Date.now(), new Uint8Array()));
await receiveUntil(receiver, session, endpoint, (packet) => packet.packetType === "accept", 5000);
const wirePayload = encode(encodeCultNetMessageForWire(message as never, "cultnet.schema.v0"));
const packets = session.sendMany("schema", wirePayload, { reliable: true, ordered: true, nowMs: Date.now() });
for (const packet of packets) await sendPacket(socket, endpoint, packet);
await receiveUntil(
receiver,
session,
endpoint,
() => session.outstandingReliablePacketCount === 0,
2000,
(frame) => {
if (frame.channelId !== "schema") return;
const response = parseCultNetMessage(decode(frame.payload));
if (response.schemaVersion === "cultnet.error.v0") {
throw new Error(`Odin rejected runtime presence: ${response.error}`);
}
},
);
} finally {
receiver.close();
socket.close();
}
}
type PacketReceiver = {
socket: dgram.Socket;
next(timeoutMs: number): Promise<CultNetRudpPacket>;
close(): void;
};
function receivePackets(socket: dgram.Socket): PacketReceiver {
const packets: CultNetRudpPacket[] = [];
const waiters: Array<{ resolve: (packet: CultNetRudpPacket) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }> = [];
const errors: Error[] = [];
const drain = () => {
while (waiters.length && (packets.length || errors.length)) {
const waiter = waiters.shift()!;
clearTimeout(waiter.timer);
if (errors.length) waiter.reject(errors.shift()!);
else waiter.resolve(packets.shift()!);
}
};
const onMessage = (wire: Buffer) => {
try { packets.push(decodeRudpPacket(wire)); } catch (error) { errors.push(error as Error); }
drain();
};
const onError = (error: Error) => { errors.push(error); drain(); };
socket.on("message", onMessage);
socket.on("error", onError);
return {
socket,
next(timeoutMs) {
if (packets.length) return Promise.resolve(packets.shift()!);
if (errors.length) return Promise.reject(errors.shift()!);
return new Promise((resolve, reject) => {
const waiter = {
resolve,
reject,
timer: setTimeout(() => {
const index = waiters.indexOf(waiter);
if (index >= 0) waiters.splice(index, 1);
const error = new Error("Timed out waiting for CultNet RUDP packet.") as NodeJS.ErrnoException;
error.code = "ETIMEDOUT";
reject(error);
}, Math.max(1, timeoutMs)),
};
waiters.push(waiter);
});
},
close() {
socket.off("message", onMessage);
socket.off("error", onError);
for (const waiter of waiters.splice(0)) {
clearTimeout(waiter.timer);
waiter.reject(new Error("CultNet RUDP publisher closed."));
}
},
};
}
async function receiveUntil(
receiver: PacketReceiver,
session: CultNetRudpSession,
endpoint: { host: string; port: number },
predicate: (packet: CultNetRudpPacket) => boolean,
timeoutMs: number,
onDelivered?: (frame: { channelId: string; payload: Uint8Array; sequence: number }) => void,
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
const packet = await receiver.next(Math.min(100, deadline - Date.now()));
const received = session.receive(packet, Date.now());
if (received.reply) throw new Error("Runtime presence received an unexpected reply-required packet.");
for (const frame of received.delivered) onDelivered?.(frame);
if (predicate(packet)) return;
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code !== "ETIMEDOUT") throw error;
}
for (const packet of session.dueResends(Date.now())) await sendPacket(receiver.socket, endpoint, packet);
}
throw new Error(`Timed out waiting for CultNet RUDP response after ${timeoutMs} ms.`);
}
async function sendPacket(socket: dgram.Socket, endpoint: { host: string; port: number }, packet: CultNetRudpPacket): Promise<void> {
const wire = encodeRudpPacket(packet);
await new Promise<void>((resolve, reject) => {
socket.send(wire, endpoint.port, endpoint.host, (error) => error ? reject(error) : resolve());
});
}
function parseRudpEndpoint(value: string): { host: string; port: number } {
const text = value.replace(/^rudp:\/\//i, "");
const match = text.match(/^\[([^\]]+)\]:(\d+)$/) ?? text.match(/^([^:]+):(\d+)$/);
if (!match) throw new Error(`Invalid Odin RUDP endpoint: ${value}`);
const port = Number(match[2]);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`Invalid Odin RUDP port: ${match[2]}`);
return { host: match[1]!, port };
}
function normalizeRudpEndpoint(value: string): string {
const normalized = value.trim().toLowerCase().replace(/^rudp:\/\//, "");
const parsed = parseRudpEndpoint(normalized);
return `${parsed.host.toLowerCase()}:${parsed.port}`;
}
function abortableDelay(milliseconds: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, milliseconds);
const onAbort = () => {
clearTimeout(timer);
reject(signal?.reason ?? new Error("Aborted."));
};
signal?.addEventListener("abort", onAbort, { once: true });
});
}

View file

@ -15,10 +15,7 @@ import {
type RuntimePresenceHealth,
} from "./runtime-presence-health";
import { truncateUtf8Bytes } from "./utf8-bound";
import { encodeCultNetMessageForWire, parseCultNetMessage, type CultNetRawDocumentRecord, type CultNetSnapshotRequestMessage } from "./contracts";
import { CultNetRudpSession, decodeRudpPacket, encodeRudpPacket } from "./rudp";
import type { CultNetRudpPacket } from "./rudp";
import dgram from "node:dgram";
import type { CultNetRawDocumentRecord, CultNetSnapshotRequestMessage } from "./contracts";
export const IDUNN_EXPECTED_INCARNATION_SCHEMA = "idunn.expected_incarnation.v2";
export const IDUNN_RUNTIME_ACTIVATION_SCHEMA = "idunn.runtime_activation.v2";
@ -32,7 +29,10 @@ const PROVIDER_PROTECTOR_CONTEXT = "gamecult-provider-health-identity-v1";
const ACTIVATION_ID_DOMAIN = Buffer.from("idunn.runtime-activation.id.v1\0", "utf8");
const CULTCACHE_STORE_FORMAT = "cultcache.store.v1";
const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
// Limit: more than this many warmings signed between Idunn's observation and
// assertWriteLease would push the leased one out and refuse a valid lease.
const MAX_RECENT_WARMING_PROOFS = 64;
const authoritiesWithSigner = new WeakSet<object>();
const authorityKeys = new WeakMap<object, { providerPrivateKey: crypto.KeyObject; activationPrivateKey: crypto.KeyObject }>();
type ExpectedIncarnation = {
@ -54,7 +54,6 @@ type ExpectedIncarnation = {
route: { routeId: string; transport: string; stableEndpoint: string; candidateEndpoint: string } | null;
capabilities: Array<{ capability: string; schema: string; compatibility: string; minimumCapacity: number }>;
dependencies: Array<{ kind: string; capability: string; schema: string; compatibility: string; providerEndpoint: string | null }>;
canonicalBytes: Buffer;
canonicalSha256: string;
};
@ -64,46 +63,77 @@ type RuntimeActivation = {
runtimeId: string;
runtimeInstanceId: string;
activationSignerIdentityId: string;
activationSignerPublicKey: Buffer;
activationSignerPublicKeyHex: string;
canonicalSha256: string;
};
export type IdunnRuntimeAuthority = {
type DeepReadonly<T> = T extends readonly (infer U)[]
? readonly DeepReadonly<U>[]
: T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
/**
* What Idunn admitted for this launch. Deeply frozen at load: bind, health
* contract, Expected, Activation and identities are read-only for the life of
* the process, so no caller can steer what the signer signs.
*/
export type IdunnRuntimeAuthority = DeepReadonly<{
expected: ExpectedIncarnation;
activation: RuntimeActivation;
providerSignerIdentityId: string;
activationSignerIdentityId: string;
boundEndpoint: string;
processWriteLeasePath?: string;
}>;
export type SignedRuntimePresence = {
document: CultNetRawDocumentRecord;
canonicalSha256: string;
};
export type IdunnRuntimePresencePublisher = {
/**
* Signs this launch's runtime presence and answers Idunn's route challenge. It
* owns no network: Odin publication, if a service wants it, is a separate
* consumer of `sign`. The signer is the single owner of the reported health:
* it starts `warming`, the app moves it with `reportHealth`, and every
* presence it signs, however it is carried, states that health.
*/
export type IdunnRuntimeSigner = {
readonly authority: IdunnRuntimeAuthority;
readonly runtimeId: string;
readonly runtimeInstanceId: string;
readonly requiresWriteLease: boolean;
latestPresenceDocument(): CultNetRawDocumentRecord | null;
publishRouteObservation(request: CultNetSnapshotRequestMessage): Promise<CultNetRawDocumentRecord>;
publish(state: RuntimePresenceHealth["state"], detail: string): Promise<string>;
waitForWriteLease(options?: { pollIntervalMs?: number; signal?: AbortSignal }): Promise<string>;
assertWriteLease(): Promise<string>;
/** Records the app's health. Until the first call the signer reports `warming`. */
reportHealth(state: RuntimePresenceHealth["state"]): void;
/**
* Signs one presence document in the reported health. Every call takes the
* next publisher sequence; every warming presence signed becomes a valid
* write-lease target.
*/
sign(detail: string): SignedRuntimePresence;
/**
* Answers an Idunn route challenge from local authority alone: signs a fresh
* presence in the reported health, with no network.
*/
answerRouteObservation(request: CultNetSnapshotRequestMessage): CultNetRawDocumentRecord;
/** Reads Idunn's write lease and checks it names this incarnation and a warming presence this signer signed. */
assertWriteLease(): string;
};
export type IdunnRuntimePresencePublisherOptions = {
export type IdunnRuntimeSignerOptions = {
authority: IdunnRuntimeAuthority;
endpoint: string;
healthContract: string;
capabilities: RuntimePresenceHealth["capabilities"];
};
/**
* Loads the root-provisioned Idunn bundle and parent-only signing descriptors.
* The expected record and activation witness are accepted only when their
* exact positional contracts and mutual incarnation binding agree.
* exact positional contracts and mutual incarnation binding agree. Nothing
* here reads Odin: whether a target talks to Odin is decided by the
* publisher a service chooses to create.
*/
export function loadIdunnRuntimeAuthorityFromEnvironment(
target: string,
healthContract: string,
odinEndpoint: string,
): IdunnRuntimeAuthority {
const bundle = requiredEnvironment(IDUNN_RUNTIME_BUNDLE_ENVIRONMENT);
const expected = readAuthorityRecord(path.join(bundle, "expected.cc"), "idunn.expected_incarnation", IDUNN_EXPECTED_INCARNATION_SCHEMA);
@ -113,11 +143,6 @@ export function loadIdunnRuntimeAuthorityFromEnvironment(
if (expectedValue.target !== target || expectedValue.healthContract !== healthContract) {
throw new Error("Idunn Expected target or health contract does not match this runtime.");
}
const odinDependency = expectedValue.dependencies.find((dependency) => dependency.kind === "shared-infrastructure"
&& dependency.capability === "odin.verse-rendezvous");
if (!odinDependency || normalizeRudpEndpoint(odinDependency.providerEndpoint ?? "") !== normalizeRudpEndpoint(odinEndpoint)) {
throw new Error("Configured Odin endpoint does not match Idunn Expected dependency authority.");
}
if (activationValue.expectedProjectionSha256 !== expectedValue.canonicalSha256
|| activationValue.runtimeId !== expectedValue.runtimeId) {
throw new Error("Idunn Activation does not bind this exact Expected incarnation.");
@ -138,7 +163,7 @@ export function loadIdunnRuntimeAuthorityFromEnvironment(
const activationPublicKey = rawEd25519PublicKey(crypto.createPublicKey(activationPrivateKey));
const activationSignerIdentityId = sha256Hex(Buffer.concat([ACTIVATION_ID_DOMAIN, activationPublicKey]));
if (activationSignerIdentityId !== activationValue.activationSignerIdentityId
|| !activationPublicKey.equals(activationValue.activationSignerPublicKey)) {
|| activationPublicKey.toString("hex") !== activationValue.activationSignerPublicKeyHex) {
throw new Error("Idunn activation credential does not match the root-provisioned Activation witness.");
}
@ -153,7 +178,7 @@ export function loadIdunnRuntimeAuthorityFromEnvironment(
throw new Error("Provider identity does not match Idunn Expected.");
}
const authority: IdunnRuntimeAuthority = {
const authority: IdunnRuntimeAuthority = deepFreeze({
expected: expectedValue,
activation: activationValue,
providerSignerIdentityId: expectedValue.expectedSignerIdentityId,
@ -162,16 +187,17 @@ export function loadIdunnRuntimeAuthorityFromEnvironment(
...(process.env[IDUNN_PROCESS_WRITE_LEASE_ENVIRONMENT]
? { processWriteLeasePath: process.env[IDUNN_PROCESS_WRITE_LEASE_ENVIRONMENT] }
: {}),
};
});
authorityKeys.set(authority, { providerPrivateKey, activationPrivateKey });
return authority;
}
/**
* Signs the fixed Idunn v2 positional runtime-presence contract with the
* provider identity and this launch's Idunn-issued activation key.
*/
export function signIdunnRuntimePresence(
function signIdunnRuntimePresence(
authority: IdunnRuntimeAuthority,
fields: Omit<RuntimePresenceHealth, "target" | "expectedProjectionSha256" | "planId" | "incarnationId" | "sealedReleaseId" | "activationWitnessSha256" | "stateSchemaGeneration" | "stateContractSha256" | "runtimeId" | "runtimeInstanceId" | "boundEndpoint" | "healthContract" | "signerIdentityId" | "activationSignerIdentityId">,
): { presence: RuntimePresenceHealth; payload: Uint8Array; canonicalSha256: string } {
@ -205,42 +231,35 @@ export function signIdunnRuntimePresence(
};
}
export function createIdunnRuntimePresencePublisher(
options: IdunnRuntimePresencePublisherOptions,
): IdunnRuntimePresencePublisher {
const endpoint = parseRudpEndpoint(options.endpoint);
const expected = options.authority.expected;
if (options.healthContract !== expected.healthContract) throw new Error("Runtime presence contract differs from Expected.");
if (options.authority.boundEndpoint !== expected.route?.candidateEndpoint) throw new Error("Runtime presence bind differs from Expected.");
for (const required of expected.capabilities) {
if (!options.capabilities.some((actual) => actual.capability === required.capability
&& actual.schema === required.schema
&& actual.compatibility === required.compatibility
&& actual.capacity >= required.minimumCapacity)) {
throw new Error(`Runtime does not provide Expected capability ${required.capability}/${required.schema}.`);
}
}
const connectionId = 0x0d1d0002;
/**
* One signer per loaded authority: the publisher sequence and the warming proofs
* are the signer's, and a second signer would restart the sequence under the
* same runtimeInstanceId.
*/
export function createIdunnRuntimeSigner(options: IdunnRuntimeSignerOptions): IdunnRuntimeSigner {
const { authority } = options;
if (authoritiesWithSigner.has(authority)) throw new Error("This Idunn runtime authority already has a signer.");
authoritiesWithSigner.add(authority);
const expected = authority.expected;
let sequence = 0;
let latestLeaseSha256: string | null = null;
let latestPresenceDocument: CultNetRawDocumentRecord | null = null;
let health: RuntimePresenceHealth["state"] = "warming";
const recentWarmingProofs: string[] = [];
const assertWriteLease = async () => {
const assertWriteLease = () => {
if (!expected.writeLeaseRequired) return "";
const leasePath = options.authority.processWriteLeasePath;
const leasePath = authority.processWriteLeasePath;
if (!leasePath) throw new Error("Idunn process write lease is required but no lease path is configured.");
const record = readAuthorityRecord(leasePath, "idunn.process_write_lease", IDUNN_PROCESS_WRITE_LEASE_SCHEMA);
const lease = decodeArray(record.payload, 14, "Idunn process write lease");
assertLeaseMatches(lease, options.authority, recentWarmingProofs);
latestLeaseSha256 = prefixedSha256(Buffer.from(encode(lease)));
return latestLeaseSha256;
assertLeaseMatches(lease, authority, recentWarmingProofs);
return prefixedSha256(Buffer.from(encode(lease)));
};
const publish = async (state: RuntimePresenceHealth["state"], detail: string) => {
const writeLeaseSha256 = state === "warming" || !expected.writeLeaseRequired ? null : await assertWriteLease();
const sign = (detail: string): SignedRuntimePresence => {
const state = health;
const writeLeaseSha256 = state === "warming" || !expected.writeLeaseRequired ? null : assertWriteLease();
sequence += 1;
const signed = signIdunnRuntimePresence(options.authority, {
const signed = signIdunnRuntimePresence(authority, {
capabilities: options.capabilities,
state,
detail: truncateUtf8Bytes(detail, RUNTIME_PRESENCE_DETAIL_MAX_BYTES),
@ -248,44 +267,35 @@ export function createIdunnRuntimePresencePublisher(
publisherSequence: sequence,
observedAtUnixMillis: Date.now(),
});
const document: CultNetRawDocumentRecord = {
schemaId: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA,
recordKey: expected.target,
storedAt: new Date().toISOString(),
payloadEncoding: "messagepack",
payload: new Uint8Array(signed.payload),
sourceRuntimeId: expected.runtimeId,
sourceRole: "runtime-presence-health-publisher",
tags: ["cultnet.transport.rudp.v0", "runtime-presence"],
};
await publishDocument(endpoint, connectionId, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: `runtime-presence:${expected.target}:${options.authority.activation.runtimeInstanceId}:${sequence}`,
document,
});
latestPresenceDocument = document;
if (state === "warming") {
recentWarmingProofs.push(signed.canonicalSha256);
if (recentWarmingProofs.length > MAX_RECENT_WARMING_PROOFS) recentWarmingProofs.shift();
}
return signed.canonicalSha256;
};
const copyLatestPresenceDocument = (): CultNetRawDocumentRecord | null => {
if (!latestPresenceDocument) return null;
return {
...latestPresenceDocument,
payload: new Uint8Array(latestPresenceDocument.payload),
...(latestPresenceDocument.tags ? { tags: [...latestPresenceDocument.tags] } : {}),
canonicalSha256: signed.canonicalSha256,
document: {
schemaId: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA,
recordKey: expected.target,
storedAt: new Date().toISOString(),
payloadEncoding: "messagepack",
payload: new Uint8Array(signed.payload),
sourceRuntimeId: expected.runtimeId,
sourceRole: "runtime-presence-health-publisher",
tags: ["cultnet.transport.rudp.v0", "runtime-presence"],
},
};
};
return {
authority,
runtimeId: expected.runtimeId,
runtimeInstanceId: options.authority.activation.runtimeInstanceId,
runtimeInstanceId: authority.activation.runtimeInstanceId,
requiresWriteLease: expected.writeLeaseRequired,
latestPresenceDocument: copyLatestPresenceDocument,
async publishRouteObservation(request) {
reportHealth(state) {
health = state;
},
sign,
answerRouteObservation(request) {
if (!request.messageId || request.messageId.trim() !== request.messageId) throw new Error("Idunn route observation requires a message id.");
if ((request.schemaIds && !request.schemaIds.includes(GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA))
|| (request.recordKeys && !request.recordKeys.includes(expected.target))
@ -293,157 +303,18 @@ export function createIdunnRuntimePresencePublisher(
|| request.shardEpoch !== undefined) {
throw new Error("Idunn route observation does not request this runtime presence document.");
}
await publish("active", `route-observation:${request.messageId}`);
const document = copyLatestPresenceDocument();
if (!document) throw new Error("Idunn route observation was published without a presence document.");
return document;
},
publish,
async waitForWriteLease({ pollIntervalMs = 5000, signal } = {}) {
if (!expected.writeLeaseRequired) return "";
let warmingSequence = 0;
while (!signal?.aborted) {
await publish("warming", "waiting-for-process-write-lease");
warmingSequence += 1;
try {
return await assertWriteLease();
} catch (error) {
if (warmingSequence > 720) throw new Error("Timed out waiting for Idunn process write lease.", { cause: error });
}
await abortableDelay(pollIntervalMs, signal);
}
throw signal?.reason ?? new Error("Aborted while waiting for Idunn process write lease.");
return sign(`route-observation:${request.messageId}`).document;
},
assertWriteLease,
};
}
async function publishDocument(
endpoint: { host: string; port: number },
connectionId: number,
message: Record<string, unknown>,
): Promise<void> {
const socket = dgram.createSocket(endpoint.host.includes(":") ? "udp6" : "udp4");
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.bind(0, endpoint.host.includes(":") ? "::" : "0.0.0.0", () => {
socket.off("error", reject);
resolve();
});
});
const receiver = receivePackets(socket);
const session = new CultNetRudpSession({ connectionId, initialSequence: 1, resendDelayMs: 100 });
try {
await sendPacket(socket, endpoint, session.createConnect(Date.now(), new Uint8Array()));
await receiveUntil(receiver, session, endpoint, (packet) => packet.packetType === "accept", 5000);
const wirePayload = encode(encodeCultNetMessageForWire(message as never, "cultnet.schema.v0"));
const packets = session.sendMany("schema", wirePayload, { reliable: true, ordered: true, nowMs: Date.now() });
for (const packet of packets) await sendPacket(socket, endpoint, packet);
await receiveUntil(
receiver,
session,
endpoint,
() => session.outstandingReliablePacketCount === 0,
2000,
(frame) => {
if (frame.channelId !== "schema") return;
const response = parseCultNetMessage(decode(frame.payload));
if (response.schemaVersion === "cultnet.error.v0") {
throw new Error(`Odin rejected runtime presence: ${response.error}`);
}
},
);
} finally {
receiver.close();
socket.close();
function deepFreeze<T>(value: T): T {
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
for (const child of Object.values(value)) deepFreeze(child);
Object.freeze(value);
}
}
type PacketReceiver = {
socket: dgram.Socket;
next(timeoutMs: number): Promise<CultNetRudpPacket>;
close(): void;
};
function receivePackets(socket: dgram.Socket): PacketReceiver {
const packets: CultNetRudpPacket[] = [];
const waiters: Array<{ resolve: (packet: CultNetRudpPacket) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }> = [];
const errors: Error[] = [];
const drain = () => {
while (waiters.length && (packets.length || errors.length)) {
const waiter = waiters.shift()!;
clearTimeout(waiter.timer);
if (errors.length) waiter.reject(errors.shift()!);
else waiter.resolve(packets.shift()!);
}
};
const onMessage = (wire: Buffer) => {
try { packets.push(decodeRudpPacket(wire)); } catch (error) { errors.push(error as Error); }
drain();
};
const onError = (error: Error) => { errors.push(error); drain(); };
socket.on("message", onMessage);
socket.on("error", onError);
return {
socket,
next(timeoutMs) {
if (packets.length) return Promise.resolve(packets.shift()!);
if (errors.length) return Promise.reject(errors.shift()!);
return new Promise((resolve, reject) => {
const waiter = {
resolve,
reject,
timer: setTimeout(() => {
const index = waiters.indexOf(waiter);
if (index >= 0) waiters.splice(index, 1);
const error = new Error("Timed out waiting for CultNet RUDP packet.") as NodeJS.ErrnoException;
error.code = "ETIMEDOUT";
reject(error);
}, Math.max(1, timeoutMs)),
};
waiters.push(waiter);
});
},
close() {
socket.off("message", onMessage);
socket.off("error", onError);
for (const waiter of waiters.splice(0)) {
clearTimeout(waiter.timer);
waiter.reject(new Error("CultNet RUDP publisher closed."));
}
},
};
}
async function receiveUntil(
receiver: PacketReceiver,
session: CultNetRudpSession,
endpoint: { host: string; port: number },
predicate: (packet: CultNetRudpPacket) => boolean,
timeoutMs: number,
onDelivered?: (frame: { channelId: string; payload: Uint8Array; sequence: number }) => void,
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
const packet = await receiver.next(Math.min(100, deadline - Date.now()));
const received = session.receive(packet, Date.now());
if (received.reply) throw new Error("Runtime presence received an unexpected reply-required packet.");
for (const frame of received.delivered) onDelivered?.(frame);
if (predicate(packet)) return;
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code !== "ETIMEDOUT") throw error;
}
for (const packet of session.dueResends(Date.now())) await sendPacket(receiver.socket, endpoint, packet);
}
throw new Error(`Timed out waiting for CultNet RUDP response after ${timeoutMs} ms.`);
}
async function sendPacket(socket: dgram.Socket, endpoint: { host: string; port: number }, packet: CultNetRudpPacket): Promise<void> {
const wire = encodeRudpPacket(packet);
await new Promise<void>((resolve, reject) => {
socket.send(wire, endpoint.port, endpoint.host, (error) => error ? reject(error) : resolve());
});
return value;
}
function readAuthorityRecord(filePath: string, type: string, schemaId: string): { payload: Uint8Array } {
@ -495,9 +366,7 @@ function decodeExpected(payload: Uint8Array): ExpectedIncarnation {
if (!/^sha256-[0-9a-f]{64}$/u.test(hash)) throw new Error(`Expected ${name} hash is malformed.`);
}
if (!/^[0-9a-f]{40}$/u.test(sourceRevision)) throw new Error("Expected source revision is malformed.");
if (!route || route.transport !== "http" || !route.candidateEndpoint.startsWith("http://")) {
throw new Error("Idunn Expected must include an HTTP candidate route.");
}
if (route) validateRoute(route);
return {
schemaVersion: string(values[0], "Expected schema"),
target,
@ -517,7 +386,6 @@ function decodeExpected(payload: Uint8Array): ExpectedIncarnation {
route,
capabilities: decodeExpectedCapabilities(values[16]),
dependencies: decodeExpectedDependencies(values[17]),
canonicalBytes,
canonicalSha256: prefixedSha256(canonicalBytes),
};
}
@ -545,7 +413,7 @@ function decodeActivation(payload: Uint8Array): RuntimeActivation {
runtimeId: string(values[2], "Activation runtime id"),
runtimeInstanceId: string(values[3], "Activation instance id"),
activationSignerIdentityId,
activationSignerPublicKey: Buffer.from(publicKey),
activationSignerPublicKeyHex: Buffer.from(publicKey).toString("hex"),
canonicalSha256: prefixedSha256(canonicalBytes),
};
}
@ -575,6 +443,24 @@ function decodeExpectedDependencies(value: unknown): ExpectedIncarnation["depend
});
}
// The Rust owner (IdunnExpectedRoute::validate) accepts http, tcp and rudp
// routes; a runtime is not entitled to a narrower admission than Idunn's.
const ROUTE_SCHEMES: Record<string, { stable: string[]; candidate: string[] }> = {
http: { stable: ["http://", "https://"], candidate: ["http://"] },
tcp: { stable: ["tcp://"], candidate: ["tcp://"] },
rudp: { stable: ["rudp://"], candidate: ["rudp://"] },
};
function validateRoute(route: NonNullable<ExpectedIncarnation["route"]>): void {
const schemes = ROUTE_SCHEMES[route.transport];
if (!schemes) throw new Error("Idunn Expected route transport is unsupported.");
if (!schemes.stable.some((scheme) => route.stableEndpoint.startsWith(scheme))
|| !schemes.candidate.some((scheme) => route.candidateEndpoint.startsWith(scheme))) {
throw new Error(`Idunn Expected ${route.transport} route endpoints use an unsupported scheme.`);
}
if (route.stableEndpoint === route.candidateEndpoint) throw new Error("Idunn Expected stable and candidate route endpoints are identical.");
}
function decodeRoute(value: unknown): ExpectedIncarnation["route"] {
if (value === null) return null;
const route = array(value, 4, "Expected route");
@ -595,7 +481,7 @@ function openProviderHealthIdentity(credentialBytes: Buffer): crypto.KeyObject {
if (enrollmentNonce.length !== 32) throw new Error("Provider-health enrollment nonce has an invalid length.");
const publicKey = Buffer.from(providerIdentityByteVector(publicKeyValue, "Provider-health public key"));
const protectedSeed = Buffer.from(providerIdentityByteVector(protectedSeedValue, "Provider-health protected seed"));
const machineId = fs.readFileSync("/etc/machine-id", "utf8").trim();
const machineId = readMachineId();
if (!machineId) throw new Error("Linux machine-id is unavailable for provider-health identity.");
const expectedBinding = `${PROVIDER_PROTECTOR_CONTEXT}:machine-id-sha256:${sha256Hex(Buffer.from(machineId))}`;
if (protectorKind !== "linux_file_mode_machine_id_binding" || binding !== expectedBinding || assurance !== "os_installation_file_bound_cloneable_baseline") {
@ -616,6 +502,15 @@ function openProviderHealthIdentity(credentialBytes: Buffer): crypto.KeyObject {
return key;
}
// Same sources, same order as the Rust identity protector.
function readMachineId(): string {
try {
return fs.readFileSync("/etc/machine-id", "utf8").trim();
} catch {
return fs.readFileSync("/var/lib/dbus/machine-id", "utf8").trim();
}
}
function readProviderHealthIdentityCredential(bytes: Buffer): Uint8Array {
const decoded = decode(bytes);
if (!Array.isArray(decoded) || decoded.length !== 1) {
@ -693,40 +588,12 @@ function assertLeaseMatches(values: unknown[], authority: IdunnRuntimeAuthority,
if (!matches) throw new Error("Idunn process write lease does not match the current Expected incarnation.");
}
function parseRudpEndpoint(value: string): { host: string; port: number } {
const text = value.replace(/^rudp:\/\//i, "");
const match = text.match(/^\[([^\]]+)\]:(\d+)$/) ?? text.match(/^([^:]+):(\d+)$/);
if (!match) throw new Error(`Invalid Odin RUDP endpoint: ${value}`);
const port = Number(match[2]);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`Invalid Odin RUDP port: ${match[2]}`);
return { host: match[1]!, port };
}
function normalizeRudpEndpoint(value: string): string {
const normalized = value.trim().toLowerCase().replace(/^rudp:\/\//, "");
const parsed = parseRudpEndpoint(normalized);
return `${parsed.host.toLowerCase()}:${parsed.port}`;
}
function requiredEnvironment(name: string): string {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is required for Idunn runtime presence.`);
return value;
}
function abortableDelay(milliseconds: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, milliseconds);
const onAbort = () => {
clearTimeout(timer);
reject(signal?.reason ?? new Error("Aborted."));
};
signal?.addEventListener("abort", onAbort, { once: true });
});
}
function array(value: unknown, length: number, label: string): unknown[] {
if (!Array.isArray(value) || value.length !== length) throw new Error(`${label} is not a ${length}-field positional contract.`);

View file

@ -205,9 +205,15 @@ export {
IDUNN_RUNTIME_BUNDLE_ENVIRONMENT,
IDUNN_RUNTIME_CANDIDATE_BIND_ENVIRONMENT,
IDUNN_RUNTIME_ACTIVATION_SCHEMA,
createIdunnRuntimePresencePublisher,
createIdunnRuntimeSigner,
loadIdunnRuntimeAuthorityFromEnvironment,
type IdunnRuntimeAuthority,
type IdunnRuntimeSigner,
type IdunnRuntimeSignerOptions,
type SignedRuntimePresence,
} from "./idunn-runtime-authority";
export {
createIdunnRuntimePresencePublisher,
type IdunnRuntimePresencePublisher,
type IdunnRuntimePresencePublisherOptions,
} from "./idunn-runtime-authority";
} from "./idunn-odin-presence-publisher";

View file

@ -0,0 +1 @@
* -text

View file

@ -0,0 +1,21 @@
# Idunn runtime fixtures
Written by the Rust owner, not by the TypeScript tests. Regenerate with
`packages/cultnet-rs/examples/idunn_runtime_fixture.rs` (CultLib `9ba6ac4`; it uses
cultnet-rs only, and Idunn's `IdunnRuntimeActivationLaunch` is that same crate's).
It needs Linux and a machine-id equal to the `machine-id` file here, because the
provider identity is bound to it. On Yggdrasil:
DOCKER_ARGS='--cap-add SYS_ADMIN --security-opt apparmor=unconfined' bash ygg-verify.sh <CultLib> HEAD rust 'cd packages/cultnet-rs && cp <fixed-machine-id> /tmp/mid && mount --bind /tmp/mid /etc/machine-id && cargo run -q --example idunn_runtime_fixture /tmp/out'
then copy `/tmp/out` over this directory. `--cap-add SYS_ADMIN` exists only so the bind mount can present the committed `machine-id`, which keeps Yggdrasil's real machine-id out of the repo. Every `presence-*.bin` was accepted by
`authenticate_runtime_presence_claim` and correlated by
`correlate_runtime_presence_claim` before being written; the
`below-minimum` vector correlates to exactly `expected-capability-000-capacity`.
Ed25519 is deterministic, so the TypeScript signer must reproduce them byte for byte.
`service-stateful` is the lease-bound shape (`streampixels-service`, state lineage set,
`write_lease_required: true`). Rust issues `lease.cc`, which names the digest of its
`presence-warming.bin`; `presence-active.bin` and `presence-degraded.bin` are sequence 2 and
carry that lease's digest. `lease-other-warming.cc` names a different warming digest and
`lease-other-target.cc` names another target; the TypeScript lease check must refuse both.

View file

@ -0,0 +1 @@
c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1

View file

@ -0,0 +1 @@
<EFBFBD>_<EFBFBD>#<23>/<2F>"Q0\<5C>iC<69>]\ʝ<><CA9D><EFBFBD><EFBFBD>;<3B>Y<EFBFBD><14><>k

View file

@ -0,0 +1 @@
Kýaźz´-2‰ËˇV>)ë»xěşÚĎ®˘T =/‰*

View file

@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD>Bf<EFBFBD><EFBFBD>=?<3F><><EFBFBD>Z<EFBFBD>j<EFBFBD><6A><EFBFBD>&<26><15><>3<EFBFBD><33>ܟf<DC9F>mJ

Binary file not shown.

View file

@ -0,0 +1 @@
¯ÂOd×f¯l*oµ‘ÀFªv‰n<E280B0>hLûÑpšS´p

Binary file not shown.

Binary file not shown.

View file

@ -1,30 +1,98 @@
import { mock, test } from "node:test";
import { test, type TestContext } from "node:test";
import assert from "node:assert/strict";
import crypto from "node:crypto";
import { spawnSync } from "node:child_process";
import dgram from "node:dgram";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { decode, encode } from "@msgpack/msgpack";
import {
createIdunnRuntimePresencePublisher,
createIdunnRuntimeSigner,
loadIdunnRuntimeAuthorityFromEnvironment,
signIdunnRuntimePresence,
systemdListenPidMatches,
} from "../src/idunn-runtime-authority";
type IdunnRuntimeAuthority,
} from "../src";
import { systemdListenPidMatches } from "../src/idunn-runtime-authority";
import type { CultNetSnapshotRequestMessage } from "../src/contracts";
import { CultNetRudpSession, decodeRudpPacket, encodeRudpPacket } from "../src/rudp";
import {
runtimePresenceActivationSigningMessage,
runtimePresenceProofPayload,
runtimePresenceProviderSigningMessage,
RUNTIME_PRESENCE_SLOT,
} from "../src/runtime-presence-health";
import { RUNTIME_PRESENCE_SLOT } from "../src/runtime-presence-health";
const providerIdentityContext = "gamecult-provider-health-identity-v1";
const providerIdDomain = Buffer.from("gamecult.provider-health.identity.v1\0", "utf8");
const activationIdDomain = Buffer.from("idunn.runtime-activation.id.v1\0", "utf8");
// Every input below was written by the Rust owner; see fixtures/idunn-runtime/README.md.
// The only values this file pins are the ones the generator also pins.
const FIXTURES = path.join(__dirname, "..", "..", "test", "fixtures", "idunn-runtime");
const OBSERVED_AT = 1_790_000_000_000;
const CHALLENGE: CultNetSnapshotRequestMessage = {
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "route-challenge-1",
};
type FixtureSet = "web" | "service" | "rudp-route" | "service-stateful";
function fixtureFile(set: FixtureSet, name: string): Buffer {
return fs.readFileSync(path.join(FIXTURES, set, name));
}
/** The Expected fields the tests need, read from the Rust-written file rather than re-spelled. */
function expectedFacts(set: FixtureSet) {
const store = decode(fixtureFile(set, "expected.cc")) as unknown[][][];
const values = decode(store[2]![0]![3] as Uint8Array) as unknown[];
const route = values[15] as string[];
const capabilities = (values[16] as Array<[string, string, string, number]>).map(([capability, schema, compatibility, minimum]) => ({
capability, schema, compatibility, capacity: minimum,
}));
const dependencies = values[17] as unknown[][];
return {
target: values[1] as string,
healthContract: values[10] as string,
candidate: route[3]!,
capabilities,
odinEndpoint: dependencies.map((dependency) => dependency[9] as string)[0],
};
}
/** Points the process at one Rust-written bundle and its inherited descriptors, as Idunn's systemd unit would. */
function openAuthority(
context: TestContext,
set: FixtureSet,
options: { machineId?: "etc" | "dbus"; lease?: string } = {},
): IdunnRuntimeAuthority {
const facts = expectedFacts(set);
const originalEnvironment = { ...process.env };
context.after(() => {
for (const key of Object.keys(process.env)) if (!(key in originalEnvironment)) delete process.env[key];
Object.assign(process.env, originalEnvironment);
});
process.env.GAMECULT_IDUNN_RUNTIME_BUNDLE = path.join(FIXTURES, set);
process.env.GAMECULT_IDUNN_CANDIDATE_BIND = facts.candidate;
if (options.lease) process.env.GAMECULT_IDUNN_PROCESS_WRITE_LEASE = path.join(FIXTURES, set, options.lease);
else delete process.env.GAMECULT_IDUNN_PROCESS_WRITE_LEASE;
process.env.LISTEN_PID = String(process.pid);
process.env.LISTEN_FDS = "2";
process.env.LISTEN_FDNAMES = "gamecult-idunn-runtime-activation-key:gamecult-runtime-presence-identity";
const machineId = fs.readFileSync(path.join(FIXTURES, "machine-id"), "utf8");
const realReadFileSync = fs.readFileSync.bind(fs);
context.mock.method(fs, "readFileSync", (filePath: fs.PathOrFileDescriptor, ...args: unknown[]) => {
if (filePath === 3) return fixtureFile(set, "activation-key.seed");
if (filePath === 4) return fixtureFile(set, "provider-identity.credential");
if (filePath === "/etc/machine-id") {
if (options.machineId === "dbus") throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
return machineId;
}
if (filePath === "/var/lib/dbus/machine-id") return machineId;
return realReadFileSync(filePath as never, ...args as never[]);
});
return loadIdunnRuntimeAuthorityFromEnvironment(facts.target, facts.healthContract);
}
function signerFor(context: TestContext, set: FixtureSet, capacityDelta = 0, lease?: string) {
const authority = openAuthority(context, set, { lease });
const capabilities = expectedFacts(set).capabilities.map((capability) => ({ ...capability, capacity: capability.capacity + capacityDelta }));
return createIdunnRuntimeSigner({ authority, capabilities });
}
function freezeClock(context: TestContext): void {
context.mock.method(Date, "now", () => OBSERVED_AT);
}
test("matches systemd's host PID for the private PID namespace init only", () => {
assert.equal(systemdListenPidMatches(String(process.pid), process.pid), true);
@ -34,216 +102,290 @@ test("matches systemd's host PID for the private PID namespace init only", () =>
assert.equal(systemdListenPidMatches(undefined, 1), false);
});
test("opens Idunn identity and publishes valid CultNet data while surfacing piggybacked application errors", async (context) => {
const directory = mkdtempSync(path.join(os.tmpdir(), "cultnet-runtime-authority-"));
context.after(() => rmSync(directory, { recursive: true, force: true }));
for (const set of ["web", "service", "rudp-route"] as const) {
test(`opens the Rust-written ${set} bundle with no Odin endpoint, whatever route transport Rust admits`, (context) => {
const authority = openAuthority(context, set);
assert.equal(authority.expected.target, expectedFacts(set).target);
assert.equal(authority.boundEndpoint, expectedFacts(set).candidate);
});
}
const machineId = "1234567890abcdef1234567890abcdef";
const providerSeed = Buffer.alloc(32, 0x31);
const activationSeed = Buffer.alloc(32, 0x72);
const providerKey = privateKeyFromSeed(providerSeed);
const activationKey = privateKeyFromSeed(activationSeed);
const providerPublic = rawPublic(crypto.createPublicKey(providerKey));
const activationPublic = rawPublic(crypto.createPublicKey(activationKey));
const providerIdentity = sha256Hex(Buffer.concat([providerIdDomain, providerPublic]));
const activationIdentity = sha256Hex(Buffer.concat([activationIdDomain, activationPublic]));
const target = "streampixels-service";
const contract = "streampixels-service.cultnet-rudp-service-health";
const candidate = "http://127.0.0.1:18831";
const expectedValues = [
"idunn.expected_incarnation.v2", target, `sha256-${"1".repeat(64)}`, "incarnation-1",
`sha256-${"2".repeat(64)}`, "https://github.com/GameCult/StreamPixels.git", "a".repeat(40),
`sha256-${"3".repeat(64)}`, "streampixels-service-yggdrasil", providerIdentity, contract,
`sha256-${"4".repeat(64)}`, "service-boundary-v1", `sha256-${"5".repeat(64)}`, false,
["streampixels-service-http", "http", "http://127.0.0.1:8831", candidate],
[["streampixels.service.api", "streampixels.api.v1", "v1", 1]],
[["shared-infrastructure", "odin.verse-rendezvous", "odin.verse-topology.v1", "v1", 1, "before-promotion", null, null, null, "rudp://127.0.0.1:17871"]],
test("a target that declares no Odin dependency opens and answers Idunn's challenge without one", (context) => {
assert.equal(expectedFacts("web").odinEndpoint, undefined);
const signer = signerFor(context, "web");
assert.ok(signer.answerRouteObservation(CHALLENGE).payload.byteLength > 0);
});
test("finds the machine-id at the dbus path when /etc/machine-id is absent, as the Rust protector does", (context) => {
assert.ok(openAuthority(context, "web", { machineId: "dbus" }));
});
test("rejects a credential file holding more than one envelope", (context) => {
openAuthority(context, "web");
const credential = decode(fixtureFile("web", "provider-identity.credential")) as unknown[][];
const doubled = Buffer.from(encode([...credential, ...credential]));
const realReadFileSync = fs.readFileSync;
context.mock.method(fs, "readFileSync", (filePath: fs.PathOrFileDescriptor, ...args: unknown[]) =>
filePath === 4 ? doubled : (realReadFileSync as (...all: unknown[]) => unknown)(filePath, ...args));
assert.throws(
() => loadIdunnRuntimeAuthorityFromEnvironment(expectedFacts("web").target, expectedFacts("web").healthContract),
/exactly one envelope/,
);
});
function presenceSlot(document: { payload: Uint8Array }, slot: number): unknown {
return (decode(document.payload) as unknown[])[slot];
}
// Every committed vector is asserted, dead ones included. Stateless sets sign at sequence 1.
for (const set of ["web", "service", "rudp-route"] as const) {
test(`${set}: a fresh signer answers warming, and after reportHealth('active') answers active, byte for byte as the Rust-verified vectors`, (context) => {
freezeClock(context);
assert.deepEqual(Buffer.from(signerFor(context, set).answerRouteObservation(CHALLENGE).payload), fixtureFile(set, "presence-warming.bin"));
const active = signerFor(context, set);
active.reportHealth("active");
assert.deepEqual(Buffer.from(active.answerRouteObservation(CHALLENGE).payload), fixtureFile(set, "presence-active.bin"));
});
}
test("a shortfall signs the vector Rust correlates to the typed capacity disagreement", (context) => {
freezeClock(context);
const signer = signerFor(context, "service", -1);
signer.reportHealth("active");
assert.deepEqual(Buffer.from(signer.answerRouteObservation(CHALLENGE).payload), fixtureFile("service", "presence-active-below-minimum.bin"));
});
test("the signer owns the reported health: a fresh signer is warming, reportHealth moves it, the sequence keeps counting", (context) => {
freezeClock(context);
const signer = signerFor(context, "web");
const first = signer.answerRouteObservation(CHALLENGE);
assert.equal(presenceSlot(first, RUNTIME_PRESENCE_SLOT.state), "warming");
assert.equal(presenceSlot(signer.sign("detail").document, RUNTIME_PRESENCE_SLOT.state), "warming");
signer.reportHealth("active");
const third = signer.answerRouteObservation(CHALLENGE);
assert.equal(presenceSlot(third, RUNTIME_PRESENCE_SLOT.state), "active");
assert.equal(presenceSlot(third, RUNTIME_PRESENCE_SLOT.publisherSequence), 3);
});
test("answers locally: synchronously, with no socket opened", (context) => {
const sockets = context.mock.method(dgram, "createSocket");
const signer = signerFor(context, "web");
const answer = signer.answerRouteObservation(CHALLENGE);
assert.equal(typeof (answer as unknown as { then?: unknown }).then, "undefined");
assert.equal(sockets.mock.callCount(), 0);
});
test("refuses a challenge that does not ask for this runtime's presence", (context) => {
const signer = signerFor(context, "web");
const cases: CultNetSnapshotRequestMessage[] = [
{ ...CHALLENGE, messageId: "" },
{ ...CHALLENGE, messageId: " padded" },
{ ...CHALLENGE, recordKeys: ["another-target"] },
{ ...CHALLENGE, schemaIds: ["another.schema"] },
{ ...CHALLENGE, shardId: "shard-1" },
];
const expectedBytes = Buffer.from(encode(expectedValues));
const expectedSha = `sha256-${sha256Hex(expectedBytes)}`;
const activationValues = [
"idunn.runtime_activation.v2", expectedSha, "streampixels-service-yggdrasil",
`sha256-${"6".repeat(64)}`, activationIdentity, activationPublic, Date.now(),
"7".repeat(64), "ed25519", new Uint8Array(64).fill(0x17),
];
const bundle = path.join(directory, "bundle");
fs.mkdirSync(bundle);
writeFileSync(path.join(bundle, "expected.cc"), cultCacheStore(
"idunn.expected_incarnation", "idunn.expected_incarnation.v2", "expected", expectedBytes,
));
writeFileSync(path.join(bundle, "activation.cc"), cultCacheStore(
"idunn.runtime_activation", "idunn.runtime_activation.v2", "activation", Buffer.from(encode(activationValues)),
));
const binding = `${providerIdentityContext}:machine-id-sha256:${sha256Hex(Buffer.from(machineId))}`;
const mask = crypto.createHash("sha256").update(Buffer.concat([
Buffer.from("gamecult-linux-service-seed-v1\0", "utf8"),
Buffer.from(providerIdentityContext, "utf8"),
Buffer.from(binding, "utf8"),
])).digest();
const protectedSeed = Buffer.from(providerSeed.map((value, index) => value ^ mask[index]!));
const providerCredential = Buffer.from(encode([[
"gamecult-provider-health-identity",
"gamecult.provider_health_identity.private.v1",
Buffer.from(encode([
"gamecult.provider_health_identity.private.v1", providerIdentity, Array.from(providerPublic), Array.from(protectedSeed),
"linux_file_mode_machine_id_binding", binding, "v1", "os_installation_file_bound_cloneable_baseline",
"2026-01-01T00:00:00Z", Array(32).fill(0x2a),
])),
"2026-01-01T00:00:00Z",
"gamecult.provider_health_identity.private.v1",
]]));
let descriptorProviderCredential = providerCredential;
const originalEnvironment = { ...process.env };
context.after(() => {
for (const key of Object.keys(process.env)) if (!(key in originalEnvironment)) delete process.env[key];
Object.assign(process.env, originalEnvironment);
});
process.env.GAMECULT_IDUNN_RUNTIME_BUNDLE = bundle;
process.env.GAMECULT_IDUNN_CANDIDATE_BIND = candidate;
delete process.env.GAMECULT_IDUNN_PROCESS_WRITE_LEASE;
process.env.LISTEN_PID = String(process.pid);
process.env.LISTEN_FDS = "2";
process.env.LISTEN_FDNAMES = "gamecult-idunn-runtime-activation-key:gamecult-runtime-presence-identity";
const realReadFileSync = fs.readFileSync.bind(fs);
mock.method(fs, "readFileSync", (filePath: fs.PathOrFileDescriptor, ...args: unknown[]) => {
if (filePath === 3) return activationSeed;
if (filePath === 4) return descriptorProviderCredential;
if (filePath === "/etc/machine-id") return machineId;
return realReadFileSync(filePath as never, ...args as never[]);
});
const authority = loadIdunnRuntimeAuthorityFromEnvironment(target, contract, "127.0.0.1:17871");
const signed = signIdunnRuntimePresence(authority, {
capabilities: [{ capability: "streampixels.service.api", schema: "streampixels.api.v1", compatibility: "v1", capacity: 1 }],
state: "warming",
detail: "starting",
writeLeaseSha256: null,
publisherSequence: 1,
observedAtUnixMillis: Date.now(),
});
const proof = runtimePresenceProofPayload(signed.presence);
assert.equal((decode(expectedBytes) as unknown[]).length, 18);
assert.equal(signed.presence.expectedProjectionSha256, expectedSha);
const signedRecord = decode(signed.payload) as unknown[];
assert.equal(crypto.verify(null, runtimePresenceProviderSigningMessage(proof), crypto.createPublicKey(providerKey), Buffer.from(signedRecord[21] as Uint8Array)), true);
assert.equal(crypto.verify(null, runtimePresenceActivationSigningMessage(proof), crypto.createPublicKey(activationKey), Buffer.from(signedRecord[23] as Uint8Array)), true);
for (const request of cases) assert.throws(() => signer.answerRouteObservation(request), /route observation/);
});
async function startOdinPeer(context: TestContext, endpoint: string) {
const peer = dgram.createSocket("udp4");
const port = Number(endpoint.slice(endpoint.lastIndexOf(":") + 1));
await new Promise<void>((resolve, reject) => {
peer.once("error", reject);
peer.bind(0, "127.0.0.1", resolve);
peer.bind(port, "127.0.0.1", resolve);
});
context.after(() => peer.close());
const address = peer.address();
assert.equal(typeof address, "object");
const peerPort = (address as { port: number }).port;
const state = {
reject: false,
received: [] as Array<{ schemaVersion?: string; document: { payload: Uint8Array } }>,
responseAck: undefined as number | undefined,
failure: undefined as Error | undefined,
};
let serverSession: CultNetRudpSession | undefined;
let receivedMessage: unknown;
let responseAck: number | undefined;
let peerFailure: Error | undefined;
let rejectOdinAdmission = true;
peer.on("message", (wire, remote) => {
try {
const packet = decodeRudpPacket(wire);
if (packet.packetType === "connect") {
serverSession = new CultNetRudpSession({ connectionId: packet.connectionId, initialSequence: 100 });
const accept = serverSession.acceptConnect(packet, Date.now());
peer.send(encodeRudpPacket(accept), remote.port, remote.address);
peer.send(encodeRudpPacket(serverSession.acceptConnect(packet, Date.now())), remote.port, remote.address);
return;
}
if (!serverSession) throw new Error("Publisher sent data before establishing RUDP.");
const result = serverSession.receive(packet, Date.now());
const frame = result.delivered.find((candidate) => candidate.channelId === "schema");
const frame = serverSession.receive(packet, Date.now()).delivered.find((candidate) => candidate.channelId === "schema");
if (!frame) return;
receivedMessage = decode(frame.payload);
const responsePayload = encode(rejectOdinAdmission ? {
schemaVersion: "cultnet.error.v0",
error: "test admission denied",
routingHint: null,
code: null,
details: null,
} : {
schemaVersion: "cultnet.snapshot_response.v0",
messageId: "ack",
documents: [],
});
const [response] = serverSession.sendMany("schema", responsePayload, {
state.received.push(decode(frame.payload) as never);
const [response] = serverSession.sendMany("schema", encode(state.reject
? { schemaVersion: "cultnet.error.v0", error: "test admission denied", routingHint: null, code: null, details: null }
: { schemaVersion: "cultnet.snapshot_response.v0", messageId: "ack", documents: [] }), {
reliable: true,
ordered: true,
nowMs: Date.now(),
});
assert.ok(response);
responseAck = response.ack;
state.responseAck = response.ack;
peer.send(encodeRudpPacket(response), remote.port, remote.address);
} catch (error) {
peerFailure = error as Error;
state.failure = error as Error;
}
});
const publisher = createIdunnRuntimePresencePublisher({
authority,
endpoint: `rudp://127.0.0.1:${peerPort}`,
healthContract: contract,
capabilities: [{ capability: "streampixels.service.api", schema: "streampixels.api.v1", compatibility: "v1", capacity: 1 }],
});
await assert.rejects(publisher.publish("warming", "local RUDP integration"), /Odin rejected runtime presence: test admission denied/);
assert.deepEqual(receivedMessage && (receivedMessage as { schemaVersion?: string }).schemaVersion, "cultnet.document_put_raw.v0");
assert.ok(Buffer.from(encode(receivedMessage)).byteLength > 0);
assert.equal(responseAck, 2, "the response data packet must acknowledge the publisher's reliable schema packet");
assert.equal(peerFailure, undefined, peerFailure?.message);
assert.equal(publisher.latestPresenceDocument(), null, "a rejected publication must not become the snapshot source");
return state;
}
rejectOdinAdmission = false;
await publisher.publish("warming", "accepted presence");
const snapshotDocument = await publisher.publishRouteObservation({
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "route-challenge-1",
schemaIds: ["gamecult.runtime_presence_health.v2"],
recordKeys: [target],
});
const publishedDocument = (receivedMessage as { document: { payload: Uint8Array; schemaId: string; recordKey: string } }).document;
assert.ok(snapshotDocument);
assert.equal(snapshotDocument.schemaId, publishedDocument.schemaId);
assert.equal(snapshotDocument.recordKey, publishedDocument.recordKey);
assert.deepEqual(Buffer.from(snapshotDocument.payload), Buffer.from(publishedDocument.payload));
const routePresence = decode(snapshotDocument.payload) as unknown[];
assert.equal(routePresence[RUNTIME_PRESENCE_SLOT.state], "active");
assert.equal(routePresence[RUNTIME_PRESENCE_SLOT.detail], "route-observation:route-challenge-1");
snapshotDocument.payload[0] = snapshotDocument.payload[0]! ^ 0xff;
assert.deepEqual(Buffer.from(publisher.latestPresenceDocument()?.payload ?? []), Buffer.from(publishedDocument.payload), "snapshot readers cannot mutate publisher state");
test("the Odin publisher publishes the signer's document to the endpoint Expected names and surfaces Odin's rejection", async (context) => {
const endpoint = expectedFacts("service").odinEndpoint!;
const peer = await startOdinPeer(context, endpoint);
const signer = signerFor(context, "service");
const publisher = createIdunnRuntimePresencePublisher({ signer, endpoint });
descriptorProviderCredential = Buffer.from(encode([
...(decode(providerCredential) as unknown[][]),
...(decode(providerCredential) as unknown[][]),
]));
await publisher.publish("accepted presence");
assert.equal(peer.received[0]?.schemaVersion, "cultnet.document_put_raw.v0");
const published = decode(peer.received[0]!.document.payload) as unknown[];
assert.equal(published[RUNTIME_PRESENCE_SLOT.state], "warming");
assert.equal(published[RUNTIME_PRESENCE_SLOT.publisherSequence], 1);
assert.equal(peer.responseAck, 2, "the response data packet must acknowledge the publisher's reliable schema packet");
peer.reject = true;
signer.reportHealth("active");
await assert.rejects(publisher.publish("denied presence"), /Odin rejected runtime presence: test admission denied/);
assert.equal(peer.failure, undefined, peer.failure?.message);
});
test("the Odin publisher leaves a capability shortfall to the Rust correlation instead of refusing to publish", async (context) => {
const endpoint = expectedFacts("service").odinEndpoint!;
const peer = await startOdinPeer(context, endpoint);
const signer = signerFor(context, "service", -1);
signer.reportHealth("active");
const publisher = createIdunnRuntimePresencePublisher({ signer, endpoint });
await publisher.publish("below minimum");
const capabilities = (decode(peer.received[0]!.document.payload) as unknown[])[RUNTIME_PRESENCE_SLOT.capabilities] as unknown[][];
assert.equal(capabilities[0]![3], expectedFacts("service").capabilities[0]!.capacity - 1);
});
test("the Odin publisher takes only the endpoint Expected names", (context) => {
assert.throws(
() => loadIdunnRuntimeAuthorityFromEnvironment(target, contract, "127.0.0.1:17871"),
/exactly one envelope/,
() => createIdunnRuntimePresencePublisher({ signer: signerFor(context, "service"), endpoint: "rudp://127.0.0.1:1" }),
/does not match Idunn Expected dependency authority/,
);
assert.throws(
() => createIdunnRuntimePresencePublisher({ signer: signerFor(context, "web"), endpoint: expectedFacts("service").odinEndpoint! }),
/does not match Idunn Expected dependency authority/,
);
});
function cultCacheStore(type: string, schemaId: string, key: string, payload: Uint8Array): Buffer {
return Buffer.from(encode([
"cultcache.store.v1",
[[schemaId, type, `${schemaId}.v1`, schemaId, "{}", [schemaId], []]],
[[key, schemaId, "2026-01-01T00:00:00Z", payload]],
]));
test("the route answer and the Odin publish report the same state, because the signer owns it", async (context) => {
const endpoint = expectedFacts("service").odinEndpoint!;
const peer = await startOdinPeer(context, endpoint);
const signer = signerFor(context, "service");
const publisher = createIdunnRuntimePresencePublisher({ signer, endpoint });
await publisher.publish("first");
assert.equal(presenceSlot(peer.received[0]!.document, RUNTIME_PRESENCE_SLOT.state), "warming");
assert.equal(presenceSlot(signer.answerRouteObservation(CHALLENGE), RUNTIME_PRESENCE_SLOT.state), "warming");
signer.reportHealth("active");
await publisher.publish("second");
assert.equal(presenceSlot(peer.received[1]!.document, RUNTIME_PRESENCE_SLOT.state), "active");
assert.equal(presenceSlot(signer.answerRouteObservation(CHALLENGE), RUNTIME_PRESENCE_SLOT.state), "active");
// One sequence across both carriers.
assert.deepEqual(
[1, 2].map((index) => presenceSlot(peer.received[index - 1]!.document, RUNTIME_PRESENCE_SLOT.publisherSequence)),
[1, 3],
);
});
// The lease-bound set: Rust issued lease.cc naming presence-warming.bin's digest, and presence-active.bin
// and presence-degraded.bin carry that lease's digest.
const STATEFUL = "service-stateful" as const;
function statefulSigner(context: TestContext, lease = "lease.cc") {
return signerFor(context, STATEFUL, 0, lease);
}
function privateKeyFromSeed(seed: Uint8Array): crypto.KeyObject {
return crypto.createPrivateKey({
key: Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), Buffer.from(seed)]),
format: "der",
type: "pkcs8",
test("stateful: warming, active and degraded match the Rust-verified vectors byte for byte, active and degraded bound to Rust's lease", (context) => {
freezeClock(context);
const signer = statefulSigner(context);
assert.deepEqual(Buffer.from(signer.answerRouteObservation(CHALLENGE).payload), fixtureFile(STATEFUL, "presence-warming.bin"));
signer.reportHealth("active");
const active = signer.answerRouteObservation(CHALLENGE);
assert.deepEqual(Buffer.from(active.payload), fixtureFile(STATEFUL, "presence-active.bin"));
assert.match(String(presenceSlot(active, RUNTIME_PRESENCE_SLOT.writeLeaseSha256)), /^sha256-[0-9a-f]{64}$/);
const degraded = statefulSigner(context);
degraded.answerRouteObservation(CHALLENGE);
degraded.reportHealth("degraded");
assert.deepEqual(Buffer.from(degraded.answerRouteObservation(CHALLENGE).payload), fixtureFile(STATEFUL, "presence-degraded.bin"));
});
test("a warming answered on the route becomes a lease target: Rust's lease naming it is accepted", (context) => {
freezeClock(context);
const signer = statefulSigner(context);
assert.throws(() => signer.assertWriteLease(), /does not match the current Expected incarnation/, "no warming signed yet");
signer.answerRouteObservation(CHALLENGE);
assert.equal(signer.assertWriteLease(), presenceSlot({ payload: fixtureFile(STATEFUL, "presence-active.bin") }, RUNTIME_PRESENCE_SLOT.writeLeaseSha256));
});
for (const [lease, reason] of [["lease-other-warming.cc", "names another warming digest"], ["lease-other-target.cc", "names another target"]] as const) {
test(`a lease that ${reason} is refused, and the signer cannot sign active under it`, (context) => {
freezeClock(context);
const signer = statefulSigner(context, lease);
signer.answerRouteObservation(CHALLENGE);
assert.throws(() => signer.assertWriteLease(), /does not match the current Expected incarnation/);
signer.reportHealth("active");
assert.throws(() => signer.answerRouteObservation(CHALLENGE), /does not match the current Expected incarnation/);
});
}
function rawPublic(key: crypto.KeyObject): Buffer {
const der = key.export({ type: "spki", format: "der" }) as Buffer;
return Buffer.from(der.subarray(der.length - 32));
test("one signer per loaded authority", (context) => {
const authority = openAuthority(context, "web");
const capabilities = expectedFacts("web").capabilities;
createIdunnRuntimeSigner({ authority, capabilities });
assert.throws(() => createIdunnRuntimeSigner({ authority, capabilities }), /already has a signer/);
});
test("the loaded authority is frozen: the bind, contract, Expected, Activation and identities cannot change, and the signer signs the loaded endpoint", (context) => {
freezeClock(context);
const authority = openAuthority(context, "web");
const loaded = expectedFacts("web");
const open = authority as unknown as { [key: string]: any };
const mutations: Array<[string, () => void]> = [
["boundEndpoint", () => { open.boundEndpoint = "http://127.0.0.1:1"; }],
["providerSignerIdentityId", () => { open.providerSignerIdentityId = "forged"; }],
["activationSignerIdentityId", () => { open.activationSignerIdentityId = "forged"; }],
["expected.healthContract", () => { open.expected.healthContract = "forged"; }],
["expected.target", () => { open.expected.target = "forged"; }],
["expected.canonicalSha256", () => { open.expected.canonicalSha256 = "forged"; }],
["expected.route.candidateEndpoint", () => { open.expected.route.candidateEndpoint = "http://127.0.0.1:1"; }],
["expected.capabilities[0].minimumCapacity", () => { open.expected.capabilities[0].minimumCapacity = 0; }],
["expected.capabilities.push", () => { open.expected.capabilities.push({}); }],
["activation.runtimeInstanceId", () => { open.activation.runtimeInstanceId = "forged"; }],
["activation.canonicalSha256", () => { open.activation.canonicalSha256 = "forged"; }],
];
for (const [name, mutate] of mutations) assert.throws(mutate, TypeError, name);
const signer = createIdunnRuntimeSigner({ authority, capabilities: loaded.capabilities });
assert.equal(presenceSlot(signer.answerRouteObservation(CHALLENGE), RUNTIME_PRESENCE_SLOT.boundEndpoint), loaded.candidate);
});
// A web app must be able to import the signer without pulling the Odin publisher and dgram.
function loadUnderDgramStub(modulePath: string): string {
const script = [
'const Module = require("node:module");',
"const load = Module._load;",
'Module._load = function (request, ...rest) { if (request === "dgram" || request === "node:dgram") throw new Error("dgram was loaded"); return load.call(this, request, ...rest); };',
"try { require(process.argv[1]); process.stdout.write(\"loaded\"); } catch (error) { process.stdout.write(\"failed: \" + error.message); }",
].join("\n");
const run = spawnSync(process.execPath, ["-e", script, modulePath], { encoding: "utf8" });
assert.equal(run.status, 0, run.stderr);
return run.stdout;
}
function sha256Hex(bytes: Uint8Array): string {
return crypto.createHash("sha256").update(bytes).digest("hex");
}
test("the idunn-runtime subpath loads the signer and loader without loading dgram; the package root does load it", () => {
const subpath = require.resolve("cultnet-ts/idunn-runtime");
assert.equal(loadUnderDgramStub(subpath), "loaded");
assert.equal(loadUnderDgramStub(require.resolve("cultnet-ts")), "failed: dgram was loaded");
const exported = require(subpath) as Record<string, unknown>;
assert.equal(typeof exported.createIdunnRuntimeSigner, "function");
assert.equal(typeof exported.loadIdunnRuntimeAuthorityFromEnvironment, "function");
assert.equal(exported.createIdunnRuntimePresencePublisher, undefined);
assert.equal(exported.signIdunnRuntimePresence, undefined);
assert.equal((require("cultnet-ts") as Record<string, unknown>).signIdunnRuntimePresence, undefined);
});