cultnet-rs fixture example: add a lease-bound stateful Idunn set

Adds `service-stateful` (streampixels-service shape, state lineage set,
write_lease_required) with a Rust-issued lease.cc, warming/active/degraded
vectors bound to it, and two wrong leases (other warming digest, other target).
Each vector still passes authenticate + correlate before it is written.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 03:05:12 +02:00
commit 3c9ce3823c
2 changed files with 111 additions and 26 deletions

View file

@ -11,6 +11,13 @@
//! 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.
//!
@ -26,9 +33,10 @@ use cultcache_rs::{
use cultnet_rs::{
GameCultProviderHealthIdentity, GameCultRuntimeCapability,
GameCultRuntimePresenceHealthPurpose, GameCultRuntimePresenceHealthRecord,
IDUNN_EXPECTED_INCARNATION_SCHEMA, IDUNN_RUNTIME_ACTIVATION_SCHEMA,
IdunnExpectedCapability, IdunnExpectedDependency, IdunnExpectedIncarnationRecord,
IdunnExpectedRoute, IdunnRuntimeActivationLaunch, IdunnRuntimeActivationRecord,
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,
@ -49,9 +57,10 @@ struct Set {
candidate: &'static str,
minimum_capacity: u32,
odin_dependency: bool,
stateful: bool,
}
const SETS: [Set; 3] = [
const SETS: [Set; 4] = [
Set {
dir: "web",
target: "streampixels-web",
@ -60,6 +69,7 @@ const SETS: [Set; 3] = [
candidate: "http://127.0.0.1:18830",
minimum_capacity: 1,
odin_dependency: false,
stateful: false,
},
Set {
dir: "service",
@ -69,6 +79,17 @@ const SETS: [Set; 3] = [
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",
@ -78,6 +99,7 @@ const SETS: [Set; 3] = [
candidate: "rudp://127.0.0.1:18832",
minimum_capacity: 1,
odin_dependency: false,
stateful: false,
},
];
@ -131,9 +153,9 @@ fn build(
expected_signer_identity_id: provider_anchor.identity_id.clone(),
health_contract: format!("{}.runtime-health", set.target),
artifact_sha256: digest('4'),
state_schema_generation: None,
state_contract_sha256: None,
write_lease_required: false,
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(),
@ -189,20 +211,13 @@ fn build(
let authority =
verify_runtime_authority(&expected, &activation, idunn_anchor, &provider_anchor.public_key)?;
let vectors = [
("presence-active.bin", "active", set.minimum_capacity, None),
("presence-warming.bin", "warming", set.minimum_capacity, None),
(
"presence-active-below-minimum.bin",
"active",
set.minimum_capacity.saturating_sub(1),
Some("expected-capability-000-capacity"),
),
];
for (name, state, capacity, disagreement) in vectors {
if capacity == 0 {
continue;
}
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(),
@ -211,8 +226,8 @@ fn build(
incarnation_id: expected.incarnation_id.clone(),
sealed_release_id: expected.sealed_release_id.clone(),
activation_witness_sha256: activation.canonical_sha256()?,
state_schema_generation: None,
state_contract_sha256: None,
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()),
@ -225,9 +240,9 @@ fn build(
health_contract: expected.health_contract.clone(),
state: state.into(),
detail: DETAIL.into(),
write_lease_sha256: None,
write_lease_sha256,
signer_identity_id: provider_anchor.identity_id.clone(),
publisher_sequence: SEQUENCE,
publisher_sequence: sequence,
observed_at_unix_millis: OBSERVED_AT,
signature_algorithm: "ed25519".into(),
signature: Vec::new(),
@ -250,6 +265,7 @@ fn build(
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()
@ -261,6 +277,69 @@ fn build(
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(())
}

View file

@ -8,8 +8,14 @@ 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. Every `presence-*.bin` was accepted by
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.