Skip to content
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ A module built against `nexum:host` runs on any Nexum-compatible host. CoW order
| `nexum/crates/nexum-sdk/` | Guest SDK: host trait seam, bind macro, chain/config/address helpers, `wasi:http` fetch, tracing facade. |
| `videre/crates/videre-sdk/` | Venue-platform SDK: the `videre:venue` client and adapter contracts. |
| `shepherd/crates/cow-venue/` | The bundled CoW venue adapter component. |
| `wit/nexum-host/` | The `nexum:host` WIT package: the host/guest contract. |
| `wit/videre-venue/` | The `videre:venue` WIT package: the venue-adapter contract. |
| `wit/shepherd-cow/` | `cow-events.wit`: the CoW event ABIs of record. |
| `nexum/wit/nexum-host/` | The `nexum:host` WIT package: the host/guest contract. |
| `videre/wit/videre-venue/` | The `videre:venue` WIT package: the venue-adapter contract. |
| `shepherd/wit/shepherd-cow/` | `cow-events.wit`: the CoW event ABIs of record. |
| `modules/` | Guest modules: TWAP and EthFlow keepers, examples, and test fixtures. |
| `docs/` | Architecture and design notes. Start with [`docs/00-overview.md`](docs/00-overview.md). |

Expand Down
31 changes: 13 additions & 18 deletions docs/00-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Nexum is a WASM Component Model runtime that provides secure, sandboxed executio
| Term | What it is | Where you find it |
|---|---|---|
| **engine** (`nexum`) | A concrete implementation that loads and runs WASM components. The 0.2 reference engine is a wasmtime-based server daemon. | `nexum/crates/nexum-runtime/`, the `nexum` binary, `cargo run -p nexum-cli` |
| **host** (`nexum:host`) | The WIT contract: the host-imported interfaces (chain, identity, local-store, ...), types, and worlds that every engine implements and every module imports. | `wit/nexum-host/`, `package nexum:host@0.1.0`, Rust path `nexum::host::*` |
| **host** (`nexum:host`) | The WIT contract: the host-imported interfaces (chain, identity, local-store, ...), types, and worlds that every engine implements and every module imports. | `nexum/wit/nexum-host/`, `package nexum:host@0.1.0`, Rust path `nexum::host::*` |

An engine implements `nexum:host` so that modules built against `nexum:host` can run on it. The reference engine ships as two crates: the `nexum-runtime` library (embeddable, no CLI surface) and the `nexum` binary in `nexum/crates/nexum-cli`. A Rust embedder constructs an `EngineConfig` in code and calls `nexum_runtime::bootstrap::run_from_config`; see `nexum/crates/nexum-runtime/examples/embed.rs`.

Expand Down Expand Up @@ -243,23 +243,18 @@ The `nexum:host` WIT contract is host-portable: any host implementing it can run

