Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,13 @@ jobs:
AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-ar
run: cargo check --workspace --all-features --locked --target aarch64-unknown-linux-gnu

# Advisory zero-leak guard that nexum-runtime stays venue-agnostic: crate
# graph, charter symbol scan, and nexum:host WIT leaf-ness
# (scripts/check-venue-agnostic.sh). `continue-on-error` keeps this a
# signal, not a gate; it flips to a required check at the physical repo cut.
# Blocking zero-leak gate: host-layer crate graphs stay venue-free, the
# runtime Rust sources carry no charter symbol and no privileged router
# field, and nexum:host names no foreign WIT package and resolves as a
# leaf (scripts/check-venue-agnostic.sh).
venue-agnostic:
name: venue-agnostic (advisory)
name: venue-agnostic
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: ./.github/actions/rust-setup
Expand Down
155 changes: 155 additions & 0 deletions crates/videre-host/tests/zero_leak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! Zero-leak oracle: the host boots the echo venue and routes a worker's
//! submission purely through the generic extension seam, while the
//! `nexum-runtime` crate graph reaches no venue-shaped crate.

use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;

use nexum_runtime::bindings::nexum;
use nexum_runtime::engine_config::{AdapterEntry, EngineConfig, ModuleEntry};
use nexum_runtime::host::component::ChainMethod;
use nexum_runtime::host::extension::Extension;
use nexum_runtime::supervisor::{Supervisor, build_linker};
use nexum_runtime::test_utils::{
MockChainProvider, MockStateStore, MockTypes, mock_components_from,
};
use videre_host::{VenueRegistry, platform};

/// Workspace-root-relative path. `CARGO_MANIFEST_DIR` is
/// `crates/videre-host`; two parents up is the workspace root.
fn workspace_path(relative: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates dir")
.parent()
.expect("workspace root")
.join(relative)
}

/// Path to a module's `.wasm` artefact under the workspace target dir.
/// A missing artefact is a hard failure under CI (the gate may not skip
/// itself) and a soft skip locally.
fn module_wasm_or_skip(module_name: &str) -> Option<PathBuf> {
let artifact = module_name.replace('-', "_");
let p = workspace_path(&format!("target/wasm32-wasip2/release/{artifact}.wasm"));
if p.exists() {
return Some(p);
}
assert!(
std::env::var_os("CI").is_none(),
"{} must be prebuilt in CI",
p.display()
);
eprintln!(
"SKIP: {} not found - build with `cargo build -p {module_name} --target wasm32-wasip2 --release`",
p.display()
);
None
}

/// The boot oracle: the venue adapter installs and a worker's submission
/// reaches it, with the platform supplied only as a generic extension.
#[tokio::test]
async fn e2e_echo_venue_boots_and_submits_through_the_generic_seam() {
let (Some(adapter_wasm), Some(module_wasm)) = (
module_wasm_or_skip("echo-venue"),
module_wasm_or_skip("echo-client"),
) else {
return;
};

// The adapter reads eth_blockNumber on submit to justify its `chain`
// grant; program the mock so that read succeeds.
let chain = MockChainProvider::new();
chain.on_method(ChainMethod::EthBlockNumber, "\"0x1\"");
let components = mock_components_from(chain, MockStateStore::new());

let mut engine_config = wasmtime::Config::new();
engine_config.wasm_component_model(true);
engine_config.consume_fuel(true);
let engine = wasmtime::Engine::new(&engine_config).expect("wasmtime engine");

let config = EngineConfig {
adapters: vec![AdapterEntry {
path: adapter_wasm,
manifest: Some(workspace_path("modules/examples/echo-venue/module.toml")),
http_allow: Vec::new(),
messaging_topics: Vec::new(),
}],
modules: vec![ModuleEntry {
path: module_wasm,
manifest: Some(workspace_path("modules/examples/echo-client/module.toml")),
}],
..Default::default()
};
let extensions: Vec<Arc<dyn Extension<MockTypes>>> = vec![Arc::new(platform(&config))];
let linker = build_linker::<MockTypes>(&engine, &extensions).expect("build_linker");

let mut supervisor =
Supervisor::boot(&engine, &linker, &config, &components, &extensions, None)
.await
.expect("boot");
assert_eq!(supervisor.adapter_alive_count(), 1, "echo-venue installed");
assert_eq!(supervisor.alive_count(), 1, "echo-client alive");

// One block drives the worker's on_block submission; the registry the
// extension published on the service map observes the accepted receipt.
let block = nexum::host::types::Block {
chain_id: 1,
number: 19_000_000,
hash: vec![0xab; 32],
timestamp: 1_700_000_000_000,
};
assert_eq!(supervisor.dispatch_block(block).await, 1);
let registry = supervisor
.services()
.get::<VenueRegistry>(VenueRegistry::NAMESPACE)
.expect("registry service");
let updates = registry.poll_status_transitions().await;
assert!(
updates.iter().any(|u| u.venue == "echo-venue"),
"the submission reached the venue; updates were: {updates:?}"
);
}

