Skip to content

feat(runtime): introduce the venue-adapter component kind#249

Merged
mfw78 merged 2 commits into
dev/m1from
feat/m1-venue-adapter-world
Jul 15, 2026
Merged

feat(runtime): introduce the venue-adapter component kind#249
mfw78 merged 2 commits into
dev/m1from
feat/m1-venue-adapter-world

Conversation

@mfw78

@mfw78 mfw78 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

Introduces the second component kind, the venue adapter, alongside the existing event-module. A venue adapter speaks one venue's protocol only: it imports the scoped nexum:host/chain and nexum:host/messaging interfaces and exports init plus the new nexum:intent/adapter face, and deliberately withholds the core-only primitives (local-store, remote-store, identity, logging) an event-module gets.

  • Added the nexum:intent/adapter interface (derive-header/submit/status/cancel), host-independent and using intent types only.
  • Added a new nexum:adapter package with a venue-adapter world that imports scoped nexum:host/chain/messaging, exports init plus the adapter face, and withholds local-store/remote-store/identity/logging.
  • Version-pinned cross-package WIT references (@0.2.0 / @0.1.0) so the new packages resolve.
  • Added a second bindgen path binding VenueAdapter beside EventModule in bindings.rs, reusing nexum:host/{types,chain,messaging} via with so the chain/messaging Host impls and the fault type are shared with event-modules.
  • Added a ModuleKind discriminator on [module] in the manifest, defaulting to event-module.
  • Added [[adapters]] engine config with per-adapter scoped http_allow and messaging_topics grants, enforced ahead of the messaging backend stub.
  • Added an adapter linker that withholds core-only interfaces, and an adapter load path in the supervisor mirroring the module machinery, without dispatch.

Why

Event-modules and venue adapters have different trust boundaries: an adapter should only ever be able to reach the venue it speaks to, never the local-store, remote-store, identity, or logging primitives an event-module gets. Modelling the adapter as its own component kind, with its own capability namespace and its own scoped transport grants in engine.toml, lets the operator (not the adapter author) decide what a venue adapter may reach, instead of relying on the adapter's own manifest to self-limit.

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace (217 tests pass)
  • Bindgen compiles for the new nexum:adapter/nexum:intent/adapter packages (the crate compiling is the bindgen smoke test)

Scope of change is limited to the nexum-runtime crate plus two WIT additions (wit/nexum-adapter/venue-adapter.wit, wit/nexum-intent/adapter.wit); no wasm modules touched, so module-level wasm build/clippy gates are not applicable.

AI Assistance

Claude (Opus) used for the implementation.

Closes #228

@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 2e53297 to a85d1d2 Compare July 7, 2026 23:12
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 37f8439 to c4e57ac Compare July 7, 2026 23:12
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from a85d1d2 to d413e12 Compare July 8, 2026 00:42
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from c4e57ac to b13657b Compare July 8, 2026 00:42
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from d413e12 to 4c8ebcd Compare July 8, 2026 01:19
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from b13657b to 440b73a Compare July 8, 2026 01:19
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 4c8ebcd to 80461ca Compare July 8, 2026 01:44
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 440b73a to 194e8f1 Compare July 8, 2026 01:44
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 80461ca to 29be466 Compare July 8, 2026 02:11
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 194e8f1 to 6fb3e4d Compare July 8, 2026 02:11
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 29be466 to ab35d8c Compare July 8, 2026 02:19
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 6fb3e4d to f547ca7 Compare July 8, 2026 02:20
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from ab35d8c to 4ae8e57 Compare July 8, 2026 02:23
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from f547ca7 to caab1dd Compare July 8, 2026 02:23
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 4ae8e57 to db172e3 Compare July 8, 2026 03:03
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from caab1dd to b4cdd00 Compare July 8, 2026 03:03
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from db172e3 to bc32798 Compare July 8, 2026 04:59
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from b4cdd00 to 2d5c02a Compare July 8, 2026 04:59
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from bc32798 to dbf69e8 Compare July 8, 2026 05:31
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 2d5c02a to a2fe188 Compare July 8, 2026 05:31
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from dbf69e8 to 6367fb7 Compare July 8, 2026 06:21
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from a2fe188 to 378311c Compare July 8, 2026 06:21
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 6367fb7 to 2872a6d Compare July 8, 2026 06:40
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 378311c to c3f32b2 Compare July 8, 2026 06:40
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 2872a6d to c4aa3c6 Compare July 8, 2026 07:07
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from c3f32b2 to e551d21 Compare July 8, 2026 07:07
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from c4aa3c6 to a6ff83c Compare July 8, 2026 07:21
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from e551d21 to 2f23d71 Compare July 8, 2026 07:21
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from a6ff83c to 605e13d Compare July 8, 2026 07:36
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 2f23d71 to a2b95dd Compare July 8, 2026 07:36
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 74d0c00 to d1098f0 Compare July 8, 2026 09:02
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from b61ed02 to 083a41e Compare July 8, 2026 09:02
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from d1098f0 to ee27f6b Compare July 8, 2026 09:18
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 083a41e to 0eee5c3 Compare July 8, 2026 09:18
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from ee27f6b to 6a65a68 Compare July 8, 2026 12:10
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 0eee5c3 to e5c2d0c Compare July 8, 2026 12:10
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 6a65a68 to 7481e40 Compare July 8, 2026 12:20
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from e5c2d0c to 67a033b Compare July 8, 2026 12:20
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 7481e40 to 9473e0d Compare July 8, 2026 12:50
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 67a033b to 058c3b6 Compare July 8, 2026 12:50
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 9473e0d to 08d2274 Compare July 8, 2026 13:41
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 058c3b6 to 9b5e208 Compare July 8, 2026 13:41
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 08d2274 to 9a4d167 Compare July 8, 2026 13:59
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 9b5e208 to 5b0c585 Compare July 8, 2026 13:59
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from 9a4d167 to b36aea9 Compare July 8, 2026 14:09
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 5b0c585 to e4c6020 Compare July 8, 2026 14:09
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from b36aea9 to d113242 Compare July 8, 2026 22:33
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from e4c6020 to 76faaec Compare July 8, 2026 22:33