```
shepherd/
├── crates/
│ ├── nexum-runtime/ Core WASM host (server) library: event system, local store, bootstrap
│ ├── nexum-cli/ The `nexum` binary: clap CLI over the runtime library
│ ├── nexum-sdk/ Generic guest SDK: host-trait seam, chain/config/address helpers, wasi:http fetch
│ ├── nexum-sdk-test/ Generic mock host for module tests
│ ├── videre-sdk/ Venue + keeper SDK: venue-adapter export trait, typed venue client
│ ├── videre-host/ Host-side venue registry + status watch
│ ├── videre-test/ Venue/keeper test surface
│ └── cow-venue/ The CoW venue: order body types + IntentBody codec
├── modules/
│ ├── twap-monitor/ TWAP order monitoring module
│ ├── ethflow-watcher/ Ethflow order monitoring module
│ └── examples/ reference modules (price-alert, balance-tracker, http-probe, echo-*)
├── wit/
│ ├── nexum-host/ Universal WIT package (chain, identity, local-store, remote-store, messaging, logging)
│ ├── shepherd-cow/ CoW event enum (cow-events)
│ └── videre-venue/ Venue-adapter contract (client + adapter faces)
├── nexum/ L1: universal runtime
│ ├── crates/ nexum-runtime, nexum-cli, nexum-sdk, nexum-sdk-test, ...
│ ├── modules/ example modules (price-alert, balance-tracker, http-probe) + runtime fixtures
│ └── wit/ nexum-host (chain, identity, local-store, remote-store, messaging, logging)
├── videre/ L2: intent/venue platform
│ ├── crates/ videre-sdk, videre-host, videre-test, ...
│ ├── modules/ echo-* reference modules + fixtures
│ └── wit/ videre-types, videre-value-flow, videre-venue; deps/ vendored via wit-deps
├── shepherd/ L3: CoW composition
│ ├── crates/ shepherd, cow-venue, composable-cow
│ ├── modules/ twap-monitor, ethflow-watcher
│ └── wit/ shepherd-cow (cow-events); deps/ vendored via wit-deps
├── Dockerfile
├── docker-compose.yml
└── docs/
Expand Down
2 changes: 1 addition & 1 deletion docs/01-runtime-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ The host uses `wasmtime::component::bindgen!` to generate Rust traits from the W

```rust
wasmtime::component::bindgen!({
path: "wit/nexum-host",
path: "nexum/wit/nexum-host",
world: "event-module",
async: true,
});
Expand Down
2 changes: 1 addition & 1 deletion docs/06-production-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Block following uses `eth_subscribe(newHeads)` on a WebSocket URL and polls `eth

## Error model

Host interfaces surface a common `fault` variant (`wit/nexum-host/types.wit`): `unsupported`, `unavailable`, `denied`, `rate-limited` (carrying `retry-after-ms` guidance), `timeout`, `invalid-input`, `internal`. A fault is a typed, recoverable return the guest can handle; a trap (fuel, memory, panic) is not, and drives the restart path above.
Host interfaces surface a common `fault` variant (`nexum/wit/nexum-host/types.wit`): `unsupported`, `unavailable`, `denied`, `rate-limited` (carrying `retry-after-ms` guidance), `timeout`, `invalid-input`, `internal`. A fault is a typed, recoverable return the guest can handle; a trap (fuel, memory, panic) is not, and drives the restart path above.

## Structured logging

Expand Down
2 changes: 1 addition & 1 deletion docs/07-rpc-namespace-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Modules reach chain state through one host function, `chain.request`, plus a bat

## The WIT interface

`nexum:host/chain` (`wit/nexum-host/chain.wit`):
`nexum:host/chain` (`nexum/wit/nexum-host/chain.wit`):

```wit
interface chain {
Expand Down
35 changes: 20 additions & 15 deletions docs/08-platform-generalisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Layer 1 is the world every module compiles against. A Layer 2 capability adds an

## Layer 1: Universal Interfaces

Six interfaces form the universal runtime contract. The signatures below are the shipped `wit/nexum-host` package.
Six interfaces form the universal runtime contract. The signatures below are the shipped `nexum/wit/nexum-host` package.

### `chain`

Expand Down Expand Up @@ -277,26 +277,31 @@ The retired predecessor to venue adapters was a Layer-3 *world* extension: a `sh
## Complete WIT Package Layout

```
wit/
├── nexum-host/
│ ├── types.wit # chain-id, block, log, tick, message, custom-event, event, config, fault
│ ├── chain.wit # chain interface (request + request-batch)
│ ├── identity.wit # identity interface (accounts, signing)
│ ├── local-store.wit
│ ├── remote-store.wit # Swarm
│ ├── messaging.wit # Waku
│ ├── logging.wit
│ ├── event-module.wit # event-module world (6 imports)
│ └── query-module.wit # experimental: no host impl in 0.2
nexum/wit/
└── nexum-host/
├── types.wit # chain-id, block, log, tick, message, custom-event, event, config, fault
├── chain.wit # chain interface (request + request-batch)
├── identity.wit # identity interface (accounts, signing)
├── local-store.wit
├── remote-store.wit # Swarm
├── messaging.wit # Waku
├── logging.wit
├── event-module.wit # event-module world (6 imports)
└── query-module.wit # experimental: no host impl in 0.2

videre/wit/
├── videre-value-flow/
│ └── types.wit # asset + asset-amount vocabulary
├── videre-types/
│ └── types.wit # intent-header, quotation, receipt, submit-outcome, intent-status, venue-error
├── videre-venue/
│ └── venue.wit # client + adapter interfaces, venue-adapter world
└── shepherd-cow/
└── cow-events.wit # CoW event-ABI package of record (legacy package name)
└── deps/ # nexum-host, vendored via wit-deps

shepherd/wit/
├── shepherd-cow/
│ └── cow-events.wit # CoW event-ABI package of record (legacy package name)
└── deps/ # nexum-host + videre-*, vendored via wit-deps
```

The `nexum-host` package is domain-agnostic. The `videre` packages are the venue-neutral intent contract. `shepherd-cow` carries only the CoW event ABIs. New domains add adapter components, not packages.
Expand Down
2 changes: 1 addition & 1 deletion docs/deployment/multi-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ chain_id = 100

## Event topics

keccak256 of the event signatures, identical on every chain; only the EthFlow `address` changes. Package of record: `wit/shepherd-cow/cow-events.wit`.
keccak256 of the event signatures, identical on every chain; only the EthFlow `address` changes. Package of record: `shepherd/wit/shepherd-cow/cow-events.wit`.

| Event | Topic-0 |
|-------|---------|
Expand Down
30 changes: 24 additions & 6 deletions docs/design/carve-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,27 @@ converges in three steps:

## WIT resolution

WIT stays in a single root `wit/` for this step. `resolve_wit_package`
(`nexum/crates/nexum-world`) walks manifest-dir ancestors to the nearest `wit/`,
so every crate resolves the shared tree regardless of depth. Splitting `wit/` into
per-group `wit/` + `wit/deps/` requires the crate-local wit-deps flip and is done
in #404/#405, not here. The hardcoded `wit_bindgen::generate!` path lists that
bypass the resolver were re-based one level deeper by the move.
Each group owns its WIT: `nexum/wit/nexum-host`, `videre/wit/videre-{types,
value-flow,venue}`, `shepherd/wit/shepherd-cow`. There is no shared root `wit/`.

Cross-group WIT follows the same tier order as the crates and is vendored into
the consuming group's `wit/deps/` by [wit-deps]: `videre/wit/deps.toml` pulls
`nexum-host`; `shepherd/wit/deps.toml` pulls `nexum-host` plus the three
`videre-*` packages. The manifests use path sources into the owning group's
tree, and the checked-in `deps.lock` digests pin the vendored copies. After
editing an owned WIT package, re-run `wit-deps` from each consuming group root
(`videre/`, `shepherd/`) and commit the refreshed `wit/deps` + `deps.lock`.

`resolve_wit_packages` (`nexum/crates/nexum-world`) walks manifest-dir ancestors
to the nearest `wit/` tree and resolves every package there, vendored
`wit/deps/<package>` before owned `wit/<package>`; it never falls through to an
outer tree, so a group cannot use WIT it has not vendored. The hardcoded
`wit_bindgen::generate!`/`bindgen!`/`include_str!` path lists point at the same
group-local trees.

Convergence at the physical carve: the path sources in each `deps.toml` flip to
pinned git-tag tarball URLs of the owning repo (wit-deps `url` + `sha256`
sources), then post-carve to wkg/OCI per-package semver releases. Only the
manifests change; the resolver and the vendored layout stay as they are.

[wit-deps]: https://github.com/bytecodealliance/wit-deps
2 changes: 1 addition & 1 deletion nexum/crates/nexum-runtime/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! so extension services can compare event payloads.

wasmtime::component::bindgen!({
path: ["../../../wit/nexum-host"],
path: ["../../wit/nexum-host"],
world: "nexum:host/event-module",
imports: { default: async },
exports: { default: async },
Expand Down
57 changes: 45 additions & 12 deletions nexum/crates/nexum-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,21 +477,29 @@ pub fn synthesize(declared: &[String], extensions: &[ExtensionRow]) -> Result<Mo
}

/// Resolve each WIT package directory for a build rooted at `start`.
/// Crate-local `wit/deps/<package>` before own `wit/<package>`, else the
/// nearest ancestor `wit/` that carries it.
/// The nearest ancestor `wit/` tree is the sole authority: vendored
/// `wit/deps/<package>` before owned `wit/<package>`. A package missing
/// from that tree is an error; outer trees are never consulted, so a
/// group cannot leak WIT it has not vendored.
pub fn resolve_wit_packages<S: AsRef<str>>(
start: &Path,
packages: &[S],
) -> Result<Vec<PathBuf>, String> {
let wit = find_wit_tree(start).ok_or_else(|| {
format!(
"no `wit/` tree exists under {} or any ancestor",
start.display()
)
})?;
packages
.iter()
.map(|package| {
let package = package.as_ref();
resolve_wit_package(start, package).ok_or_else(|| {
resolve_wit_package(&wit, package).ok_or_else(|| {
format!(
"declared capabilities need the `{package}` WIT package, but neither \
`wit/deps/{package}` nor `wit/{package}` exists under {} or any ancestor",
start.display()
`wit/deps/{package}` nor `wit/{package}` exists in {}",
wit.display()
)
})
})
Expand Down Expand Up @@ -522,22 +530,28 @@ pub fn is_plain_type(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Path(tp) if tp.qself.is_none())
}

/// Find one package directory: crate-local `wit/deps/<package>` then
/// `wit/<package>`, walking up on a miss.
fn resolve_wit_package(start: &Path, package: &str) -> Option<PathBuf> {
/// The nearest ancestor `wit/` directory of `start`: the crate-local or
/// group-local WIT tree the build resolves against.
fn find_wit_tree(start: &Path) -> Option<PathBuf> {
let mut dir = Some(start);
while let Some(cur) = dir {
let wit = cur.join("wit");
for candidate in [wit.join("deps").join(package), wit.join(package)] {
if candidate.is_dir() {
return Some(candidate);
}
if wit.is_dir() {
return Some(wit);
}
dir = cur.parent();
}
None
}

/// One package directory within a WIT tree: vendored `deps/<package>`
/// before owned `<package>`.
fn resolve_wit_package(wit: &Path, package: &str) -> Option<PathBuf> {
[wit.join("deps").join(package), wit.join(package)]
.into_iter()
.find(|candidate| candidate.is_dir())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -867,11 +881,30 @@ allow = []
#[test]
fn missing_package_names_the_paths_tried() {
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("wit")).unwrap();
let err = resolve_wit_packages(dir.path(), &["pkg"]).unwrap_err();
assert!(err.contains("`pkg` WIT package"));
assert!(err.contains("wit/deps/pkg"));
}

#[test]
fn absent_wit_tree_is_an_error() {
let dir = tempfile::tempdir().unwrap();
let err = resolve_wit_packages(dir.path(), &["pkg"]).unwrap_err();
assert!(err.contains("no `wit/` tree"));
}

#[test]
fn nearest_tree_never_falls_through_to_an_outer_one() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
std::fs::create_dir_all(root.join("wit/pkg")).unwrap();
let leaf = root.join("crates/leaf");
std::fs::create_dir_all(leaf.join("wit/deps/other")).unwrap();
let err = resolve_wit_packages(&leaf, &["pkg"]).unwrap_err();
assert!(err.contains("`pkg` WIT package"));
}

#[test]
fn read_surface_methods_parse() {
for m in [
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/clock-reader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::time::{SystemTime, UNIX_EPOCH};

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/flaky-bomb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/fuel-bomb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/memory-bomb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/panic-bomb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
2 changes: 1 addition & 1 deletion nexum/modules/fixtures/slow-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

wit_bindgen::generate!({
path: [
"../../../../wit/nexum-host",
"../../../wit/nexum-host",
],
world: "nexum:host/event-module",
generate_all,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
18 changes: 9 additions & 9 deletions scripts/check-venue-agnostic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,30 @@ esac
# anywhere in its sources, no cross-package use/import, no venue-domain
# vocabulary, and the package resolves standalone.
wit_charter='nexum:intent|nexum:adapter|value-flow|videre:|shepherd:cow'
rg -n --no-heading -e "$wit_charter" wit/nexum-host
rg -n --no-heading -e "$wit_charter" nexum/wit/nexum-host
case $? in
0) fail "a foreign WIT namespace leaks into wit/nexum-host" ;;
0) fail "a foreign WIT namespace leaks into nexum/wit/nexum-host" ;;
1) pass "no foreign WIT namespace named" ;;
*) fail "WIT namespace scan errored (wit/nexum-host missing?)" ;;
*) fail "WIT namespace scan errored (nexum/wit/nexum-host missing?)" ;;
esac
rg -n --no-heading -e '^\s*(use|import)\s+[a-z0-9-]+:' wit/nexum-host
rg -n --no-heading -e '^\s*(use|import)\s+[a-z0-9-]+:' nexum/wit/nexum-host
case $? in
0) fail "nexum:host references another WIT package" ;;
1) pass "nexum:host has no cross-package reference" ;;
*) fail "WIT scan errored (wit/nexum-host missing?)" ;;
*) fail "WIT scan errored (nexum/wit/nexum-host missing?)" ;;
esac
# Venue-domain vocabulary must not appear in the core event surface: the
# intent-status envelope is a videre-side borsh struct crossing `custom`
# as opaque bytes, so a term leaking back into nexum:host is a regression
# of the extraction.
rg -n --no-heading -i -e 'venue|receipt|intent-status' wit/nexum-host
rg -n --no-heading -i -e 'venue|receipt|intent-status' nexum/wit/nexum-host
case $? in
0) fail "venue-domain vocabulary leaks into wit/nexum-host" ;;
0) fail "venue-domain vocabulary leaks into nexum/wit/nexum-host" ;;
1) pass "nexum:host carries no venue-domain vocabulary" ;;
*) fail "WIT vocabulary scan errored (wit/nexum-host missing?)" ;;
*) fail "WIT vocabulary scan errored (nexum/wit/nexum-host missing?)" ;;
esac
if command -v wasm-tools >/dev/null; then
if wasm-tools component wit wit/nexum-host >/dev/null; then
if wasm-tools component wit nexum/wit/nexum-host >/dev/null; then
pass "nexum:host resolves standalone"
else
fail "nexum:host does not resolve standalone"
Expand Down
Loading
Loading