/// The graph oracle: `cargo tree` for the host crate (normal + build
/// edges) names no videre, intent, venue, or cow crate.
#[test]
fn host_crate_graph_reaches_no_venue_shaped_crate() {
let output = Command::new(env!("CARGO"))
.args([
"tree",
"-p",
"nexum-runtime",
"-e",
"normal,build",
"--all-features",
"--prefix",
"none",
"--locked",
])
.current_dir(workspace_path(""))
.output()
.expect("cargo tree runs");
assert!(
output.status.success(),
"cargo tree failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let tree = String::from_utf8_lossy(&output.stdout);
let reached: Vec<&str> = tree
.lines()
.filter_map(|line| line.split_whitespace().next())
.filter(|name| {
let name = name.to_lowercase();
["videre", "intent", "venue", "cow"]
.iter()
.any(|word| name.contains(word))
})
.collect();
assert!(
reached.is_empty(),
"venue-shaped crates reached: {reached:?}"
);
}
5 changes: 3 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ build-e2e: build-m2 build-m3
run-e2e: build-e2e build-engine
cargo run -p shepherd -- --engine-config engine.e2e.toml

# Assert nexum-runtime is venue-agnostic: crate graph, symbol scan, and
# the nexum:host WIT leaf. Advisory in CI until the physical cut lands.
# Zero-leak gate: host-layer crate graphs, runtime charter-symbol and
# router-field scans, and the nexum:host WIT leaf and foreign-namespace
# scans. Blocking in CI.
check-venue-agnostic:
./scripts/check-venue-agnostic.sh

Expand Down
36 changes: 28 additions & 8 deletions scripts/check-venue-agnostic.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#!/usr/bin/env bash
# Zero-leak check for the host layer: no host-layer crate graph
# (runtime, launcher, bare engine) reaches a videre/intent/venue/cow
# crate, the runtime sources carry no charter symbol
# (nexum:intent|value-flow|VenueAdapter|synthesize_venue|nexum:adapter|PoolRouter),
# and nexum:host resolves as a leaf WIT package. Advisory in CI until
# the physical cut lands; run locally via `just check-venue-agnostic`.
# Zero-leak check for the host layer, scoped precisely: no host-layer
# crate graph (runtime, launcher, bare engine) reaches a
# videre/intent/venue/cow crate; the runtime Rust sources carry no
# charter symbol
# (nexum:intent|value-flow|VenueAdapter|synthesize_venue|nexum:adapter|PoolRouter)
# and no privileged router field; and nexum:host names no foreign WIT
# package and resolves as a leaf. The opaque-status envelope
# (intent-status-update, its venue id string) is ratified host surface,
# not a leak. Blocking in CI; run locally via `just check-venue-agnostic`.

set -uo pipefail

Expand Down Expand Up @@ -46,8 +49,25 @@ case $? in
*) fail "symbol scan errored (crates/nexum-runtime/src missing?)" ;;
esac

# 3. WIT DAG: nexum:host is a leaf. No cross-package use/import, and the
# package resolves standalone.
# 3. Privileged-field scan: the venue registry rides the extension
# service map; no router field may return to the runtime.
rg -n --no-heading -e 'venue_registry|pool_router' crates/nexum-runtime/src

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern (venue_registry|pool_router) doesn't match the actual field — videre-host/src/lib.rs holds it as registry: Arc<VenueRegistry> (confirmed at this PR's head commit), not venue_registry, and pool_router was renamed away in #428/#447. This scan can never fail on any commit, past or future, so it blocks CI while providing zero real protection against a router field leaking back into nexum-runtime. Suggest matching the actual current symbol, e.g. \bregistry\s*:\s*Arc<VenueRegistry>|VenueRegistryBuilder or at minimum including VenueRegistry in the pattern.

case $? in
0) fail "a privileged router field returned to nexum-runtime" ;;
1) pass "no privileged router field" ;;
*) fail "field scan errored (crates/nexum-runtime/src missing?)" ;;
esac

# 4. WIT surface: nexum:host is a leaf. No foreign package named
# anywhere in its sources, no cross-package use/import, and the
# package resolves standalone. The opaque-status envelope stays.
wit_charter='nexum:intent|nexum:adapter|value-flow|videre:|shepherd:cow'
rg -n --no-heading -e "$wit_charter" wit/nexum-host
case $? in
0) fail "a foreign WIT namespace leaks into wit/nexum-host" ;;
1) pass "no foreign WIT namespace named" ;;
*) fail "WIT namespace scan errored (wit/nexum-host missing?)" ;;
esac
rg -n --no-heading -e '^\s*(use|import)\s+[a-z0-9-]+:' wit/nexum-host
case $? in
0) fail "nexum:host references another WIT package" ;;
Expand Down
Loading