@lgahdl lgahdl left a comment

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.

Solid scaffold for the adapter kind. Three items below.

@@ -21,3 +51,39 @@ impl<T: RuntimeTypes> nexum::host::messaging::Host for HostState<T> {
Ok(vec![])

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.

query is not scope-checked. publish enforces topic_in_scope, but query takes _content_topic and returns Ok(vec![]) unconditionally. An adapter with messaging_topics = ["/nexum/1/cow-orders/proto"] can call query on /nexum/99/anything without refusal. This is inert while the backend is deferred, but when the Waku backend lands in 0.3 the scope hole will be live. The enforcement machinery already exists — a one-liner mirrors publish.

Suggested change
Ok(vec![])
Ok(vec![])
// TODO(0.3): enforce topic_in_scope here before the Waku backend lands.

/// way as a module's `[capabilities.http].allow`.
#[serde(default)]
pub http_allow: Vec<String>,
/// Messaging content topics this adapter may reach.

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.

"May reach" is inaccurate — only publish is gated; query is unscoped. Mirrors the more precise phrasing on HostState::messaging_topics.

Suggested change
/// Messaging content topics this adapter may reach.
/// Messaging content topics this adapter may publish to.

Comment on lines +618 to +622
if kind != ModuleKind::VenueAdapter {
return Err(anyhow!(
"adapter {} declares module kind {kind:?}; an [[adapters]] entry requires \
a module.toml with [module] kind = \"venue-adapter\"",
entry.path.display(),

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.

When no module.toml exists, fallback_manifest() supplies ModuleKind::EventModule (the #[default]) and this error fires saying the adapter "declares" EventModule — but the adapter declared nothing; it has no manifest. The warn! two lines up says "no module.toml", which correctly diagnoses the situation, but the error that propagates to the caller implies the operator should fix a kind field that doesn't exist yet. Two-branch message:

  • No manifest found → "no module.toml found; an [[adapters]] entry requires one with [module] kind = \"venue-adapter\""
  • Manifest present but wrong kind → current text ("declares module kind…")

Comment on lines +14 to +16
/// Project an opaque intent body onto the stable header guard policy
/// runs on. A pure derivation: no transport, no side effects, so the
/// host can derive and inspect a header before deciding to submit.

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.

The purity claim is convention only. The nexum:adapter/venue-adapter world imports chain and messaging at the world level, making both available to derive-header — WIT has no per-function import restriction. An adapter can call chain.get_block() or messaging.publish() inside derive-header, and those side effects happen before the host runs guard policy (the router calls derive-header first). Before the router lands, document this as a known gap, or restrict derive-header to a sub-world that withholds chain+messaging.

Suggested change
/// Project an opaque intent body onto the stable header guard policy
/// runs on. A pure derivation: no transport, no side effects, so the
/// host can derive and inspect a header before deciding to submit.
/// Project an opaque intent body onto the stable header guard policy
/// runs on. Intended as a pure derivation (no transport, no side effects)
/// so the host can inspect a header before deciding to submit — but the
/// WIT interface cannot enforce this; enforcement is by adapter-author
/// convention. The router must assume `derive-header` may call chain or
/// messaging and account for those side effects in its policy timing.
derive-header: func(body: list<u8>) -> result<intent-header, venue-error>;

@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from d113242 to f6256f7 Compare July 8, 2026 23:05
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch 2 times, most recently from 24bf6f3 to 892cd0c Compare July 8, 2026 23:24
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch 2 times, most recently from 8fab2d5 to b3bdd6f Compare July 8, 2026 23:29
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 892cd0c to 1f3c3f5 Compare July 8, 2026 23:29
@mfw78 mfw78 force-pushed the feat/m1-venue-adapter-world branch from 1f3c3f5 to a0d9541 Compare July 9, 2026 02:09
@mfw78 mfw78 force-pushed the feat/m1-wit-intent branch from b3bdd6f to 824d390 Compare July 9, 2026 02:09
mfw78 added 2 commits July 15, 2026 00:22
Add the second component kind alongside the event-module. A venue
adapter speaks one venue's protocol over scoped transport only and
exports the intent adapter face.

- WIT: a nexum:intent/adapter interface (derive-header, submit, status,
  cancel) and a nexum:adapter/venue-adapter world that imports scoped
  chain and messaging, exports init plus the adapter face, and withholds
  the core-only primitives.
- Bindings: a second bindgen path binding VenueAdapter beside
  EventModule, reusing the shared nexum:host interfaces via with.
- Manifest: a module-kind discriminator defaulting to event-module, plus
  an adapter capability registry that recognises only the scoped
  transport.
- Engine config: an [[adapters]] table carrying path, manifest, a
  per-adapter HTTP allowlist, and messaging-topic scopes.
- Supervisor: adapters instantiate into supervised stores against a
  dedicated scoped linker, reusing the store, fuel, and restart
  machinery; the kind discriminator gates the load path. No routing yet.
- Messaging: per-store content-topic scope enforced ahead of the
  deferred backend.
The bindgen module rustdoc split the `nexum:adapter/venue-adapter` code
span across a line break after the slash, so rustdoc rendered it with a
stray space. Rewrap so the token stays intact.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-train Part of the current stacked PR train; lands in sequence, do not merge out of order.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants